Skip to content

Commit cde2afe

Browse files
authored
[Tock Studio] Replay of a full dialog + Dialogs view refacto + Search by dialog id + Link to observability trace + NlpStats on dialogs actions + Pass connector and footnotes to faq creation (#1799)
* Single dialog view * Chat ui dialog logger component * Progress spinner on test dialog * Fix css of faqs view * Componentization of dialogs filters. Add search by dialog id. Normalization of satisfaction view css. Cleaning * Link to observability trace if provided * Dialogs with nlpStats * Display connector on dialogs summary + misc * Pass connector and footnotes when creating a faq based on a rag exchange
1 parent 1b96346 commit cde2afe

File tree

43 files changed

+1289
-653
lines changed

Some content is hidden

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

43 files changed

+1289
-653
lines changed

bot/admin/web/src/app/analytics/analytics.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ import { SatisfactionDetailsComponent } from './satisfaction/satisfaction-detail
6161
import { AnalyticsRoutingModule } from './analytics-routing.module';
6262
import { DialogsListComponent } from './dialogs/dialogs-list/dialogs-list.component';
6363
import { DialogComponent } from './dialog/dialog.component';
64+
import { DialogsListFiltersComponent } from './dialogs/dialogs-list/dialogs-list-filters/dialogs-list-filters.component';
6465

6566
@NgModule({
6667
schemas: [NO_ERRORS_SCHEMA],
@@ -114,6 +115,7 @@ import { DialogComponent } from './dialog/dialog.component';
114115
ActivateSatisfactionComponent,
115116
SatisfactionDetailsComponent,
116117
DialogsListComponent,
118+
DialogsListFiltersComponent,
117119
DialogComponent
118120
],
119121
exports: [],
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
<nb-card class="mb-2">
2+
<nb-card-body>
3+
<form [formGroup]="form">
4+
<div class="d-flex justify-content-between align-items-center">
5+
<div class="d-flex gap-1 align-items-center w-100">
6+
<nb-form-field class="flex-grow-1">
7+
<nb-icon
8+
nbPrefix
9+
icon="search"
10+
></nb-icon>
11+
<input
12+
nbInput
13+
fullWidth
14+
fieldSize="medium"
15+
placeholder="Search user text"
16+
type="text"
17+
formControlName="text"
18+
spellcheck="false"
19+
autocomplete="off"
20+
/>
21+
<button
22+
*ngIf="!!getFormControl('text').value"
23+
nbButton
24+
nbSuffix
25+
ghost
26+
nbTooltip="Clear text search"
27+
type="button"
28+
(click)="resetControl(getFormControl('text'))"
29+
>
30+
<nb-icon icon="x-lg"></nb-icon>
31+
</button>
32+
33+
<button
34+
*ngIf="!!getFormControl('exactMatch').value"
35+
nbButton
36+
nbSuffix
37+
ghost
38+
nbTooltip="Exact text match : Search dialogs containing a user question corresponding exactly to the text indicated. Click to launch a partial search."
39+
type="button"
40+
(click)="patchControl(getFormControl('exactMatch'), false)"
41+
>
42+
<nb-icon icon="braces"></nb-icon>
43+
</button>
44+
<button
45+
*ngIf="!getFormControl('exactMatch').value"
46+
nbButton
47+
nbSuffix
48+
ghost
49+
nbTooltip="Partial text match : Search dialogs containing a user question containing the text indicated. Click to search exactly the given text."
50+
type="button"
51+
(click)="patchControl(getFormControl('exactMatch'), true)"
52+
>
53+
<nb-icon icon="braces-asterisk"></nb-icon>
54+
</button>
55+
</nb-form-field>
56+
</div>
57+
58+
<div class="ml-3">
59+
<nb-icon
60+
*ngIf="!advanced"
61+
(click)="swapAdvanced()"
62+
class="pointer"
63+
nbTooltip="Display advanced search options"
64+
icon="chevron-down-outline"
65+
pack="nebular-essentials"
66+
></nb-icon>
67+
<nb-icon
68+
*ngIf="advanced"
69+
(click)="swapAdvanced()"
70+
class="pointer"
71+
nbTooltip="Hide advanced search options"
72+
icon="chevron-up-outline"
73+
pack="nebular-essentials"
74+
></nb-icon>
75+
</div>
76+
</div>
77+
</form>
78+
79+
<div
80+
class="mt-2"
81+
*ngIf="advanced"
82+
>
83+
<form [formGroup]="form">
84+
<div class="row">
85+
<div class="col-6 col-lg-3">
86+
<nb-form-field class="mb-2">
87+
<nb-icon
88+
nbPrefix
89+
icon="link-45deg"
90+
></nb-icon>
91+
<nb-select
92+
fullWidth
93+
nbTooltip="Search for dialogs using this configuration."
94+
placeholder="Configuration"
95+
formControlName="configuration"
96+
>
97+
<nb-option value="">All</nb-option>
98+
<nb-option
99+
*ngFor="let config of configurationNameList"
100+
[value]="config.applicationId"
101+
>
102+
{{ config.label }}
103+
</nb-option>
104+
</nb-select>
105+
</nb-form-field>
106+
</div>
107+
108+
<div class="col-6 col-lg-3">
109+
<nb-form-field class="mb-2">
110+
<nb-icon
111+
nbPrefix
112+
icon="plug"
113+
></nb-icon>
114+
<nb-select
115+
fullWidth
116+
nbTooltip="Search for dialogs using this connector."
117+
placeholder="Connector"
118+
formControlName="connectorType"
119+
>
120+
<nb-option [value]="null">All</nb-option>
121+
<nb-option
122+
*ngFor="let c of connectorTypes"
123+
[value]="c"
124+
>
125+
<img
126+
src="{{ getConnectorTypeIconById(c.id) }}"
127+
class="select-icon align-self-center mr-2"
128+
/>
129+
{{ c.id }}</nb-option
130+
>
131+
</nb-select>
132+
</nb-form-field>
133+
</div>
134+
135+
<div class="col-6 col-lg-3">
136+
<nb-form-field class="mb-2">
137+
<nb-icon
138+
nbPrefix
139+
icon="compass"
140+
></nb-icon>
141+
<nb-select
142+
fullWidth
143+
nbTooltip="Search dialogs containing exchanges corresponding to the selected intent."
144+
placeholder="Intent"
145+
formControlName="intentName"
146+
>
147+
<nb-option [value]="null">All</nb-option>
148+
<nb-option
149+
*ngFor="let intent of state.currentApplication.intents"
150+
[value]="intent.name"
151+
>
152+
{{ intent.label || intent.name }}
153+
</nb-option>
154+
<nb-option value="unknown">Unknown</nb-option>
155+
</nb-select>
156+
</nb-form-field>
157+
</div>
158+
159+
<div class="col-6 col-lg-3">
160+
<nb-form-field class="mb-2">
161+
<nb-icon
162+
nbPrefix
163+
icon="compass"
164+
></nb-icon>
165+
<nb-select
166+
fullWidth
167+
multiple
168+
nbTooltip="Hide exchanges corresponding to selected intents."
169+
placeholder="Hide intents"
170+
formControlName="intentsToHide"
171+
>
172+
<nb-option>Clear selection</nb-option>
173+
<nb-option
174+
*ngFor="let intent of state.currentApplication.intents"
175+
[value]="intent.name"
176+
>
177+
{{ intent.label || intent.name }}
178+
</nb-option>
179+
</nb-select>
180+
</nb-form-field>
181+
</div>
182+
</div>
183+
184+
<div class="row">
185+
<div class="col-6 col-lg-3">
186+
<nb-form-field>
187+
<nb-icon
188+
nbPrefix
189+
icon="wechat"
190+
></nb-icon>
191+
<input
192+
nbInput
193+
fullWidth
194+
fieldSize="medium"
195+
nbTooltip="Search a dialog by id"
196+
placeholder="Dialog id"
197+
type="text"
198+
formControlName="dialogId"
199+
spellcheck="false"
200+
autocomplete="off"
201+
/>
202+
<button
203+
*ngIf="!!getFormControl('dialogId').value"
204+
nbButton
205+
nbSuffix
206+
ghost
207+
nbTooltip="Clear"
208+
type="button"
209+
(click)="resetControl(getFormControl('dialogId'))"
210+
>
211+
<nb-icon icon="x-lg"></nb-icon>
212+
</button>
213+
</nb-form-field>
214+
</div>
215+
216+
<div class="col-6 col-lg-3 pt-2">
217+
<nb-checkbox
218+
nbTooltip="Display only dialogs containing Rag responses"
219+
class="text-nowrap"
220+
formControlName="isGenAiRagDialog"
221+
>Rag responses only
222+
</nb-checkbox>
223+
</div>
224+
225+
<div class="col-6 col-lg-3 pt-2">
226+
<nb-checkbox
227+
nbTooltip="Display dialogues held from the studio test view"
228+
class="text-nowrap"
229+
formControlName="displayTests"
230+
>Display tests
231+
</nb-checkbox>
232+
</div>
233+
</div>
234+
</form>
235+
</div>
236+
</nb-card-body>
237+
</nb-card>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.select-icon {
2+
height: var(--button-medium-icon-size);
3+
width: var(--button-medium-icon-size);
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
3+
import { DialogsListFiltersComponent } from './dialogs-list-filters.component';
4+
5+
describe('DialogsListFiltersComponent', () => {
6+
let component: DialogsListFiltersComponent;
7+
let fixture: ComponentFixture<DialogsListFiltersComponent>;
8+
9+
beforeEach(async () => {
10+
await TestBed.configureTestingModule({
11+
imports: [DialogsListFiltersComponent]
12+
})
13+
.compileComponents();
14+
15+
fixture = TestBed.createComponent(DialogsListFiltersComponent);
16+
component = fixture.componentInstance;
17+
fixture.detectChanges();
18+
});
19+
20+
it('should create', () => {
21+
expect(component).toBeTruthy();
22+
});
23+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
2+
import { FormControl, FormGroup } from '@angular/forms';
3+
import { ConnectorType } from '../../../../core/model/configuration';
4+
import { Subject, debounceTime, take, takeUntil } from 'rxjs';
5+
import { ExtractFormControlTyping } from '../../../../shared/utils/typescript.utils';
6+
import { BotSharedService } from '../../../../shared/bot-shared.service';
7+
import { StateService } from '../../../../core-nlp/state.service';
8+
import { BotConfigurationService } from '../../../../core/bot-configuration.service';
9+
import { RestService } from '../../../../core-nlp/rest/rest.service';
10+
11+
interface DialogListFiltersForm {
12+
exactMatch: FormControl<boolean>;
13+
displayTests: FormControl<boolean>;
14+
dialogId?: FormControl<string>;
15+
text?: FormControl<string>;
16+
intentName?: FormControl<string>;
17+
connectorType?: FormControl<ConnectorType>;
18+
ratings?: FormControl<number[]>;
19+
configuration?: FormControl<string>;
20+
intentsToHide?: FormControl<string[]>;
21+
isGenAiRagDialog?: FormControl<boolean>;
22+
}
23+
24+
export type DialogListFilters = ExtractFormControlTyping<DialogListFiltersForm>;
25+
26+
@Component({
27+
selector: 'tock-dialogs-list-filters',
28+
templateUrl: './dialogs-list-filters.component.html',
29+
styleUrl: './dialogs-list-filters.component.scss'
30+
})
31+
export class DialogsListFiltersComponent implements OnInit {
32+
private readonly destroy$: Subject<boolean> = new Subject();
33+
34+
advanced: boolean = false;
35+
connectorTypes: ConnectorType[] = [];
36+
configurationNameList: { label: string; applicationId: string }[];
37+
38+
@Input() initialFilters: Partial<DialogListFilters>;
39+
@Output() onFilter = new EventEmitter<Partial<DialogListFilters>>();
40+
41+
constructor(public botSharedService: BotSharedService, public state: StateService, private botConfiguration: BotConfigurationService) {}
42+
43+
ngOnInit() {
44+
this.botSharedService
45+
.getConnectorTypes()
46+
.pipe(take(1))
47+
.subscribe((conf) => {
48+
this.connectorTypes = conf.map((it) => it.connectorType);
49+
});
50+
51+
this.botConfiguration.configurations.pipe(takeUntil(this.destroy$)).subscribe((configs) => {
52+
this.configurationNameList = configs
53+
.filter((item) => item.targetConfigurationId == null)
54+
.map((item) => {
55+
const label = `${item.name} > ${item.connectorType.label()} (${item.applicationId})`;
56+
return { label: label, applicationId: item.applicationId };
57+
});
58+
});
59+
60+
if (this.initialFilters) {
61+
this.form.patchValue(this.initialFilters);
62+
}
63+
64+
this.form.valueChanges.pipe(debounceTime(500), takeUntil(this.destroy$)).subscribe(() => this.submitFiltersChange());
65+
}
66+
67+
form = new FormGroup<DialogListFiltersForm>({
68+
exactMatch: new FormControl(),
69+
displayTests: new FormControl(),
70+
dialogId: new FormControl(),
71+
text: new FormControl(),
72+
intentName: new FormControl(),
73+
connectorType: new FormControl(),
74+
ratings: new FormControl(),
75+
configuration: new FormControl(),
76+
intentsToHide: new FormControl([]),
77+
isGenAiRagDialog: new FormControl()
78+
});
79+
80+
getFormControl(formControlName: string): FormControl {
81+
return this.form.get(formControlName) as FormControl;
82+
}
83+
84+
submitFiltersChange(): void {
85+
const formValue = this.form.value;
86+
87+
this.onFilter.emit(formValue);
88+
}
89+
90+
resetControl(ctrl: FormControl, input?: HTMLInputElement): void {
91+
ctrl.reset();
92+
if (input) {
93+
input.value = '';
94+
}
95+
}
96+
97+
patchControl(ctrl: FormControl, value: any): void {
98+
ctrl.patchValue(value);
99+
}
100+
101+
swapAdvanced(): void {
102+
this.advanced = !this.advanced;
103+
}
104+
105+
getConnectorTypeIconById(connectorId: string): string {
106+
if (connectorId === null) connectorId = 'web';
107+
return RestService.connectorIconUrl(connectorId);
108+
}
109+
110+
ngOnDestroy(): void {
111+
this.destroy$.next(true);
112+
this.destroy$.complete();
113+
}
114+
}

0 commit comments

Comments
 (0)