diff --git a/AIClasses/Gemini/Gemini.ts b/AIClasses/Gemini/Gemini.ts index c0be4f7..bbe4d79 100644 --- a/AIClasses/Gemini/Gemini.ts +++ b/AIClasses/Gemini/Gemini.ts @@ -5,6 +5,8 @@ import type { GeminiActionDefinitions } from "Actioner/Gemini/GeminiActionDefini import type { IAIClass } from "AIClasses/IAIClass"; import type { IPrompt } from "AIClasses/IPrompt"; import { StreamingService, type StreamChunk } from "Services/StreamingService"; +import type { Conversation } from "Conversations/Conversation"; +import { Role } from "Enums/Role"; export class Gemini implements IAIClass { private readonly apiKey: string; @@ -23,10 +25,18 @@ export class Gemini implements IAIClass { * Stream response from Gemini API */ public async* streamRequest( - userInput: string, + conversation: Conversation, actioner: IActioner ): AsyncGenerator { - const prompt = "The users prompt is: " + userInput; + + const contents = conversation.contents.map(content => ({ + role: content.role === Role.User ? "user" : "model", + parts: [ + { + text: content.content + } + ] + })); const requestBody = { system_instruction: { @@ -35,20 +45,11 @@ export class Gemini implements IAIClass { text: this.aiPrompt.systemInstruction() }, { - text: this.aiPrompt.userInstruction() + text: await this.aiPrompt.userInstruction() } ] }, - contents: [ - { - role: "user", - parts: [ - { - text: "" - }, - ], - }, - ], + contents: contents, tools: [ { google_search: {}, diff --git a/AIClasses/IAIClass.ts b/AIClasses/IAIClass.ts index 0674399..9a1f6dc 100644 --- a/AIClasses/IAIClass.ts +++ b/AIClasses/IAIClass.ts @@ -1,6 +1,7 @@ import type { IActioner } from "Actioner/IActioner"; import type { StreamChunk } from "Services/StreamingService"; +import type { Conversation } from "Conversations/Conversation"; export interface IAIClass { - streamRequest(userInput: string, actioner: IActioner): AsyncGenerator; + streamRequest(conversation: Conversation, actioner: IActioner): AsyncGenerator; } \ No newline at end of file diff --git a/Components/ChatWindow.svelte b/Components/ChatWindow.svelte index 8328ac2..ee17658 100644 --- a/Components/ChatWindow.svelte +++ b/Components/ChatWindow.svelte @@ -59,7 +59,7 @@ // Stream the response let accumulatedContent = ""; - for await (const chunk of ai.streamRequest(requestToSend, actioner)) { + for await (const chunk of ai.streamRequest(conversation, actioner)) { if (chunk.error) { console.error("Streaming error:", chunk.error); // Update message with error diff --git a/ODB/Core/DynamicRecord.ts b/ODB/Core/DynamicRecord.ts index ea72dd3..a1eeba1 100644 --- a/ODB/Core/DynamicRecord.ts +++ b/ODB/Core/DynamicRecord.ts @@ -1,157 +1,157 @@ -import { DynamicRecordProp } from 'Enums/DynamicRecord'; -import { version as uuidVersion } from 'uuid'; -import { validate as uuidValidate } from 'uuid'; -import type { OdbCache } from './OdbCache'; -import { Resolve } from 'Services/DependencyService'; -import { Services } from 'Services/Services'; -import type AIAgentPlugin from 'main'; -import { TAbstractFile, TFile, type Vault } from 'obsidian'; -interface DynamicProps { - [key: string]: any; -} +// import { version as uuidVersion } from 'uuid'; +// import { validate as uuidValidate } from 'uuid'; +// import type { OdbCache } from './OdbCache'; +// import { Resolve } from 'Services/DependencyService'; +// import { Services } from 'Services/Services'; +// import type AIAgentPlugin from 'main'; +// import { TAbstractFile, TFile, type Vault } from 'obsidian'; -export class DynamicRecord { +// interface DynamicProps { +// [key: string]: any; +// } - public readonly type: string; - public readonly schema: object; - public readonly objectId: string; - public readonly recordPath: string; +// export class DynamicRecord { - public props: DynamicProps; +// public readonly type: string; +// public readonly schema: object; +// public readonly objectId: string; +// public readonly recordPath: string; - private vault: Vault; - private odbCache: OdbCache; +// public props: DynamicProps; - public constructor(recordPath: string, schema: object, record: { [key: string]: any }) { - this.odbCache = Resolve(Services.OdbCache); - this.vault = Resolve(Services.AIAgentPlugin).app.vault; +// private vault: Vault; +// private odbCache: OdbCache; - this.schema = schema; - this.recordPath = recordPath; +// public constructor(recordPath: string, schema: object, record: { [key: string]: any }) { +// this.odbCache = Resolve(Services.OdbCache); +// this.vault = Resolve(Services.AIAgentPlugin).app.vault; - if (record[DynamicRecordProp.Type] === undefined || record[DynamicRecordProp.ObjectId] === undefined) { - throw new Error(`Invalid record format: missing ${DynamicRecordProp.Type} or ${DynamicRecordProp.ObjectId}`); - } +// this.schema = schema; +// this.recordPath = recordPath; + +// if (record[DynamicRecordProp.Type] === undefined || record[DynamicRecordProp.ObjectId] === undefined) { +// throw new Error(`Invalid record format: missing ${DynamicRecordProp.Type} or ${DynamicRecordProp.ObjectId}`); +// } - this.type = record[DynamicRecordProp.Type]; - this.objectId = record[DynamicRecordProp.ObjectId]; +// this.type = record[DynamicRecordProp.Type]; +// this.objectId = record[DynamicRecordProp.ObjectId]; - let parsedRecord: Record = {}; - for (let key in schema) { - if (!(key in record) || key === DynamicRecordProp.Type || key === DynamicRecordProp.ObjectId) { - continue; - } +// let parsedRecord: Record = {}; +// for (let key in schema) { +// if (!(key in record) || key === DynamicRecordProp.Type || key === DynamicRecordProp.ObjectId) { +// continue; +// } - parsedRecord[key] = this.parse(record[key]); - } +// parsedRecord[key] = this.parse(record[key]); +// } - this.props = this.createProxy(parsedRecord); - } +// this.props = this.createProxy(parsedRecord); +// } - public save() { - let file: TAbstractFile | null = this.vault.getAbstractFileByPath(this.recordPath); +// public save() { +// let file: TAbstractFile | null = this.vault.getAbstractFileByPath(this.recordPath); - if (file == null || !(file instanceof TAbstractFile)) { - this.vault.create(this.recordPath, this.toFileContent()); - } +// if (file == null || !(file instanceof TAbstractFile)) { +// this.vault.create(this.recordPath, this.toFileContent()); +// } - this.vault.modify(file as TFile, this.toFileContent()); - } +// this.vault.modify(file as TFile, this.toFileContent()); +// } - public delete() { - let file: TAbstractFile | null = this.vault.getAbstractFileByPath(this.recordPath); +// public delete() { +// let file: TAbstractFile | null = this.vault.getAbstractFileByPath(this.recordPath); - if (file == null || !(file instanceof TAbstractFile)) { - return; - } +// if (file == null || !(file instanceof TAbstractFile)) { +// return; +// } - this.vault.delete(file as TFile); - } +// this.vault.delete(file as TFile); +// } - public move(newPath: string) { - let file: TAbstractFile | null = this.vault.getAbstractFileByPath(this.recordPath); +// public move(newPath: string) { +// let file: TAbstractFile | null = this.vault.getAbstractFileByPath(this.recordPath); - if (file == null || !(file instanceof TAbstractFile)) { - return; - } +// if (file == null || !(file instanceof TAbstractFile)) { +// return; +// } - this.vault.rename(file as TFile, newPath); - } +// this.vault.rename(file as TFile, newPath); +// } - private parse(data: any): any { - if (typeof data === null) { - return data; - } - if (Array.isArray(data)) { - let arr: any[] = []; - for (let item of data) { - arr.push(this.parse(item)); - } - return arr; - } - if (typeof data === "object") { - let obj: Record = {}; - for (let key in data) { - obj[key] = this.parse(data[key]); - } - return this.createProxy(obj); - } - return data; - } +// private parse(data: any): any { +// if (typeof data === null) { +// return data; +// } +// if (Array.isArray(data)) { +// let arr: any[] = []; +// for (let item of data) { +// arr.push(this.parse(item)); +// } +// return arr; +// } +// if (typeof data === "object") { +// let obj: Record = {}; +// for (let key in data) { +// obj[key] = this.parse(data[key]); +// } +// return this.createProxy(obj); +// } +// return data; +// } - private createProxy(data: object): DynamicProps { - return new Proxy(data, { - get: (target, prop, receiver) => { - if (this.isObjectId(String(prop))) { - return this.odbCache.getRecord(String(prop)); - } - return Reflect.get(target, prop, receiver); - }, - set: (target, prop, value, receiver) => { - return Reflect.set(target, prop, value, receiver); - } - }); - } +// private createProxy(data: object): DynamicProps { +// return new Proxy(data, { +// get: (target, prop, receiver) => { +// if (this.isObjectId(String(prop))) { +// return this.odbCache.getRecord(String(prop)); +// } +// return Reflect.get(target, prop, receiver); +// }, +// set: (target, prop, value, receiver) => { +// return Reflect.set(target, prop, value, receiver); +// } +// }); +// } - private isObjectId(key: string): boolean { - return uuidValidate(key) && uuidVersion(key) === 4; - } +// private isObjectId(key: string): boolean { +// return uuidValidate(key) && uuidVersion(key) === 4; +// } - private toFileContent(): string { - let content: Record = {}; +// private toFileContent(): string { +// let content: Record = {}; - content[DynamicRecordProp.Type] = this.type; - content[DynamicRecordProp.ObjectId] = this.objectId; +// content[DynamicRecordProp.Type] = this.type; +// content[DynamicRecordProp.ObjectId] = this.objectId; - // use the schema when saving to file in case the AI did something funky - for (let key in this.schema) { - if (!(key in this.props) || key === DynamicRecordProp.Type || key === DynamicRecordProp.ObjectId) { - continue; - } - content[key] = this.serialise(this.props[key]); - } - return JSON.stringify(content, null, 4); - } +// // use the schema when saving to file in case the AI did something funky +// for (let key in this.schema) { +// if (!(key in this.props) || key === DynamicRecordProp.Type || key === DynamicRecordProp.ObjectId) { +// continue; +// } +// content[key] = this.serialise(this.props[key]); +// } +// return JSON.stringify(content, null, 4); +// } - private serialise(value: any): any { - if (value instanceof DynamicRecord) { - return value.objectId; - } - if (Array.isArray(value)) { - let arr: any[] = []; - for (let item of value) { - arr.push(this.serialise(item)); - } - return arr; - } - if (typeof value === "object") { - let obj: Record = {}; - for (let key in value) { - obj[key] = this.serialise(value[key]); - } - return obj; - } - return value; - } -} \ No newline at end of file +// private serialise(value: any): any { +// if (value instanceof DynamicRecord) { +// return value.objectId; +// } +// if (Array.isArray(value)) { +// let arr: any[] = []; +// for (let item of value) { +// arr.push(this.serialise(item)); +// } +// return arr; +// } +// if (typeof value === "object") { +// let obj: Record = {}; +// for (let key in value) { +// obj[key] = this.serialise(value[key]); +// } +// return obj; +// } +// return value; +// } +// } \ No newline at end of file diff --git a/ODB/Core/OdbCache.ts b/ODB/Core/OdbCache.ts index 836e05b..5edba88 100644 --- a/ODB/Core/OdbCache.ts +++ b/ODB/Core/OdbCache.ts @@ -1,131 +1,131 @@ -import { TAbstractFile, TFile, TFolder, Vault } from "obsidian"; -import { DynamicRecord } from "./DynamicRecord"; -import { Resolve } from "Services/DependencyService"; -import type AIAgentPlugin from "main"; -import { Services } from "Services/Services"; -import { Path } from "Enums/Path"; -import { DynamicRecordProp } from "Enums/DynamicRecord"; -import { FileAction } from "Enums/FileAction"; -import { isValidJson } from "Helpers/Helpers"; +// import { TAbstractFile, TFile, TFolder, Vault } from "obsidian"; +// import { DynamicRecord } from "./DynamicRecord"; +// import { Resolve } from "Services/DependencyService"; +// import type AIAgentPlugin from "main"; +// import { Services } from "Services/Services"; +// import { Path } from "Enums/Path"; +// import { DynamicRecordProp } from "Enums/DynamicRecord"; +// import { FileAction } from "Enums/FileAction"; +// import { isValidJson } from "Helpers/Helpers"; -export class OdbCache { +// export class OdbCache { - private vault: Vault; +// private vault: Vault; - private schemas: Map = new Map(); - private cache: Map = new Map(); +// private schemas: Map = new Map(); +// private cache: Map = new Map(); - public constructor() { - this.vault = Resolve(Services.AIAgentPlugin).app.vault; - } +// public constructor() { +// this.vault = Resolve(Services.AIAgentPlugin).app.vault; +// } - public getSchemas(): Map { - return this.schemas; - } +// public getSchemas(): Map { +// return this.schemas; +// } - public getRecord(objectId: string): DynamicRecord | null { - return this.cache.get(objectId) ?? null; - } +// public getRecord(objectId: string): DynamicRecord | null { +// return this.cache.get(objectId) ?? null; +// } - public async buildCache() { - await this.loadSchemas(); +// public async buildCache() { +// await this.loadSchemas(); - let recordDir: TAbstractFile | null = this.vault.getAbstractFileByPath(Path.Records); +// let recordDir: TAbstractFile | null = this.vault.getAbstractFileByPath(Path.Records); - if (!(recordDir instanceof TFolder)) { - return; - } +// if (!(recordDir instanceof TFolder)) { +// return; +// } - this.recursiveBuild(recordDir); - } +// this.recursiveBuild(recordDir); +// } - public async onFileChanged(file: TAbstractFile, fileAction: FileAction) { - if (file instanceof TFolder) { - for (let child of file.children) { - this.onFileChanged(child, fileAction); - } - return; - } +// public async onFileChanged(file: TAbstractFile, fileAction: FileAction) { +// if (file instanceof TFolder) { +// for (let child of file.children) { +// this.onFileChanged(child, fileAction); +// } +// return; +// } - for (let [_, record] of this.cache) { - if (record.recordPath === file.path) { - switch (fileAction) { - case FileAction.Create: - this.addToCache(file as TFile); - break; - case FileAction.Modify, FileAction.Rename: - this.removeFromCache(record.objectId); - this.addToCache(file as TFile); - break; - case FileAction.Delete: - this.removeFromCache(record.objectId); - break; - } - } - } - } +// for (let [_, record] of this.cache) { +// if (record.recordPath === file.path) { +// switch (fileAction) { +// case FileAction.Create: +// this.addToCache(file as TFile); +// break; +// case FileAction.Modify, FileAction.Rename: +// this.removeFromCache(record.objectId); +// this.addToCache(file as TFile); +// break; +// case FileAction.Delete: +// this.removeFromCache(record.objectId); +// break; +// } +// } +// } +// } - private async recursiveBuild(child : TAbstractFile) { - if (child instanceof TFolder) { - for (let subChild of child.children) { - await this.recursiveBuild(subChild); - } - } else if (child instanceof TFile && child.extension === "json") { - await this.addToCache(child); - } - } +// private async recursiveBuild(child : TAbstractFile) { +// if (child instanceof TFolder) { +// for (let subChild of child.children) { +// await this.recursiveBuild(subChild); +// } +// } else if (child instanceof TFile && child.extension === "json") { +// await this.addToCache(child); +// } +// } - private async createDynamicRecord(file: TFile): Promise { - let contents: { [key: string]: any } = JSON.parse(await this.vault.read(file)); +// private async createDynamicRecord(file: TFile): Promise { +// let contents: { [key: string]: any } = JSON.parse(await this.vault.read(file)); - if (contents[DynamicRecordProp.Type] === undefined || contents[DynamicRecordProp.ObjectId] === undefined) { - throw new Error(`Invalid record format in file ${file.path}: missing ${DynamicRecordProp.Type} or ${DynamicRecordProp.ObjectId}`); - } +// if (contents[DynamicRecordProp.Type] === undefined || contents[DynamicRecordProp.ObjectId] === undefined) { +// throw new Error(`Invalid record format in file ${file.path}: missing ${DynamicRecordProp.Type} or ${DynamicRecordProp.ObjectId}`); +// } - let schema: object | undefined = this.schemas.get(contents[DynamicRecordProp.Type]); +// let schema: object | undefined = this.schemas.get(contents[DynamicRecordProp.Type]); - if (schema === undefined) { - throw new Error(`No schema found for record type ${contents[DynamicRecordProp.Type]} in file ${file.path}`); - } +// if (schema === undefined) { +// throw new Error(`No schema found for record type ${contents[DynamicRecordProp.Type]} in file ${file.path}`); +// } - return new DynamicRecord(file.path, schema, contents); - } +// return new DynamicRecord(file.path, schema, contents); +// } - private async loadSchemas() { - let schemaDir: TAbstractFile | null = this.vault.getAbstractFileByPath(Path.Schemas); +// private async loadSchemas() { +// let schemaDir: TAbstractFile | null = this.vault.getAbstractFileByPath(Path.Schemas); - if (!(schemaDir instanceof TFolder)) { - return; - } +// if (!(schemaDir instanceof TFolder)) { +// return; +// } - for (let child of schemaDir.children) { - if (!(child instanceof TFile) || child.extension !== "json") { - continue; - } +// for (let child of schemaDir.children) { +// if (!(child instanceof TFile) || child.extension !== "json") { +// continue; +// } - let contents: string = await this.vault.read(child); +// let contents: string = await this.vault.read(child); - if (!isValidJson(contents)) { - console.warn(`Invalid schema format in file ${child.path}`); - continue; - } +// if (!isValidJson(contents)) { +// console.warn(`Invalid schema format in file ${child.path}`); +// continue; +// } - let schema: Record = JSON.parse(contents); +// let schema: Record = JSON.parse(contents); - if (schema[DynamicRecordProp.Type] === undefined || schema[DynamicRecordProp.ObjectId] === undefined) { - throw new Error(`Invalid schema format in file ${child.path}: missing ${DynamicRecordProp.Type} or ${DynamicRecordProp.ObjectId}`); - } +// if (schema[DynamicRecordProp.Type] === undefined || schema[DynamicRecordProp.ObjectId] === undefined) { +// throw new Error(`Invalid schema format in file ${child.path}: missing ${DynamicRecordProp.Type} or ${DynamicRecordProp.ObjectId}`); +// } - this.schemas.set(schema[DynamicRecordProp.Type], schema); - } - } +// this.schemas.set(schema[DynamicRecordProp.Type], schema); +// } +// } - private async addToCache(file: TFile) { - let record: DynamicRecord = await this.createDynamicRecord(file); - this.cache.set(record.objectId, record); - } +// private async addToCache(file: TFile) { +// let record: DynamicRecord = await this.createDynamicRecord(file); +// this.cache.set(record.objectId, record); +// } - private async removeFromCache(objectId: string) { - this.cache.delete(objectId); - } -} \ No newline at end of file +// private async removeFromCache(objectId: string) { +// this.cache.delete(objectId); +// } +// } \ No newline at end of file diff --git a/Services/ConversationFileSystemService.ts b/Services/ConversationFileSystemService.ts index 018c012..ff85da9 100644 --- a/Services/ConversationFileSystemService.ts +++ b/Services/ConversationFileSystemService.ts @@ -41,6 +41,7 @@ export class ConversationFileSystemService { }; await this.fileSystemService.writeObjectToFile(this.currentConversationPath, conversationData); + console.log("Conversation saved to:", this.currentConversationPath); return this.currentConversationPath; } diff --git a/Services/ServiceRegistration.ts b/Services/ServiceRegistration.ts index 2c54c72..bedb83f 100644 --- a/Services/ServiceRegistration.ts +++ b/Services/ServiceRegistration.ts @@ -1,6 +1,6 @@ import { AIProvider } from "Enums/ApiProvider"; import type AIAgentPlugin from "main"; -import { OdbCache } from "ODB/Core/OdbCache"; +//import { OdbCache } from "ODB/Core/OdbCache"; import { RegisterSingleton, RegisterTransient } from "./DependencyService"; import { ModalService } from "./ModalService"; import { Services } from "./Services"; @@ -19,7 +19,7 @@ import { ConversationFileSystemService } from "./ConversationFileSystemService"; export function RegisterDependencies(plugin: AIAgentPlugin) { RegisterSingleton(Services.MessageService, new MessageService()); RegisterSingleton(Services.AIAgentPlugin, plugin); - RegisterSingleton(Services.OdbCache, new OdbCache()); + //RegisterSingleton(Services.OdbCache, new OdbCache()); RegisterSingleton(Services.ModalService, new ModalService()) RegisterSingleton(Services.FileSystemService, new FileSystemService()); RegisterSingleton(Services.ConversationFileSystemService, new ConversationFileSystemService()); diff --git a/main.ts b/main.ts index 2fef405..1a26fb4 100644 --- a/main.ts +++ b/main.ts @@ -4,8 +4,8 @@ import { AIProvider } from './Enums/ApiProvider'; import { MainView, VIEW_TYPE_MAIN } from 'Views/MainView'; import { Services } from 'Services/Services'; -import { OdbCache } from 'ODB/Core/OdbCache'; -import { FileAction } from 'Enums/FileAction'; +// import { OdbCache } from 'ODB/Core/OdbCache'; +// import { FileAction } from 'Enums/FileAction'; import { Path } from 'Enums/Path'; import { RegisterAiProvider, RegisterDependencies } from 'Services/ServiceRegistration'; @@ -71,22 +71,22 @@ export default class AIAgentPlugin extends Plugin { console.log('click', evt); }); - let odbCache: OdbCache = Resolve(Services.OdbCache) + // let odbCache: OdbCache = Resolve(Services.OdbCache) - await odbCache.buildCache(); + // await odbCache.buildCache(); - this.registerEvent( - this.app.vault.on(FileAction.Create, (file) => odbCache.onFileChanged(file, FileAction.Create)) - ); - this.registerEvent( - this.app.vault.on(FileAction.Modify, (file) => odbCache.onFileChanged(file, FileAction.Modify)) - ); - this.registerEvent( - this.app.vault.on(FileAction.Delete, (file) => odbCache.onFileChanged(file, FileAction.Delete)) - ); - this.registerEvent( - this.app.vault.on(FileAction.Rename, (file) => odbCache.onFileChanged(file, FileAction.Rename)) - ); + // this.registerEvent( + // this.app.vault.on(FileAction.Create, (file) => odbCache.onFileChanged(file, FileAction.Create)) + // ); + // this.registerEvent( + // this.app.vault.on(FileAction.Modify, (file) => odbCache.onFileChanged(file, FileAction.Modify)) + // ); + // this.registerEvent( + // this.app.vault.on(FileAction.Delete, (file) => odbCache.onFileChanged(file, FileAction.Delete)) + // ); + // this.registerEvent( + // this.app.vault.on(FileAction.Rename, (file) => odbCache.onFileChanged(file, FileAction.Rename)) + // ); } async onunload() { @@ -186,12 +186,6 @@ function CreateDirectories(plugin: AIAgentPlugin) { if (vault.getAbstractFileByPath(Path.Root) == null) { vault.createFolder(Path.Root); } - if (vault.getAbstractFileByPath(Path.Schemas) == null) { - vault.createFolder(Path.Schemas); - } - if (vault.getAbstractFileByPath(Path.Records) == null) { - vault.createFolder(Path.Records); - } }); }