Skip to content

Commit

Permalink
Merge pull request #16 from freemocap/jon/persist-conversations
Browse files Browse the repository at this point in the history
Jon/persist conversations
  • Loading branch information
jonmatthis authored Jan 14, 2024
2 parents 0e5efb7 + b3e2a0a commit ef3c38a
Show file tree
Hide file tree
Showing 33 changed files with 34,117 additions and 25,143 deletions.
14 changes: 7 additions & 7 deletions Writerside/.obsidian/plugins/copilot/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -38338,8 +38338,8 @@ var require_Client2 = __commonJS({
this._options = _options;
}
/**
* The `chat` endpoint allows users to have conversations with a Large Language Model (LLM) from Cohere. Users can send messages as part of a persisted conversation using the `conversation_id` parameter, or they can pass in their own conversation history using the `chat_history` parameter.
* The endpoint features additional parameters such as [connectors](https://docs.cohere.com/docs/connectors) and `documents` that enable conversations enriched by external knowledge. We call this "Retrieval Augmented Generation", or "RAG".
* The `chat` endpoint allows users to have ai-chats with a Large Language Model (LLM) from Cohere. Users can send messages as part of a persisted conversation using the `conversation_id` parameter, or they can pass in their own conversation history using the `chat_history` parameter.
* The endpoint features additional parameters such as [connectors](https://docs.cohere.com/docs/connectors) and `documents` that enable ai-chats enriched by external knowledge. We call this "Retrieval Augmented Generation", or "RAG".
*
*/
chatStream(request2, requestOptions) {
Expand Down Expand Up @@ -38397,8 +38397,8 @@ var require_Client2 = __commonJS({
});
}
/**
* The `chat` endpoint allows users to have conversations with a Large Language Model (LLM) from Cohere. Users can send messages as part of a persisted conversation using the `conversation_id` parameter, or they can pass in their own conversation history using the `chat_history` parameter.
* The endpoint features additional parameters such as [connectors](https://docs.cohere.com/docs/connectors) and `documents` that enable conversations enriched by external knowledge. We call this "Retrieval Augmented Generation", or "RAG".
* The `chat` endpoint allows users to have ai-chats with a Large Language Model (LLM) from Cohere. Users can send messages as part of a persisted conversation using the `conversation_id` parameter, or they can pass in their own conversation history using the `chat_history` parameter.
* The endpoint features additional parameters such as [connectors](https://docs.cohere.com/docs/connectors) and `documents` that enable ai-chats enriched by external knowledge. We call this "Retrieval Augmented Generation", or "RAG".
*
*/
chat(request2, requestOptions) {
Expand Down Expand Up @@ -72823,7 +72823,7 @@ var DEFAULT_SETTINGS = {
ttlDays: 30,
stream: true,
embeddingProvider: OPENAI,
defaultSaveFolder: "copilot-conversations",
defaultSaveFolder: "copilot-ai-chats",
debug: false
};

Expand Down Expand Up @@ -86568,8 +86568,8 @@ var CopilotSettingTab = class extends import_obsidian10.PluginSettingTab {
await this.plugin.saveSettings();
});
});
new import_obsidian10.Setting(containerEl).setName("Default Conversation Folder Name").setDesc("The default folder name where chat conversations will be saved. Default is 'copilot-conversations'").addText(
(text4) => text4.setPlaceholder("copilot-conversations").setValue(this.plugin.settings.defaultSaveFolder).onChange(async (value) => {
new import_obsidian10.Setting(containerEl).setName("Default Conversation Folder Name").setDesc("The default folder name where chat ai-chats will be saved. Default is 'copilot-ai-chats'").addText(
(text4) => text4.setPlaceholder("copilot-ai-chats").setValue(this.plugin.settings.defaultSaveFolder).onChange(async (value) => {
this.plugin.settings.defaultSaveFolder = value;
await this.plugin.saveSettings();
})
Expand Down
58,430 changes: 33,473 additions & 24,957 deletions Writerside/.obsidian/plugins/graph-analysis/main.js

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"@slack/bolt": "^3.17.0",
"bson": "^6.2.0",
"bufferutil": "^4.0.8",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"discord.js": "^14.14.1",
"langchain": "^0.0.209",
Expand Down
6 changes: 3 additions & 3 deletions src/core/bot/bot.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,14 @@ export class BotService {
this._logger.log(
`Responding to message '${humanMessage}' with chatbotId: ${chatbotId}`,
);
const chatbot = this.getChatbotById(chatbotId);
const chatbot = this.getBotById(chatbotId);
return await chatbot.chain.invoke({
text: humanMessage,
...additionalArgs,
});
}

getChatbotById(chatbotId: string | number) {
getBotById(chatbotId: string | number) {
try {
this._logger.log(`Fetching chatbot with id: ${chatbotId}`);
return this._chatbots.get(String(chatbotId));
Expand All @@ -77,7 +77,7 @@ export class BotService {
...options,
};
const { splitAt } = normalizedOptions;
const chatbot = this.getChatbotById(chatbotId);
const chatbot = this.getBotById(chatbotId);
const chatStream = await chatbot.chain.stream({
input: humanMessage,
...additionalArgs,
Expand Down
41 changes: 41 additions & 0 deletions src/core/database/collections/ai-chats/ai-chat-create.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { IsNotEmpty, IsString, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
import { ContextRoute } from './context-route.provider';
import { UserIdentifier } from '../users/user-identifiers';
import { Couplet } from '../couplets/couplet.schema';

export class AiChatCreateDto {
@IsString()
@IsNotEmpty()
aiChatId: string;

@ValidateNested()
@Type(() => UserIdentifier)
ownerUser: UserIdentifier;

@ValidateNested()
@Type(() => ContextRoute)
contextRoute: ContextRoute;

@ValidateNested({ each: true })
@Type(() => Couplet)
couplets: Couplet[];
}

export class UpdateAiChatDto {
@ValidateNested({ each: true })
@Type(() => Couplet)
couplets: Couplet[];
}

export class GetAiChatDto {
@IsString()
@IsNotEmpty()
id: string;
}

export class ListAiChatsDto {
@ValidateNested()
@Type(() => ContextRoute)
contextRoute: ContextRoute;
}
41 changes: 41 additions & 0 deletions src/core/database/collections/ai-chats/ai-chat.schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document, Types } from 'mongoose';
import { ContextRoute } from './context-route.provider';
import { User } from '../users/user.schema';
import { Couplet } from '../couplets/couplet.schema';

export type AiChatDocument = AiChat & Document;

@Schema({ timestamps: true })
export class AiChat {
@Prop({ type: ContextRoute, required: true })
contextRoute: ContextRoute;

@Prop({ required: true, unique: true })
aiChatId: string;

@Prop({ type: Types.ObjectId, ref: 'User', required: true })
ownerUser: User;

@Prop({ type: [{ type: Types.ObjectId, ref: 'Couplet' }], required: true })
couplets: Couplet[];
}

export const AiChatSchema = SchemaFactory.createForClass(AiChat);

AiChatSchema.post(
'save',
function (doc: AiChatDocument, next: (err?: Error) => void) {
const lastIdentifier =
doc.contextRoute.identifiers[doc.contextRoute.identifiers.length - 1];
if (doc.aiChatId !== lastIdentifier.contextId) {
next(
new Error(
'aiChatId should match the bottom-most identifier in the context route',
),
);
} else {
next();
}
},
);
13 changes: 13 additions & 0 deletions src/core/database/collections/ai-chats/ai-chats.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Module } from '@nestjs/common';
import { AiChatsService } from './ai-chats.service';
import { MongooseModule } from '@nestjs/mongoose';
import { AiChat, AiChatSchema } from './ai-chat.schema';

@Module({
imports: [
MongooseModule.forFeature([{ name: AiChat.name, schema: AiChatSchema }]),
],
providers: [AiChatsService],
exports: [AiChatsService],
})
export class AiChatsModule {}
79 changes: 79 additions & 0 deletions src/core/database/collections/ai-chats/ai-chats.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';

import { AiChatCreateDto, UpdateAiChatDto } from './ai-chat-create.dto';
import { v4 as uuidv4 } from 'uuid';
import { AiChat, AiChatDocument } from './ai-chat.schema';
import { Couplet } from '../couplets/couplet.schema';

@Injectable()
export class AiChatsService {
constructor(
@InjectModel(AiChat.name)
private readonly aiChatModel: Model<AiChatDocument>,
) {}

async findAll(): Promise<AiChatDocument[]> {
return this.aiChatModel.find().exec();
}

async findOne(aiChatId: string): Promise<AiChatDocument> {
return this.aiChatModel.findOne({ aiChatId: aiChatId }).exec();
}

public async createAiChat(
createAiChatDto: AiChatCreateDto,
): Promise<AiChatDocument> {
const createdAiChat = new this.aiChatModel({
...createAiChatDto,
uuid: uuidv4(),
});
return createdAiChat.save();
}

public async addCouplets(
aiChatId: string,
couplets: [Couplet],
): Promise<void> {
const existingAiChat = await this.aiChatModel
.findOne({ aiChatId: aiChatId })
.exec();

if (!existingAiChat) {
throw new Error(`AiChat with id ${aiChatId} not found`);
}

this._updateAiChat(aiChatId, { couplets });
}
async _updateAiChat(
aiChatId: string,
updateAiChatDto: UpdateAiChatDto,
): Promise<AiChatDocument> {
const existingAiChat = await this.aiChatModel
.findOne({ aiChatId: aiChatId })
.exec();
if (!existingAiChat) {
throw new Error(`AiChat with id ${aiChatId} not found`);
}

// Push the new couplet(s) to the couplet list in the aiChat
return await this.aiChatModel
.findOneAndUpdate(
{ aiChatId: aiChatId },
{ $push: { couplets: { $each: updateAiChatDto.couplets } } },
{ new: true },
)
.exec();
}

async remove(aiChatId: string): Promise<AiChatDocument> {
const deletedAiChat = await this.aiChatModel
.findOneAndDelete({ aiChatId: aiChatId })
.exec();
if (!deletedAiChat) {
throw new Error(`AiChat with id ${aiChatId} not found`);
}
return deletedAiChat;
}
}
62 changes: 62 additions & 0 deletions src/core/database/collections/ai-chats/context-route.provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { Injectable, Scope } from '@nestjs/common';

export class ContextIdentifier {
type: 'server' | 'category' | 'channel' | 'thread' | 'direct-message';
contextId: string;
contextName: string;
}

export class ContextRoute {
sourceInterface: 'discord' | 'slack';
identifiers: ContextIdentifier[];
isDirectMessage: boolean;

constructor(
sourceInterface: 'discord' | 'slack',
identifiers: ContextIdentifier[],
isDirectMessage: boolean,
) {
this.sourceInterface = sourceInterface;
this.identifiers = identifiers;
this.isDirectMessage = isDirectMessage;

// if (!this.validateIdentifiers()) {
// throw new Error('Invalid parent-child relationship in route');
// }
}

// private validateIdentifiers(): boolean {
// for (let index = 1; index < this.identifiers.length; index++) {
// const currentIdentifier = this.identifiers[index];
// const previousIdentifier = this.identifiers[index - 1];
//
// if (
// !currentIdentifier.parentIdentifier ||
// currentIdentifier.parentIdentifier.id !== previousIdentifier.id
// ) {
// return false;
// }
// }
// return true;
// }
}

@Injectable({ scope: Scope.REQUEST })
export class DiscordContextRouteFactory {
static create(
isDirectMessage: boolean,
channel: ContextIdentifier,
server?: ContextIdentifier,
category?: ContextIdentifier,
thread?: ContextIdentifier,
): ContextRoute {
const identifiers: ContextIdentifier[] = [
server,
category,
channel,
thread,
].filter(Boolean) as ContextIdentifier[];
const sourceInterface = 'discord';
return new ContextRoute(sourceInterface, identifiers, isDirectMessage);
}
}
21 changes: 21 additions & 0 deletions src/core/database/collections/couplets/couplet.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { IsOptional, ValidateNested } from 'class-validator';
import { Message } from '../messages/message.schema';
import { Prop } from '@nestjs/mongoose';
import { Types } from 'mongoose';
import { Context } from '@slack/bolt';
import { ContextRoute } from '../ai-chats/context-route.provider';

export class CreateCoupletDto {
@ValidateNested()
@Prop({ type: Types.ObjectId, ref: 'Message', required: true })
humanMessage: Message;

@ValidateNested()
@Prop({ type: Types.ObjectId, ref: 'Message', required: true })
aiResponse: Message;

@IsOptional()
@ValidateNested()
@Prop({ type: ContextRoute })
contextRoute: ContextRoute;
}
20 changes: 20 additions & 0 deletions src/core/database/collections/couplets/couplet.schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document, Types } from 'mongoose';
import { Message } from 'discord.js';
import { ContextRoute } from '../ai-chats/context-route.provider';

export type CoupletDocument = Couplet & Document;

@Schema({ timestamps: true })
export class Couplet {
@Prop({ type: Types.ObjectId, ref: 'Message', required: true })
humanMessage: Message;

@Prop({ type: Types.ObjectId, ref: 'Message', required: true })
aiResponse: Message;

@Prop({ type: ContextRoute })
contextRoute: ContextRoute;
}

export const CoupletSchema = SchemaFactory.createForClass(Couplet);
13 changes: 13 additions & 0 deletions src/core/database/collections/couplets/couplets.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Module } from '@nestjs/common';
import { CoupletsService } from './couplets.service';
import { MongooseModule } from '@nestjs/mongoose';
import { Couplet, CoupletSchema } from './couplet.schema';

@Module({
imports: [
MongooseModule.forFeature([{ name: Couplet.name, schema: CoupletSchema }]),
],
providers: [CoupletsService],
exports: [CoupletsService],
})
export class CoupletsModule {}
Loading

0 comments on commit ef3c38a

Please sign in to comment.