Skip to content

Commit

Permalink
feat(AI-130): displaying previous thread messages (#23)
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastianmusial committed Feb 8, 2024
1 parent 18cb2e1 commit 374032c
Show file tree
Hide file tree
Showing 14 changed files with 131 additions and 26 deletions.
2 changes: 1 addition & 1 deletion apps/api/src/app/chat/chat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const assistantParams: AssistantCreateParams = {
};

export const assistantConfig: AssistantConfigParams = {
id: process.env.ASSISTANT_ID || '',
id: process.env['ASSISTANT_ID'] || '',
params: assistantParams,
filesDir: './apps/api/src/app/knowledge',
files: [],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
@if (isAudioEnabled) {
<span class="chat-audio" [ngClass]="'chat-audio--' + message?.role">
@if (isAudioEnabled && message) {
<span class="chat-audio" [ngClass]="'chat-audio--' + message.role">
@if(!isStarted) {
<mat-icon (click)="speech()">play_circle</mat-icon>
} @else {
Expand Down
15 changes: 5 additions & 10 deletions apps/spa/src/app/modules/+chat/shared/chat.model.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { ThreadCreateParams } from 'openai/resources/beta';

export interface AudioResponse {
content: string;
}
Expand All @@ -11,19 +9,16 @@ export enum ChatRole {
}

export interface Message {
metadata?: Record<string, unknown>;
content: string;
role: ChatRole;
}

export interface ThreadResponse {
id: string;
}

export interface ThreadConfig {
messages?: ThreadCreateParams.Message[];
}

export enum ChatEvents {
SendMessage = 'send_message',
MessageReceived = 'message_received',
}

export enum MessageStatus {
Invisible = 'invisible',
}
49 changes: 46 additions & 3 deletions apps/spa/src/app/modules/+chat/shared/chat.service.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, Subscription, take } from 'rxjs';
import { ChatRole, Message } from './chat.model';
import {
BehaviorSubject,
distinctUntilChanged,
map,
mergeMap,
Subscription,
take,
} from 'rxjs';
import { ChatRole, Message, MessageStatus } from './chat.model';
import { ChatGatewayService } from './chat-gateway.service';
import { ChatClientService } from './chat-client.service';
import { ThreadService } from './thread.service';
import { ChatFilesService } from './chat-files.service';
import { environment } from '../../../../environments/environment';
import { OpenAiFile } from '@boldare/ai-assistant';
import { OpenAiFile, ThreadResponse } from '@boldare/ai-assistant';
import { Threads } from 'openai/resources/beta';
import MessageContentText = Threads.MessageContentText;
import { ThreadMessage } from 'openai/resources/beta/threads';

@Injectable({ providedIn: 'root' })
export class ChatService {
Expand All @@ -22,10 +32,43 @@ export class ChatService {
) {
document.body.classList.add('ai-chat');

this.setInitialValues();
this.watchMessages();
this.watchVisibility();
}

isMessageInvisible(message: ThreadMessage): boolean {
const metadata = message.metadata as Record<string, unknown>;
return metadata?.['status'] === MessageStatus.Invisible;
}

isTextMessage(message: ThreadMessage): boolean {
return message.content[0].type === 'text';
}

parseMessages(thread: ThreadResponse): Message[] {
return thread.messages
.reverse()
.filter(
message =>
this.isTextMessage(message) && !this.isMessageInvisible(message),
)
.map(message => ({
content: (message.content[0] as MessageContentText).text.value,
role: message.role as ChatRole,
}));
}

setInitialValues(): void {
this.threadService.threadId$
.pipe(
distinctUntilChanged(),
mergeMap(threadId => this.threadService.getThread(threadId)),
map((response: ThreadResponse) => this.parseMessages(response)),
)
.subscribe(data => this.messages$.next(data));
}

toggle(): void {
this.isVisible$.next(!this.isVisible$.value);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { ThreadConfig, ThreadResponse } from '@boldare/ai-assistant';
import { Observable } from 'rxjs';
import { environment } from '../../../../environments/environment';
import { ThreadConfig, ThreadResponse } from './chat.model';

@Injectable({ providedIn: 'root' })
export class ThreadClientService {
Expand All @@ -14,4 +14,10 @@ export class ThreadClientService {
payload,
);
}

getThread(id: string): Observable<ThreadResponse> {
return this.httpClient.get<ThreadResponse>(
`${environment.apiUrl}/assistant/threads/${id}`,
);
}
}
6 changes: 5 additions & 1 deletion apps/spa/src/app/modules/+chat/shared/thread.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable, Subject, take, tap } from 'rxjs';
import { environment } from '../../../../environments/environment';
import { ThreadClientService } from './thread-client.service';
import { ThreadResponse } from './chat.model';
import { ConfigurationFormService } from '../../+configuration/shared/configuration-form.service';
import { ThreadResponse } from '@boldare/ai-assistant';

@Injectable({ providedIn: 'root' })
export class ThreadService {
Expand Down Expand Up @@ -41,4 +41,8 @@ export class ThreadService {

localStorage.removeItem(this.key);
}

getThread(id: string): Observable<ThreadResponse> {
return this.threadClientService.getThread(id).pipe(take(1));
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Injectable } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { SpeechVoice } from '@boldare/ai-assistant';
import { SpeechVoice, ThreadConfig } from '@boldare/ai-assistant';
import { ConfigurationForm } from './configuration.model';
import { ThreadConfig } from '../../+chat/shared/chat.model';
import { MessageStatus } from '../../+chat/shared/chat.model';

@Injectable({ providedIn: 'root' })
export class ConfigurationFormService {
Expand All @@ -19,6 +19,9 @@ export class ConfigurationFormService {
content: `Below you can find my details:
* first name: ${this.form.controls.firstName.value || '-'}
`,
metadata: {
status: MessageStatus.Invisible,
},
},
],
};
Expand Down
1 change: 1 addition & 0 deletions libs/ai-assistant/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from './lib/ai';
export * from './lib/chat';
export * from './lib/run';
export * from './lib/files';
export * from './lib/threads';
35 changes: 35 additions & 0 deletions libs/ai-assistant/src/lib/agent/agent.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { AgentService } from './agent.service';
import { AssistantCreateParams } from 'openai/resources/beta';

describe('AgentService', () => {
let agentService: AgentService;
const agentNameMock = 'agent-name';
const agentMock = async () => 'agent-result';
const definitionMock: AssistantCreateParams.AssistantToolsFunction = {
type: 'function',
function: { name: agentNameMock },
};

beforeEach(() => {
agentService = new AgentService();
});

describe('add', () => {
it('should add new tool', async () => {
agentService.add(definitionMock, agentMock);
expect(agentService.tools).toContain(definitionMock);
});

it('should add new agent', async () => {
agentService.add(definitionMock, agentMock);
expect(agentService.agents[agentNameMock]).toEqual(agentMock);
});
});

describe('get', () => {
it('should return agent', async () => {
agentService.add(definitionMock, agentMock);
expect(agentService.get(agentNameMock)).toEqual(agentMock);
});
});
});
5 changes: 5 additions & 0 deletions libs/ai-assistant/src/lib/chat/chat.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export interface ChatCall {
content: string;
threadId: string;
file_ids?: string[];
metadata?: Record<string, unknown>;
}

export interface ChatAudio {
Expand All @@ -25,3 +26,7 @@ export enum ChatEvents {
SendAudio = 'send_audio',
AudioReceived = 'audio_received',
}

export enum MessageStatus {
Invisible = 'invisible',
}
3 changes: 2 additions & 1 deletion libs/ai-assistant/src/lib/chat/chat.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@ export class ChatService {
) {}

async call(payload: ChatCall): Promise<ChatCallResponse> {
const { threadId, content, file_ids } = payload;
const { threadId, content, file_ids, metadata } = payload;
const message: MessageCreateParams = {
role: 'user',
content,
file_ids,
metadata,
};

await this.threads.messages.create(threadId, message);
Expand Down
4 changes: 2 additions & 2 deletions libs/ai-assistant/src/lib/threads/threads.controller.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { Body, Controller, Get, Param, Post } from '@nestjs/common';
import { Thread } from 'openai/resources/beta';
import { GetThreadParams, ThreadConfig } from './threads.model';
import { GetThreadParams, ThreadConfig, ThreadResponse } from './threads.model';
import { ThreadsService } from './threads.service';

@Controller('assistant/threads')
export class ThreadsController {
constructor(private readonly threadsService: ThreadsService) {}

@Get(':id')
async getThread(@Param() params: GetThreadParams): Promise<Thread> {
async getThread(@Param() params: GetThreadParams): Promise<ThreadResponse> {
return await this.threadsService.getThread(params.id);
}

Expand Down
6 changes: 6 additions & 0 deletions libs/ai-assistant/src/lib/threads/threads.model.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ThreadCreateParams } from 'openai/resources/beta';
import { ThreadMessage } from 'openai/resources/beta/threads';

export interface GetThreadParams {
id: string;
Expand All @@ -7,3 +8,8 @@ export interface GetThreadParams {
export interface ThreadConfig {
messages?: ThreadCreateParams.Message[];
}

export interface ThreadResponse {
id: string;
messages: ThreadMessage[];
}
12 changes: 9 additions & 3 deletions libs/ai-assistant/src/lib/threads/threads.service.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import { Injectable } from '@nestjs/common';
import { Thread } from 'openai/resources/beta';
import { AiService } from '../ai';
import { ThreadConfig } from './threads.model';
import { ThreadConfig, ThreadResponse } from './threads.model';

@Injectable()
export class ThreadsService {
constructor(private readonly aiService: AiService) {}

async getThread(id: string): Promise<Thread> {
return this.aiService.provider.beta.threads.retrieve(id);
async getThread(id: string): Promise<ThreadResponse> {
const messages = await this.aiService.provider.beta.threads.messages.list(
id,
);
return {
id,
messages: messages.data || [],
};
}

async createThread({ messages }: ThreadConfig = {}): Promise<Thread> {
Expand Down

0 comments on commit 374032c

Please sign in to comment.