Skip to content

Commit

Permalink
feat(assistant): changed default model to 4o && vision support (images)
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastianmusial committed Jun 10, 2024
1 parent ce390cf commit 8e937d4
Show file tree
Hide file tree
Showing 30 changed files with 373 additions and 52 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 @@ -6,7 +6,7 @@ export const assistantParams: AssistantCreateParams = {
name: '@boldare/openai-assistant',
instructions: `You are a chatbot assistant. Use the general knowledge to answer questions. Speak briefly and clearly.`,
tools: [{ type: 'code_interpreter' }, { type: 'file_search' }],
model: 'gpt-4-turbo',
model: 'gpt-4o',
temperature: 0.05,
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
@if (isAudioEnabled && message) {
<span class="chat-audio" [ngClass]="'chat-audio--' + message.role">
@if(!isStarted) {
<mat-icon (click)="speech()">play_circle</mat-icon>
} @else {
<mat-icon (click)="pause()">pause_circle</mat-icon>
}
</span>
@if (getMessageText) {
<span class="chat-audio" [ngClass]="'chat-audio--' + message.role">
@if (!isStarted) {
<mat-icon (click)="speech()">play_circle</mat-icon>
} @else {
<mat-icon (click)="pause()">pause_circle</mat-icon>
}
</span>
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { Component, Input, OnInit } from '@angular/core';
import { MatIconModule } from '@angular/material/icon';
import { delay } from 'rxjs';
import { ChatAudioResponse, PostSpeechDto } from '@boldare/openai-assistant';
import { NgClass } from '@angular/common';
import { getMessageText } from '../../controls/message-content/message-content.helpers';
import { ChatClientService } from '../../../modules/+chat/shared/chat-client.service';
import {
ChatMessage,
SpeechVoice,
} from '../../../modules/+chat/shared/chat.model';
import { environment } from '../../../../environments/environment';
import { MatIconModule } from '@angular/material/icon';
import { delay } from 'rxjs';
import { ChatAudioResponse, PostSpeechDto } from '@boldare/openai-assistant';
import { NgClass } from '@angular/common';

@Component({
selector: 'ai-chat-audio',
Expand All @@ -19,10 +20,17 @@ import { NgClass } from '@angular/common';
})
export class ChatAudioComponent implements OnInit {
@Input() message!: ChatMessage;
isAudioEnabled = environment.isAudioEnabled;
isStarted = false;
audio = new Audio();

get getMessageText(): string {
if (!environment.isAudioEnabled || !this.message) {
return '';
}

return getMessageText(this.message);
}

constructor(private readonly chatService: ChatClientService) {}

ngOnInit(): void {
Expand All @@ -42,6 +50,10 @@ export class ChatAudioComponent implements OnInit {
}

speech(): void {
if (!this.getMessageText) {
return;
}

this.isStarted = true;

if (this.audio.src) {
Expand All @@ -50,7 +62,7 @@ export class ChatAudioComponent implements OnInit {
}

const payload: PostSpeechDto = {
content: this.message.content,
content: getMessageText(this.message),
voice: SpeechVoice.Onyx,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
[matTooltip]="!isDisabled ? 'Add files' : ''"
[isDisabled]="isDisabled" />
}
@if (isImageContentEnabled) {
<ai-message-content
[matTooltip]="!isDisabled ? 'Add images' : ''"
[isDisabled]="isDisabled" />
}
</ai-controls>

<ai-input
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
RecorderComponent,
} from '../../controls';
import { MatTooltip } from '@angular/material/tooltip';
import { MessageContentComponent } from '../../controls/message-content/message-content.component';

@Component({
selector: 'ai-chat-footer',
Expand All @@ -20,6 +21,7 @@ import { MatTooltip } from '@angular/material/tooltip';
RecorderComponent,
FilesComponent,
MatTooltip,
MessageContentComponent,
],
})
export class ChatFooterComponent {
Expand All @@ -28,4 +30,5 @@ export class ChatFooterComponent {
@Input() isDisabled = false;
@Input() isTranscriptionEnabled = false;
@Input() isAttachmentEnabled = false;
@Input() isImageContentEnabled = false;
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
@if (message) { @if (message.role === 'assistant') {
<ai-chat-avatar></ai-chat-avatar>
}
@if (message) {
@if (message.role === 'assistant') {
<ai-chat-avatar></ai-chat-avatar>
}

<div class="chat-message">
<span markdown [data]="message.content"></span>
<div class="chat-message">
@if (messageText) {
<span markdown [data]="messageText"></span>
}

@if (message.role !== chatRole.System) {
<ai-chat-audio [message]="message"></ai-chat-audio>
}
</div>
@if (message.role !== chatRole.System) {
<ai-chat-audio [message]="message"></ai-chat-audio>
}

@if (messageImage.length) {
<div class="chat-message__file">
@for (image of messageImage; track messageImage) {
<div>File ID: {{ image.image_file.file_id }}</div>
}
</div>
}
</div>
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,10 @@
max-width: 80%;
z-index: 1;
}

.chat-message__file {
border-top: 1px dashed rgba(0, 0, 0, 0.4);
margin-top: $size-2;
padding-top: $size-2;
font-size: 11px;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ import { MarkdownComponent } from 'ngx-markdown';
import { ChatAudioComponent } from '../chat-audio/chat-audio.component';
import { NgClass } from '@angular/common';
import { ChatAvatarComponent } from '../chat-avatar/chat-avatar.component';
import {
getMessageImage,
getMessageText,
} from '../../controls/message-content/message-content.helpers';
import { ImageFileContentBlock } from 'openai/resources/beta/threads';

@Component({
selector: 'ai-chat-message',
Expand All @@ -25,6 +30,14 @@ export class ChatMessageComponent {
@Input() class = '';
chatRole = ChatRole;

get messageText(): string {
return getMessageText(this.message);
}

get messageImage(): ImageFileContentBlock[] {
return getMessageImage(this.message);
}

@HostBinding('class') get getClasses(): string {
return `${this.class} is-${this.message?.role || 'none'}`;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<div class="messages">
@for (message of initialMessages.concat(messages); track message) {
<span #item>
<ai-chat-message [message]="message"></ai-chat-message>
</span>
<ai-chat-message [message]="message"></ai-chat-message>
</span>
} @empty {
<ai-chat-tips [tips]="tips" (tipSelected$)="tipSelected$.emit($event)" />
<ai-chat-tips [tips]="tips" (tipSelected$)="tipSelected$.emit($event)" />
}
<ai-chat-typing [isTyping]="isTyping"></ai-chat-typing>
</div>
7 changes: 4 additions & 3 deletions apps/spa/src/app/components/controls/files/files.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, Input, ViewChild } from '@angular/core';
import { Component, ElementRef, Input, ViewChild } from '@angular/core';
import { MatIcon } from '@angular/material/icon';
import { AiFilesDirective } from './files.directive';
import { toSignal } from '@angular/core/rxjs-interop';
Expand All @@ -19,7 +19,7 @@ import { ControlIconComponent } from '../control-icon/control-icon.component';
styleUrl: './files.component.scss',
})
export class FilesComponent {
@ViewChild('input') input!: HTMLInputElement;
@ViewChild('input') input!: ElementRef<HTMLInputElement>;
@Input() isDisabled = false;
files = toSignal(this.fileService.files$, { initialValue: [] });

Expand All @@ -37,7 +37,8 @@ export class FilesComponent {
clear(event: Event): void {
event.preventDefault();
event.stopPropagation();
this.input.files = null;
this.input.nativeElement.files = null;
this.input.nativeElement.value = '';
this.fileService.clear();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ import {
EventEmitter,
Input,
} from '@angular/core';
import { MessageContent } from 'openai/src/resources/beta/threads/messages';

@Directive({
standalone: true,
selector: '[aiFiles]',
})
export class AiFilesDirective {
@Output() drop$: EventEmitter<FileList> = new EventEmitter();
@Input() files: File[] = [];
@Input() files: Array<File | MessageContent> = [];
event = 'init';

@HostBinding('class') get getClasses(): string {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<ai-control-item
aiFiles
(click)="input.click()"
(drop$)="addFiles($event)"
[files]="imageContentList$()"
[isDisabled]="isDisabled">
<ai-control-icon>image</ai-control-icon>

@if (imageContentList$().length) {
<span class="files__counter" (click)="clear($event)">
<span class="files__number">
{{ imageContentList$().length }}
</span>
<mat-icon class="files__clear"> close </mat-icon>
</span>
}

<input
class="files__input"
type="file"
(change)="onFileChange($event)"
multiple
accept=".jpeg, .jpg, .gif, .png"
#input />
</ai-control-item>
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
.files__input {
display: none;
}

.files__counter {
display: flex;
justify-content: center;
align-items: center;
background-color: var(--color-red-500);
border-radius: 50%;
min-width: 20px;
min-height: 20px;
text-align: center;
font-size: 10px;
color: var(--color-white);
position: absolute;
right: 2px;
top: 2px;
z-index: 1;

&:hover {
.files__number {
display: none;
}

.files__clear {
display: block;
}
}
}

.files__clear {
display: none;
font-size: 12px;
height: 12px;
width: 12px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { MessageContentComponent } from './message-content.component';
import { HttpClientTestingModule } from '@angular/common/http/testing';

describe('MessageContentComponent', () => {
let component: MessageContentComponent;
let fixture: ComponentFixture<MessageContentComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [MessageContentComponent, HttpClientTestingModule],
}).compileComponents();

fixture = TestBed.createComponent(MessageContentComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Component, ElementRef, Input, ViewChild } from '@angular/core';
import { MatIcon } from '@angular/material/icon';
import { toSignal } from '@angular/core/rxjs-interop';
import { MessageContentService } from './message-content.service';
import { ControlItemComponent } from '../control-item/control-item.component';
import { ControlIconComponent } from '../control-icon/control-icon.component';
import { AiFilesDirective } from '../files/files.directive';

@Component({
selector: 'ai-message-content',
standalone: true,
imports: [
MatIcon,
AiFilesDirective,
ControlItemComponent,
ControlIconComponent,
],
templateUrl: './message-content.component.html',
styleUrl: './message-content.component.scss',
})
export class MessageContentComponent {
@ViewChild('input') input!: ElementRef<HTMLInputElement>;
@Input() isDisabled = false;
imageContentList$ = toSignal(this.messageContentService.data$, {
initialValue: [],
});

constructor(private readonly messageContentService: MessageContentService) {}

addFiles(files: FileList) {
this.messageContentService.add(files);
}

onFileChange(event: Event) {
const input = event.target as HTMLInputElement;
this.addFiles(input.files as FileList);
}

clear(event: Event): void {
event.preventDefault();
event.stopPropagation();
this.input.nativeElement.files = null;
this.input.nativeElement.value = '';
this.messageContentService.clear();
}
}
Loading

0 comments on commit 8e937d4

Please sign in to comment.