Skip to content

Commit

Permalink
Merge pull request #66 from boldare/feat/vision
Browse files Browse the repository at this point in the history
feat(assistant): changed default model to 4o && vision support (images)
  • Loading branch information
sebastianmusial authored Jun 13, 2024
2 parents 3d52538 + c2f8eec commit 9f4aade
Show file tree
Hide file tree
Showing 65 changed files with 949 additions and 148 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
@@ -0,0 +1,7 @@
<div
class="annotation"
(mouseover)="showDetails()"
[matTooltip]="fileDetails ? fileDetails.filename : 'Reading data...'"
matTooltipPosition="above">
[{{ index }}]
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@import 'settings';

.annotation {
cursor: pointer;

&:hover {
background-color: rgba(0, 0, 0, 0.15);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ChatAnnotationComponent } from './chat-annotation.component';
import { HttpClientTestingModule } from '@angular/common/http/testing';

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

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

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

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Component, Input } from '@angular/core';
import { MatTooltip } from '@angular/material/tooltip';
import { Annotation } from 'openai/resources/beta/threads';
import { ChatClientService } from '../../../modules/+chat/shared/chat-client.service';
import { FileObject } from 'openai/resources';
import { isFileCitation } from '../../../pipes/annotation.pipe';
import { take } from 'rxjs';

@Component({
selector: 'ai-chat-annotation',
standalone: true,
templateUrl: './chat-annotation.component.html',
styleUrl: './chat-annotation.component.scss',
imports: [MatTooltip],
})
export class ChatAnnotationComponent {
@Input() annotation!: Annotation;
@Input() index = 1;
fileDetails!: FileObject;

get fileId(): string {
return isFileCitation(this.annotation)
? this.annotation.file_citation.file_id
: this.annotation.file_path.file_id;
}

constructor(private chatClientService: ChatClientService) {}

showDetails() {
if (!this.fileId || !!this.fileDetails) {
return;
}

this.chatClientService
.retriveFile(this.fileId)
.pipe(take(1))
.subscribe(details => (this.fileDetails = details));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@if (message && message.type === 'text' && message.text.annotations.length) {
<div class="title">Annotations:</div>
<div class="content">
@for (
annotation of message.text.annotations;
track annotation.text + $index
) {
<ai-chat-annotation [annotation]="annotation" [index]="$index + 1">
[{{ $index + 1 }}]
</ai-chat-annotation>
}
</div>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
@import 'settings';

:host {
display: flex;
align-items: baseline;
gap: $size-2;
border-top: 1px dashed var(--color-grey-500);
margin-top: $size-3;
padding-top: $size-3;
font-size: 12px;

&:empty {
display: none;
}
}

.title {
font-weight: 500;
}

.content {
display: flex;
gap: $size-1;
margin-top: $size-2;
}

.annotation {
cursor: pointer;

&:hover {
background-color: rgba(0, 0, 0, 0.15);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MarkdownModule } from 'ngx-markdown';
import { ChatAnnotationsComponent } from './chat-annotations.component';

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

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ChatAnnotationsComponent, MarkdownModule.forRoot()],
}).compileComponents();

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

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Component, Input } from '@angular/core';
import { MatTooltip } from '@angular/material/tooltip';
import { MessageContent } from 'openai/resources/beta/threads';
import { ChatAnnotationComponent } from '../chat-annotation/chat-annotation.component';

@Component({
selector: 'ai-chat-annotations',
standalone: true,
templateUrl: './chat-annotations.component.html',
styleUrl: './chat-annotations.component.scss',
imports: [MatTooltip, ChatAnnotationComponent],
})
export class ChatAnnotationsComponent {
@Input() message!: MessageContent;
}
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 (isAudioEnabled && message && (message | messageText)) {
<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,29 +1,34 @@
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 { CommonModule } from '@angular/common';
import { ChatClientService } from '../../../modules/+chat/shared/chat-client.service';
import {
ChatMessage,
SpeechVoice,
} from '../../../modules/+chat/shared/chat.model';
import { MessageTextPipe } from '../../../pipes/message-text.pipe';
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',
standalone: true,
imports: [MatIconModule, NgClass],
imports: [MatIconModule, CommonModule, MessageTextPipe],
providers: [MessageTextPipe],
templateUrl: './chat-audio.component.html',
styleUrl: './chat-audio.component.scss',
})
export class ChatAudioComponent implements OnInit {
@Input() message!: ChatMessage;
isAudioEnabled = environment.isAudioEnabled;
isStarted = false;
audio = new Audio();
isAudioEnabled = environment.isAudioEnabled;

constructor(private readonly chatService: ChatClientService) {}
constructor(
private readonly chatService: ChatClientService,
private readonly messageTextPipe: MessageTextPipe,
) {}

ngOnInit(): void {
this.audio.onended = this.onEnded.bind(this);
Expand All @@ -42,6 +47,12 @@ export class ChatAudioComponent implements OnInit {
}

speech(): void {
const content = this.messageTextPipe.transform(this.message);

if (!content) {
return;
}

this.isStarted = true;

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

const payload: PostSpeechDto = {
content: this.message.content,
content,
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,25 @@
@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">
@for (msg of message.content; track $index) {
@if (msg.type === 'text') {
<span markdown [data]="msg | annotation"></span>
}
<ai-chat-annotations [message]="msg" />
}

@if (message.role !== chatRole.System) {
<ai-chat-audio [message]="message"></ai-chat-audio>
}
</div>
@if ((message | messageImageFile).length) {
<div class="chat-message__file">
@for (
image of message | messageImageFile;
track image.image_file.file_id
) {
<div>File ID: {{ image.image_file.file_id }}</div>
}
</div>
}
</div>
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
justify-content: flex-end;

.chat-message {
border-bottom-left-radius: 0;
background: var(--color-primary-200);
border-bottom-right-radius: 0;
align-self: flex-end;
Expand Down Expand Up @@ -46,3 +45,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
@@ -1,7 +1,7 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MarkdownModule } from 'ngx-markdown';

import { ChatMessageComponent } from './chat-message.component';
import { MarkdownModule } from 'ngx-markdown';

describe('ChatMessageComponent', () => {
let component: ChatMessageComponent;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ 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 { MessageImageFilePipe } from '../../../pipes/message-file.pipe';
import { AnnotationPipe } from '../../../pipes/annotation.pipe';
import { ChatAnnotationsComponent } from '../chat-annotations/chat-annotations.component';

@Component({
selector: 'ai-chat-message',
Expand All @@ -18,10 +21,13 @@ import { ChatAvatarComponent } from '../chat-avatar/chat-avatar.component';
MarkdownComponent,
ChatAudioComponent,
ChatAvatarComponent,
MessageImageFilePipe,
AnnotationPipe,
ChatAnnotationsComponent,
],
})
export class ChatMessageComponent {
@Input() message!: ChatMessage;
@Input() message!: Partial<ChatMessage>;
@Input() class = '';
chatRole = ChatRole;

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) {
@for (message of initialMessages.concat(messages); track message.id) {
<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>
Loading

0 comments on commit 9f4aade

Please sign in to comment.