Skip to content

Commit c2f8eec

Browse files
Merge pull request #67 from boldare/feat/annotations
Annotations - displaying numbers and dedicated section in the message
2 parents 8e937d4 + 42582b8 commit c2f8eec

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+675
-195
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<div
2+
class="annotation"
3+
(mouseover)="showDetails()"
4+
[matTooltip]="fileDetails ? fileDetails.filename : 'Reading data...'"
5+
matTooltipPosition="above">
6+
[{{ index }}]
7+
</div>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
@import 'settings';
2+
3+
.annotation {
4+
cursor: pointer;
5+
6+
&:hover {
7+
background-color: rgba(0, 0, 0, 0.15);
8+
}
9+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
import { ChatAnnotationComponent } from './chat-annotation.component';
3+
import { HttpClientTestingModule } from '@angular/common/http/testing';
4+
5+
describe('ChatAnnotationComponent', () => {
6+
let component: ChatAnnotationComponent;
7+
let fixture: ComponentFixture<ChatAnnotationComponent>;
8+
9+
beforeEach(async () => {
10+
await TestBed.configureTestingModule({
11+
imports: [ChatAnnotationComponent, HttpClientTestingModule],
12+
}).compileComponents();
13+
14+
fixture = TestBed.createComponent(ChatAnnotationComponent);
15+
component = fixture.componentInstance;
16+
fixture.detectChanges();
17+
});
18+
19+
it('should create', () => {
20+
expect(component).toBeTruthy();
21+
});
22+
});
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { Component, Input } from '@angular/core';
2+
import { MatTooltip } from '@angular/material/tooltip';
3+
import { Annotation } from 'openai/resources/beta/threads';
4+
import { ChatClientService } from '../../../modules/+chat/shared/chat-client.service';
5+
import { FileObject } from 'openai/resources';
6+
import { isFileCitation } from '../../../pipes/annotation.pipe';
7+
import { take } from 'rxjs';
8+
9+
@Component({
10+
selector: 'ai-chat-annotation',
11+
standalone: true,
12+
templateUrl: './chat-annotation.component.html',
13+
styleUrl: './chat-annotation.component.scss',
14+
imports: [MatTooltip],
15+
})
16+
export class ChatAnnotationComponent {
17+
@Input() annotation!: Annotation;
18+
@Input() index = 1;
19+
fileDetails!: FileObject;
20+
21+
get fileId(): string {
22+
return isFileCitation(this.annotation)
23+
? this.annotation.file_citation.file_id
24+
: this.annotation.file_path.file_id;
25+
}
26+
27+
constructor(private chatClientService: ChatClientService) {}
28+
29+
showDetails() {
30+
if (!this.fileId || !!this.fileDetails) {
31+
return;
32+
}
33+
34+
this.chatClientService
35+
.retriveFile(this.fileId)
36+
.pipe(take(1))
37+
.subscribe(details => (this.fileDetails = details));
38+
}
39+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
@if (message && message.type === 'text' && message.text.annotations.length) {
2+
<div class="title">Annotations:</div>
3+
<div class="content">
4+
@for (
5+
annotation of message.text.annotations;
6+
track annotation.text + $index
7+
) {
8+
<ai-chat-annotation [annotation]="annotation" [index]="$index + 1">
9+
[{{ $index + 1 }}]
10+
</ai-chat-annotation>
11+
}
12+
</div>
13+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
@import 'settings';
2+
3+
:host {
4+
display: flex;
5+
align-items: baseline;
6+
gap: $size-2;
7+
border-top: 1px dashed var(--color-grey-500);
8+
margin-top: $size-3;
9+
padding-top: $size-3;
10+
font-size: 12px;
11+
12+
&:empty {
13+
display: none;
14+
}
15+
}
16+
17+
.title {
18+
font-weight: 500;
19+
}
20+
21+
.content {
22+
display: flex;
23+
gap: $size-1;
24+
margin-top: $size-2;
25+
}
26+
27+
.annotation {
28+
cursor: pointer;
29+
30+
&:hover {
31+
background-color: rgba(0, 0, 0, 0.15);
32+
}
33+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
import { MarkdownModule } from 'ngx-markdown';
3+
import { ChatAnnotationsComponent } from './chat-annotations.component';
4+
5+
describe('ChatAnnotationsComponent', () => {
6+
let component: ChatAnnotationsComponent;
7+
let fixture: ComponentFixture<ChatAnnotationsComponent>;
8+
9+
beforeEach(async () => {
10+
await TestBed.configureTestingModule({
11+
imports: [ChatAnnotationsComponent, MarkdownModule.forRoot()],
12+
}).compileComponents();
13+
14+
fixture = TestBed.createComponent(ChatAnnotationsComponent);
15+
component = fixture.componentInstance;
16+
fixture.detectChanges();
17+
});
18+
19+
it('should create', () => {
20+
expect(component).toBeTruthy();
21+
});
22+
});
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { Component, Input } from '@angular/core';
2+
import { MatTooltip } from '@angular/material/tooltip';
3+
import { MessageContent } from 'openai/resources/beta/threads';
4+
import { ChatAnnotationComponent } from '../chat-annotation/chat-annotation.component';
5+
6+
@Component({
7+
selector: 'ai-chat-annotations',
8+
standalone: true,
9+
templateUrl: './chat-annotations.component.html',
10+
styleUrl: './chat-annotations.component.scss',
11+
imports: [MatTooltip, ChatAnnotationComponent],
12+
})
13+
export class ChatAnnotationsComponent {
14+
@Input() message!: MessageContent;
15+
}

apps/spa/src/app/components/chat/chat-audio/chat-audio.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
@if (getMessageText) {
1+
@if (isAudioEnabled && message && (message | messageText)) {
22
<span class="chat-audio" [ngClass]="'chat-audio--' + message.role">
33
@if (!isStarted) {
44
<mat-icon (click)="speech()">play_circle</mat-icon>

apps/spa/src/app/components/chat/chat-audio/chat-audio.component.ts

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,33 @@ import { Component, Input, OnInit } from '@angular/core';
22
import { MatIconModule } from '@angular/material/icon';
33
import { delay } from 'rxjs';
44
import { ChatAudioResponse, PostSpeechDto } from '@boldare/openai-assistant';
5-
import { NgClass } from '@angular/common';
6-
import { getMessageText } from '../../controls/message-content/message-content.helpers';
5+
import { CommonModule } from '@angular/common';
76
import { ChatClientService } from '../../../modules/+chat/shared/chat-client.service';
87
import {
98
ChatMessage,
109
SpeechVoice,
1110
} from '../../../modules/+chat/shared/chat.model';
11+
import { MessageTextPipe } from '../../../pipes/message-text.pipe';
1212
import { environment } from '../../../../environments/environment';
1313

1414
@Component({
1515
selector: 'ai-chat-audio',
1616
standalone: true,
17-
imports: [MatIconModule, NgClass],
17+
imports: [MatIconModule, CommonModule, MessageTextPipe],
18+
providers: [MessageTextPipe],
1819
templateUrl: './chat-audio.component.html',
1920
styleUrl: './chat-audio.component.scss',
2021
})
2122
export class ChatAudioComponent implements OnInit {
2223
@Input() message!: ChatMessage;
2324
isStarted = false;
2425
audio = new Audio();
26+
isAudioEnabled = environment.isAudioEnabled;
2527

26-
get getMessageText(): string {
27-
if (!environment.isAudioEnabled || !this.message) {
28-
return '';
29-
}
30-
31-
return getMessageText(this.message);
32-
}
33-
34-
constructor(private readonly chatService: ChatClientService) {}
28+
constructor(
29+
private readonly chatService: ChatClientService,
30+
private readonly messageTextPipe: MessageTextPipe,
31+
) {}
3532

3633
ngOnInit(): void {
3734
this.audio.onended = this.onEnded.bind(this);
@@ -50,7 +47,9 @@ export class ChatAudioComponent implements OnInit {
5047
}
5148

5249
speech(): void {
53-
if (!this.getMessageText) {
50+
const content = this.messageTextPipe.transform(this.message);
51+
52+
if (!content) {
5453
return;
5554
}
5655

@@ -62,7 +61,7 @@ export class ChatAudioComponent implements OnInit {
6261
}
6362

6463
const payload: PostSpeechDto = {
65-
content: getMessageText(this.message),
64+
content,
6665
voice: SpeechVoice.Onyx,
6766
};
6867

0 commit comments

Comments
 (0)