@memberjunction/ng-skip-chat 2.48.0 → 2.49.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/artifacts/skip-artifact-viewer.component.js +254 -270
- package/dist/lib/artifacts/skip-artifact-viewer.component.js.map +1 -1
- package/dist/lib/artifacts/skip-artifacts-counter.component.js +82 -90
- package/dist/lib/artifacts/skip-artifacts-counter.component.js.map +1 -1
- package/dist/lib/drill-down-info.js +4 -3
- package/dist/lib/drill-down-info.js.map +1 -1
- package/dist/lib/dynamic-report/base-report.js +147 -164
- package/dist/lib/dynamic-report/base-report.js.map +1 -1
- package/dist/lib/dynamic-report/dynamic-chart.js +77 -86
- package/dist/lib/dynamic-report/dynamic-chart.js.map +1 -1
- package/dist/lib/dynamic-report/dynamic-grid.js +80 -93
- package/dist/lib/dynamic-report/dynamic-grid.js.map +1 -1
- package/dist/lib/dynamic-report/dynamic-ui-component.js +173 -188
- package/dist/lib/dynamic-report/dynamic-ui-component.js.map +1 -1
- package/dist/lib/dynamic-report/linear-report.js +16 -26
- package/dist/lib/dynamic-report/linear-report.js.map +1 -1
- package/dist/lib/dynamic-report/skip-dynamic-report-wrapper.js +28 -28
- package/dist/lib/dynamic-report/skip-dynamic-report-wrapper.js.map +1 -1
- package/dist/lib/dynamic-report/skip-react-component-host.js +203 -212
- package/dist/lib/dynamic-report/skip-react-component-host.js.map +1 -1
- package/dist/lib/module.js +22 -22
- package/dist/lib/module.js.map +1 -1
- package/dist/lib/report-cache.js +2 -5
- package/dist/lib/report-cache.js.map +1 -1
- package/dist/lib/skip-chat/skip-chat.component.js +1052 -1087
- package/dist/lib/skip-chat/skip-chat.component.js.map +1 -1
- package/dist/lib/skip-single-message/skip-single-message.component.js +251 -259
- package/dist/lib/skip-single-message/skip-single-message.component.js.map +1 -1
- package/dist/lib/split-panel/skip-split-panel.component.js +52 -51
- package/dist/lib/split-panel/skip-split-panel.component.js.map +1 -1
- package/package.json +13 -13
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
-
});
|
|
9
|
-
};
|
|
10
1
|
import { Component, ViewChild, ViewContainerRef, Input, Output, EventEmitter, } from '@angular/core';
|
|
11
2
|
import { ActivationEnd } from '@angular/router';
|
|
12
3
|
import { LogError, CompositeKey, LogStatus, RunView } from '@memberjunction/core';
|
|
@@ -367,6 +358,103 @@ function SkipChatComponent_kendo_dialog_23_Template(rf, ctx) { if (rf & 1) {
|
|
|
367
358
|
i0.ɵɵtextInterpolate1(" Would you like to ", ctx_r2.messageEditOrDeleteType, " this message? Doing so will result in any subsequent messages in the conversation being deleted. ");
|
|
368
359
|
} }
|
|
369
360
|
export class SkipChatComponent extends BaseAngularComponent {
|
|
361
|
+
el;
|
|
362
|
+
renderer;
|
|
363
|
+
route;
|
|
364
|
+
router;
|
|
365
|
+
location;
|
|
366
|
+
cdRef;
|
|
367
|
+
notificationService;
|
|
368
|
+
AllowSend = true;
|
|
369
|
+
Messages = [];
|
|
370
|
+
Conversations = [];
|
|
371
|
+
SelectedConversation;
|
|
372
|
+
ConversationEditMode = false;
|
|
373
|
+
/**
|
|
374
|
+
* If true, the component will show the conversation list. Default is true.
|
|
375
|
+
*/
|
|
376
|
+
ShowConversationList = true;
|
|
377
|
+
AllowNewConversations = true;
|
|
378
|
+
Title = 'Ask Skip';
|
|
379
|
+
DataContextID = '';
|
|
380
|
+
LinkedEntity = '';
|
|
381
|
+
LinkedEntityCompositeKey = new CompositeKey();
|
|
382
|
+
ShowDataContextButton = true;
|
|
383
|
+
IncludeLinkedConversationsInList = false;
|
|
384
|
+
SkipLogoURL = "assets/Skip Full Logo - Transparent.png";
|
|
385
|
+
SkipMarkOnlyLogoURL = "assets/Skip - Mark Only - Small.png";
|
|
386
|
+
/**
|
|
387
|
+
* Set this property in order to set the user image. This can either be a URL or a Blob
|
|
388
|
+
*/
|
|
389
|
+
UserImage = undefined;
|
|
390
|
+
VerboseLogging = false;
|
|
391
|
+
/**
|
|
392
|
+
* If true, the component will update the browser URL when the conversation changes. If false, it will not update the URL. Default is true.
|
|
393
|
+
*/
|
|
394
|
+
UpdateAppRoute = true;
|
|
395
|
+
/**
|
|
396
|
+
* When set to true, the small Skip logo is shown in the conversation list on the top left of the component
|
|
397
|
+
*/
|
|
398
|
+
ShowSkipLogoInConversationList = false;
|
|
399
|
+
/**
|
|
400
|
+
* When set to true, the component will show a sharing button that allows the user to share the conversation with others. Default is true.
|
|
401
|
+
*/
|
|
402
|
+
ShowSharingButton = true;
|
|
403
|
+
/**
|
|
404
|
+
* This array of role names will be excluded from the list of possible roles to share the conversation with.
|
|
405
|
+
*/
|
|
406
|
+
SharingExcludeRoleNames = [];
|
|
407
|
+
/**
|
|
408
|
+
* This array of emails will be excluded from the list of possible roles to share the conversation with.
|
|
409
|
+
*/
|
|
410
|
+
SharingExcludeEmails = [];
|
|
411
|
+
/**
|
|
412
|
+
* Whether to enable the split-panel viewing for artifacts. Default is true.
|
|
413
|
+
*/
|
|
414
|
+
EnableArtifactSplitView = true;
|
|
415
|
+
/**
|
|
416
|
+
* Default ratio for split panels when viewing artifacts (0-1). Default is 0.5 (left panel takes 50% of width).
|
|
417
|
+
*/
|
|
418
|
+
DefaultSplitRatio = 0.5;
|
|
419
|
+
/**
|
|
420
|
+
* This property is used to set the placeholder text for the textbox where the user types their message to Skip.
|
|
421
|
+
*/
|
|
422
|
+
DefaultTextboxPlaceholder = 'Type your message to Skip here...';
|
|
423
|
+
/**
|
|
424
|
+
* This property is used to set the placeholder text for the textbox where the user types their message to Skip when Skip is processing a request and the text area is disabled.
|
|
425
|
+
*/
|
|
426
|
+
ProcessingTextBoxPlaceholder = 'Please wait...';
|
|
427
|
+
/**
|
|
428
|
+
* Event emitted when the user clicks on a matching report and the application needs to handle the navigation
|
|
429
|
+
*/
|
|
430
|
+
NavigateToMatchingReport = new EventEmitter();
|
|
431
|
+
/**
|
|
432
|
+
* Event emitted whenever a conversation is selected
|
|
433
|
+
*/
|
|
434
|
+
ConversationSelected = new EventEmitter();
|
|
435
|
+
/**
|
|
436
|
+
* This event fires whenever a new report is created.
|
|
437
|
+
*/
|
|
438
|
+
NewReportCreated = new EventEmitter();
|
|
439
|
+
/**
|
|
440
|
+
* This event fires whenever a drill down is requested within a given report.
|
|
441
|
+
*/
|
|
442
|
+
DrillDownEvent = new EventEmitter();
|
|
443
|
+
/**
|
|
444
|
+
* This event fires when an artifact is selected
|
|
445
|
+
*/
|
|
446
|
+
ArtifactSelected = new EventEmitter();
|
|
447
|
+
/**
|
|
448
|
+
* This event fires when an artifact is viewed in the split panel
|
|
449
|
+
*/
|
|
450
|
+
ArtifactViewed = new EventEmitter();
|
|
451
|
+
askSkip;
|
|
452
|
+
askSkipPanel;
|
|
453
|
+
mjContainerRef;
|
|
454
|
+
conversationList;
|
|
455
|
+
askSkipInput;
|
|
456
|
+
scrollContainer;
|
|
457
|
+
topLevelDiv;
|
|
370
458
|
set resourcePermissionsRef(component) {
|
|
371
459
|
if (component) {
|
|
372
460
|
// Component is instantiated
|
|
@@ -377,6 +465,68 @@ export class SkipChatComponent extends BaseAngularComponent {
|
|
|
377
465
|
this.resourcePermissions = null;
|
|
378
466
|
}
|
|
379
467
|
}
|
|
468
|
+
resourcePermissions = null;
|
|
469
|
+
/**
|
|
470
|
+
* Internal state variable to track if the conversation list is visible or not. Defaults to true. Conversation List only is shown if this is true and ShowConversationList is true.
|
|
471
|
+
*/
|
|
472
|
+
IsConversationListVisible = true;
|
|
473
|
+
SelectedConversationUser;
|
|
474
|
+
DataContext;
|
|
475
|
+
_showScrollToBottomIcon = false;
|
|
476
|
+
_AskSkipTextboxPlaceholder = 'Type your message here...';
|
|
477
|
+
_messageInProgress = false;
|
|
478
|
+
_conversationsInProgress = {};
|
|
479
|
+
_conversationsToReload = {};
|
|
480
|
+
_conversationLoadComplete = false;
|
|
481
|
+
_temporaryMessage;
|
|
482
|
+
_intersectionObserver;
|
|
483
|
+
static __skipChatWindowsCurrentlyVisible = 0;
|
|
484
|
+
sub;
|
|
485
|
+
/**
|
|
486
|
+
* Currently selected artifact for viewing in the split panel
|
|
487
|
+
*/
|
|
488
|
+
selectedArtifact = null;
|
|
489
|
+
/**
|
|
490
|
+
* Artifact header info to display in split panel
|
|
491
|
+
*/
|
|
492
|
+
artifactHeaderInfo = null;
|
|
493
|
+
/**
|
|
494
|
+
* Artifact version list for dropdown
|
|
495
|
+
*/
|
|
496
|
+
artifactVersionList = [];
|
|
497
|
+
/**
|
|
498
|
+
* Selected artifact version ID
|
|
499
|
+
*/
|
|
500
|
+
selectedArtifactVersionId = '';
|
|
501
|
+
/**
|
|
502
|
+
* Current split ratio for the split panel
|
|
503
|
+
*/
|
|
504
|
+
SplitRatio = this.DefaultSplitRatio;
|
|
505
|
+
/**
|
|
506
|
+
* The questions that will be displayed in the welcome screen.
|
|
507
|
+
*/
|
|
508
|
+
WelcomeQuestions = [
|
|
509
|
+
{
|
|
510
|
+
topLine: 'Create a report',
|
|
511
|
+
bottomLine: 'with any of your data in it, just ask',
|
|
512
|
+
prompt: "I'd like help creating a new report with data in my system. Can you help me get started?",
|
|
513
|
+
},
|
|
514
|
+
{
|
|
515
|
+
topLine: 'Learn more about',
|
|
516
|
+
bottomLine: 'specific records in the database',
|
|
517
|
+
prompt: 'I would like to dig deeper into my database and get you to analyze a specific record in the database, can you help me with that?',
|
|
518
|
+
},
|
|
519
|
+
{
|
|
520
|
+
topLine: 'Get business advice',
|
|
521
|
+
bottomLine: 'to improve operating results and more',
|
|
522
|
+
prompt: 'I need some advice on how to improve my business operations, can you help me analyze my data and then think about ways to improve my operating results?',
|
|
523
|
+
},
|
|
524
|
+
{
|
|
525
|
+
topLine: 'Seek marketing help',
|
|
526
|
+
bottomLine: 'to segment your audience or build campaigns',
|
|
527
|
+
prompt: 'I need help with marketing, can you help me analyze my data and then think about ways to segment my audience and build campaigns to improve revenue and retention?',
|
|
528
|
+
},
|
|
529
|
+
];
|
|
380
530
|
constructor(el, renderer, route, router, location, cdRef, notificationService) {
|
|
381
531
|
super();
|
|
382
532
|
this.el = el;
|
|
@@ -386,170 +536,15 @@ export class SkipChatComponent extends BaseAngularComponent {
|
|
|
386
536
|
this.location = location;
|
|
387
537
|
this.cdRef = cdRef;
|
|
388
538
|
this.notificationService = notificationService;
|
|
389
|
-
this.AllowSend = true;
|
|
390
|
-
this.Messages = [];
|
|
391
|
-
this.Conversations = [];
|
|
392
|
-
this.ConversationEditMode = false;
|
|
393
|
-
/**
|
|
394
|
-
* If true, the component will show the conversation list. Default is true.
|
|
395
|
-
*/
|
|
396
|
-
this.ShowConversationList = true;
|
|
397
|
-
this.AllowNewConversations = true;
|
|
398
|
-
this.Title = 'Ask Skip';
|
|
399
|
-
this.DataContextID = '';
|
|
400
|
-
this.LinkedEntity = '';
|
|
401
|
-
this.LinkedEntityCompositeKey = new CompositeKey();
|
|
402
|
-
this.ShowDataContextButton = true;
|
|
403
|
-
this.IncludeLinkedConversationsInList = false;
|
|
404
|
-
this.SkipLogoURL = "assets/Skip Full Logo - Transparent.png";
|
|
405
|
-
this.SkipMarkOnlyLogoURL = "assets/Skip - Mark Only - Small.png";
|
|
406
|
-
/**
|
|
407
|
-
* Set this property in order to set the user image. This can either be a URL or a Blob
|
|
408
|
-
*/
|
|
409
|
-
this.UserImage = undefined;
|
|
410
|
-
this.VerboseLogging = false;
|
|
411
|
-
/**
|
|
412
|
-
* If true, the component will update the browser URL when the conversation changes. If false, it will not update the URL. Default is true.
|
|
413
|
-
*/
|
|
414
|
-
this.UpdateAppRoute = true;
|
|
415
|
-
/**
|
|
416
|
-
* When set to true, the small Skip logo is shown in the conversation list on the top left of the component
|
|
417
|
-
*/
|
|
418
|
-
this.ShowSkipLogoInConversationList = false;
|
|
419
|
-
/**
|
|
420
|
-
* When set to true, the component will show a sharing button that allows the user to share the conversation with others. Default is true.
|
|
421
|
-
*/
|
|
422
|
-
this.ShowSharingButton = true;
|
|
423
|
-
/**
|
|
424
|
-
* This array of role names will be excluded from the list of possible roles to share the conversation with.
|
|
425
|
-
*/
|
|
426
|
-
this.SharingExcludeRoleNames = [];
|
|
427
|
-
/**
|
|
428
|
-
* This array of emails will be excluded from the list of possible roles to share the conversation with.
|
|
429
|
-
*/
|
|
430
|
-
this.SharingExcludeEmails = [];
|
|
431
|
-
/**
|
|
432
|
-
* Whether to enable the split-panel viewing for artifacts. Default is true.
|
|
433
|
-
*/
|
|
434
|
-
this.EnableArtifactSplitView = true;
|
|
435
|
-
/**
|
|
436
|
-
* Default ratio for split panels when viewing artifacts (0-1). Default is 0.5 (left panel takes 50% of width).
|
|
437
|
-
*/
|
|
438
|
-
this.DefaultSplitRatio = 0.5;
|
|
439
|
-
/**
|
|
440
|
-
* This property is used to set the placeholder text for the textbox where the user types their message to Skip.
|
|
441
|
-
*/
|
|
442
|
-
this.DefaultTextboxPlaceholder = 'Type your message to Skip here...';
|
|
443
|
-
/**
|
|
444
|
-
* This property is used to set the placeholder text for the textbox where the user types their message to Skip when Skip is processing a request and the text area is disabled.
|
|
445
|
-
*/
|
|
446
|
-
this.ProcessingTextBoxPlaceholder = 'Please wait...';
|
|
447
|
-
/**
|
|
448
|
-
* Event emitted when the user clicks on a matching report and the application needs to handle the navigation
|
|
449
|
-
*/
|
|
450
|
-
this.NavigateToMatchingReport = new EventEmitter();
|
|
451
|
-
/**
|
|
452
|
-
* Event emitted whenever a conversation is selected
|
|
453
|
-
*/
|
|
454
|
-
this.ConversationSelected = new EventEmitter();
|
|
455
|
-
/**
|
|
456
|
-
* This event fires whenever a new report is created.
|
|
457
|
-
*/
|
|
458
|
-
this.NewReportCreated = new EventEmitter();
|
|
459
|
-
/**
|
|
460
|
-
* This event fires whenever a drill down is requested within a given report.
|
|
461
|
-
*/
|
|
462
|
-
this.DrillDownEvent = new EventEmitter();
|
|
463
|
-
/**
|
|
464
|
-
* This event fires when an artifact is selected
|
|
465
|
-
*/
|
|
466
|
-
this.ArtifactSelected = new EventEmitter();
|
|
467
|
-
/**
|
|
468
|
-
* This event fires when an artifact is viewed in the split panel
|
|
469
|
-
*/
|
|
470
|
-
this.ArtifactViewed = new EventEmitter();
|
|
471
|
-
this.resourcePermissions = null;
|
|
472
|
-
/**
|
|
473
|
-
* Internal state variable to track if the conversation list is visible or not. Defaults to true. Conversation List only is shown if this is true and ShowConversationList is true.
|
|
474
|
-
*/
|
|
475
|
-
this.IsConversationListVisible = true;
|
|
476
|
-
this._showScrollToBottomIcon = false;
|
|
477
|
-
this._AskSkipTextboxPlaceholder = 'Type your message here...';
|
|
478
|
-
this._messageInProgress = false;
|
|
479
|
-
this._conversationsInProgress = {};
|
|
480
|
-
this._conversationsToReload = {};
|
|
481
|
-
this._conversationLoadComplete = false;
|
|
482
|
-
/**
|
|
483
|
-
* Currently selected artifact for viewing in the split panel
|
|
484
|
-
*/
|
|
485
|
-
this.selectedArtifact = null;
|
|
486
|
-
/**
|
|
487
|
-
* Artifact header info to display in split panel
|
|
488
|
-
*/
|
|
489
|
-
this.artifactHeaderInfo = null;
|
|
490
|
-
/**
|
|
491
|
-
* Artifact version list for dropdown
|
|
492
|
-
*/
|
|
493
|
-
this.artifactVersionList = [];
|
|
494
|
-
/**
|
|
495
|
-
* Selected artifact version ID
|
|
496
|
-
*/
|
|
497
|
-
this.selectedArtifactVersionId = '';
|
|
498
|
-
/**
|
|
499
|
-
* Current split ratio for the split panel
|
|
500
|
-
*/
|
|
501
|
-
this.SplitRatio = this.DefaultSplitRatio;
|
|
502
|
-
/**
|
|
503
|
-
* The questions that will be displayed in the welcome screen.
|
|
504
|
-
*/
|
|
505
|
-
this.WelcomeQuestions = [
|
|
506
|
-
{
|
|
507
|
-
topLine: 'Create a report',
|
|
508
|
-
bottomLine: 'with any of your data in it, just ask',
|
|
509
|
-
prompt: "I'd like help creating a new report with data in my system. Can you help me get started?",
|
|
510
|
-
},
|
|
511
|
-
{
|
|
512
|
-
topLine: 'Learn more about',
|
|
513
|
-
bottomLine: 'specific records in the database',
|
|
514
|
-
prompt: 'I would like to dig deeper into my database and get you to analyze a specific record in the database, can you help me with that?',
|
|
515
|
-
},
|
|
516
|
-
{
|
|
517
|
-
topLine: 'Get business advice',
|
|
518
|
-
bottomLine: 'to improve operating results and more',
|
|
519
|
-
prompt: 'I need some advice on how to improve my business operations, can you help me analyze my data and then think about ways to improve my operating results?',
|
|
520
|
-
},
|
|
521
|
-
{
|
|
522
|
-
topLine: 'Seek marketing help',
|
|
523
|
-
bottomLine: 'to segment your audience or build campaigns',
|
|
524
|
-
prompt: 'I need help with marketing, can you help me analyze my data and then think about ways to segment my audience and build campaigns to improve revenue and retention?',
|
|
525
|
-
},
|
|
526
|
-
];
|
|
527
|
-
// Track the polling intervals so we can clear them when needed
|
|
528
|
-
this._requestStatusPollingIntervals = {};
|
|
529
|
-
this.conversationResourceTypeID = undefined;
|
|
530
|
-
this._initialLoadComplete = false;
|
|
531
|
-
this._isLoading = false;
|
|
532
|
-
this._numLoads = 0;
|
|
533
|
-
/**
|
|
534
|
-
* This property is used to determine if the component should automatically load the data when it is first shown. Default is true. Turn this off if you want to have more control over the loading sequence and manually call the Load() method when ready.
|
|
535
|
-
*/
|
|
536
|
-
this.AutoLoad = true;
|
|
537
|
-
this._scrollToBottom = false;
|
|
538
|
-
this._oldConvoName = '';
|
|
539
|
-
this.confirmDeleteConversationDialogOpen = false;
|
|
540
|
-
this.SelectedConversationCurrentUserPermissionLevel = null;
|
|
541
|
-
this._usedStartMessages = [];
|
|
542
|
-
this._processingStatus = {};
|
|
543
|
-
this.isDataContextDialogVisible = false;
|
|
544
|
-
this.isSharingDialogVisible = false;
|
|
545
|
-
this.confirmMessageEditOrDeleteDialogOpen = false;
|
|
546
|
-
this.messageEditOrDeleteType = 'edit';
|
|
547
539
|
}
|
|
540
|
+
paramsSubscription;
|
|
548
541
|
ngOnInit() {
|
|
549
542
|
}
|
|
550
543
|
static get SkipChatWindowsCurrentlyVisible() {
|
|
551
544
|
return SkipChatComponent.__skipChatWindowsCurrentlyVisible;
|
|
552
545
|
}
|
|
546
|
+
_mjGlobalEventSub;
|
|
547
|
+
_providerPushStatusSub;
|
|
553
548
|
SubscribeToNotifications() {
|
|
554
549
|
try {
|
|
555
550
|
// subscribe to MJ events for push status updates, but first unsubscribe if we are already subscribed
|
|
@@ -607,12 +602,11 @@ export class SkipChatComponent extends BaseAngularComponent {
|
|
|
607
602
|
}
|
|
608
603
|
}
|
|
609
604
|
HandlePushStatusUpdate(statusObj) {
|
|
610
|
-
var _a, _b, _c;
|
|
611
605
|
try {
|
|
612
606
|
const obj = statusObj;
|
|
613
|
-
if (
|
|
607
|
+
if (obj.type?.trim().toLowerCase() === 'askskip' && obj.status?.trim().toLowerCase() === 'ok') {
|
|
614
608
|
if (obj.conversationID && this._conversationsInProgress[obj.conversationID]) {
|
|
615
|
-
if (obj.conversationID ===
|
|
609
|
+
if (obj.conversationID === this.SelectedConversation?.ID) {
|
|
616
610
|
if (obj.message && obj.message.length > 0) {
|
|
617
611
|
// we are in the midst of a possibly long running process for Skip, and we got a message here, so go ahead and display it in the temporary message
|
|
618
612
|
this.LogVerbose(`Skip Chat: Received Push Status for conversation ${obj.conversationID} with message: ${obj.message}`);
|
|
@@ -652,9 +646,8 @@ export class SkipChatComponent extends BaseAngularComponent {
|
|
|
652
646
|
}
|
|
653
647
|
}
|
|
654
648
|
GetConversationItemClass(item) {
|
|
655
|
-
var _a;
|
|
656
649
|
let classInfo = '';
|
|
657
|
-
if (
|
|
650
|
+
if (this.SelectedConversation?.ID === item.ID)
|
|
658
651
|
classInfo += 'conversation-item-selected';
|
|
659
652
|
if (item.LinkedEntityID && item.LinkedRecordID) {
|
|
660
653
|
// an embedded conversation
|
|
@@ -698,22 +691,19 @@ export class SkipChatComponent extends BaseAngularComponent {
|
|
|
698
691
|
this._AskSkipTextboxPlaceholder = this.DefaultTextboxPlaceholder;
|
|
699
692
|
}
|
|
700
693
|
}
|
|
701
|
-
SetSelectedConversationUser() {
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
if (
|
|
705
|
-
const
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
});
|
|
711
|
-
this.SelectedConversationUser = result && result.Success ? result.Results[0] : undefined;
|
|
712
|
-
}
|
|
713
|
-
else
|
|
714
|
-
this.SelectedConversationUser = p.CurrentUser; // current user is the one for this convo, just use that to avoid the extra query
|
|
694
|
+
async SetSelectedConversationUser() {
|
|
695
|
+
if (this.SelectedConversation?.UserID) {
|
|
696
|
+
const p = this.ProviderToUse;
|
|
697
|
+
if (p.CurrentUser.ID !== this.SelectedConversation.UserID) {
|
|
698
|
+
const result = await this.RunViewToUse.RunView({
|
|
699
|
+
EntityName: 'Users',
|
|
700
|
+
ExtraFilter: `ID='${this.SelectedConversation.UserID}'`,
|
|
701
|
+
});
|
|
702
|
+
this.SelectedConversationUser = result && result.Success ? result.Results[0] : undefined;
|
|
715
703
|
}
|
|
716
|
-
|
|
704
|
+
else
|
|
705
|
+
this.SelectedConversationUser = p.CurrentUser; // current user is the one for this convo, just use that to avoid the extra query
|
|
706
|
+
}
|
|
717
707
|
}
|
|
718
708
|
get LinkedEntityID() {
|
|
719
709
|
if (this.LinkedEntity && this.LinkedEntity.length > 0) {
|
|
@@ -751,6 +741,8 @@ export class SkipChatComponent extends BaseAngularComponent {
|
|
|
751
741
|
LogError(`Error checking for processing conversations: ${error}`);
|
|
752
742
|
}
|
|
753
743
|
}
|
|
744
|
+
// Track the polling intervals so we can clear them when needed
|
|
745
|
+
_requestStatusPollingIntervals = {};
|
|
754
746
|
/**
|
|
755
747
|
* Starts polling for conversation status updates
|
|
756
748
|
* @param conversationId The ID of the conversation to poll for
|
|
@@ -759,9 +751,9 @@ export class SkipChatComponent extends BaseAngularComponent {
|
|
|
759
751
|
// Clear any existing polling for this conversation
|
|
760
752
|
this.stopRequestStatusPolling(convoID);
|
|
761
753
|
// Set up polling every 3 seconds
|
|
762
|
-
this._requestStatusPollingIntervals[convoID] = setInterval(() =>
|
|
763
|
-
|
|
764
|
-
}
|
|
754
|
+
this._requestStatusPollingIntervals[convoID] = setInterval(async () => {
|
|
755
|
+
await this.checkRequestStatus(convoID);
|
|
756
|
+
}, 3000);
|
|
765
757
|
}
|
|
766
758
|
/**
|
|
767
759
|
* Stops polling for conversation status updates
|
|
@@ -780,105 +772,100 @@ export class SkipChatComponent extends BaseAngularComponent {
|
|
|
780
772
|
* @param limit Optional limit on number of records to return
|
|
781
773
|
* @returns Array of ConversationDetailEntity objects
|
|
782
774
|
*/
|
|
783
|
-
LoadRecentConversationDetails(conversationId, role, limit) {
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
if (!conversationId || conversationId.length === 0) {
|
|
787
|
-
return [];
|
|
788
|
-
}
|
|
789
|
-
// Construct the query to run
|
|
790
|
-
const extraFilter = role ?
|
|
791
|
-
`ConversationID='${conversationId}' AND Role='${role}'` :
|
|
792
|
-
`ConversationID='${conversationId}'`;
|
|
793
|
-
// Use RunView for convenience
|
|
794
|
-
const result = yield this.RunViewToUse.RunView({
|
|
795
|
-
EntityName: 'Conversation Details',
|
|
796
|
-
ExtraFilter: extraFilter,
|
|
797
|
-
ResultType: 'entity_object',
|
|
798
|
-
OrderBy: '__mj_CreatedAt DESC', // Most recent first
|
|
799
|
-
IgnoreMaxRows: false,
|
|
800
|
-
MaxRows: limit,
|
|
801
|
-
});
|
|
802
|
-
if (result && result.Success) {
|
|
803
|
-
return result.Results;
|
|
804
|
-
}
|
|
775
|
+
async LoadRecentConversationDetails(conversationId, role, limit) {
|
|
776
|
+
try {
|
|
777
|
+
if (!conversationId || conversationId.length === 0) {
|
|
805
778
|
return [];
|
|
806
779
|
}
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
780
|
+
// Construct the query to run
|
|
781
|
+
const extraFilter = role ?
|
|
782
|
+
`ConversationID='${conversationId}' AND Role='${role}'` :
|
|
783
|
+
`ConversationID='${conversationId}'`;
|
|
784
|
+
// Use RunView for convenience
|
|
785
|
+
const result = await this.RunViewToUse.RunView({
|
|
786
|
+
EntityName: 'Conversation Details',
|
|
787
|
+
ExtraFilter: extraFilter,
|
|
788
|
+
ResultType: 'entity_object',
|
|
789
|
+
OrderBy: '__mj_CreatedAt DESC', // Most recent first
|
|
790
|
+
IgnoreMaxRows: false,
|
|
791
|
+
MaxRows: limit,
|
|
792
|
+
});
|
|
793
|
+
if (result && result.Success) {
|
|
794
|
+
return result.Results;
|
|
810
795
|
}
|
|
811
|
-
|
|
796
|
+
return [];
|
|
797
|
+
}
|
|
798
|
+
catch (err) {
|
|
799
|
+
LogError(`Error loading conversation details: ${err}`);
|
|
800
|
+
return [];
|
|
801
|
+
}
|
|
812
802
|
}
|
|
813
803
|
/**
|
|
814
804
|
* Checks the status of a conversation request
|
|
815
805
|
* @param conversationId The ID of the conversation to check
|
|
816
806
|
*/
|
|
817
|
-
checkRequestStatus(convoID) {
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
this
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
// we are on the first message so skip renamed the convo, use that
|
|
840
|
-
this.SelectedConversation.Name = conversation.Name; // this will update the UI
|
|
841
|
-
}
|
|
842
|
-
const convoDetails = yield this.LoadRecentConversationDetails(convoID, 'AI', 1);
|
|
843
|
-
const aiDetail = convoDetails[0];
|
|
844
|
-
if (aiDetail) {
|
|
845
|
-
this.AddMessageToCurrentConversation(aiDetail, true, true);
|
|
846
|
-
// Ensure scroll to bottom after adding AI message from status polling
|
|
847
|
-
setTimeout(() => {
|
|
848
|
-
this.scrollToBottom();
|
|
849
|
-
}, 100);
|
|
850
|
-
// Automatically show artifact if the new AI message has one
|
|
851
|
-
this.autoShowArtifactIfPresent(aiDetail);
|
|
852
|
-
}
|
|
853
|
-
// NOTE: we don't create a user notification at this point, that is done on the server and via GraphQL subscriptions it tells us and we update the UI automatically...
|
|
854
|
-
}
|
|
855
|
-
if (this.SelectedConversation) {
|
|
856
|
-
this.setProcessingStatus(this.SelectedConversation.ID, false);
|
|
807
|
+
async checkRequestStatus(convoID) {
|
|
808
|
+
try {
|
|
809
|
+
const p = this.ProviderToUse;
|
|
810
|
+
const conversation = await p.GetEntityObject('Conversations', p.CurrentUser);
|
|
811
|
+
const loadResult = await conversation.Load(convoID);
|
|
812
|
+
if (loadResult && conversation.Status === 'Available') {
|
|
813
|
+
// Conversation is no longer processing, stop polling and refresh the conversation
|
|
814
|
+
this.stopRequestStatusPolling(conversation.ID);
|
|
815
|
+
this.cdRef.detach();
|
|
816
|
+
if (convoID !== this.SelectedConversation?.ID) {
|
|
817
|
+
// this scenario arises when we have a selected convo change after we submitted our request to skip
|
|
818
|
+
// so we do nothing here other than update the status.
|
|
819
|
+
this.setProcessingStatus(convoID, false);
|
|
820
|
+
//the next time the user selects this convo, we will fetch messages
|
|
821
|
+
//from the server rather than using the ones in cache
|
|
822
|
+
this._conversationsToReload[convoID] = true;
|
|
823
|
+
}
|
|
824
|
+
else {
|
|
825
|
+
this.setProcessingStatus(convoID, false);
|
|
826
|
+
if (this.SelectedConversation.Name === 'New Chat' || this.SelectedConversation.Name?.trim().length === 0 || this.SelectedConversation.Name !== conversation.Name) {
|
|
827
|
+
// we are on the first message so skip renamed the convo, use that
|
|
828
|
+
this.SelectedConversation.Name = conversation.Name; // this will update the UI
|
|
857
829
|
}
|
|
858
|
-
const
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
this.
|
|
862
|
-
//
|
|
863
|
-
|
|
830
|
+
const convoDetails = await this.LoadRecentConversationDetails(convoID, 'AI', 1);
|
|
831
|
+
const aiDetail = convoDetails[0];
|
|
832
|
+
if (aiDetail) {
|
|
833
|
+
this.AddMessageToCurrentConversation(aiDetail, true, true);
|
|
834
|
+
// Ensure scroll to bottom after adding AI message from status polling
|
|
835
|
+
setTimeout(() => {
|
|
836
|
+
this.scrollToBottom();
|
|
837
|
+
}, 100);
|
|
838
|
+
// Automatically show artifact if the new AI message has one
|
|
839
|
+
this.autoShowArtifactIfPresent(aiDetail);
|
|
864
840
|
}
|
|
865
|
-
this
|
|
866
|
-
this._conversationsInProgress[convoID] = false;
|
|
867
|
-
this._messageInProgress = false;
|
|
868
|
-
// now tell Angular to resume its change detection
|
|
869
|
-
this.cdRef.reattach();
|
|
870
|
-
this.cdRef.detectChanges();
|
|
871
|
-
// invoke manual resize with a delay to ensure that the scroll to bottom has taken place
|
|
872
|
-
//InvokeManualResize();
|
|
873
|
-
this.SetSkipStatusMessage('', 500); // slight delay to ensure that the message is removed after the UI has updated with the new response message
|
|
874
|
-
// now set focus on the input box
|
|
875
|
-
this.askSkipInput.nativeElement.focus();
|
|
841
|
+
// NOTE: we don't create a user notification at this point, that is done on the server and via GraphQL subscriptions it tells us and we update the UI automatically...
|
|
876
842
|
}
|
|
843
|
+
if (this.SelectedConversation) {
|
|
844
|
+
this.setProcessingStatus(this.SelectedConversation.ID, false);
|
|
845
|
+
}
|
|
846
|
+
const idx = this.Conversations.findIndex((c) => c.ID === convoID);
|
|
847
|
+
if (idx >= 0) {
|
|
848
|
+
// update our this.Conversations array to reflect the updated conversation. First find the index of the conversation and then get that item and update it
|
|
849
|
+
this.Conversations[idx] = conversation;
|
|
850
|
+
//rerender the list box
|
|
851
|
+
this.Conversations = [...this.Conversations];
|
|
852
|
+
}
|
|
853
|
+
this.AllowSend = true;
|
|
854
|
+
this._conversationsInProgress[convoID] = false;
|
|
855
|
+
this._messageInProgress = false;
|
|
856
|
+
// now tell Angular to resume its change detection
|
|
857
|
+
this.cdRef.reattach();
|
|
858
|
+
this.cdRef.detectChanges();
|
|
859
|
+
// invoke manual resize with a delay to ensure that the scroll to bottom has taken place
|
|
860
|
+
//InvokeManualResize();
|
|
861
|
+
this.SetSkipStatusMessage('', 500); // slight delay to ensure that the message is removed after the UI has updated with the new response message
|
|
862
|
+
// now set focus on the input box
|
|
863
|
+
this.askSkipInput.nativeElement.focus();
|
|
877
864
|
}
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
}
|
|
881
|
-
}
|
|
865
|
+
}
|
|
866
|
+
catch (error) {
|
|
867
|
+
LogError(`Error checking request status for conversation with ID ${convoID}: ${error}`);
|
|
868
|
+
}
|
|
882
869
|
}
|
|
883
870
|
ngOnDestroy() {
|
|
884
871
|
// Unsubscribe to prevent memory leaks
|
|
@@ -920,78 +907,81 @@ export class SkipChatComponent extends BaseAngularComponent {
|
|
|
920
907
|
}
|
|
921
908
|
}
|
|
922
909
|
}
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
910
|
+
conversationResourceTypeID = undefined;
|
|
911
|
+
_initialLoadComplete = false;
|
|
912
|
+
_isLoading = false;
|
|
913
|
+
_numLoads = 0;
|
|
914
|
+
async ngAfterViewInit() {
|
|
915
|
+
if (this.AutoLoad)
|
|
916
|
+
await this.Load();
|
|
928
917
|
}
|
|
929
918
|
get ResourcePermissionEngine() {
|
|
930
919
|
return ResourcePermissionEngine.GetProviderInstance(this.ProviderToUse, ResourcePermissionEngine);
|
|
931
920
|
}
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
this.
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
921
|
+
/**
|
|
922
|
+
* This property is used to determine if the component should automatically load the data when it is first shown. Default is true. Turn this off if you want to have more control over the loading sequence and manually call the Load() method when ready.
|
|
923
|
+
*/
|
|
924
|
+
AutoLoad = true;
|
|
925
|
+
async Load(forceRefresh = false) {
|
|
926
|
+
if (!this._initialLoadComplete || forceRefresh) {
|
|
927
|
+
this.SubscribeToNotifications(); // subscribe to notifications, this auto-cleans up old subs if they exist - we do this HERE becuase the ProviderToUse is set by this point in time whereas in ngOnInit it's not necessarily set yet
|
|
928
|
+
await this.ResourcePermissionEngine.Config(false, this.ProviderToUse.CurrentUser, this.ProviderToUse);
|
|
929
|
+
this.conversationResourceTypeID = this.ResourcePermissionEngine.ResourceTypes.find((rt) => rt.Name === 'Conversations')?.ID;
|
|
930
|
+
MJGlobal.Instance.ObjectCache.Remove('Conversations'); // clear the cache so we reload the conversations
|
|
931
|
+
if (this.paramsSubscription) {
|
|
932
|
+
this.paramsSubscription.unsubscribe();
|
|
933
|
+
}
|
|
934
|
+
this.updateParentTabPanelStyling();
|
|
935
|
+
SkipChatComponent.__skipChatWindowsCurrentlyVisible = 0; // set to zero each time we are called here
|
|
936
|
+
// create an intersection observer to see if we are visible
|
|
937
|
+
this._intersectionObserver = new IntersectionObserver(async (entries) => {
|
|
938
|
+
const [entry] = entries;
|
|
939
|
+
if (!entry.isIntersecting) {
|
|
940
|
+
// we are NOT visible, so decrement the count of visible instances, but only if we were ever visible, meaning sometimes we get this situation before we are ever shown
|
|
941
|
+
if (this._initialLoadComplete || forceRefresh) {
|
|
942
|
+
// don't go below 0
|
|
943
|
+
SkipChatComponent.__skipChatWindowsCurrentlyVisible = Math.max(0, SkipChatComponent.__skipChatWindowsCurrentlyVisible - 1);
|
|
954
944
|
}
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
this._initialLoadComplete = true; // do this once
|
|
967
|
-
const conversationId = params.conversationId;
|
|
968
|
-
if (conversationId) {
|
|
969
|
-
yield this.loadConversations(conversationId); // Load the conversation based on the conversationId
|
|
970
|
-
}
|
|
971
|
-
else {
|
|
972
|
-
yield this.loadConversations();
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
|
-
}));
|
|
976
|
-
}
|
|
977
|
-
else if (this.LinkedEntity && this.CompositeKeyIsPopulated()) {
|
|
978
|
-
// now, do stuff if we are embedded in another component with a LinkedEntity/LinkedEntityRecordID
|
|
945
|
+
}
|
|
946
|
+
else {
|
|
947
|
+
// we are now visible, increment the count of visible instances
|
|
948
|
+
SkipChatComponent.__skipChatWindowsCurrentlyVisible++;
|
|
949
|
+
if (!this._initialLoadComplete || forceRefresh) {
|
|
950
|
+
// we are now visible, for the first time, first fire off an InvokeManualResize to ensure the parent container is resized properly
|
|
951
|
+
InvokeManualResize();
|
|
952
|
+
// first do stuff if we're on "global" skip chat mode...
|
|
953
|
+
if (this.ShowConversationList && !this.LinkedEntity && this.LinkedEntity.trim().length === 0 && !this.CompositeKeyIsPopulated()) {
|
|
954
|
+
// only subscribe to the route params if we don't have a linked entity and record id, meaning we're in the context of the top level Skip Chat UI, not embedded somewhere
|
|
955
|
+
this.paramsSubscription = this.route.params.subscribe(async (params) => {
|
|
979
956
|
if (!this._initialLoadComplete || forceRefresh) {
|
|
980
957
|
this._initialLoadComplete = true; // do this once
|
|
981
|
-
|
|
958
|
+
const conversationId = params.conversationId;
|
|
959
|
+
if (conversationId) {
|
|
960
|
+
await this.loadConversations(conversationId); // Load the conversation based on the conversationId
|
|
961
|
+
}
|
|
962
|
+
else {
|
|
963
|
+
await this.loadConversations();
|
|
964
|
+
}
|
|
982
965
|
}
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
else if (this.LinkedEntity && this.CompositeKeyIsPopulated()) {
|
|
969
|
+
// now, do stuff if we are embedded in another component with a LinkedEntity/LinkedEntityRecordID
|
|
970
|
+
if (!this._initialLoadComplete || forceRefresh) {
|
|
971
|
+
this._initialLoadComplete = true; // do this once
|
|
972
|
+
await this.loadConversations(); // Load the conversation which will filter by the linked entity and record id
|
|
983
973
|
}
|
|
984
|
-
this.checkScroll();
|
|
985
974
|
}
|
|
986
|
-
|
|
987
|
-
//this._intersectionObserver!.unobserve(this.topLevelDiv.nativeElement);
|
|
975
|
+
this.checkScroll();
|
|
988
976
|
}
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
977
|
+
// Only care about the first time we are visible, so unobserve here to save resources
|
|
978
|
+
//this._intersectionObserver!.unobserve(this.topLevelDiv.nativeElement);
|
|
979
|
+
}
|
|
980
|
+
});
|
|
981
|
+
// now fire up the observer on the top level div
|
|
982
|
+
this._intersectionObserver.observe(this.topLevelDiv.nativeElement);
|
|
983
|
+
this.cdRef.detectChanges();
|
|
984
|
+
}
|
|
995
985
|
}
|
|
996
986
|
/**
|
|
997
987
|
* This method is used to refresh the data in the component. This will reload the conversations and messages from the server.
|
|
@@ -999,6 +989,7 @@ export class SkipChatComponent extends BaseAngularComponent {
|
|
|
999
989
|
Refresh() {
|
|
1000
990
|
this.Load(true);
|
|
1001
991
|
}
|
|
992
|
+
_scrollToBottom = false;
|
|
1002
993
|
ngAfterViewChecked() {
|
|
1003
994
|
if (this._scrollToBottom) {
|
|
1004
995
|
this._scrollToBottom = false;
|
|
@@ -1014,102 +1005,100 @@ export class SkipChatComponent extends BaseAngularComponent {
|
|
|
1014
1005
|
this.IncludeLinkedConversationsInList = !this.IncludeLinkedConversationsInList;
|
|
1015
1006
|
this.loadConversations();
|
|
1016
1007
|
}
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
if (!cachedConversations) {
|
|
1042
|
-
LogError('Error loading conversations from the database');
|
|
1043
|
-
return; // we couldn't load the conversations, so just return
|
|
1044
|
-
}
|
|
1045
|
-
// now setup the array we use to bind to the UI
|
|
1046
|
-
if (this.IncludeLinkedConversationsInList) {
|
|
1047
|
-
this.Conversations = cachedConversations; // dont filter out linked conversations
|
|
1008
|
+
static _cacheRootKey = '___SkipChat__';
|
|
1009
|
+
async loadConversations(conversationIdToLoad = undefined) {
|
|
1010
|
+
this._isLoading = true;
|
|
1011
|
+
const cacheConversationsKey = `${SkipChatComponent._cacheRootKey}_Conversations`;
|
|
1012
|
+
const cacheConversationServerURLKey = `${SkipChatComponent._cacheRootKey}_ConversationsServerURL`;
|
|
1013
|
+
let cachedConversations = MJGlobal.Instance.ObjectCache.Find(cacheConversationsKey);
|
|
1014
|
+
const cacheConversationsServerURL = MJGlobal.Instance.ObjectCache.Find(cacheConversationServerURLKey);
|
|
1015
|
+
const gqlConfig = this.ProviderToUse.ConfigData;
|
|
1016
|
+
if (!cachedConversations || gqlConfig.URL !== cacheConversationsServerURL) {
|
|
1017
|
+
// load up from the database as we don't have any cached conversations
|
|
1018
|
+
// or we have a different URL
|
|
1019
|
+
const result = await this.RunViewToUse.RunView({
|
|
1020
|
+
EntityName: 'Conversations',
|
|
1021
|
+
ExtraFilter: `UserID='${this.ProviderToUse.CurrentUser.ID}'`,
|
|
1022
|
+
OrderBy: '__mj_CreatedAt DESC', // get in reverse order so we have latest on top
|
|
1023
|
+
});
|
|
1024
|
+
if (result && result.Success) {
|
|
1025
|
+
// now, cache the conversations for future use
|
|
1026
|
+
MJGlobal.Instance.ObjectCache.Replace(cacheConversationsKey, result.Results); // use Replace for safety in case someone else has added to the cache between when we checked and now
|
|
1027
|
+
MJGlobal.Instance.ObjectCache.Replace(cacheConversationServerURLKey, gqlConfig.URL); // ensure the key for the conversations object is set to the current server URL
|
|
1028
|
+
// also set the local variable so we can use it below
|
|
1029
|
+
cachedConversations = result.Results;
|
|
1048
1030
|
}
|
|
1049
|
-
|
|
1050
|
-
|
|
1031
|
+
}
|
|
1032
|
+
if (!cachedConversations) {
|
|
1033
|
+
LogError('Error loading conversations from the database');
|
|
1034
|
+
return; // we couldn't load the conversations, so just return
|
|
1035
|
+
}
|
|
1036
|
+
// now setup the array we use to bind to the UI
|
|
1037
|
+
if (this.IncludeLinkedConversationsInList) {
|
|
1038
|
+
this.Conversations = cachedConversations; // dont filter out linked conversations
|
|
1039
|
+
}
|
|
1040
|
+
else if (this.LinkedEntity && this.LinkedEntity.length > 0 && this.CompositeKeyIsPopulated()) {
|
|
1041
|
+
this.Conversations = cachedConversations.filter((c) => c.LinkedEntity === this.LinkedEntity && c.LinkedRecordID === this.LinkedEntityCompositeKey.Values()); // ONLY include the linked conversations
|
|
1042
|
+
}
|
|
1043
|
+
else {
|
|
1044
|
+
this.Conversations = cachedConversations.filter((c) => !(c.LinkedEntity && c.LinkedEntity.length > 0 && c.LinkedRecordID && c.LinkedRecordID.length > 0)); // filter OUT linked conversations
|
|
1045
|
+
}
|
|
1046
|
+
if (this.Conversations.length === 0 && !conversationIdToLoad) {
|
|
1047
|
+
// no conversations, so create a new one, BUT ONLY IF we weren't asked to load a specific conversation
|
|
1048
|
+
// that can happen when a given user doesn't have their own conversations, but they are trying to view a shared conversation
|
|
1049
|
+
await this.CreateNewConversation();
|
|
1050
|
+
InvokeManualResize(1);
|
|
1051
|
+
}
|
|
1052
|
+
else if (conversationIdToLoad) {
|
|
1053
|
+
// we are being asked to load a specific conversation
|
|
1054
|
+
const convo = this.Conversations.find((c) => c.ID == conversationIdToLoad);
|
|
1055
|
+
if (convo) {
|
|
1056
|
+
await this.SelectConversation(convo);
|
|
1051
1057
|
}
|
|
1052
1058
|
else {
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
// that can happen when a given user doesn't have their own conversations, but they are trying to view a shared conversation
|
|
1058
|
-
yield this.CreateNewConversation();
|
|
1059
|
-
InvokeManualResize(1);
|
|
1060
|
-
}
|
|
1061
|
-
else if (conversationIdToLoad) {
|
|
1062
|
-
// we are being asked to load a specific conversation
|
|
1063
|
-
const convo = this.Conversations.find((c) => c.ID == conversationIdToLoad);
|
|
1064
|
-
if (convo) {
|
|
1065
|
-
yield this.SelectConversation(convo);
|
|
1059
|
+
// we didn't find the conversation so check to see if it exists at all, could be a shared conversation
|
|
1060
|
+
const sharedConvo = await this.LoadSingleConversation(conversationIdToLoad);
|
|
1061
|
+
if (sharedConvo) {
|
|
1062
|
+
await this.SelectConversation(sharedConvo);
|
|
1066
1063
|
}
|
|
1067
1064
|
else {
|
|
1068
|
-
//
|
|
1069
|
-
|
|
1070
|
-
if (
|
|
1071
|
-
|
|
1065
|
+
// no shared conversation, load the first conversation but alert user that the convo they tried to load isn't available
|
|
1066
|
+
this.notificationService.CreateSimpleNotification(`Conversation ${conversationIdToLoad} not found`, 'error', 5000);
|
|
1067
|
+
if (this.Conversations.length > 0) {
|
|
1068
|
+
await this.SelectConversation(this.Conversations[0]);
|
|
1072
1069
|
}
|
|
1073
1070
|
else {
|
|
1074
|
-
|
|
1075
|
-
this.notificationService.CreateSimpleNotification(`Conversation ${conversationIdToLoad} not found`, 'error', 5000);
|
|
1076
|
-
if (this.Conversations.length > 0) {
|
|
1077
|
-
yield this.SelectConversation(this.Conversations[0]);
|
|
1078
|
-
}
|
|
1079
|
-
else {
|
|
1080
|
-
yield this.CreateNewConversation();
|
|
1081
|
-
}
|
|
1071
|
+
await this.CreateNewConversation();
|
|
1082
1072
|
}
|
|
1083
1073
|
}
|
|
1084
1074
|
}
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1075
|
+
}
|
|
1076
|
+
else {
|
|
1077
|
+
// select the first conversation since no param was provided and we have > 0 convos
|
|
1078
|
+
await this.SelectConversation(this.Conversations[0]);
|
|
1079
|
+
}
|
|
1080
|
+
// Update UI for conversations that are processing
|
|
1081
|
+
this.checkForProcessingConversations();
|
|
1082
|
+
this._isLoading = false;
|
|
1083
|
+
this._numLoads++;
|
|
1094
1084
|
}
|
|
1095
1085
|
/**
|
|
1096
1086
|
* Loads a conversation from the database based on the conversation ID provided.
|
|
1097
1087
|
* @param conversationId
|
|
1098
1088
|
* @returns
|
|
1099
1089
|
*/
|
|
1100
|
-
LoadSingleConversation(conversationId) {
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
ResultType: 'entity_object'
|
|
1107
|
-
});
|
|
1108
|
-
if (result && result.Success) {
|
|
1109
|
-
return result.Results[0];
|
|
1110
|
-
}
|
|
1090
|
+
async LoadSingleConversation(conversationId) {
|
|
1091
|
+
const rv = new RunView(this.RunViewToUse);
|
|
1092
|
+
const result = await rv.RunView({
|
|
1093
|
+
EntityName: 'Conversations',
|
|
1094
|
+
ExtraFilter: `ID='${conversationId}'`,
|
|
1095
|
+
ResultType: 'entity_object'
|
|
1111
1096
|
});
|
|
1097
|
+
if (result && result.Success) {
|
|
1098
|
+
return result.Results[0];
|
|
1099
|
+
}
|
|
1112
1100
|
}
|
|
1101
|
+
_oldConvoName = '';
|
|
1113
1102
|
editConvo(conversation) {
|
|
1114
1103
|
this._oldConvoName = conversation.Name ? conversation.Name : '';
|
|
1115
1104
|
this.ConversationEditMode = true;
|
|
@@ -1118,155 +1107,147 @@ export class SkipChatComponent extends BaseAngularComponent {
|
|
|
1118
1107
|
conversation.Name = this._oldConvoName;
|
|
1119
1108
|
this.ConversationEditMode = false;
|
|
1120
1109
|
}
|
|
1121
|
-
saveConvoName(conversation) {
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
cachedConversations[idx] = newConvoObject;
|
|
1146
|
-
}
|
|
1110
|
+
async saveConvoName(conversation) {
|
|
1111
|
+
let newConvoObject;
|
|
1112
|
+
if (conversation.Save !== undefined) {
|
|
1113
|
+
newConvoObject = conversation;
|
|
1114
|
+
}
|
|
1115
|
+
else {
|
|
1116
|
+
const p = this.ProviderToUse;
|
|
1117
|
+
newConvoObject = await p.GetEntityObject('Conversations', p.CurrentUser);
|
|
1118
|
+
await newConvoObject.Load(conversation.ID);
|
|
1119
|
+
// now replace conversation in the list with the new object
|
|
1120
|
+
this.Conversations = this.Conversations.map((c) => (c.ID == conversation.ID ? newConvoObject : c));
|
|
1121
|
+
}
|
|
1122
|
+
newConvoObject.Name = conversation.Name;
|
|
1123
|
+
if (await newConvoObject.Save()) {
|
|
1124
|
+
this.ConversationEditMode = false;
|
|
1125
|
+
// we've already updated the bound UI element, but let's make sure to update the cache as well
|
|
1126
|
+
const cachedConversations = MJGlobal.Instance.ObjectCache.Find('Conversations');
|
|
1127
|
+
if (cachedConversations) {
|
|
1128
|
+
// find the item in the cache
|
|
1129
|
+
const idx = cachedConversations.findIndex((c) => c.ID === conversation.ID);
|
|
1130
|
+
if (idx >= 0) {
|
|
1131
|
+
// replace the item in the cache with the new one, we are pointing to the same object in the cache here since
|
|
1132
|
+
// we are just updating an element within the array so don't need to tell the cache
|
|
1133
|
+
cachedConversations[idx] = newConvoObject;
|
|
1147
1134
|
}
|
|
1148
1135
|
}
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
}
|
|
1153
|
-
showDeleteConvoDialog(conversation) {
|
|
1154
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
1155
|
-
this.confirmDeleteConversationDialogOpen = true;
|
|
1156
|
-
this._conversationToDelete = conversation;
|
|
1157
|
-
});
|
|
1136
|
+
}
|
|
1137
|
+
else
|
|
1138
|
+
this.notificationService.CreateSimpleNotification('Error saving conversation name', 'error', 5000);
|
|
1158
1139
|
}
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
});
|
|
1140
|
+
confirmDeleteConversationDialogOpen = false;
|
|
1141
|
+
_conversationToDelete;
|
|
1142
|
+
async showDeleteConvoDialog(conversation) {
|
|
1143
|
+
this.confirmDeleteConversationDialogOpen = true;
|
|
1144
|
+
this._conversationToDelete = conversation;
|
|
1165
1145
|
}
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
if (this.Conversations.length > 0) {
|
|
1185
|
-
const newIdx = idx > 0 ? idx - 1 : 0;
|
|
1186
|
-
this.SelectConversation(this.Conversations[newIdx]);
|
|
1187
|
-
}
|
|
1188
|
-
else {
|
|
1189
|
-
this.Messages = [];
|
|
1190
|
-
}
|
|
1146
|
+
async closeDeleteConversation(yesno) {
|
|
1147
|
+
this.confirmDeleteConversationDialogOpen = false;
|
|
1148
|
+
if (this._conversationToDelete && yesno === 'yes')
|
|
1149
|
+
await this.deleteConvo(this._conversationToDelete);
|
|
1150
|
+
}
|
|
1151
|
+
async deleteConvo(conversation) {
|
|
1152
|
+
// delete the conversation - we might need to load the entity if the current object isn't a "real object"
|
|
1153
|
+
if (await this.DeleteConversation(conversation.ID)) {
|
|
1154
|
+
// we need to remove the conversation from the request status polling
|
|
1155
|
+
this.stopRequestStatusPolling(conversation.ID);
|
|
1156
|
+
// get the index of the conversation
|
|
1157
|
+
const idx = this.Conversations.findIndex((c) => c.ID === conversation.ID);
|
|
1158
|
+
// remove the conversation from the list that is bound to the UI
|
|
1159
|
+
this.Conversations = this.Conversations.filter((c) => c.ID != conversation.ID);
|
|
1160
|
+
// also, remove the conversation from the cache
|
|
1161
|
+
const cachedConversations = MJGlobal.Instance.ObjectCache.Find('Conversations');
|
|
1162
|
+
if (cachedConversations) {
|
|
1163
|
+
MJGlobal.Instance.ObjectCache.Replace('Conversations', cachedConversations.filter((c) => c.ID != conversation.ID));
|
|
1191
1164
|
}
|
|
1192
1165
|
else {
|
|
1193
|
-
|
|
1166
|
+
MJGlobal.Instance.ObjectCache.Add('Conversations', this.Conversations);
|
|
1194
1167
|
}
|
|
1195
|
-
|
|
1168
|
+
if (this.Conversations.length > 0) {
|
|
1169
|
+
const newIdx = idx > 0 ? idx - 1 : 0;
|
|
1170
|
+
this.SelectConversation(this.Conversations[newIdx]);
|
|
1171
|
+
}
|
|
1172
|
+
else {
|
|
1173
|
+
this.Messages = [];
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
else {
|
|
1177
|
+
this.notificationService.CreateSimpleNotification('Error deleting conversation', 'error', 5000);
|
|
1178
|
+
}
|
|
1196
1179
|
}
|
|
1197
|
-
CreateNewConversation() {
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
if
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
dci.ViewID = this.LinkedEntityCompositeKey.GetValueByIndex(0);
|
|
1225
|
-
}
|
|
1226
|
-
else if (this.LinkedEntity === 'Queries') {
|
|
1227
|
-
dci.Type = 'query';
|
|
1228
|
-
dci.QueryID = this.LinkedEntityCompositeKey.GetValueByIndex(0);
|
|
1229
|
-
}
|
|
1230
|
-
else {
|
|
1231
|
-
dci.Type = 'single_record';
|
|
1232
|
-
dci.RecordID = this.LinkedEntityCompositeKey.Values();
|
|
1233
|
-
dci.EntityID = this.LinkedEntityID;
|
|
1234
|
-
}
|
|
1235
|
-
let dciSaveResult = yield dci.Save();
|
|
1236
|
-
if (!dciSaveResult) {
|
|
1237
|
-
this.notificationService.CreateSimpleNotification('Error creating data context item', 'error', 5000);
|
|
1238
|
-
LogError('Error creating data context item', undefined, dci.LatestResult);
|
|
1239
|
-
}
|
|
1240
|
-
}
|
|
1241
|
-
convo.DataContextID = dc.ID;
|
|
1242
|
-
this.DataContextID = dc.ID;
|
|
1243
|
-
const convoSaveResult = yield convo.Save();
|
|
1244
|
-
if (!convoSaveResult) {
|
|
1245
|
-
this.notificationService.CreateSimpleNotification('Error creating conversation', 'error', 5000);
|
|
1246
|
-
LogError('Error creating conversation', undefined, convo.LatestResult);
|
|
1247
|
-
return;
|
|
1180
|
+
async CreateNewConversation() {
|
|
1181
|
+
const p = this.ProviderToUse;
|
|
1182
|
+
const convo = await p.GetEntityObject('Conversations', p.CurrentUser);
|
|
1183
|
+
convo.NewRecord();
|
|
1184
|
+
convo.Name = 'New Chat'; // default value
|
|
1185
|
+
convo.UserID = p.CurrentUser.ID;
|
|
1186
|
+
convo.Type = 'skip';
|
|
1187
|
+
convo.IsArchived = false;
|
|
1188
|
+
const linkedEntityID = this.LinkedEntityID;
|
|
1189
|
+
if (linkedEntityID && linkedEntityID.length > 0 && this.CompositeKeyIsPopulated()) {
|
|
1190
|
+
convo.LinkedEntityID = linkedEntityID;
|
|
1191
|
+
convo.LinkedRecordID = this.LinkedEntityCompositeKey.Values();
|
|
1192
|
+
}
|
|
1193
|
+
// next, create a new data context for this conversation
|
|
1194
|
+
const dc = await p.GetEntityObject('Data Contexts', p.CurrentUser);
|
|
1195
|
+
dc.NewRecord();
|
|
1196
|
+
dc.Name = 'Data Context for Skip Conversation';
|
|
1197
|
+
dc.UserID = p.CurrentUser.ID;
|
|
1198
|
+
if (await dc.Save()) {
|
|
1199
|
+
// now create a data context item for the linked record if we have one
|
|
1200
|
+
if (this.LinkedEntityID && this.LinkedEntityID.length > 0 && this.CompositeKeyIsPopulated()) {
|
|
1201
|
+
const dci = await p.GetEntityObject('Data Context Items', p.CurrentUser);
|
|
1202
|
+
dci.NewRecord();
|
|
1203
|
+
dci.DataContextID = dc.ID;
|
|
1204
|
+
if (this.LinkedEntity === 'User Views') {
|
|
1205
|
+
dci.Type = 'view';
|
|
1206
|
+
dci.ViewID = this.LinkedEntityCompositeKey.GetValueByIndex(0);
|
|
1248
1207
|
}
|
|
1249
|
-
this.
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
// also update the cache
|
|
1253
|
-
const cachedConversations = MJGlobal.Instance.ObjectCache.Find('Conversations');
|
|
1254
|
-
if (cachedConversations) {
|
|
1255
|
-
MJGlobal.Instance.ObjectCache.Replace('Conversations', [convo, ...cachedConversations]);
|
|
1208
|
+
else if (this.LinkedEntity === 'Queries') {
|
|
1209
|
+
dci.Type = 'query';
|
|
1210
|
+
dci.QueryID = this.LinkedEntityCompositeKey.GetValueByIndex(0);
|
|
1256
1211
|
}
|
|
1257
1212
|
else {
|
|
1258
|
-
|
|
1213
|
+
dci.Type = 'single_record';
|
|
1214
|
+
dci.RecordID = this.LinkedEntityCompositeKey.Values();
|
|
1215
|
+
dci.EntityID = this.LinkedEntityID;
|
|
1216
|
+
}
|
|
1217
|
+
let dciSaveResult = await dci.Save();
|
|
1218
|
+
if (!dciSaveResult) {
|
|
1219
|
+
this.notificationService.CreateSimpleNotification('Error creating data context item', 'error', 5000);
|
|
1220
|
+
LogError('Error creating data context item', undefined, dci.LatestResult);
|
|
1259
1221
|
}
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1222
|
+
}
|
|
1223
|
+
convo.DataContextID = dc.ID;
|
|
1224
|
+
this.DataContextID = dc.ID;
|
|
1225
|
+
const convoSaveResult = await convo.Save();
|
|
1226
|
+
if (!convoSaveResult) {
|
|
1227
|
+
this.notificationService.CreateSimpleNotification('Error creating conversation', 'error', 5000);
|
|
1228
|
+
LogError('Error creating conversation', undefined, convo.LatestResult);
|
|
1229
|
+
return;
|
|
1230
|
+
}
|
|
1231
|
+
this.DataContext = new DataContext();
|
|
1232
|
+
await this.DataContext.LoadMetadata(this.DataContextID, p.CurrentUser, p);
|
|
1233
|
+
this.Conversations = [convo, ...this.Conversations]; // do this way instead of unshift to ensure that binding refreshes
|
|
1234
|
+
// also update the cache
|
|
1235
|
+
const cachedConversations = MJGlobal.Instance.ObjectCache.Find('Conversations');
|
|
1236
|
+
if (cachedConversations) {
|
|
1237
|
+
MJGlobal.Instance.ObjectCache.Replace('Conversations', [convo, ...cachedConversations]);
|
|
1265
1238
|
}
|
|
1266
1239
|
else {
|
|
1267
|
-
|
|
1240
|
+
MJGlobal.Instance.ObjectCache.Add('Conversations', [convo, ...this.Conversations]);
|
|
1268
1241
|
}
|
|
1269
|
-
|
|
1242
|
+
await this.SelectConversation(convo);
|
|
1243
|
+
// Ensure scroll to bottom for new conversation
|
|
1244
|
+
setTimeout(() => {
|
|
1245
|
+
this.scrollToBottom();
|
|
1246
|
+
}, 100);
|
|
1247
|
+
}
|
|
1248
|
+
else {
|
|
1249
|
+
this.notificationService.CreateSimpleNotification('Error creating data context', 'error', 5000);
|
|
1250
|
+
}
|
|
1270
1251
|
}
|
|
1271
1252
|
onEnter(event) {
|
|
1272
1253
|
this.sendSkipMessage();
|
|
@@ -1275,25 +1256,23 @@ export class SkipChatComponent extends BaseAngularComponent {
|
|
|
1275
1256
|
* This method returns true if the specified user can access the conversation provided, otherwise false.
|
|
1276
1257
|
* @param conversation
|
|
1277
1258
|
*/
|
|
1278
|
-
UserCanAccessConversation(user, conversation) {
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1259
|
+
async UserCanAccessConversation(user, conversation) {
|
|
1260
|
+
if (!this.conversationResourceTypeID) {
|
|
1261
|
+
LogError('Resource type ID for conversations is not loaded - metadata loading error');
|
|
1262
|
+
return false;
|
|
1263
|
+
}
|
|
1264
|
+
if (!user || !conversation) {
|
|
1265
|
+
return false;
|
|
1266
|
+
}
|
|
1267
|
+
else {
|
|
1268
|
+
if (conversation.UserID === user.ID) {
|
|
1269
|
+
return true;
|
|
1286
1270
|
}
|
|
1287
1271
|
else {
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
}
|
|
1291
|
-
else {
|
|
1292
|
-
const level = yield this.GetUserConversationPermissionLevel(user, conversation);
|
|
1293
|
-
return level !== null;
|
|
1294
|
-
}
|
|
1272
|
+
const level = await this.GetUserConversationPermissionLevel(user, conversation);
|
|
1273
|
+
return level !== null;
|
|
1295
1274
|
}
|
|
1296
|
-
}
|
|
1275
|
+
}
|
|
1297
1276
|
}
|
|
1298
1277
|
/**
|
|
1299
1278
|
* Returns the permission level of the user for the conversation provided.
|
|
@@ -1301,139 +1280,135 @@ export class SkipChatComponent extends BaseAngularComponent {
|
|
|
1301
1280
|
* @param conversation
|
|
1302
1281
|
* @returns
|
|
1303
1282
|
*/
|
|
1304
|
-
GetUserConversationPermissionLevel(user, conversation) {
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1283
|
+
async GetUserConversationPermissionLevel(user, conversation) {
|
|
1284
|
+
if (!this.conversationResourceTypeID) {
|
|
1285
|
+
LogError('Resource type ID for conversations is not loaded - metadata loading error');
|
|
1286
|
+
return null;
|
|
1287
|
+
}
|
|
1288
|
+
else {
|
|
1289
|
+
if (user.ID === conversation.UserID) {
|
|
1290
|
+
return 'Owner';
|
|
1309
1291
|
}
|
|
1310
1292
|
else {
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
// check resource permissions for sharing
|
|
1316
|
-
const engine = this.ResourcePermissionEngine;
|
|
1317
|
-
yield engine.Config(false, this.ProviderToUse.CurrentUser, this.ProviderToUse);
|
|
1318
|
-
return engine.GetUserResourcePermissionLevel(this.conversationResourceTypeID, conversation.ID, user);
|
|
1319
|
-
}
|
|
1293
|
+
// check resource permissions for sharing
|
|
1294
|
+
const engine = this.ResourcePermissionEngine;
|
|
1295
|
+
await engine.Config(false, this.ProviderToUse.CurrentUser, this.ProviderToUse);
|
|
1296
|
+
return engine.GetUserResourcePermissionLevel(this.conversationResourceTypeID, conversation.ID, user);
|
|
1320
1297
|
}
|
|
1321
|
-
}
|
|
1298
|
+
}
|
|
1322
1299
|
}
|
|
1300
|
+
SelectedConversationCurrentUserPermissionLevel = null;
|
|
1323
1301
|
/**
|
|
1324
1302
|
* Sets the currently displayed conversation to the one provided
|
|
1325
1303
|
* @param conversation
|
|
1326
1304
|
* @returns
|
|
1327
1305
|
*/
|
|
1328
|
-
SelectConversation(conversation) {
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
//
|
|
1332
|
-
if (
|
|
1333
|
-
|
|
1334
|
-
if (!
|
|
1335
|
-
this.
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
// select the next one in the list
|
|
1342
|
-
yield this.SelectConversation(this.Conversations[currentIndex + 1]);
|
|
1343
|
-
}
|
|
1344
|
-
else {
|
|
1345
|
-
// select the first one in the list
|
|
1346
|
-
yield this.SelectConversation(this.Conversations[0]);
|
|
1347
|
-
}
|
|
1306
|
+
async SelectConversation(conversation) {
|
|
1307
|
+
// load up the conversation if not already the one that's loaded
|
|
1308
|
+
if (conversation && conversation.ID !== this.SelectedConversation?.ID) {
|
|
1309
|
+
// check to see if the user has access to the conversation
|
|
1310
|
+
if (!await this.UserCanAccessConversation(this.ProviderToUse.CurrentUser, conversation)) {
|
|
1311
|
+
this.notificationService.CreateSimpleNotification(`You do not have access to conversation ${conversation.ID}`, 'error', 5000);
|
|
1312
|
+
if (!this.SelectedConversation) {
|
|
1313
|
+
if (this.Conversations.length > 0) {
|
|
1314
|
+
// no current convo selected, so select the first one in the list
|
|
1315
|
+
const currentIndex = this.Conversations.findIndex((c) => c.ID === conversation.ID);
|
|
1316
|
+
if (currentIndex >= 0) {
|
|
1317
|
+
// select the next one in the list
|
|
1318
|
+
await this.SelectConversation(this.Conversations[currentIndex + 1]);
|
|
1348
1319
|
}
|
|
1349
1320
|
else {
|
|
1350
|
-
//
|
|
1351
|
-
|
|
1321
|
+
// select the first one in the list
|
|
1322
|
+
await this.SelectConversation(this.Conversations[0]);
|
|
1352
1323
|
}
|
|
1353
1324
|
}
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
this.SelectedConversationCurrentUserPermissionLevel = yield this.GetUserConversationPermissionLevel(this.ProviderToUse.CurrentUser, conversation);
|
|
1358
|
-
this._conversationLoadComplete = false;
|
|
1359
|
-
this.ClearMessages();
|
|
1360
|
-
const oldStatus = this.IsSkipProcessing(conversation);
|
|
1361
|
-
this.setProcessingStatus(conversation.ID, true);
|
|
1362
|
-
this.SelectedConversation = conversation;
|
|
1363
|
-
this.SetSelectedConversationUser();
|
|
1364
|
-
this.DataContextID = conversation.DataContextID ? conversation.DataContextID : '';
|
|
1365
|
-
const convoAny = conversation;
|
|
1366
|
-
if (convoAny._DataContext) {
|
|
1367
|
-
// we have cached data context, so just use it
|
|
1368
|
-
this.DataContext = convoAny._DataContext;
|
|
1369
|
-
}
|
|
1370
|
-
else {
|
|
1371
|
-
this.DataContext = new DataContext();
|
|
1372
|
-
const start = new Date().getTime();
|
|
1373
|
-
yield this.DataContext.LoadMetadata(this.DataContextID, this.ProviderToUse.CurrentUser, this.ProviderToUse);
|
|
1374
|
-
LogStatus('Skip Chat: Time to load data context: ' + (new Date().getTime() - start) + 'ms');
|
|
1375
|
-
// cache it for later
|
|
1376
|
-
convoAny._DataContext = this.DataContext;
|
|
1377
|
-
}
|
|
1378
|
-
const convoShouldReload = this._conversationsToReload[conversation.ID];
|
|
1379
|
-
if (convoAny._Messages && !convoShouldReload) {
|
|
1380
|
-
// we have cached messages, so just use them, but don't point directly to the array, create new array with the same objects
|
|
1381
|
-
this.Messages = [...convoAny._Messages];
|
|
1382
|
-
}
|
|
1383
|
-
else {
|
|
1384
|
-
this._conversationsToReload[conversation.ID] = false; // reset this flag since we're reloading from the DB right now
|
|
1385
|
-
const start = new Date().getTime();
|
|
1386
|
-
const result = yield this.RunViewToUse.RunView({
|
|
1387
|
-
EntityName: 'Conversation Details',
|
|
1388
|
-
ExtraFilter: `ConversationID='${conversation.ID}'`,
|
|
1389
|
-
OrderBy: '__mj_CreatedAt ASC' // show messages in order of creation,
|
|
1390
|
-
});
|
|
1391
|
-
LogStatus('Skip Chat: Time to load messages from database: ' + (new Date().getTime() - start) + 'ms');
|
|
1392
|
-
if (result && result.Success) {
|
|
1393
|
-
// copy the results into NEW objects into the array, we don't want to modify the original objects
|
|
1394
|
-
this.Messages = result.Results;
|
|
1395
|
-
// also, cache the messages within the conversation, but create new array with the same objects
|
|
1396
|
-
convoAny._Messages = [...this.Messages];
|
|
1397
|
-
}
|
|
1398
|
-
}
|
|
1399
|
-
if (this.Messages && this.Messages.length > 0) {
|
|
1400
|
-
this.cdRef.detach(); // temporarily stop change detection to improve performance
|
|
1401
|
-
for (const m of this.Messages) {
|
|
1402
|
-
this.AddMessageToPanel(m, false);
|
|
1325
|
+
else {
|
|
1326
|
+
// doesn't have any conversations, so create a new one
|
|
1327
|
+
await this.CreateNewConversation();
|
|
1403
1328
|
}
|
|
1404
|
-
this.cdRef.reattach(); // resume change detection
|
|
1405
|
-
// Force scroll to bottom after rendering messages
|
|
1406
|
-
setTimeout(() => {
|
|
1407
|
-
this.scrollToBottom();
|
|
1408
|
-
}, 300); // Give DOM time to render all messages
|
|
1409
1329
|
}
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1330
|
+
return;
|
|
1331
|
+
}
|
|
1332
|
+
this.selectedArtifact = null;
|
|
1333
|
+
this.SelectedConversationCurrentUserPermissionLevel = await this.GetUserConversationPermissionLevel(this.ProviderToUse.CurrentUser, conversation);
|
|
1334
|
+
this._conversationLoadComplete = false;
|
|
1335
|
+
this.ClearMessages();
|
|
1336
|
+
const oldStatus = this.IsSkipProcessing(conversation);
|
|
1337
|
+
this.setProcessingStatus(conversation.ID, true);
|
|
1338
|
+
this.SelectedConversation = conversation;
|
|
1339
|
+
this.SetSelectedConversationUser();
|
|
1340
|
+
this.DataContextID = conversation.DataContextID ? conversation.DataContextID : '';
|
|
1341
|
+
const convoAny = conversation;
|
|
1342
|
+
if (convoAny._DataContext) {
|
|
1343
|
+
// we have cached data context, so just use it
|
|
1344
|
+
this.DataContext = convoAny._DataContext;
|
|
1345
|
+
}
|
|
1346
|
+
else {
|
|
1347
|
+
this.DataContext = new DataContext();
|
|
1348
|
+
const start = new Date().getTime();
|
|
1349
|
+
await this.DataContext.LoadMetadata(this.DataContextID, this.ProviderToUse.CurrentUser, this.ProviderToUse);
|
|
1350
|
+
LogStatus('Skip Chat: Time to load data context: ' + (new Date().getTime() - start) + 'ms');
|
|
1351
|
+
// cache it for later
|
|
1352
|
+
convoAny._DataContext = this.DataContext;
|
|
1353
|
+
}
|
|
1354
|
+
const convoShouldReload = this._conversationsToReload[conversation.ID];
|
|
1355
|
+
if (convoAny._Messages && !convoShouldReload) {
|
|
1356
|
+
// we have cached messages, so just use them, but don't point directly to the array, create new array with the same objects
|
|
1357
|
+
this.Messages = [...convoAny._Messages];
|
|
1358
|
+
}
|
|
1359
|
+
else {
|
|
1360
|
+
this._conversationsToReload[conversation.ID] = false; // reset this flag since we're reloading from the DB right now
|
|
1361
|
+
const start = new Date().getTime();
|
|
1362
|
+
const result = await this.RunViewToUse.RunView({
|
|
1363
|
+
EntityName: 'Conversation Details',
|
|
1364
|
+
ExtraFilter: `ConversationID='${conversation.ID}'`,
|
|
1365
|
+
OrderBy: '__mj_CreatedAt ASC' // show messages in order of creation,
|
|
1366
|
+
});
|
|
1367
|
+
LogStatus('Skip Chat: Time to load messages from database: ' + (new Date().getTime() - start) + 'ms');
|
|
1368
|
+
if (result && result.Success) {
|
|
1369
|
+
// copy the results into NEW objects into the array, we don't want to modify the original objects
|
|
1370
|
+
this.Messages = result.Results;
|
|
1371
|
+
// also, cache the messages within the conversation, but create new array with the same objects
|
|
1372
|
+
convoAny._Messages = [...this.Messages];
|
|
1424
1373
|
}
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
this.
|
|
1428
|
-
this.
|
|
1429
|
-
|
|
1430
|
-
// finally update the browser URL since we've changed the conversation ID
|
|
1431
|
-
this.location.go('/askskip/' + conversation.ID);
|
|
1374
|
+
}
|
|
1375
|
+
if (this.Messages && this.Messages.length > 0) {
|
|
1376
|
+
this.cdRef.detach(); // temporarily stop change detection to improve performance
|
|
1377
|
+
for (const m of this.Messages) {
|
|
1378
|
+
this.AddMessageToPanel(m, false);
|
|
1432
1379
|
}
|
|
1433
|
-
this.cdRef.
|
|
1434
|
-
|
|
1380
|
+
this.cdRef.reattach(); // resume change detection
|
|
1381
|
+
// Force scroll to bottom after rendering messages
|
|
1382
|
+
setTimeout(() => {
|
|
1383
|
+
this.scrollToBottom();
|
|
1384
|
+
}, 300); // Give DOM time to render all messages
|
|
1435
1385
|
}
|
|
1436
|
-
|
|
1386
|
+
this.setProcessingStatus(conversation.ID, oldStatus); // set back to old status as it might have been processing
|
|
1387
|
+
// Check if this conversation is in 'Processing' status and restore the streaming state
|
|
1388
|
+
if (conversation.Status === 'Processing') {
|
|
1389
|
+
// This conversation is currently being processed
|
|
1390
|
+
this.setProcessingStatus(conversation.ID, true);
|
|
1391
|
+
this._conversationsInProgress[conversation.ID] = true;
|
|
1392
|
+
this._messageInProgress = true;
|
|
1393
|
+
this.AllowSend = false;
|
|
1394
|
+
// Create the temporary status message after a brief delay to ensure DOM is ready
|
|
1395
|
+
setTimeout(() => {
|
|
1396
|
+
this.SetSkipStatusMessage("Processing...", 0, conversation.__mj_UpdatedAt);
|
|
1397
|
+
// Start polling after the temporary message is created
|
|
1398
|
+
this.startRequestStatusPolling(conversation.ID);
|
|
1399
|
+
}, 100);
|
|
1400
|
+
}
|
|
1401
|
+
InvokeManualResize(500);
|
|
1402
|
+
// ensure the list box has the conversation in view
|
|
1403
|
+
this.scrollToConversation(conversation.ID);
|
|
1404
|
+
this._conversationLoadComplete = true;
|
|
1405
|
+
if (this.UpdateAppRoute) {
|
|
1406
|
+
// finally update the browser URL since we've changed the conversation ID
|
|
1407
|
+
this.location.go('/askskip/' + conversation.ID);
|
|
1408
|
+
}
|
|
1409
|
+
this.cdRef.detectChanges(); // first this off since conversation changed
|
|
1410
|
+
this.ConversationSelected.emit(conversation.ID);
|
|
1411
|
+
}
|
|
1437
1412
|
}
|
|
1438
1413
|
scrollToConversation(conversationId) {
|
|
1439
1414
|
if (this.conversationList) {
|
|
@@ -1464,6 +1439,17 @@ export class SkipChatComponent extends BaseAngularComponent {
|
|
|
1464
1439
|
LogError(e);
|
|
1465
1440
|
}
|
|
1466
1441
|
}
|
|
1442
|
+
static _startMessages = [
|
|
1443
|
+
'On it, let me get back to you in a moment with the results!🤖',
|
|
1444
|
+
"I'm on it, just a moment! 🙂",
|
|
1445
|
+
"I'll get started in a jiffy!",
|
|
1446
|
+
"You bet, I'd love to help, give me a moment!",
|
|
1447
|
+
"I understand, I'll start running in that direction 👟",
|
|
1448
|
+
"No problem, I'll get started right away!",
|
|
1449
|
+
"Ok, heard loud and clear, I'll jump right on it! 👂",
|
|
1450
|
+
"Aye aye captain, I'll get started right away! ⚓",
|
|
1451
|
+
];
|
|
1452
|
+
_usedStartMessages = [];
|
|
1467
1453
|
pickSkipStartMessage() {
|
|
1468
1454
|
// goal here is to randomly select one of the above _startMessages, however we want to track for our instance of the class the ones we use so that we don't reuse any of them until we use them all
|
|
1469
1455
|
if (this._usedStartMessages.length === SkipChatComponent._startMessages.length) {
|
|
@@ -1485,113 +1471,108 @@ export class SkipChatComponent extends BaseAngularComponent {
|
|
|
1485
1471
|
}
|
|
1486
1472
|
}
|
|
1487
1473
|
}
|
|
1488
|
-
sendPrompt(val) {
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
this.
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1474
|
+
async sendPrompt(val) {
|
|
1475
|
+
const convoID = this.SelectedConversation ? this.SelectedConversation.ID : '';
|
|
1476
|
+
if (this._conversationsInProgress[convoID]) {
|
|
1477
|
+
// don't allow sending another message if we're in the midst of sending one
|
|
1478
|
+
return;
|
|
1479
|
+
}
|
|
1480
|
+
if (this.SelectedConversation) {
|
|
1481
|
+
this.setProcessingStatus(this.SelectedConversation?.ID, true);
|
|
1482
|
+
}
|
|
1483
|
+
if (val && val.length > 0) {
|
|
1484
|
+
this._conversationsInProgress[convoID] = true;
|
|
1485
|
+
this._messageInProgress = true;
|
|
1486
|
+
this.AllowSend = false;
|
|
1487
|
+
const p = this.ProviderToUse;
|
|
1488
|
+
const convoDetail = await p.GetEntityObject('Conversation Details', p.CurrentUser);
|
|
1489
|
+
convoDetail.NewRecord();
|
|
1490
|
+
convoDetail.Message = val;
|
|
1491
|
+
convoDetail.Role = 'User';
|
|
1492
|
+
// this is NOT saved here because it is saved on the server side. Later on in this code after the save we will update the object with the ID from the server, and below
|
|
1493
|
+
this.AddMessageToCurrentConversation(convoDetail, true, true);
|
|
1494
|
+
this.askSkipInput.nativeElement.value = '';
|
|
1495
|
+
this.resizeTextInput();
|
|
1496
|
+
this.SetSkipStatusMessage(this.pickSkipStartMessage(), 850);
|
|
1497
|
+
// Ensure scroll to bottom after adding user message AND progress message
|
|
1498
|
+
setTimeout(() => {
|
|
1499
|
+
this.scrollToBottom();
|
|
1500
|
+
}, 950); // Slightly after the progress message is shown (850ms + 100ms buffer)
|
|
1501
|
+
const graphQLRawResult = await this.ExecuteAskSkipQuery(val, await this.GetCreateDataContextID(), this.SelectedConversation);
|
|
1502
|
+
const skipResult = graphQLRawResult?.ExecuteAskSkipAnalysisQuery;
|
|
1503
|
+
// temporarily ask Angular to stop its change detection as many of the ops below are slow and async, we don't want flicker in the UI as stuff happens
|
|
1504
|
+
this.cdRef.detach();
|
|
1505
|
+
if (skipResult?.Success) {
|
|
1506
|
+
if (convoID !== this.SelectedConversation?.ID) {
|
|
1507
|
+
// this scenario arises when we have a selected convo change after we submitted our request to skip
|
|
1508
|
+
// so we do nothing here other than update the status.
|
|
1509
|
+
this.setProcessingStatus(convoID, false);
|
|
1510
|
+
//the next time the user selects this convo, we will fetch messages
|
|
1511
|
+
//from the server rather than using the ones in cache
|
|
1512
|
+
this._conversationsToReload[convoID] = true;
|
|
1513
|
+
}
|
|
1514
|
+
else {
|
|
1515
|
+
this.setProcessingStatus(convoID, false);
|
|
1516
|
+
const innerResult = JSON.parse(skipResult.Result);
|
|
1517
|
+
if (!this.SelectedConversation) {
|
|
1518
|
+
const convo = await p.GetEntityObject('Conversations', p.CurrentUser);
|
|
1519
|
+
await convo.Load(skipResult.ConversationId);
|
|
1520
|
+
this.setProcessingStatus(skipResult.ConversationId, true);
|
|
1521
|
+
this.Conversations.push(convo);
|
|
1522
|
+
this.SelectedConversation = convo;
|
|
1523
|
+
this.cdRef.detectChanges(); // first this off since conversation changed
|
|
1524
|
+
this.SetSelectedConversationUser();
|
|
1529
1525
|
}
|
|
1530
|
-
else {
|
|
1531
|
-
this.
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
this.
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
else if (innerResult.responsePhase === SkipResponsePhase.analysis_complete) {
|
|
1543
|
-
if (this.SelectedConversation.Name === 'New Chat' || ((_c = this.SelectedConversation.Name) === null || _c === void 0 ? void 0 : _c.trim().length) === 0) {
|
|
1544
|
-
// we are on the first message so skip renamed the convo, use that
|
|
1545
|
-
this.SelectedConversation.Name = innerResult.title; // this will update the UI
|
|
1546
|
-
// the below LOOKS redundant to just updating this.SelectedConversation.Name, but it is needed to ensure that the list box is updated
|
|
1547
|
-
// otherwise Angular binding doesn't pick up the change without the below.
|
|
1548
|
-
const idx = this.Conversations.findIndex((c) => { var _a; return c.ID === ((_a = this.SelectedConversation) === null || _a === void 0 ? void 0 : _a.ID); });
|
|
1549
|
-
if (idx >= 0) {
|
|
1550
|
-
// update our this.Conversations array to reflect the new name. First find the index of the conversation and then get that item and update it
|
|
1551
|
-
this.Conversations[idx].Name = this.SelectedConversation.Name;
|
|
1552
|
-
//reredner the list box
|
|
1553
|
-
this.Conversations = [...this.Conversations];
|
|
1554
|
-
}
|
|
1526
|
+
else if (innerResult.responsePhase === SkipResponsePhase.analysis_complete) {
|
|
1527
|
+
if (this.SelectedConversation.Name === 'New Chat' || this.SelectedConversation.Name?.trim().length === 0) {
|
|
1528
|
+
// we are on the first message so skip renamed the convo, use that
|
|
1529
|
+
this.SelectedConversation.Name = innerResult.title; // this will update the UI
|
|
1530
|
+
// the below LOOKS redundant to just updating this.SelectedConversation.Name, but it is needed to ensure that the list box is updated
|
|
1531
|
+
// otherwise Angular binding doesn't pick up the change without the below.
|
|
1532
|
+
const idx = this.Conversations.findIndex((c) => c.ID === this.SelectedConversation?.ID);
|
|
1533
|
+
if (idx >= 0) {
|
|
1534
|
+
// update our this.Conversations array to reflect the new name. First find the index of the conversation and then get that item and update it
|
|
1535
|
+
this.Conversations[idx].Name = this.SelectedConversation.Name;
|
|
1536
|
+
//reredner the list box
|
|
1537
|
+
this.Conversations = [...this.Conversations];
|
|
1555
1538
|
}
|
|
1556
1539
|
}
|
|
1557
|
-
yield convoDetail.Load(skipResult.UserMessageConversationDetailId); // update the object to load from DB
|
|
1558
|
-
const aiDetail = yield p.GetEntityObject('Conversation Details', p.CurrentUser);
|
|
1559
|
-
yield aiDetail.Load(skipResult.AIMessageConversationDetailId); // get record from the database
|
|
1560
|
-
this.AddMessageToCurrentConversation(aiDetail, true, true);
|
|
1561
|
-
// Ensure scroll to bottom after AI response
|
|
1562
|
-
setTimeout(() => {
|
|
1563
|
-
this.scrollToBottom();
|
|
1564
|
-
}, 100);
|
|
1565
|
-
// Automatically show artifact if the new AI message has one
|
|
1566
|
-
this.autoShowArtifactIfPresent(aiDetail);
|
|
1567
|
-
// NOTE: we don't create a user notification at this point, that is done on the server and via GraphQL subscriptions it tells us and we update the UI automatically...
|
|
1568
1540
|
}
|
|
1541
|
+
await convoDetail.Load(skipResult.UserMessageConversationDetailId); // update the object to load from DB
|
|
1542
|
+
const aiDetail = await p.GetEntityObject('Conversation Details', p.CurrentUser);
|
|
1543
|
+
await aiDetail.Load(skipResult.AIMessageConversationDetailId); // get record from the database
|
|
1544
|
+
this.AddMessageToCurrentConversation(aiDetail, true, true);
|
|
1545
|
+
// Ensure scroll to bottom after AI response
|
|
1546
|
+
setTimeout(() => {
|
|
1547
|
+
this.scrollToBottom();
|
|
1548
|
+
}, 100);
|
|
1549
|
+
// Automatically show artifact if the new AI message has one
|
|
1550
|
+
this.autoShowArtifactIfPresent(aiDetail);
|
|
1551
|
+
// NOTE: we don't create a user notification at this point, that is done on the server and via GraphQL subscriptions it tells us and we update the UI automatically...
|
|
1569
1552
|
}
|
|
1570
|
-
if (this.SelectedConversation) {
|
|
1571
|
-
this.setProcessingStatus(this.SelectedConversation.ID, false);
|
|
1572
|
-
}
|
|
1573
|
-
this.AllowSend = true;
|
|
1574
|
-
this._conversationsInProgress[convoID] = false;
|
|
1575
|
-
this._messageInProgress = false;
|
|
1576
|
-
// now tell Angular to resume its change detection
|
|
1577
|
-
this.cdRef.reattach();
|
|
1578
|
-
this.cdRef.detectChanges();
|
|
1579
|
-
// invoke manual resize with a delay to ensure that the scroll to bottom has taken place
|
|
1580
|
-
//InvokeManualResize();
|
|
1581
|
-
this.SetSkipStatusMessage('', 500); // slight delay to ensure that the message is removed after the UI has updated with the new response message
|
|
1582
|
-
// now set focus on the input box
|
|
1583
|
-
this.askSkipInput.nativeElement.focus();
|
|
1584
1553
|
}
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
sendSkipMessage() {
|
|
1588
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
1589
|
-
if (this.IsTextAreaEmpty()) {
|
|
1590
|
-
return;
|
|
1554
|
+
if (this.SelectedConversation) {
|
|
1555
|
+
this.setProcessingStatus(this.SelectedConversation.ID, false);
|
|
1591
1556
|
}
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1557
|
+
this.AllowSend = true;
|
|
1558
|
+
this._conversationsInProgress[convoID] = false;
|
|
1559
|
+
this._messageInProgress = false;
|
|
1560
|
+
// now tell Angular to resume its change detection
|
|
1561
|
+
this.cdRef.reattach();
|
|
1562
|
+
this.cdRef.detectChanges();
|
|
1563
|
+
// invoke manual resize with a delay to ensure that the scroll to bottom has taken place
|
|
1564
|
+
//InvokeManualResize();
|
|
1565
|
+
this.SetSkipStatusMessage('', 500); // slight delay to ensure that the message is removed after the UI has updated with the new response message
|
|
1566
|
+
// now set focus on the input box
|
|
1567
|
+
this.askSkipInput.nativeElement.focus();
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
async sendSkipMessage() {
|
|
1571
|
+
if (this.IsTextAreaEmpty()) {
|
|
1572
|
+
return;
|
|
1573
|
+
}
|
|
1574
|
+
const input = this.askSkipInput.nativeElement.value;
|
|
1575
|
+
await this.sendPrompt(input);
|
|
1595
1576
|
}
|
|
1596
1577
|
ClearMessages() {
|
|
1597
1578
|
this.Messages = []; // clear out the messages
|
|
@@ -1792,82 +1773,79 @@ export class SkipChatComponent extends BaseAngularComponent {
|
|
|
1792
1773
|
// Calculate the center of the conversation panel
|
|
1793
1774
|
return rect.left + (rect.width / 2);
|
|
1794
1775
|
}
|
|
1795
|
-
GetCreateDataContextID() {
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
console.log('AskSkipComponent: Error creating data context item');
|
|
1851
|
-
}
|
|
1776
|
+
async GetCreateDataContextID() {
|
|
1777
|
+
// temporary hack for now, we will have more functionality to do robust UX around DataCOntext viewing and editing soon
|
|
1778
|
+
// and get rid of this
|
|
1779
|
+
if (!this.DataContextID && this.SelectedConversation) {
|
|
1780
|
+
// need to create a data context
|
|
1781
|
+
// add to the new data context a single item for the passed in linked record, which could be a query, view, or something else
|
|
1782
|
+
const p = this.ProviderToUse;
|
|
1783
|
+
const dc = await p.GetEntityObject('Data Contexts', p.CurrentUser);
|
|
1784
|
+
dc.NewRecord();
|
|
1785
|
+
const e = p.Entities.find((e) => e.Name === this.LinkedEntity);
|
|
1786
|
+
dc.Name =
|
|
1787
|
+
'Data Context for Skip Conversation ' + (e ? ' for ' + e.Name + ' - Record ID: ' + this.LinkedEntityCompositeKey.Values() : '');
|
|
1788
|
+
dc.UserID = p.CurrentUser.ID;
|
|
1789
|
+
if (await dc.Save()) {
|
|
1790
|
+
this.DataContextID = dc.ID;
|
|
1791
|
+
// update the conversation with the data context id
|
|
1792
|
+
const convo = await p.GetEntityObject('Conversations', p.CurrentUser);
|
|
1793
|
+
await convo.Load(this.SelectedConversation.ID);
|
|
1794
|
+
await convo.Save(); // save to the database
|
|
1795
|
+
this.SelectedConversation.DataContextID = dc.ID; // update the in-memory object
|
|
1796
|
+
if (this.LinkedEntity && this.CompositeKeyIsPopulated() && e) {
|
|
1797
|
+
// now create a single data context item for the new data context
|
|
1798
|
+
let type;
|
|
1799
|
+
switch (e.Name.trim().toLowerCase()) {
|
|
1800
|
+
case 'user views':
|
|
1801
|
+
type = 'view';
|
|
1802
|
+
break;
|
|
1803
|
+
case 'queries':
|
|
1804
|
+
type = 'query';
|
|
1805
|
+
break;
|
|
1806
|
+
default:
|
|
1807
|
+
if (this.CompositeKeyIsPopulated()) {
|
|
1808
|
+
type = 'single_record';
|
|
1809
|
+
}
|
|
1810
|
+
else
|
|
1811
|
+
type = 'full_entity';
|
|
1812
|
+
break;
|
|
1813
|
+
}
|
|
1814
|
+
const dci = await p.GetEntityObject('Data Context Items', p.CurrentUser);
|
|
1815
|
+
dci.NewRecord();
|
|
1816
|
+
dci.DataContextID = dc.ID;
|
|
1817
|
+
dci.Type = type;
|
|
1818
|
+
if (type === 'view')
|
|
1819
|
+
dci.ViewID = this.LinkedEntityCompositeKey.GetValueByIndex(0);
|
|
1820
|
+
else if (type === 'query')
|
|
1821
|
+
dci.QueryID = this.LinkedEntityCompositeKey.GetValueByIndex(0);
|
|
1822
|
+
else if (type === 'single_record') {
|
|
1823
|
+
dci.RecordID = this.LinkedEntityCompositeKey.Values();
|
|
1824
|
+
dci.EntityID = e.ID;
|
|
1825
|
+
}
|
|
1826
|
+
else if (type === 'full_entity')
|
|
1827
|
+
dci.EntityID = e.ID;
|
|
1828
|
+
if (!(await dci.Save())) {
|
|
1829
|
+
this.notificationService.CreateSimpleNotification('Error creating data context item', 'error', 5000);
|
|
1830
|
+
console.log('AskSkipComponent: Error creating data context item');
|
|
1852
1831
|
}
|
|
1853
|
-
}
|
|
1854
|
-
else {
|
|
1855
|
-
this.notificationService.CreateSimpleNotification('Error creating data context', 'error', 5000);
|
|
1856
|
-
console.log('AskSkipComponent: Error creating data context');
|
|
1857
1832
|
}
|
|
1858
1833
|
}
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
yield this.DataContext.LoadMetadata(this.DataContextID, this.ProviderToUse.CurrentUser, this.ProviderToUse);
|
|
1834
|
+
else {
|
|
1835
|
+
this.notificationService.CreateSimpleNotification('Error creating data context', 'error', 5000);
|
|
1836
|
+
console.log('AskSkipComponent: Error creating data context');
|
|
1863
1837
|
}
|
|
1864
|
-
|
|
1865
|
-
|
|
1838
|
+
}
|
|
1839
|
+
if (!this.DataContext) {
|
|
1840
|
+
// load the actual data context object
|
|
1841
|
+
this.DataContext = new DataContext();
|
|
1842
|
+
await this.DataContext.LoadMetadata(this.DataContextID, this.ProviderToUse.CurrentUser, this.ProviderToUse);
|
|
1843
|
+
}
|
|
1844
|
+
return this.DataContextID;
|
|
1866
1845
|
}
|
|
1867
|
-
ExecuteAskSkipQuery(question, dataContextId, SelectedConversation) {
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
const gql = `query ExecuteAskSkipAnalysisQuery($userQuestion: String!, $dataContextId: String!, $conversationId: String!) {
|
|
1846
|
+
async ExecuteAskSkipQuery(question, dataContextId, SelectedConversation) {
|
|
1847
|
+
try {
|
|
1848
|
+
const gql = `query ExecuteAskSkipAnalysisQuery($userQuestion: String!, $dataContextId: String!, $conversationId: String!) {
|
|
1871
1849
|
ExecuteAskSkipAnalysisQuery(UserQuestion: $userQuestion, DataContextId: $dataContextId, ConversationId: $conversationId) {
|
|
1872
1850
|
Success
|
|
1873
1851
|
Status
|
|
@@ -1877,34 +1855,32 @@ export class SkipChatComponent extends BaseAngularComponent {
|
|
|
1877
1855
|
AIMessageConversationDetailId
|
|
1878
1856
|
}
|
|
1879
1857
|
}`;
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
const p = this.ProviderToUse;
|
|
1891
|
-
const errorMessage = yield p.GetEntityObject('Conversation Details', p.CurrentUser);
|
|
1892
|
-
errorMessage.NewRecord();
|
|
1893
|
-
errorMessage.Role = 'Error';
|
|
1894
|
-
errorMessage.Message = 'Error took place' + err;
|
|
1895
|
-
this.AddMessageToCurrentConversation(errorMessage, true, false);
|
|
1896
|
-
this.AllowSend = true;
|
|
1897
|
-
}
|
|
1898
|
-
});
|
|
1899
|
-
}
|
|
1900
|
-
DeleteConversation(ConversationID) {
|
|
1901
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
1858
|
+
const gqlProvider = this.ProviderToUse;
|
|
1859
|
+
const result = await gqlProvider.ExecuteGQL(gql, {
|
|
1860
|
+
userQuestion: question,
|
|
1861
|
+
conversationId: SelectedConversation ? SelectedConversation.ID : '',
|
|
1862
|
+
dataContextId: dataContextId,
|
|
1863
|
+
});
|
|
1864
|
+
return result;
|
|
1865
|
+
}
|
|
1866
|
+
catch (err) {
|
|
1867
|
+
LogError('Error executing AskSkip query', undefined, err);
|
|
1902
1868
|
const p = this.ProviderToUse;
|
|
1903
|
-
const
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1869
|
+
const errorMessage = await p.GetEntityObject('Conversation Details', p.CurrentUser);
|
|
1870
|
+
errorMessage.NewRecord();
|
|
1871
|
+
errorMessage.Role = 'Error';
|
|
1872
|
+
errorMessage.Message = 'Error took place' + err;
|
|
1873
|
+
this.AddMessageToCurrentConversation(errorMessage, true, false);
|
|
1874
|
+
this.AllowSend = true;
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
async DeleteConversation(ConversationID) {
|
|
1878
|
+
const p = this.ProviderToUse;
|
|
1879
|
+
const convEntity = await p.GetEntityObject('Conversations', p.CurrentUser);
|
|
1880
|
+
await convEntity.Load(ConversationID);
|
|
1881
|
+
return await convEntity.Delete();
|
|
1907
1882
|
}
|
|
1883
|
+
_processingStatus = {};
|
|
1908
1884
|
IsSkipProcessing(Conversation) {
|
|
1909
1885
|
if (!Conversation) {
|
|
1910
1886
|
return false;
|
|
@@ -1929,29 +1905,29 @@ export class SkipChatComponent extends BaseAngularComponent {
|
|
|
1929
1905
|
}
|
|
1930
1906
|
return false;
|
|
1931
1907
|
}
|
|
1908
|
+
isDataContextDialogVisible = false;
|
|
1932
1909
|
showDataContextDialog() {
|
|
1933
1910
|
this.isDataContextDialogVisible = true;
|
|
1934
1911
|
}
|
|
1935
1912
|
closeDataContextDialog() {
|
|
1936
1913
|
this.isDataContextDialogVisible = false;
|
|
1937
1914
|
}
|
|
1915
|
+
isSharingDialogVisible = false;
|
|
1938
1916
|
showSharingDialog() {
|
|
1939
1917
|
this.isSharingDialogVisible = true;
|
|
1940
1918
|
}
|
|
1941
|
-
closeSharingDialog(action) {
|
|
1942
|
-
|
|
1943
|
-
if (
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
this.notificationService.CreateSimpleNotification('Failed to save permissions', 'error', 2500);
|
|
1947
|
-
}
|
|
1948
|
-
else {
|
|
1949
|
-
// let the user know that sharing was successful
|
|
1950
|
-
this.notificationService.CreateSimpleNotification('Conversation sharing settings updated', 'success', 1500);
|
|
1951
|
-
}
|
|
1919
|
+
async closeSharingDialog(action) {
|
|
1920
|
+
if (action === 'yes' && this.resourcePermissions) {
|
|
1921
|
+
if (!await this.resourcePermissions.SavePermissions()) {
|
|
1922
|
+
// let the user know that sharing failed
|
|
1923
|
+
this.notificationService.CreateSimpleNotification('Failed to save permissions', 'error', 2500);
|
|
1952
1924
|
}
|
|
1953
|
-
|
|
1954
|
-
|
|
1925
|
+
else {
|
|
1926
|
+
// let the user know that sharing was successful
|
|
1927
|
+
this.notificationService.CreateSimpleNotification('Conversation sharing settings updated', 'success', 1500);
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
this.isSharingDialogVisible = false;
|
|
1955
1931
|
}
|
|
1956
1932
|
CompositeKeyIsPopulated() {
|
|
1957
1933
|
return this.LinkedEntityCompositeKey.KeyValuePairs && this.LinkedEntityCompositeKey.KeyValuePairs.length > 0;
|
|
@@ -1980,6 +1956,9 @@ export class SkipChatComponent extends BaseAngularComponent {
|
|
|
1980
1956
|
this.loadConversations(convoIDParam);
|
|
1981
1957
|
});
|
|
1982
1958
|
}
|
|
1959
|
+
confirmMessageEditOrDeleteDialogOpen = false;
|
|
1960
|
+
messageToEditOrDelete;
|
|
1961
|
+
messageEditOrDeleteType = 'edit';
|
|
1983
1962
|
HandleMessageEditOrDeleteRequest(message, type) {
|
|
1984
1963
|
if (this.SelectedConversation && !this.IsSkipProcessing(this.SelectedConversation)) {
|
|
1985
1964
|
this.messageToEditOrDelete = message;
|
|
@@ -2009,61 +1988,57 @@ export class SkipChatComponent extends BaseAngularComponent {
|
|
|
2009
1988
|
}
|
|
2010
1989
|
}
|
|
2011
1990
|
}
|
|
2012
|
-
editMessage(message) {
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
// this will let the user edit the message and submit it
|
|
2019
|
-
});
|
|
1991
|
+
async editMessage(message) {
|
|
1992
|
+
const oldMessageText = message.Message;
|
|
1993
|
+
await this.deleteMessage(message);
|
|
1994
|
+
// now add the text from the message to the input box
|
|
1995
|
+
this.askSkipInput.nativeElement.value = oldMessageText;
|
|
1996
|
+
// this will let the user edit the message and submit it
|
|
2020
1997
|
}
|
|
2021
|
-
deleteMessage(message) {
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
for
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
if
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
yield actualEntityObject.Delete();
|
|
2042
|
-
}
|
|
2043
|
-
else {
|
|
2044
|
-
// didn't load successfully, drop to console, possibly the record was already deleted, non-fatal
|
|
2045
|
-
console.log('Error loading conversation detail entity for deletion', m);
|
|
2046
|
-
}
|
|
1998
|
+
async deleteMessage(message) {
|
|
1999
|
+
if (!this.SelectedConversation || this.IsSkipProcessing(this.SelectedConversation)) {
|
|
2000
|
+
return; // don't allow deleting messages while we're processing or don't have a selected convo
|
|
2001
|
+
}
|
|
2002
|
+
this.setProcessingStatus(this.SelectedConversation.ID, true);
|
|
2003
|
+
// first find all the subsequent messages in the conversation
|
|
2004
|
+
const idx = this.Messages.findIndex((m) => m.ID === message.ID);
|
|
2005
|
+
if (idx >= 0) {
|
|
2006
|
+
const currentAndSubsequentMessages = this.Messages.slice(idx);
|
|
2007
|
+
const tg = await this.ProviderToUse.CreateTransactionGroup();
|
|
2008
|
+
for (const m of currentAndSubsequentMessages) {
|
|
2009
|
+
// need to create the BaseEntity subclass for the conversation detail entity
|
|
2010
|
+
// as our initial load of the conversation detail entity is not a full object it is
|
|
2011
|
+
// a simple javascript object.
|
|
2012
|
+
const actualEntityObject = await this.ProviderToUse.GetEntityObject('Conversation Details', this.ProviderToUse.CurrentUser);
|
|
2013
|
+
if (await actualEntityObject.Load(m.ID)) {
|
|
2014
|
+
// check to see if it loaded succesfully or not, sometimes it is already deleted
|
|
2015
|
+
if (actualEntityObject.ConversationID === this.SelectedConversation.ID) {
|
|
2016
|
+
actualEntityObject.TransactionGroup = tg;
|
|
2017
|
+
await actualEntityObject.Delete();
|
|
2047
2018
|
}
|
|
2048
2019
|
else {
|
|
2049
|
-
//
|
|
2020
|
+
// didn't load successfully, drop to console, possibly the record was already deleted, non-fatal
|
|
2050
2021
|
console.log('Error loading conversation detail entity for deletion', m);
|
|
2051
2022
|
}
|
|
2052
2023
|
}
|
|
2053
|
-
// now submit the transaction group
|
|
2054
|
-
if (yield tg.Submit()) {
|
|
2055
|
-
this.setProcessingStatus(this.SelectedConversation.ID, false); // done
|
|
2056
|
-
const convo = this.SelectedConversation;
|
|
2057
|
-
this.SelectedConversation = undefined; // wipe out so the below does something
|
|
2058
|
-
this.SelectConversation(convo); // reload the conversation to get the latest messages
|
|
2059
|
-
}
|
|
2060
2024
|
else {
|
|
2061
|
-
//
|
|
2062
|
-
|
|
2063
|
-
this.notificationService.CreateSimpleNotification('Error deleting messages', 'error', 3000);
|
|
2025
|
+
// problem loading the entity, drop to console, possibly the record was already deleted, non-fatal
|
|
2026
|
+
console.log('Error loading conversation detail entity for deletion', m);
|
|
2064
2027
|
}
|
|
2065
2028
|
}
|
|
2066
|
-
|
|
2029
|
+
// now submit the transaction group
|
|
2030
|
+
if (await tg.Submit()) {
|
|
2031
|
+
this.setProcessingStatus(this.SelectedConversation.ID, false); // done
|
|
2032
|
+
const convo = this.SelectedConversation;
|
|
2033
|
+
this.SelectedConversation = undefined; // wipe out so the below does something
|
|
2034
|
+
this.SelectConversation(convo); // reload the conversation to get the latest messages
|
|
2035
|
+
}
|
|
2036
|
+
else {
|
|
2037
|
+
// alert the user to the error
|
|
2038
|
+
this.setProcessingStatus(this.SelectedConversation.ID, false); // done
|
|
2039
|
+
this.notificationService.CreateSimpleNotification('Error deleting messages', 'error', 3000);
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2067
2042
|
}
|
|
2068
2043
|
get NumVisibleButtons() {
|
|
2069
2044
|
let count = 1;
|
|
@@ -2073,6 +2048,7 @@ export class SkipChatComponent extends BaseAngularComponent {
|
|
|
2073
2048
|
count++;
|
|
2074
2049
|
return count;
|
|
2075
2050
|
}
|
|
2051
|
+
splitPanel;
|
|
2076
2052
|
/**
|
|
2077
2053
|
* Handles when an artifact is selected from a message
|
|
2078
2054
|
* @param artifact The artifact information
|
|
@@ -2141,7 +2117,10 @@ export class SkipChatComponent extends BaseAngularComponent {
|
|
|
2141
2117
|
onArtifactVersionSelected(versionId) {
|
|
2142
2118
|
if (this.selectedArtifact) {
|
|
2143
2119
|
// Update the selected artifact with the new version
|
|
2144
|
-
this.selectedArtifact =
|
|
2120
|
+
this.selectedArtifact = {
|
|
2121
|
+
...this.selectedArtifact,
|
|
2122
|
+
artifactVersionId: versionId
|
|
2123
|
+
};
|
|
2145
2124
|
// The artifact viewer will handle loading the new version
|
|
2146
2125
|
this.ArtifactViewed.emit(this.selectedArtifact);
|
|
2147
2126
|
}
|
|
@@ -2170,83 +2149,81 @@ export class SkipChatComponent extends BaseAngularComponent {
|
|
|
2170
2149
|
* - Deletes the last user message
|
|
2171
2150
|
* - Restores the text to the input area
|
|
2172
2151
|
*/
|
|
2173
|
-
stopProcessing() {
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2152
|
+
async stopProcessing() {
|
|
2153
|
+
if (!this.SelectedConversation || !this.IsSkipProcessing(this.SelectedConversation)) {
|
|
2154
|
+
return;
|
|
2155
|
+
}
|
|
2156
|
+
try {
|
|
2157
|
+
// Get proper entity object for conversation if needed
|
|
2158
|
+
let conversationEntity;
|
|
2159
|
+
if (this.SelectedConversation.Save !== undefined) {
|
|
2160
|
+
conversationEntity = this.SelectedConversation;
|
|
2177
2161
|
}
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2162
|
+
else {
|
|
2163
|
+
const p = this.ProviderToUse;
|
|
2164
|
+
conversationEntity = await p.GetEntityObject('Conversations', p.CurrentUser);
|
|
2165
|
+
await conversationEntity.Load(this.SelectedConversation.ID);
|
|
2166
|
+
}
|
|
2167
|
+
// Find the last user message
|
|
2168
|
+
const lastUserMessage = this.Messages
|
|
2169
|
+
.slice()
|
|
2170
|
+
.reverse()
|
|
2171
|
+
.find(m => m.Role === 'User');
|
|
2172
|
+
if (lastUserMessage) {
|
|
2173
|
+
// Store the message text to restore to input
|
|
2174
|
+
const messageText = lastUserMessage.Message;
|
|
2175
|
+
// Update conversation status to Available
|
|
2176
|
+
conversationEntity.Status = 'Available';
|
|
2177
|
+
await conversationEntity.Save();
|
|
2178
|
+
// Update the selected conversation object if we loaded a new one
|
|
2179
|
+
if (this.SelectedConversation !== conversationEntity) {
|
|
2180
|
+
this.SelectedConversation = conversationEntity;
|
|
2181
|
+
// Also update in the conversations list
|
|
2182
|
+
const idx = this.Conversations.findIndex(c => c.ID === conversationEntity.ID);
|
|
2183
|
+
if (idx >= 0) {
|
|
2184
|
+
this.Conversations[idx] = conversationEntity;
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
// Get proper entity object for the message if needed
|
|
2188
|
+
let messageEntity;
|
|
2189
|
+
if (lastUserMessage.Delete !== undefined) {
|
|
2190
|
+
messageEntity = lastUserMessage;
|
|
2183
2191
|
}
|
|
2184
2192
|
else {
|
|
2185
2193
|
const p = this.ProviderToUse;
|
|
2186
|
-
|
|
2187
|
-
|
|
2194
|
+
messageEntity = await p.GetEntityObject('Conversation Details', p.CurrentUser);
|
|
2195
|
+
await messageEntity.Load(lastUserMessage.ID);
|
|
2188
2196
|
}
|
|
2189
|
-
//
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
if (lastUserMessage) {
|
|
2195
|
-
// Store the message text to restore to input
|
|
2196
|
-
const messageText = lastUserMessage.Message;
|
|
2197
|
-
// Update conversation status to Available
|
|
2198
|
-
conversationEntity.Status = 'Available';
|
|
2199
|
-
yield conversationEntity.Save();
|
|
2200
|
-
// Update the selected conversation object if we loaded a new one
|
|
2201
|
-
if (this.SelectedConversation !== conversationEntity) {
|
|
2202
|
-
this.SelectedConversation = conversationEntity;
|
|
2203
|
-
// Also update in the conversations list
|
|
2204
|
-
const idx = this.Conversations.findIndex(c => c.ID === conversationEntity.ID);
|
|
2205
|
-
if (idx >= 0) {
|
|
2206
|
-
this.Conversations[idx] = conversationEntity;
|
|
2207
|
-
}
|
|
2208
|
-
}
|
|
2209
|
-
// Get proper entity object for the message if needed
|
|
2210
|
-
let messageEntity;
|
|
2211
|
-
if (lastUserMessage.Delete !== undefined) {
|
|
2212
|
-
messageEntity = lastUserMessage;
|
|
2213
|
-
}
|
|
2214
|
-
else {
|
|
2215
|
-
const p = this.ProviderToUse;
|
|
2216
|
-
messageEntity = yield p.GetEntityObject('Conversation Details', p.CurrentUser);
|
|
2217
|
-
yield messageEntity.Load(lastUserMessage.ID);
|
|
2218
|
-
}
|
|
2219
|
-
// Delete the last user message
|
|
2220
|
-
yield messageEntity.Delete();
|
|
2221
|
-
// Remove from UI
|
|
2222
|
-
this.RemoveMessageFromCurrentConversation(lastUserMessage);
|
|
2223
|
-
// Restore text to input area
|
|
2224
|
-
if (this.askSkipInput && this.askSkipInput.nativeElement) {
|
|
2225
|
-
this.askSkipInput.nativeElement.value = messageText;
|
|
2226
|
-
this.resizeTextInput();
|
|
2227
|
-
}
|
|
2228
|
-
}
|
|
2229
|
-
// Clear processing state
|
|
2230
|
-
this.setProcessingStatus(this.SelectedConversation.ID, false);
|
|
2231
|
-
this._conversationsInProgress[this.SelectedConversation.ID] = false;
|
|
2232
|
-
this._messageInProgress = false;
|
|
2233
|
-
this.AllowSend = true;
|
|
2234
|
-
// Stop polling
|
|
2235
|
-
this.stopRequestStatusPolling(this.SelectedConversation.ID);
|
|
2236
|
-
// Clear any temporary messages
|
|
2237
|
-
this.SetSkipStatusMessage('', 0);
|
|
2238
|
-
// Update the UI
|
|
2239
|
-
this.cdRef.detectChanges();
|
|
2240
|
-
// Focus on the input
|
|
2197
|
+
// Delete the last user message
|
|
2198
|
+
await messageEntity.Delete();
|
|
2199
|
+
// Remove from UI
|
|
2200
|
+
this.RemoveMessageFromCurrentConversation(lastUserMessage);
|
|
2201
|
+
// Restore text to input area
|
|
2241
2202
|
if (this.askSkipInput && this.askSkipInput.nativeElement) {
|
|
2242
|
-
this.askSkipInput.nativeElement.
|
|
2203
|
+
this.askSkipInput.nativeElement.value = messageText;
|
|
2204
|
+
this.resizeTextInput();
|
|
2243
2205
|
}
|
|
2244
2206
|
}
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2207
|
+
// Clear processing state
|
|
2208
|
+
this.setProcessingStatus(this.SelectedConversation.ID, false);
|
|
2209
|
+
this._conversationsInProgress[this.SelectedConversation.ID] = false;
|
|
2210
|
+
this._messageInProgress = false;
|
|
2211
|
+
this.AllowSend = true;
|
|
2212
|
+
// Stop polling
|
|
2213
|
+
this.stopRequestStatusPolling(this.SelectedConversation.ID);
|
|
2214
|
+
// Clear any temporary messages
|
|
2215
|
+
this.SetSkipStatusMessage('', 0);
|
|
2216
|
+
// Update the UI
|
|
2217
|
+
this.cdRef.detectChanges();
|
|
2218
|
+
// Focus on the input
|
|
2219
|
+
if (this.askSkipInput && this.askSkipInput.nativeElement) {
|
|
2220
|
+
this.askSkipInput.nativeElement.focus();
|
|
2248
2221
|
}
|
|
2249
|
-
}
|
|
2222
|
+
}
|
|
2223
|
+
catch (error) {
|
|
2224
|
+
LogError(`Error stopping processing: ${error}`);
|
|
2225
|
+
this.notificationService.CreateSimpleNotification('Failed to stop processing', 'error', 3000);
|
|
2226
|
+
}
|
|
2250
2227
|
}
|
|
2251
2228
|
/**
|
|
2252
2229
|
* Automatically shows an artifact if the provided AI message has one
|
|
@@ -2300,88 +2277,76 @@ export class SkipChatComponent extends BaseAngularComponent {
|
|
|
2300
2277
|
// Same artifact and version, not new
|
|
2301
2278
|
return false;
|
|
2302
2279
|
}
|
|
2280
|
+
static ɵfac = function SkipChatComponent_Factory(t) { return new (t || SkipChatComponent)(i0.ɵɵdirectiveInject(i0.ElementRef), i0.ɵɵdirectiveInject(i0.Renderer2), i0.ɵɵdirectiveInject(i1.ActivatedRoute), i0.ɵɵdirectiveInject(i1.Router), i0.ɵɵdirectiveInject(i2.Location), i0.ɵɵdirectiveInject(i0.ChangeDetectorRef), i0.ɵɵdirectiveInject(i3.MJNotificationService)); };
|
|
2281
|
+
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: SkipChatComponent, selectors: [["skip-chat"]], viewQuery: function SkipChatComponent_Query(rf, ctx) { if (rf & 1) {
|
|
2282
|
+
i0.ɵɵviewQuery(Container, 7);
|
|
2283
|
+
i0.ɵɵviewQuery(_c0, 7);
|
|
2284
|
+
i0.ɵɵviewQuery(_c1, 5, ViewContainerRef);
|
|
2285
|
+
i0.ɵɵviewQuery(_c2, 5);
|
|
2286
|
+
i0.ɵɵviewQuery(_c3, 5);
|
|
2287
|
+
i0.ɵɵviewQuery(_c4, 5);
|
|
2288
|
+
i0.ɵɵviewQuery(_c5, 5);
|
|
2289
|
+
i0.ɵɵviewQuery(_c6, 5);
|
|
2290
|
+
i0.ɵɵviewQuery(_c7, 5);
|
|
2291
|
+
} if (rf & 2) {
|
|
2292
|
+
let _t;
|
|
2293
|
+
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.askSkip = _t.first);
|
|
2294
|
+
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.askSkipPanel = _t.first);
|
|
2295
|
+
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.mjContainerRef = _t.first);
|
|
2296
|
+
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.conversationList = _t.first);
|
|
2297
|
+
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.askSkipInput = _t.first);
|
|
2298
|
+
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.scrollContainer = _t.first);
|
|
2299
|
+
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.topLevelDiv = _t.first);
|
|
2300
|
+
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.resourcePermissionsRef = _t.first);
|
|
2301
|
+
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.splitPanel = _t.first);
|
|
2302
|
+
} }, inputs: { AllowSend: "AllowSend", Messages: "Messages", Conversations: "Conversations", SelectedConversation: "SelectedConversation", ConversationEditMode: "ConversationEditMode", ShowConversationList: "ShowConversationList", AllowNewConversations: "AllowNewConversations", Title: "Title", DataContextID: "DataContextID", LinkedEntity: "LinkedEntity", LinkedEntityCompositeKey: "LinkedEntityCompositeKey", ShowDataContextButton: "ShowDataContextButton", IncludeLinkedConversationsInList: "IncludeLinkedConversationsInList", SkipLogoURL: "SkipLogoURL", SkipMarkOnlyLogoURL: "SkipMarkOnlyLogoURL", UserImage: "UserImage", VerboseLogging: "VerboseLogging", UpdateAppRoute: "UpdateAppRoute", ShowSkipLogoInConversationList: "ShowSkipLogoInConversationList", ShowSharingButton: "ShowSharingButton", SharingExcludeRoleNames: "SharingExcludeRoleNames", SharingExcludeEmails: "SharingExcludeEmails", EnableArtifactSplitView: "EnableArtifactSplitView", DefaultSplitRatio: "DefaultSplitRatio", DefaultTextboxPlaceholder: "DefaultTextboxPlaceholder", ProcessingTextBoxPlaceholder: "ProcessingTextBoxPlaceholder", WelcomeQuestions: "WelcomeQuestions", AutoLoad: "AutoLoad" }, outputs: { NavigateToMatchingReport: "NavigateToMatchingReport", ConversationSelected: "ConversationSelected", NewReportCreated: "NewReportCreated", DrillDownEvent: "DrillDownEvent", ArtifactSelected: "ArtifactSelected", ArtifactViewed: "ArtifactViewed" }, features: [i0.ɵɵInheritDefinitionFeature], decls: 24, vars: 20, consts: [["topLevelDiv", ""], ["splitPanel", ""], ["AskSkipPanel", ""], ["scrollContainer", ""], ["conversationList", ""], ["AskSkipInput", ""], ["resourcePermissions", ""], ["mjFillContainer", "", 1, "chat-container", 3, "bottomMargin", "rightMargin"], [1, "layout"], [1, "left-panel"], [1, "fa-solid", "fa-table-columns", "toggle-icon"], [1, "right-panel"], ["mjFillContainer", "", 3, "SplitRatioChanged", "VersionSelected", "Mode", "SplitRatio", "RightPanelHeaderContent", "VersionList", "SelectedVersionId", "fillWidth", "fillHeight"], ["left-panel", "", 1, "conversation-wrapper"], [2, "width", "0", "height", "0", "overflow", "hidden", "position", "absolute"], [1, "messages", 3, "scroll"], ["class", "welcome-wrapper", 4, "ngIf"], [1, "loading-convo-messages-wrapper"], ["mjContainer", "", "mjSkipResize", "true", 1, "messages-container"], ["class", "scroll-to-bottom-icon", 3, "left", "click", 4, "ngIf"], [1, "input-area"], ["right-panel", ""], [3, "ArtifactID", "ArtifactVersionID", "DataContext", "NavigateToMatchingReport", "NewReportCreated", "DrillDownEvent", "ArtifactInfoChanged", 4, "ngIf"], [3, "dataContextId", "Provider"], ["title", "Share Conversation", 3, "width", "height"], ["title", "Please confirm", 3, "minWidth", "width", "close", 4, "ngIf"], [1, "conversation-history"], [1, "new-chat-area"], [1, "fa-solid", "fa-table-columns", "toggle-icon", 3, "click"], [1, "avatar", 3, "src"], [1, "fa-solid", "fa-pen-to-square", "new-convo-icon"], ["mjFillContainer", "", 1, "conversation-list", 3, "data", "itemClass", "fillWidth", "bottomMargin"], ["kendoListViewItemTemplate", ""], [1, "fa-solid", "fa-pen-to-square", "new-convo-icon", 3, "click"], [1, "conversation-item", 3, "click", "ngClass", "title"], ["class", "fa-regular fa-clock", 4, "ngIf"], [1, "text-container"], [4, "ngIf"], ["maxlength", "100", 3, "ngModel", "ngModelChange", 4, "ngIf"], ["class", "edit-conversation-panel", 4, "ngIf"], [1, "fa-regular", "fa-clock"], ["maxlength", "100", 3, "ngModelChange", "ngModel"], [1, "edit-conversation-panel"], ["class", "fa-solid fa-pen-to-square", 3, "click", 4, "ngIf"], ["class", "fa-regular fa-trash-can", 3, "click", 4, "ngIf"], ["class", "fa-solid fa-check", 3, "click", 4, "ngIf"], ["class", "fa-solid fa-xmark", 3, "click", 4, "ngIf"], [1, "fa-solid", "fa-pen-to-square", 3, "click"], [1, "fa-regular", "fa-trash-can", 3, "click"], [1, "fa-solid", "fa-check", 3, "click"], [1, "fa-solid", "fa-xmark", 3, "click"], [1, "welcome-wrapper"], [1, "welcome-message"], [3, "src"], [1, "welcome-header-text"], [1, "welcome-suggested-questions"], [1, "welcome-suggested-questions-col"], [1, "welcome-question", 3, "click"], [1, "welcome-question-header"], [1, "scroll-to-bottom-icon", 3, "click"], [1, "fas", "fa-arrow-down"], [1, "text-area-wrapper"], ["type", "text", 3, "keyup.enter", "input", "disabled", "placeholder"], [1, "button-area"], ["kendoButton", ""], ["kendoButton", "", 1, "stop-button"], ["kendoButton", "", 3, "disabled"], ["kendoButton", "", 1, "share-button"], [1, "fa-solid", "fa-gear", 3, "click"], ["kendoButton", "", 1, "stop-button", 3, "click"], [1, "fas", "fa-solid", "fa-stop"], ["kendoButton", "", 3, "click", "disabled"], [1, "fas", "fa-solid", "fa-arrow-up"], [1, "fa-solid", "fa-share", 3, "click"], [3, "NavigateToMatchingReport", "NewReportCreated", "DrillDownEvent", "ArtifactInfoChanged", "ArtifactID", "ArtifactVersionID", "DataContext"], [3, "dialogClosed", "dataContextId", "Provider"], ["title", "Share Conversation", 3, "close", "width", "height"], [3, "Provider", "ResourceTypeID", "ResourceRecordID", "ExcludedRoleNames", "ExcludedUserEmails"], ["kendoButton", "", "themeColor", "primary", 3, "click"], ["kendoButton", "", 3, "click"], ["title", "Please confirm", 3, "close", "minWidth", "width"], [2, "margin", "30px", "text-align", "center"]], template: function SkipChatComponent_Template(rf, ctx) { if (rf & 1) {
|
|
2303
|
+
const _r1 = i0.ɵɵgetCurrentView();
|
|
2304
|
+
i0.ɵɵelementStart(0, "div", 7, 0)(2, "div", 8);
|
|
2305
|
+
i0.ɵɵtemplate(3, SkipChatComponent_Conditional_3_Template, 9, 9, "div", 9)(4, SkipChatComponent_Conditional_4_Template, 1, 0, "span", 10);
|
|
2306
|
+
i0.ɵɵelementStart(5, "div", 11)(6, "skip-split-panel", 12, 1);
|
|
2307
|
+
i0.ɵɵlistener("SplitRatioChanged", function SkipChatComponent_Template_skip_split_panel_SplitRatioChanged_6_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.onSplitRatioChanged($event)); })("VersionSelected", function SkipChatComponent_Template_skip_split_panel_VersionSelected_6_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.onArtifactVersionSelected($event)); });
|
|
2308
|
+
i0.ɵɵelementStart(8, "div", 13);
|
|
2309
|
+
i0.ɵɵelement(9, "div", 14, 2);
|
|
2310
|
+
i0.ɵɵelementStart(11, "div", 15, 3);
|
|
2311
|
+
i0.ɵɵlistener("scroll", function SkipChatComponent_Template_div_scroll_11_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.checkScroll()); });
|
|
2312
|
+
i0.ɵɵtemplate(13, SkipChatComponent_div_13_Template, 28, 9, "div", 16)(14, SkipChatComponent_Conditional_14_Template, 2, 0, "div", 17);
|
|
2313
|
+
i0.ɵɵelement(15, "div", 18);
|
|
2314
|
+
i0.ɵɵtemplate(16, SkipChatComponent_span_16_Template, 2, 2, "span", 19);
|
|
2315
|
+
i0.ɵɵelementEnd();
|
|
2316
|
+
i0.ɵɵtemplate(17, SkipChatComponent_Conditional_17_Template, 9, 7, "div", 20);
|
|
2317
|
+
i0.ɵɵelementEnd();
|
|
2318
|
+
i0.ɵɵelementStart(18, "div", 21);
|
|
2319
|
+
i0.ɵɵtemplate(19, SkipChatComponent_skip_artifact_viewer_19_Template, 1, 3, "skip-artifact-viewer", 22);
|
|
2320
|
+
i0.ɵɵelementEnd()()()()();
|
|
2321
|
+
i0.ɵɵtemplate(20, SkipChatComponent_Conditional_20_Template, 1, 2, "mj-data-context-dialog", 23)(21, SkipChatComponent_Conditional_21_Template, 8, 7, "kendo-dialog", 24)(22, SkipChatComponent_kendo_dialog_22_Template, 8, 3, "kendo-dialog", 25)(23, SkipChatComponent_kendo_dialog_23_Template, 8, 3, "kendo-dialog", 25);
|
|
2322
|
+
} if (rf & 2) {
|
|
2323
|
+
i0.ɵɵproperty("bottomMargin", 10)("rightMargin", 10);
|
|
2324
|
+
i0.ɵɵadvance(3);
|
|
2325
|
+
i0.ɵɵconditional(ctx.IsConversationListVisible ? 3 : -1);
|
|
2326
|
+
i0.ɵɵadvance();
|
|
2327
|
+
i0.ɵɵconditional(!ctx.IsConversationListVisible ? 4 : -1);
|
|
2328
|
+
i0.ɵɵadvance(2);
|
|
2329
|
+
i0.ɵɵproperty("Mode", ctx.EnableArtifactSplitView && ctx.selectedArtifact ? "BothSides" : "LeftOnly")("SplitRatio", ctx.SplitRatio)("RightPanelHeaderContent", ctx.artifactHeaderInfo)("VersionList", ctx.artifactVersionList)("SelectedVersionId", ctx.selectedArtifactVersionId)("fillWidth", false)("fillHeight", true);
|
|
2330
|
+
i0.ɵɵadvance(7);
|
|
2331
|
+
i0.ɵɵproperty("ngIf", (!ctx.Messages || ctx.Messages.length === 0) && ctx._conversationLoadComplete);
|
|
2332
|
+
i0.ɵɵadvance();
|
|
2333
|
+
i0.ɵɵconditional(!ctx._conversationLoadComplete ? 14 : -1);
|
|
2334
|
+
i0.ɵɵadvance(2);
|
|
2335
|
+
i0.ɵɵproperty("ngIf", ctx._showScrollToBottomIcon && ctx.Messages && ctx.Messages.length > 0);
|
|
2336
|
+
i0.ɵɵadvance();
|
|
2337
|
+
i0.ɵɵconditional(ctx.SelectedConversationCurrentUserPermissionLevel === "Owner" || ctx.SelectedConversationCurrentUserPermissionLevel === "Edit" ? 17 : -1);
|
|
2338
|
+
i0.ɵɵadvance(2);
|
|
2339
|
+
i0.ɵɵproperty("ngIf", ctx.selectedArtifact);
|
|
2340
|
+
i0.ɵɵadvance();
|
|
2341
|
+
i0.ɵɵconditional(ctx.isDataContextDialogVisible ? 20 : -1);
|
|
2342
|
+
i0.ɵɵadvance();
|
|
2343
|
+
i0.ɵɵconditional(ctx.isSharingDialogVisible && ctx.SelectedConversation && ctx.conversationResourceTypeID ? 21 : -1);
|
|
2344
|
+
i0.ɵɵadvance();
|
|
2345
|
+
i0.ɵɵproperty("ngIf", ctx.confirmDeleteConversationDialogOpen);
|
|
2346
|
+
i0.ɵɵadvance();
|
|
2347
|
+
i0.ɵɵproperty("ngIf", ctx.confirmMessageEditOrDeleteDialogOpen);
|
|
2348
|
+
} }, dependencies: [i2.NgClass, i2.NgIf, i4.DefaultValueAccessor, i4.NgControlStatus, i4.MaxLengthValidator, i4.NgModel, i5.LoaderComponent, i6.DialogComponent, i6.DialogActionsComponent, i7.FillContainer, i7.Container, i8.ItemTemplateDirective, i8.ListViewComponent, i9.ButtonComponent, i10.DataContextDialogComponent, i11.ResourcePermissionsComponent, i12.SkipSplitPanelComponent, i13.SkipArtifactViewerComponent], styles: [".layout[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: row; \n\n height: 100%; \n\n width: 100%; \n\n position: relative;\n overflow: hidden; \n\n}\n\n.left-panel[_ngcontent-%COMP%] {\n width: 272px; \n\n background-color: #f8f9fa; \n\n border-right: 1px solid #ddd; \n\n overflow-y: auto; \n\n overflow-x: hidden; \n\n position: relative;\n\n scrollbar-width: thin; \n\n scrollbar-color: #d3d3d3 #f8f9fa; \n\n}\n\n\n\n.left-panel[_ngcontent-%COMP%]::-webkit-scrollbar {\n width: 8px; \n\n background-color: #f8f9fa; \n\n}\n\n.left-panel[_ngcontent-%COMP%]::-webkit-scrollbar-thumb {\n background-color: #d3d3d3; \n\n border-radius: 4px; \n\n}\n\n.left-panel[_ngcontent-%COMP%]::-webkit-scrollbar-thumb:hover {\n background-color: #c0c0c0; \n\n}\n\n.left-panel[_ngcontent-%COMP%]::-webkit-scrollbar-track {\n background-color: #f8f9fa; \n\n}\n\n.right-panel[_ngcontent-%COMP%] {\n flex: 1; \n\n display: flex;\n flex-direction: column;\n height: 100%;\n max-height: 100%; \n\n overflow: hidden; \n\n}\n\n.conversation-header[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 8px 16px;\n background-color: #f5f7f9;\n border-bottom: 1px solid #dde4ee;\n height: 40px;\n flex-shrink: 0;\n}\n\n.conversation-title[_ngcontent-%COMP%] {\n font-size: 15px;\n font-weight: 500;\n color: #333;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 70%;\n}\n\n.artifact-counter-container[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n}\n\n\n.new-convo-icon[_ngcontent-%COMP%] {\n color: #808080; \n\n font-size: 18px; \n\n cursor: pointer; \n\n z-index: 10; \n\n padding: 5px;\n border-radius: 4px;\n}\n\n.toggle-icon[_ngcontent-%COMP%] {\n color: #808080; \n\n font-size: 18px; \n\n cursor: pointer; \n\n z-index: 10; \n\n margin-left: 6px;\n padding: 3px;\n border-radius: 3px;\n}\n \n\n.right-panel[_ngcontent-%COMP%] .toggle-icon[_ngcontent-%COMP%] {\n margin-left: 3px;\n margin-top: 2px;\n position: absolute;\n top: 10px;\n left: auto;\n right: 10px; \n\n}\n\n\n.chat-container[_ngcontent-%COMP%] {\n padding: 5px;\n display: flex;\n flex-direction: row;\n height: 100%;\n font-family: S\u00F6hne, ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", Roboto, Ubuntu, Cantarell, \"Noto Sans\", sans-serif, \"Helvetica Neue\", Arial, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n font-size: 1rem;\n\n \n\n width: 100%;\n height: 100%;\n max-height: 100vh; \n\n overflow: hidden; \n\n\n background-color: #f9f9f9;\n}\n\n.conversation-wrapper[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n position: relative; \n\n background-color: #f9f9f9;\n height: 100%; \n\n max-height: 100%; \n\n flex: 1;\n overflow: auto; \n\n}\n\n.new-conversation[_ngcontent-%COMP%] {\n height: 30px;\n font-size: large;\n}\n\n.conversation-history[_ngcontent-%COMP%] {\n width: 240px;\n min-width: 240px;\n height: 95%;\n overflow-y: auto; \n\n overflow-x: hidden; \n\n margin-right: 10px;\n padding-top: 5px;\n background-color: #f9f9f9;\n margin-top: 0px; \n padding: 12px; \n}\n\n.k-tabstrip-content-for-skip[_ngcontent-%COMP%] {\n padding: 0;\n padding-block: 0;\n}\n\n\n.conversation-history[_ngcontent-%COMP%] > button[_ngcontent-%COMP%] {\n height: 25px;\n}\n\n.skip-title[_ngcontent-%COMP%] {\n font-size: larger;\n margin-bottom: 5px;\n height: 20px;\n margin-top: 5px;\n}\n\n.conversation-list[_ngcontent-%COMP%] {\n margin-top: 5px;\n padding-top: 5px;\n \n border: 0;\n background-color: #f9f9f9;\n}\n\n\n\n\n\n.welcome-wrapper[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n height: 100%;\n width: 100%;\n overflow: hidden;\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n z-index: 5;\n}\n\n.welcome-message[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n text-align: center;\n overflow: hidden;\n height: 100%;\n padding-bottom: 100px; \n\n}\n\n.embedded-conversations[_ngcontent-%COMP%] {\n margin-left: 3px;\n margin-top: 5px;\n font-size: 10pt;\n color: rgb(48, 48, 235);\n}\n.embedded-conversations[_ngcontent-%COMP%] > span[_ngcontent-%COMP%] {\n margin-top: 4px;\n margin-left: 5px;\n cursor: pointer;\n}\n.conversation-item-linked[_ngcontent-%COMP%] {\n color: rgb(48, 48, 235);\n}\n\n.welcome-message[_ngcontent-%COMP%] img[_ngcontent-%COMP%] {\n width: 120px;\n height: 50px;\n margin-bottom: 20px; \n\n position: relative;\n z-index: 10;\n}\n\n.welcome-header-text[_ngcontent-%COMP%] {\n font-size: larger;\n font-weight: bold;\n}\n\n\n\n.welcome-suggested-questions[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-content: center;\n margin-top: 30px; \n\n}\n.welcome-suggested-questions-col[_ngcontent-%COMP%] {\n display: flex;\n margin-bottom: 10px; \n\n}\n\n\n\n.welcome-question[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column; \n\n align-items: left;;\n width: 300px; \n justify-content: space-between;\n margin: 5px; \n\n border: solid 1px rgba(41, 28, 28, 0.08);\n border-radius: 15px;\n padding: 10px;\n cursor: pointer;\n}\n\n.welcome-question[_ngcontent-%COMP%]:hover {\n background-color: rgba(0, 0, 0, 0.05);\n}\n\n\n.welcome-question-header[_ngcontent-%COMP%] {\n font-size: 12pt;\n font-weight: bold;\n display: block; \n\n}\n\n\n\n.welcome-question[_ngcontent-%COMP%] span[_ngcontent-%COMP%]:not(.welcome-question-header) {\n font-weight: normal;\n font-size: 10pt;\n}\n\n\n.messages[_ngcontent-%COMP%] {\n overflow-y: auto !important; \n\n overflow-x: hidden !important; \n\n \n\n margin-bottom: 5px;\n\n margin-top: 2px; \n\n\n background-color: #f9f9f9;\n flex: 1 1 auto; \n\n height: calc(100% - 50px); \n\n max-height: 100%; \n\n scrollbar-width: thin; \n\n scrollbar-color: #d3d3d3 #f8f9fa; \n\n position: relative; \n\n}\n\n\n\n.messages[_ngcontent-%COMP%]::-webkit-scrollbar {\n width: 8px; \n\n background-color: #f8f9fa; \n\n}\n\n.messages[_ngcontent-%COMP%]::-webkit-scrollbar-thumb {\n background-color: #d3d3d3; \n\n border-radius: 4px; \n\n}\n\n.messages[_ngcontent-%COMP%]::-webkit-scrollbar-thumb:hover {\n background-color: #c0c0c0; \n\n}\n\n.messages[_ngcontent-%COMP%]::-webkit-scrollbar-track {\n background-color: #f8f9fa; \n\n}\n\n\n\n.messages-container[_ngcontent-%COMP%] {\n min-height: 20px; \n\n}\n\n\n\n.new-chat-area[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between; \n\n align-items: center; \n\n}\n.avatar[_ngcontent-%COMP%] {\n max-height: 24px;\n margin-right: 10px;\n margin-left: 5px;\n margin-bottom: 3px;\n \n\n margin-right: auto; \n\n}\n\n.conversation-item[_ngcontent-%COMP%] {\n margin-left: 5px;\n margin-right: 5px;\n padding-top: 10px;\n padding-bottom: 10px;\n padding-left: 5px;\n padding-right: 5px;\n border-radius: 5px;\n cursor: pointer;\n overflow: hidden;\n max-height: 150px;\n font-size: 14px;\n\n display: flex;\n align-items: flex-start; \n\n\n flex-wrap: wrap; \n\n}\n\n.text-container[_ngcontent-%COMP%] {\n flex: 1; \n\n display: flex;\n flex-direction: column; \n\n}\n\n.text-container[_ngcontent-%COMP%] textarea[_ngcontent-%COMP%] {\n resize: none; \n\n \n\n}\n\n.conversation-item[_ngcontent-%COMP%] > .conversation-icon[_ngcontent-%COMP%] {\n margin-top: 3px;\n}\n\n.conversation-item[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n display: inline-block;\n white-space: pre-wrap; \n\n overflow: auto;\n word-wrap: break-word;\n margin-left: 3px; \n\n}\n\n.conversation-item[_ngcontent-%COMP%]:hover {\n background-color: rgba(0, 0, 0, 0.05);\n}\n.conversation-item-selected[_ngcontent-%COMP%] {\n background-color: rgba(0, 0, 0, 0.15);\n}\n\n\n.conversation-item[_ngcontent-%COMP%] > .conversation-icon[_ngcontent-%COMP%] {\n margin-right: 11px;\n}\n.edit-conversation-panel[_ngcontent-%COMP%] {\n display: flex;\n justify-content: flex-end; \n\n margin-top: 2px; \n\n margin-right: 2px; \n\n}\n.edit-conversation-panel[_ngcontent-%COMP%] > .k-icon[_ngcontent-%COMP%] {\n margin-left: 5px;\n cursor: pointer;\n}\n.edit-conversation-panel[_ngcontent-%COMP%] > .k-icon[_ngcontent-%COMP%]:hover {\n color: #ff0000;\n}\n\n\n\n.input-area[_ngcontent-%COMP%] {\n min-height: 35px;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-bottom: 15px;\n position: sticky;\n bottom: 0;\n background-color: #f9f9f9;\n z-index: 10;\n}\n\n.input-area[_ngcontent-%COMP%] > .button-area[_ngcontent-%COMP%] {\n vertical-align: top;\n margin-top: 3px;\n margin-left: -65px;\n}\n\n\n.button-area[_ngcontent-%COMP%] > button[_ngcontent-%COMP%] {\n width: 30px;\n height: 30px;\n border-radius: 12px;\n margin-left: 3px;\n}\n\n\n\n.button-area[_ngcontent-%COMP%] > button.stop-button[_ngcontent-%COMP%] {\n background-color: #dc3545;\n color: white;\n}\n\n.button-area[_ngcontent-%COMP%] > button.stop-button[_ngcontent-%COMP%]:hover {\n background-color: #c82333;\n}\n\n\n\n\n\n\n\n\n\n\n\n.text-area-wrapper[_ngcontent-%COMP%] {\n padding: 3px;\n border: solid 1px rgba(0, 0, 0, 0.08) ;\n border-radius: 15px;\n\n margin-top: 4px;\n margin-right: -1px;\n min-height: 42px;\n max-height: 100%; \n\n\n overflow: hidden; \n align-items: center;\n\n \n\n width: 710px; \n padding-right: 90px\n} \n.text-area-wrapper[_ngcontent-%COMP%] > textarea[_ngcontent-%COMP%] {\n border: 0;\n outline: 0;\n resize: none;\n\n min-height: 20px; \n\n\n width: 100%;\n overflow-y: hidden; \n\n\n font-family: S\u00F6hne, ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", Roboto, Ubuntu, Cantarell, \"Noto Sans\", sans-serif, \"Helvetica Neue\", Arial, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n font-size: 1rem;\n\n margin-left: 7px;\n margin-top: 7px;\n margin-bottom: 5px;\n\n background-color: #f9f9f9;\n}\n\n\n\n\n\n\n.input-wrapper[_ngcontent-%COMP%] {\n flex-grow: 1; \n\n height: 100%;\n}\n\n.waiting-for-ai[_ngcontent-%COMP%] {\n position: absolute;\n display: flex; \n\n bottom: 100px;\n z-index: 999;\n left: 10px; \n}\n \n.scroll-to-bottom-icon[_ngcontent-%COMP%] {\n position: fixed; \n\n bottom: 120px; \n\n \n\n transform: translateX(-50%); \n\n z-index: 1000; \n\n background-color: white; \n\n color: black; \n\n border-radius: 50%; \n\n width: 40px; \n\n height: 40px; \n\n display: flex;\n justify-content: center;\n align-items: center;\n box-shadow: 0px 0px 5px rgba(0,0,0,0.3); \n\n cursor: pointer;\n opacity: 0.9; \n\n}\n\n.loading-convo-messages-wrapper[_ngcontent-%COMP%] {\n display: flex;\n justify-content: center;\n align-items: center;\n height: 100%;\n width: 100%;\n position: absolute;\n z-index: 1000;\n}\n\n@media (min-width: 600px) {\n .welcome-suggested-questions[_ngcontent-%COMP%] {\n display: flex;\n flex-wrap: wrap; \n\n align-content: flex-end; \n\n }\n}"] });
|
|
2303
2349
|
}
|
|
2304
|
-
SkipChatComponent.__skipChatWindowsCurrentlyVisible = 0;
|
|
2305
|
-
SkipChatComponent._cacheRootKey = '___SkipChat__';
|
|
2306
|
-
SkipChatComponent._startMessages = [
|
|
2307
|
-
'On it, let me get back to you in a moment with the results!🤖',
|
|
2308
|
-
"I'm on it, just a moment! 🙂",
|
|
2309
|
-
"I'll get started in a jiffy!",
|
|
2310
|
-
"You bet, I'd love to help, give me a moment!",
|
|
2311
|
-
"I understand, I'll start running in that direction 👟",
|
|
2312
|
-
"No problem, I'll get started right away!",
|
|
2313
|
-
"Ok, heard loud and clear, I'll jump right on it! 👂",
|
|
2314
|
-
"Aye aye captain, I'll get started right away! ⚓",
|
|
2315
|
-
];
|
|
2316
|
-
SkipChatComponent.ɵfac = function SkipChatComponent_Factory(t) { return new (t || SkipChatComponent)(i0.ɵɵdirectiveInject(i0.ElementRef), i0.ɵɵdirectiveInject(i0.Renderer2), i0.ɵɵdirectiveInject(i1.ActivatedRoute), i0.ɵɵdirectiveInject(i1.Router), i0.ɵɵdirectiveInject(i2.Location), i0.ɵɵdirectiveInject(i0.ChangeDetectorRef), i0.ɵɵdirectiveInject(i3.MJNotificationService)); };
|
|
2317
|
-
SkipChatComponent.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: SkipChatComponent, selectors: [["skip-chat"]], viewQuery: function SkipChatComponent_Query(rf, ctx) { if (rf & 1) {
|
|
2318
|
-
i0.ɵɵviewQuery(Container, 7);
|
|
2319
|
-
i0.ɵɵviewQuery(_c0, 7);
|
|
2320
|
-
i0.ɵɵviewQuery(_c1, 5, ViewContainerRef);
|
|
2321
|
-
i0.ɵɵviewQuery(_c2, 5);
|
|
2322
|
-
i0.ɵɵviewQuery(_c3, 5);
|
|
2323
|
-
i0.ɵɵviewQuery(_c4, 5);
|
|
2324
|
-
i0.ɵɵviewQuery(_c5, 5);
|
|
2325
|
-
i0.ɵɵviewQuery(_c6, 5);
|
|
2326
|
-
i0.ɵɵviewQuery(_c7, 5);
|
|
2327
|
-
} if (rf & 2) {
|
|
2328
|
-
let _t;
|
|
2329
|
-
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.askSkip = _t.first);
|
|
2330
|
-
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.askSkipPanel = _t.first);
|
|
2331
|
-
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.mjContainerRef = _t.first);
|
|
2332
|
-
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.conversationList = _t.first);
|
|
2333
|
-
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.askSkipInput = _t.first);
|
|
2334
|
-
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.scrollContainer = _t.first);
|
|
2335
|
-
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.topLevelDiv = _t.first);
|
|
2336
|
-
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.resourcePermissionsRef = _t.first);
|
|
2337
|
-
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.splitPanel = _t.first);
|
|
2338
|
-
} }, inputs: { AllowSend: "AllowSend", Messages: "Messages", Conversations: "Conversations", SelectedConversation: "SelectedConversation", ConversationEditMode: "ConversationEditMode", ShowConversationList: "ShowConversationList", AllowNewConversations: "AllowNewConversations", Title: "Title", DataContextID: "DataContextID", LinkedEntity: "LinkedEntity", LinkedEntityCompositeKey: "LinkedEntityCompositeKey", ShowDataContextButton: "ShowDataContextButton", IncludeLinkedConversationsInList: "IncludeLinkedConversationsInList", SkipLogoURL: "SkipLogoURL", SkipMarkOnlyLogoURL: "SkipMarkOnlyLogoURL", UserImage: "UserImage", VerboseLogging: "VerboseLogging", UpdateAppRoute: "UpdateAppRoute", ShowSkipLogoInConversationList: "ShowSkipLogoInConversationList", ShowSharingButton: "ShowSharingButton", SharingExcludeRoleNames: "SharingExcludeRoleNames", SharingExcludeEmails: "SharingExcludeEmails", EnableArtifactSplitView: "EnableArtifactSplitView", DefaultSplitRatio: "DefaultSplitRatio", DefaultTextboxPlaceholder: "DefaultTextboxPlaceholder", ProcessingTextBoxPlaceholder: "ProcessingTextBoxPlaceholder", WelcomeQuestions: "WelcomeQuestions", AutoLoad: "AutoLoad" }, outputs: { NavigateToMatchingReport: "NavigateToMatchingReport", ConversationSelected: "ConversationSelected", NewReportCreated: "NewReportCreated", DrillDownEvent: "DrillDownEvent", ArtifactSelected: "ArtifactSelected", ArtifactViewed: "ArtifactViewed" }, features: [i0.ɵɵInheritDefinitionFeature], decls: 24, vars: 20, consts: [["topLevelDiv", ""], ["splitPanel", ""], ["AskSkipPanel", ""], ["scrollContainer", ""], ["conversationList", ""], ["AskSkipInput", ""], ["resourcePermissions", ""], ["mjFillContainer", "", 1, "chat-container", 3, "bottomMargin", "rightMargin"], [1, "layout"], [1, "left-panel"], [1, "fa-solid", "fa-table-columns", "toggle-icon"], [1, "right-panel"], ["mjFillContainer", "", 3, "SplitRatioChanged", "VersionSelected", "Mode", "SplitRatio", "RightPanelHeaderContent", "VersionList", "SelectedVersionId", "fillWidth", "fillHeight"], ["left-panel", "", 1, "conversation-wrapper"], [2, "width", "0", "height", "0", "overflow", "hidden", "position", "absolute"], [1, "messages", 3, "scroll"], ["class", "welcome-wrapper", 4, "ngIf"], [1, "loading-convo-messages-wrapper"], ["mjContainer", "", "mjSkipResize", "true", 1, "messages-container"], ["class", "scroll-to-bottom-icon", 3, "left", "click", 4, "ngIf"], [1, "input-area"], ["right-panel", ""], [3, "ArtifactID", "ArtifactVersionID", "DataContext", "NavigateToMatchingReport", "NewReportCreated", "DrillDownEvent", "ArtifactInfoChanged", 4, "ngIf"], [3, "dataContextId", "Provider"], ["title", "Share Conversation", 3, "width", "height"], ["title", "Please confirm", 3, "minWidth", "width", "close", 4, "ngIf"], [1, "conversation-history"], [1, "new-chat-area"], [1, "fa-solid", "fa-table-columns", "toggle-icon", 3, "click"], [1, "avatar", 3, "src"], [1, "fa-solid", "fa-pen-to-square", "new-convo-icon"], ["mjFillContainer", "", 1, "conversation-list", 3, "data", "itemClass", "fillWidth", "bottomMargin"], ["kendoListViewItemTemplate", ""], [1, "fa-solid", "fa-pen-to-square", "new-convo-icon", 3, "click"], [1, "conversation-item", 3, "click", "ngClass", "title"], ["class", "fa-regular fa-clock", 4, "ngIf"], [1, "text-container"], [4, "ngIf"], ["maxlength", "100", 3, "ngModel", "ngModelChange", 4, "ngIf"], ["class", "edit-conversation-panel", 4, "ngIf"], [1, "fa-regular", "fa-clock"], ["maxlength", "100", 3, "ngModelChange", "ngModel"], [1, "edit-conversation-panel"], ["class", "fa-solid fa-pen-to-square", 3, "click", 4, "ngIf"], ["class", "fa-regular fa-trash-can", 3, "click", 4, "ngIf"], ["class", "fa-solid fa-check", 3, "click", 4, "ngIf"], ["class", "fa-solid fa-xmark", 3, "click", 4, "ngIf"], [1, "fa-solid", "fa-pen-to-square", 3, "click"], [1, "fa-regular", "fa-trash-can", 3, "click"], [1, "fa-solid", "fa-check", 3, "click"], [1, "fa-solid", "fa-xmark", 3, "click"], [1, "welcome-wrapper"], [1, "welcome-message"], [3, "src"], [1, "welcome-header-text"], [1, "welcome-suggested-questions"], [1, "welcome-suggested-questions-col"], [1, "welcome-question", 3, "click"], [1, "welcome-question-header"], [1, "scroll-to-bottom-icon", 3, "click"], [1, "fas", "fa-arrow-down"], [1, "text-area-wrapper"], ["type", "text", 3, "keyup.enter", "input", "disabled", "placeholder"], [1, "button-area"], ["kendoButton", ""], ["kendoButton", "", 1, "stop-button"], ["kendoButton", "", 3, "disabled"], ["kendoButton", "", 1, "share-button"], [1, "fa-solid", "fa-gear", 3, "click"], ["kendoButton", "", 1, "stop-button", 3, "click"], [1, "fas", "fa-solid", "fa-stop"], ["kendoButton", "", 3, "click", "disabled"], [1, "fas", "fa-solid", "fa-arrow-up"], [1, "fa-solid", "fa-share", 3, "click"], [3, "NavigateToMatchingReport", "NewReportCreated", "DrillDownEvent", "ArtifactInfoChanged", "ArtifactID", "ArtifactVersionID", "DataContext"], [3, "dialogClosed", "dataContextId", "Provider"], ["title", "Share Conversation", 3, "close", "width", "height"], [3, "Provider", "ResourceTypeID", "ResourceRecordID", "ExcludedRoleNames", "ExcludedUserEmails"], ["kendoButton", "", "themeColor", "primary", 3, "click"], ["kendoButton", "", 3, "click"], ["title", "Please confirm", 3, "close", "minWidth", "width"], [2, "margin", "30px", "text-align", "center"]], template: function SkipChatComponent_Template(rf, ctx) { if (rf & 1) {
|
|
2339
|
-
const _r1 = i0.ɵɵgetCurrentView();
|
|
2340
|
-
i0.ɵɵelementStart(0, "div", 7, 0)(2, "div", 8);
|
|
2341
|
-
i0.ɵɵtemplate(3, SkipChatComponent_Conditional_3_Template, 9, 9, "div", 9)(4, SkipChatComponent_Conditional_4_Template, 1, 0, "span", 10);
|
|
2342
|
-
i0.ɵɵelementStart(5, "div", 11)(6, "skip-split-panel", 12, 1);
|
|
2343
|
-
i0.ɵɵlistener("SplitRatioChanged", function SkipChatComponent_Template_skip_split_panel_SplitRatioChanged_6_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.onSplitRatioChanged($event)); })("VersionSelected", function SkipChatComponent_Template_skip_split_panel_VersionSelected_6_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.onArtifactVersionSelected($event)); });
|
|
2344
|
-
i0.ɵɵelementStart(8, "div", 13);
|
|
2345
|
-
i0.ɵɵelement(9, "div", 14, 2);
|
|
2346
|
-
i0.ɵɵelementStart(11, "div", 15, 3);
|
|
2347
|
-
i0.ɵɵlistener("scroll", function SkipChatComponent_Template_div_scroll_11_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.checkScroll()); });
|
|
2348
|
-
i0.ɵɵtemplate(13, SkipChatComponent_div_13_Template, 28, 9, "div", 16)(14, SkipChatComponent_Conditional_14_Template, 2, 0, "div", 17);
|
|
2349
|
-
i0.ɵɵelement(15, "div", 18);
|
|
2350
|
-
i0.ɵɵtemplate(16, SkipChatComponent_span_16_Template, 2, 2, "span", 19);
|
|
2351
|
-
i0.ɵɵelementEnd();
|
|
2352
|
-
i0.ɵɵtemplate(17, SkipChatComponent_Conditional_17_Template, 9, 7, "div", 20);
|
|
2353
|
-
i0.ɵɵelementEnd();
|
|
2354
|
-
i0.ɵɵelementStart(18, "div", 21);
|
|
2355
|
-
i0.ɵɵtemplate(19, SkipChatComponent_skip_artifact_viewer_19_Template, 1, 3, "skip-artifact-viewer", 22);
|
|
2356
|
-
i0.ɵɵelementEnd()()()()();
|
|
2357
|
-
i0.ɵɵtemplate(20, SkipChatComponent_Conditional_20_Template, 1, 2, "mj-data-context-dialog", 23)(21, SkipChatComponent_Conditional_21_Template, 8, 7, "kendo-dialog", 24)(22, SkipChatComponent_kendo_dialog_22_Template, 8, 3, "kendo-dialog", 25)(23, SkipChatComponent_kendo_dialog_23_Template, 8, 3, "kendo-dialog", 25);
|
|
2358
|
-
} if (rf & 2) {
|
|
2359
|
-
i0.ɵɵproperty("bottomMargin", 10)("rightMargin", 10);
|
|
2360
|
-
i0.ɵɵadvance(3);
|
|
2361
|
-
i0.ɵɵconditional(ctx.IsConversationListVisible ? 3 : -1);
|
|
2362
|
-
i0.ɵɵadvance();
|
|
2363
|
-
i0.ɵɵconditional(!ctx.IsConversationListVisible ? 4 : -1);
|
|
2364
|
-
i0.ɵɵadvance(2);
|
|
2365
|
-
i0.ɵɵproperty("Mode", ctx.EnableArtifactSplitView && ctx.selectedArtifact ? "BothSides" : "LeftOnly")("SplitRatio", ctx.SplitRatio)("RightPanelHeaderContent", ctx.artifactHeaderInfo)("VersionList", ctx.artifactVersionList)("SelectedVersionId", ctx.selectedArtifactVersionId)("fillWidth", false)("fillHeight", true);
|
|
2366
|
-
i0.ɵɵadvance(7);
|
|
2367
|
-
i0.ɵɵproperty("ngIf", (!ctx.Messages || ctx.Messages.length === 0) && ctx._conversationLoadComplete);
|
|
2368
|
-
i0.ɵɵadvance();
|
|
2369
|
-
i0.ɵɵconditional(!ctx._conversationLoadComplete ? 14 : -1);
|
|
2370
|
-
i0.ɵɵadvance(2);
|
|
2371
|
-
i0.ɵɵproperty("ngIf", ctx._showScrollToBottomIcon && ctx.Messages && ctx.Messages.length > 0);
|
|
2372
|
-
i0.ɵɵadvance();
|
|
2373
|
-
i0.ɵɵconditional(ctx.SelectedConversationCurrentUserPermissionLevel === "Owner" || ctx.SelectedConversationCurrentUserPermissionLevel === "Edit" ? 17 : -1);
|
|
2374
|
-
i0.ɵɵadvance(2);
|
|
2375
|
-
i0.ɵɵproperty("ngIf", ctx.selectedArtifact);
|
|
2376
|
-
i0.ɵɵadvance();
|
|
2377
|
-
i0.ɵɵconditional(ctx.isDataContextDialogVisible ? 20 : -1);
|
|
2378
|
-
i0.ɵɵadvance();
|
|
2379
|
-
i0.ɵɵconditional(ctx.isSharingDialogVisible && ctx.SelectedConversation && ctx.conversationResourceTypeID ? 21 : -1);
|
|
2380
|
-
i0.ɵɵadvance();
|
|
2381
|
-
i0.ɵɵproperty("ngIf", ctx.confirmDeleteConversationDialogOpen);
|
|
2382
|
-
i0.ɵɵadvance();
|
|
2383
|
-
i0.ɵɵproperty("ngIf", ctx.confirmMessageEditOrDeleteDialogOpen);
|
|
2384
|
-
} }, dependencies: [i2.NgClass, i2.NgIf, i4.DefaultValueAccessor, i4.NgControlStatus, i4.MaxLengthValidator, i4.NgModel, i5.LoaderComponent, i6.DialogComponent, i6.DialogActionsComponent, i7.FillContainer, i7.Container, i8.ItemTemplateDirective, i8.ListViewComponent, i9.ButtonComponent, i10.DataContextDialogComponent, i11.ResourcePermissionsComponent, i12.SkipSplitPanelComponent, i13.SkipArtifactViewerComponent], styles: [".layout[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: row; \n\n height: 100%; \n\n width: 100%; \n\n position: relative;\n overflow: hidden; \n\n}\n\n.left-panel[_ngcontent-%COMP%] {\n width: 272px; \n\n background-color: #f8f9fa; \n\n border-right: 1px solid #ddd; \n\n overflow-y: auto; \n\n overflow-x: hidden; \n\n position: relative;\n\n scrollbar-width: thin; \n\n scrollbar-color: #d3d3d3 #f8f9fa; \n\n}\n\n\n\n.left-panel[_ngcontent-%COMP%]::-webkit-scrollbar {\n width: 8px; \n\n background-color: #f8f9fa; \n\n}\n\n.left-panel[_ngcontent-%COMP%]::-webkit-scrollbar-thumb {\n background-color: #d3d3d3; \n\n border-radius: 4px; \n\n}\n\n.left-panel[_ngcontent-%COMP%]::-webkit-scrollbar-thumb:hover {\n background-color: #c0c0c0; \n\n}\n\n.left-panel[_ngcontent-%COMP%]::-webkit-scrollbar-track {\n background-color: #f8f9fa; \n\n}\n\n.right-panel[_ngcontent-%COMP%] {\n flex: 1; \n\n display: flex;\n flex-direction: column;\n height: 100%;\n max-height: 100%; \n\n overflow: hidden; \n\n}\n\n.conversation-header[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 8px 16px;\n background-color: #f5f7f9;\n border-bottom: 1px solid #dde4ee;\n height: 40px;\n flex-shrink: 0;\n}\n\n.conversation-title[_ngcontent-%COMP%] {\n font-size: 15px;\n font-weight: 500;\n color: #333;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 70%;\n}\n\n.artifact-counter-container[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n}\n\n\n.new-convo-icon[_ngcontent-%COMP%] {\n color: #808080; \n\n font-size: 18px; \n\n cursor: pointer; \n\n z-index: 10; \n\n padding: 5px;\n border-radius: 4px;\n}\n\n.toggle-icon[_ngcontent-%COMP%] {\n color: #808080; \n\n font-size: 18px; \n\n cursor: pointer; \n\n z-index: 10; \n\n margin-left: 6px;\n padding: 3px;\n border-radius: 3px;\n}\n \n\n.right-panel[_ngcontent-%COMP%] .toggle-icon[_ngcontent-%COMP%] {\n margin-left: 3px;\n margin-top: 2px;\n position: absolute;\n top: 10px;\n left: auto;\n right: 10px; \n\n}\n\n\n.chat-container[_ngcontent-%COMP%] {\n padding: 5px;\n display: flex;\n flex-direction: row;\n height: 100%;\n font-family: S\u00F6hne, ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", Roboto, Ubuntu, Cantarell, \"Noto Sans\", sans-serif, \"Helvetica Neue\", Arial, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n font-size: 1rem;\n\n \n\n width: 100%;\n height: 100%;\n max-height: 100vh; \n\n overflow: hidden; \n\n\n background-color: #f9f9f9;\n}\n\n.conversation-wrapper[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n position: relative; \n\n background-color: #f9f9f9;\n height: 100%; \n\n max-height: 100%; \n\n flex: 1;\n overflow: auto; \n\n}\n\n.new-conversation[_ngcontent-%COMP%] {\n height: 30px;\n font-size: large;\n}\n\n.conversation-history[_ngcontent-%COMP%] {\n width: 240px;\n min-width: 240px;\n height: 95%;\n overflow-y: auto; \n\n overflow-x: hidden; \n\n margin-right: 10px;\n padding-top: 5px;\n background-color: #f9f9f9;\n margin-top: 0px; \n padding: 12px; \n}\n\n.k-tabstrip-content-for-skip[_ngcontent-%COMP%] {\n padding: 0;\n padding-block: 0;\n}\n\n\n.conversation-history[_ngcontent-%COMP%] > button[_ngcontent-%COMP%] {\n height: 25px;\n}\n\n.skip-title[_ngcontent-%COMP%] {\n font-size: larger;\n margin-bottom: 5px;\n height: 20px;\n margin-top: 5px;\n}\n\n.conversation-list[_ngcontent-%COMP%] {\n margin-top: 5px;\n padding-top: 5px;\n \n border: 0;\n background-color: #f9f9f9;\n}\n\n\n\n\n\n.welcome-wrapper[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n height: 100%;\n width: 100%;\n overflow: hidden;\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n z-index: 5;\n}\n\n.welcome-message[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n text-align: center;\n overflow: hidden;\n height: 100%;\n padding-bottom: 100px; \n\n}\n\n.embedded-conversations[_ngcontent-%COMP%] {\n margin-left: 3px;\n margin-top: 5px;\n font-size: 10pt;\n color: rgb(48, 48, 235);\n}\n.embedded-conversations[_ngcontent-%COMP%] > span[_ngcontent-%COMP%] {\n margin-top: 4px;\n margin-left: 5px;\n cursor: pointer;\n}\n.conversation-item-linked[_ngcontent-%COMP%] {\n color: rgb(48, 48, 235);\n}\n\n.welcome-message[_ngcontent-%COMP%] img[_ngcontent-%COMP%] {\n width: 120px;\n height: 50px;\n margin-bottom: 20px; \n\n position: relative;\n z-index: 10;\n}\n\n.welcome-header-text[_ngcontent-%COMP%] {\n font-size: larger;\n font-weight: bold;\n}\n\n\n\n.welcome-suggested-questions[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-content: center;\n margin-top: 30px; \n\n}\n.welcome-suggested-questions-col[_ngcontent-%COMP%] {\n display: flex;\n margin-bottom: 10px; \n\n}\n\n\n\n.welcome-question[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column; \n\n align-items: left;;\n width: 300px; \n justify-content: space-between;\n margin: 5px; \n\n border: solid 1px rgba(41, 28, 28, 0.08);\n border-radius: 15px;\n padding: 10px;\n cursor: pointer;\n}\n\n.welcome-question[_ngcontent-%COMP%]:hover {\n background-color: rgba(0, 0, 0, 0.05);\n}\n\n\n.welcome-question-header[_ngcontent-%COMP%] {\n font-size: 12pt;\n font-weight: bold;\n display: block; \n\n}\n\n\n\n.welcome-question[_ngcontent-%COMP%] span[_ngcontent-%COMP%]:not(.welcome-question-header) {\n font-weight: normal;\n font-size: 10pt;\n}\n\n\n.messages[_ngcontent-%COMP%] {\n overflow-y: auto !important; \n\n overflow-x: hidden !important; \n\n \n\n margin-bottom: 5px;\n\n margin-top: 2px; \n\n\n background-color: #f9f9f9;\n flex: 1 1 auto; \n\n height: calc(100% - 50px); \n\n max-height: 100%; \n\n scrollbar-width: thin; \n\n scrollbar-color: #d3d3d3 #f8f9fa; \n\n position: relative; \n\n}\n\n\n\n.messages[_ngcontent-%COMP%]::-webkit-scrollbar {\n width: 8px; \n\n background-color: #f8f9fa; \n\n}\n\n.messages[_ngcontent-%COMP%]::-webkit-scrollbar-thumb {\n background-color: #d3d3d3; \n\n border-radius: 4px; \n\n}\n\n.messages[_ngcontent-%COMP%]::-webkit-scrollbar-thumb:hover {\n background-color: #c0c0c0; \n\n}\n\n.messages[_ngcontent-%COMP%]::-webkit-scrollbar-track {\n background-color: #f8f9fa; \n\n}\n\n\n\n.messages-container[_ngcontent-%COMP%] {\n min-height: 20px; \n\n}\n\n\n\n.new-chat-area[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between; \n\n align-items: center; \n\n}\n.avatar[_ngcontent-%COMP%] {\n max-height: 24px;\n margin-right: 10px;\n margin-left: 5px;\n margin-bottom: 3px;\n \n\n margin-right: auto; \n\n}\n\n.conversation-item[_ngcontent-%COMP%] {\n margin-left: 5px;\n margin-right: 5px;\n padding-top: 10px;\n padding-bottom: 10px;\n padding-left: 5px;\n padding-right: 5px;\n border-radius: 5px;\n cursor: pointer;\n overflow: hidden;\n max-height: 150px;\n font-size: 14px;\n\n display: flex;\n align-items: flex-start; \n\n\n flex-wrap: wrap; \n\n}\n\n.text-container[_ngcontent-%COMP%] {\n flex: 1; \n\n display: flex;\n flex-direction: column; \n\n}\n\n.text-container[_ngcontent-%COMP%] textarea[_ngcontent-%COMP%] {\n resize: none; \n\n \n\n}\n\n.conversation-item[_ngcontent-%COMP%] > .conversation-icon[_ngcontent-%COMP%] {\n margin-top: 3px;\n}\n\n.conversation-item[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n display: inline-block;\n white-space: pre-wrap; \n\n overflow: auto;\n word-wrap: break-word;\n margin-left: 3px; \n\n}\n\n.conversation-item[_ngcontent-%COMP%]:hover {\n background-color: rgba(0, 0, 0, 0.05);\n}\n.conversation-item-selected[_ngcontent-%COMP%] {\n background-color: rgba(0, 0, 0, 0.15);\n}\n\n\n.conversation-item[_ngcontent-%COMP%] > .conversation-icon[_ngcontent-%COMP%] {\n margin-right: 11px;\n}\n.edit-conversation-panel[_ngcontent-%COMP%] {\n display: flex;\n justify-content: flex-end; \n\n margin-top: 2px; \n\n margin-right: 2px; \n\n}\n.edit-conversation-panel[_ngcontent-%COMP%] > .k-icon[_ngcontent-%COMP%] {\n margin-left: 5px;\n cursor: pointer;\n}\n.edit-conversation-panel[_ngcontent-%COMP%] > .k-icon[_ngcontent-%COMP%]:hover {\n color: #ff0000;\n}\n\n\n\n.input-area[_ngcontent-%COMP%] {\n min-height: 35px;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-bottom: 15px;\n position: sticky;\n bottom: 0;\n background-color: #f9f9f9;\n z-index: 10;\n}\n\n.input-area[_ngcontent-%COMP%] > .button-area[_ngcontent-%COMP%] {\n vertical-align: top;\n margin-top: 3px;\n margin-left: -65px;\n}\n\n\n.button-area[_ngcontent-%COMP%] > button[_ngcontent-%COMP%] {\n width: 30px;\n height: 30px;\n border-radius: 12px;\n margin-left: 3px;\n}\n\n\n\n.button-area[_ngcontent-%COMP%] > button.stop-button[_ngcontent-%COMP%] {\n background-color: #dc3545;\n color: white;\n}\n\n.button-area[_ngcontent-%COMP%] > button.stop-button[_ngcontent-%COMP%]:hover {\n background-color: #c82333;\n}\n\n\n\n\n\n\n\n\n\n\n\n.text-area-wrapper[_ngcontent-%COMP%] {\n padding: 3px;\n border: solid 1px rgba(0, 0, 0, 0.08) ;\n border-radius: 15px;\n\n margin-top: 4px;\n margin-right: -1px;\n min-height: 42px;\n max-height: 100%; \n\n\n overflow: hidden; \n align-items: center;\n\n \n\n width: 710px; \n padding-right: 90px\n} \n.text-area-wrapper[_ngcontent-%COMP%] > textarea[_ngcontent-%COMP%] {\n border: 0;\n outline: 0;\n resize: none;\n\n min-height: 20px; \n\n\n width: 100%;\n overflow-y: hidden; \n\n\n font-family: S\u00F6hne, ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", Roboto, Ubuntu, Cantarell, \"Noto Sans\", sans-serif, \"Helvetica Neue\", Arial, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n font-size: 1rem;\n\n margin-left: 7px;\n margin-top: 7px;\n margin-bottom: 5px;\n\n background-color: #f9f9f9;\n}\n\n\n\n\n\n\n.input-wrapper[_ngcontent-%COMP%] {\n flex-grow: 1; \n\n height: 100%;\n}\n\n.waiting-for-ai[_ngcontent-%COMP%] {\n position: absolute;\n display: flex; \n\n bottom: 100px;\n z-index: 999;\n left: 10px; \n}\n \n.scroll-to-bottom-icon[_ngcontent-%COMP%] {\n position: fixed; \n\n bottom: 120px; \n\n \n\n transform: translateX(-50%); \n\n z-index: 1000; \n\n background-color: white; \n\n color: black; \n\n border-radius: 50%; \n\n width: 40px; \n\n height: 40px; \n\n display: flex;\n justify-content: center;\n align-items: center;\n box-shadow: 0px 0px 5px rgba(0,0,0,0.3); \n\n cursor: pointer;\n opacity: 0.9; \n\n}\n\n.loading-convo-messages-wrapper[_ngcontent-%COMP%] {\n display: flex;\n justify-content: center;\n align-items: center;\n height: 100%;\n width: 100%;\n position: absolute;\n z-index: 1000;\n}\n\n@media (min-width: 600px) {\n .welcome-suggested-questions[_ngcontent-%COMP%] {\n display: flex;\n flex-wrap: wrap; \n\n align-content: flex-end; \n\n }\n}"] });
|
|
2385
2350
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(SkipChatComponent, [{
|
|
2386
2351
|
type: Component,
|
|
2387
2352
|
args: [{ selector: 'skip-chat', template: "<div class = \"chat-container\" mjFillContainer #topLevelDiv [bottomMargin]=\"10\" [rightMargin]=\"10\">\n <div class=\"layout\">\n @if (IsConversationListVisible) {\n <div class=\"left-panel\">\n <div class=\"conversation-history\">\n <div class=\"new-chat-area\">\n <span class=\"fa-solid fa-table-columns toggle-icon\" (click)=\"DisplayConversationList(false)\"></span>\n @if (ShowSkipLogoInConversationList) {\n <img [src]=\"SkipLogoURL\" class=\"avatar\" />\n }\n @if (AllowNewConversations) {\n <span class=\"fa-solid fa-pen-to-square new-convo-icon\" (click)=\"CreateNewConversation()\"></span> \n }\n </div>\n <kendo-listview\n class=\"conversation-list\"\n [data]=\"Conversations\"\n [style.height.px]=\"280\"\n [itemClass]=\"{ 'item-border': true }\" \n mjFillContainer \n [fillWidth]=\"false\"\n [bottomMargin]=\"50\"\n #conversationList\n >\n <ng-template kendoListViewItemTemplate let-dataItem=\"dataItem\">\n <div class=\"conversation-item\" \n [ngClass]=\"GetConversationItemClass(dataItem)\"\n [title]=\"dataItem.Name\" \n (click)=\"SelectConversation(dataItem)\"> \n <span *ngIf=\"SelectedConversation && IsSkipProcessing(dataItem)\" class=\"fa-regular fa-clock\"></span>\n <div class=\"text-container\">\n <span *ngIf=\"dataItem.ID !== SelectedConversation?.ID || !ConversationEditMode\">{{ dataItem.Name }}</span>\n <textarea *ngIf=\"dataItem.ID === SelectedConversation?.ID && ConversationEditMode\" [(ngModel)]=\"dataItem.Name\" maxlength=\"100\"></textarea>\n </div>\n <div *ngIf=\"SelectedConversation?.ID === dataItem.ID\" class=\"edit-conversation-panel\">\n <span *ngIf=\"!ConversationEditMode\" class=\"fa-solid fa-pen-to-square\" (click)=\"editConvo(dataItem)\"></span>\n <span *ngIf=\"!ConversationEditMode\" class=\"fa-regular fa-trash-can\" (click)=\"showDeleteConvoDialog(dataItem)\"></span>\n <span *ngIf=\"ConversationEditMode\" class=\"fa-solid fa-check\" (click)=\"saveConvoName(dataItem)\"></span>\n <span *ngIf=\"ConversationEditMode\" class=\"fa-solid fa-xmark\" (click)=\"cancelConvoEdit(dataItem)\"></span>\n </div>\n </div>\n </ng-template>\n </kendo-listview>\n <!-- COMMENTED OUT as we don't want to support embedded conversations in the UI for now\n <div class=\"embedded-conversations\"><input kendoCheckBox type=\"checkbox\" [(ngModel)]=\"IncludeLinkedConversationsInList\" (ngModelChange)=\"loadConversations()\"/> <span (click)=\"FlipEmbeddedConversationState()\">Show Linked Conversations</span></div> -->\n </div> \n </div>\n }\n @if (!IsConversationListVisible) {\n <span class=\"fa-solid fa-table-columns toggle-icon\" (click)=\"DisplayConversationList(true)\"></span>\n }\n\n <div class=\"right-panel\">\n <skip-split-panel \n #splitPanel\n [Mode]=\"EnableArtifactSplitView && selectedArtifact ? 'BothSides' : 'LeftOnly'\" \n [SplitRatio]=\"SplitRatio\" \n (SplitRatioChanged)=\"onSplitRatioChanged($event)\"\n [RightPanelHeaderContent]=\"artifactHeaderInfo\"\n [VersionList]=\"artifactVersionList\"\n [SelectedVersionId]=\"selectedArtifactVersionId\"\n (VersionSelected)=\"onArtifactVersionSelected($event)\"\n mjFillContainer [fillWidth]=\"false\" [fillHeight]=\"true\">\n \n <!-- Left Panel (Chat) -->\n <div left-panel class=\"conversation-wrapper\">\n <!-- Use this for reference only, but don't display - hidden via width:0, height:0 -->\n <div #AskSkipPanel style=\"width:0; height:0; overflow:hidden; position:absolute;\"></div>\n \n <div class=\"messages\" #scrollContainer (scroll)=\"checkScroll()\">\n <div class=\"welcome-wrapper\" *ngIf=\"(!Messages || Messages.length ===0) && _conversationLoadComplete\">\n <div class='welcome-message'>\n <img [src]=\"SkipLogoURL\" />\n <div class=\"welcome-header-text\">What can I help with today?</div>\n </div>\n <div class='welcome-suggested-questions'>\n <div class=\"welcome-suggested-questions-col\">\n <div class=\"welcome-question\" (click)=\"sendPrompt(WelcomeQuestions[0].prompt)\">\n <span class=\"welcome-question-header\">{{WelcomeQuestions[0].topLine}}</span>\n <span>{{WelcomeQuestions[0].bottomLine}}</span>\n </div>\n <div class=\"welcome-question\" (click)=\"sendPrompt(WelcomeQuestions[1].prompt)\">\n <span class=\"welcome-question-header\">{{WelcomeQuestions[1].topLine}}</span>\n <span>{{WelcomeQuestions[1].bottomLine}}</span>\n </div> \n </div>\n <div class=\"welcome-suggested-questions-col\">\n <div class=\"welcome-question\" (click)=\"sendPrompt(WelcomeQuestions[2].prompt)\">\n <span class=\"welcome-question-header\">{{WelcomeQuestions[2].topLine}}</span>\n <span>{{WelcomeQuestions[2].bottomLine}}</span>\n </div>\n <div class=\"welcome-question\" (click)=\"sendPrompt(WelcomeQuestions[3].prompt)\">\n <span class=\"welcome-question-header\">{{WelcomeQuestions[3].topLine}}</span>\n <span>{{WelcomeQuestions[3].bottomLine}}</span>\n </div> \n </div>\n </div> \n </div>\n @if (!_conversationLoadComplete) {\n <div class=\"loading-convo-messages-wrapper\">\n <kendo-loader></kendo-loader>\n </div>\n } \n <div class=\"messages-container\" mjContainer mjSkipResize=\"true\"><!--mjSkipResize results in everything below this level NOT being resized, performance optimization-->\n <!-- Dynamic messages will be injected here -->\n </div>\n <span class=\"scroll-to-bottom-icon\" \n *ngIf=\"_showScrollToBottomIcon && Messages && Messages.length > 0\" \n [style.left.px]=\"getScrollToBottomIconPosition()\"\n (click)=\"scrollToBottomAnimate()\">\n <i class=\"fas fa-arrow-down\"></i>\n </span>\n </div>\n @if (SelectedConversationCurrentUserPermissionLevel === 'Owner' || \n SelectedConversationCurrentUserPermissionLevel === 'Edit') {\n <div class=\"input-area\">\n <div class=\"text-area-wrapper\">\n <textarea\n #AskSkipInput \n [disabled]=\"SelectedConversation && IsSkipProcessing(SelectedConversation)\" \n (keyup.enter)=\"onEnter($event)\" \n (input)=\"onInputChange($event)\"\n type=\"text\" \n [placeholder]=\"_AskSkipTextboxPlaceholder\"></textarea>\n </div>\n <div class=\"button-area\" [style.marginLeft.px]=\"-35 * NumVisibleButtons\">\n @if (ShowDataContextButton) {\n <button kendoButton >\n <span class=\"fa-solid fa-gear\" \n (click)=\"showDataContextDialog()\"></span>\n </button> \n }\n @if (SelectedConversation && IsSkipProcessing(SelectedConversation)) {\n <button kendoButton \n class=\"stop-button\"\n (click)=\"stopProcessing()\">\n <span class=\"fas fa-solid fa-stop\"></span>\n </button>\n }\n @else {\n <button kendoButton \n [disabled]=\"IsTextAreaEmpty()\" \n (click)=\"sendSkipMessage()\">\n <span class=\"fas fa-solid fa-arrow-up\"></span>\n </button>\n }\n @if (ShowSharingButton && SelectedConversationCurrentUserPermissionLevel === 'Owner') {\n <button kendoButton class=\"share-button\">\n <span class=\"fa-solid fa-share\"\n (click)=\"showSharingDialog()\"></span>\n </button> \n }\n </div>\n </div>\n }\n </div>\n \n <!-- Right Panel (Artifact Viewer) -->\n <div right-panel>\n <skip-artifact-viewer\n *ngIf=\"selectedArtifact\"\n [ArtifactID]=\"selectedArtifact.artifactId\"\n [ArtifactVersionID]=\"selectedArtifact.artifactVersionId\"\n [DataContext]=\"DataContext\"\n (NavigateToMatchingReport)=\"NavigateToMatchingReport.emit($event)\"\n (NewReportCreated)=\"NewReportCreated.emit($event)\"\n (DrillDownEvent)=\"DrillDownEvent.emit($event)\"\n (ArtifactInfoChanged)=\"onArtifactInfoChanged($event)\">\n </skip-artifact-viewer>\n </div>\n </skip-split-panel>\n </div> \n </div> \n</div> \n\n@if(isDataContextDialogVisible) {\n <mj-data-context-dialog [dataContextId]=\"DataContextID\" (dialogClosed)=\"closeDataContextDialog()\" [Provider]=\"ProviderToUse\"></mj-data-context-dialog>\n}\n@if(isSharingDialogVisible && SelectedConversation && conversationResourceTypeID) {\n <kendo-dialog\n title=\"Share Conversation\"\n (close)=\"closeSharingDialog('no')\"\n [width]=\"650\"\n [height]=\"400\"\n >\n <mj-resource-permissions \n [Provider]=\"Provider\"\n [ResourceTypeID]=\"conversationResourceTypeID\"\n [ResourceRecordID]=\"SelectedConversation.ID\"\n [ExcludedRoleNames]=\"SharingExcludeRoleNames\"\n [ExcludedUserEmails]=\"SharingExcludeEmails\"\n #resourcePermissions\n >\n </mj-resource-permissions>\n <kendo-dialog-actions>\n <button kendoButton (click)=\"closeSharingDialog('yes')\" themeColor=\"primary\">\n Save\n </button>\n <button kendoButton (click)=\"closeSharingDialog('no')\">\n Cancel\n </button>\n </kendo-dialog-actions>\n </kendo-dialog> \n}\n\n<kendo-dialog\n title=\"Please confirm\"\n *ngIf=\"confirmDeleteConversationDialogOpen\"\n (close)=\"closeDeleteConversation('no')\"\n [minWidth]=\"250\"\n [width]=\"450\"\n>\n <p style=\"margin: 30px; text-align: center;\">\n Would you like to delete {{SelectedConversation?.Name}}?\n </p>\n <kendo-dialog-actions>\n <button kendoButton (click)=\"closeDeleteConversation('yes')\" themeColor=\"primary\">\n Yes\n </button>\n <button kendoButton (click)=\"closeDeleteConversation('no')\">\n No\n </button>\n </kendo-dialog-actions>\n</kendo-dialog> \n\n<kendo-dialog\n title=\"Please confirm\"\n *ngIf=\"confirmMessageEditOrDeleteDialogOpen\"\n (close)=\"closeMessageEditOrDeleteDialog('no')\"\n [minWidth]=\"250\"\n [width]=\"450\"\n>\n <p style=\"margin: 30px; text-align: center;\">\n Would you like to {{messageEditOrDeleteType}} this message? Doing so will result in any subsequent messages in the conversation being deleted.\n </p>\n <kendo-dialog-actions>\n <button kendoButton (click)=\"closeMessageEditOrDeleteDialog('yes')\" themeColor=\"primary\">\n Yes\n </button>\n <button kendoButton (click)=\"closeMessageEditOrDeleteDialog('no')\">\n No\n </button>\n </kendo-dialog-actions>\n</kendo-dialog> ", styles: [".layout {\n display: flex;\n flex-direction: row; /* Ensures left and right panels are side by side */\n height: 100%; /* Fill the available height */\n width: 100%; /* Fill the available width */\n position: relative;\n overflow: hidden; /* Prevent content from expanding beyond container */\n}\n\n.left-panel {\n width: 272px; /* Fixed width for the conversation list */\n background-color: #f8f9fa; /* Optional: Background color */\n border-right: 1px solid #ddd; /* Optional: Add a divider */\n overflow-y: auto; /* Enable scrolling if content overflows */\n overflow-x: hidden; /* Hide horizontal scrollbar */\n position: relative;\n\n scrollbar-width: thin; /* For Firefox */\n scrollbar-color: #d3d3d3 #f8f9fa; /* Thumb color and track color */\n}\n\n/* For WebKit-based browsers (Chrome, Edge, Safari) */\n.left-panel::-webkit-scrollbar {\n width: 8px; /* Narrower scrollbar */\n background-color: #f8f9fa; /* Scrollbar track color */\n}\n\n.left-panel::-webkit-scrollbar-thumb {\n background-color: #d3d3d3; /* Lighter gray scrollbar thumb */\n border-radius: 4px; /* Rounded corners for the thumb */\n}\n\n.left-panel::-webkit-scrollbar-thumb:hover {\n background-color: #c0c0c0; /* Slightly darker gray on hover */\n}\n\n.left-panel::-webkit-scrollbar-track {\n background-color: #f8f9fa; /* Background of the scrollbar track */\n}\n\n.right-panel {\n flex: 1; /* Take up the remaining space */\n display: flex;\n flex-direction: column;\n height: 100%;\n max-height: 100%; /* Don't exceed parent container height */\n overflow: hidden; /* Hide overflow to prevent double scrollbars */\n}\n\n.conversation-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 8px 16px;\n background-color: #f5f7f9;\n border-bottom: 1px solid #dde4ee;\n height: 40px;\n flex-shrink: 0;\n}\n\n.conversation-title {\n font-size: 15px;\n font-weight: 500;\n color: #333;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 70%;\n}\n\n.artifact-counter-container {\n display: flex;\n align-items: center;\n}\n\n\n.new-convo-icon {\n color: #808080; /* Mid-gray */\n font-size: 18px; /* Adjust icon size */\n cursor: pointer; /* Make it clear the icon is clickable */\n z-index: 10; /* Ensure the icon is above other content */\n padding: 5px;\n border-radius: 4px;\n}\n\n.toggle-icon {\n color: #808080; /* Mid-gray */\n font-size: 18px; /* Adjust icon size */\n cursor: pointer; /* Make it clear the icon is clickable */\n z-index: 10; /* Ensure the icon is above other content */\n margin-left: 6px;\n padding: 3px;\n border-radius: 3px;\n}\n \n\n.right-panel .toggle-icon {\n margin-left: 3px;\n margin-top: 2px;\n position: absolute;\n top: 10px;\n left: auto;\n right: 10px; /* For the right panel toggle */\n}\n\n\n.chat-container {\n padding: 5px;\n display: flex;\n flex-direction: row;\n height: 100%;\n font-family: S\u00F6hne, ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", Roboto, Ubuntu, Cantarell, \"Noto Sans\", sans-serif, \"Helvetica Neue\", Arial, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n font-size: 1rem;\n\n /*initial sizes*/\n width: 100%;\n height: 100%;\n max-height: 100vh; /* Limit to viewport height */\n overflow: hidden; /* Prevent container from growing beyond viewport */\n\n background-color: #f9f9f9;\n}\n\n.conversation-wrapper {\n display: flex;\n flex-direction: column;\n position: relative; /* This ensures child absolute elements position relative to this container */\n background-color: #f9f9f9;\n height: 100%; /* Ensure it takes full height */\n max-height: 100%; /* Don't exceed parent container height */\n flex: 1;\n overflow: auto; /* Allow content to scroll */\n}\n\n.new-conversation {\n height: 30px;\n font-size: large;\n}\n\n.conversation-history {\n width: 240px;\n min-width: 240px;\n height: 95%;\n overflow-y: auto; /* Add scroll if the content exceeds the height */\n overflow-x: hidden; /* Hide horizontal scrollbar */\n margin-right: 10px;\n padding-top: 5px;\n background-color: #f9f9f9;\n margin-top: 0px; \n padding: 12px; \n}\n\n.k-tabstrip-content-for-skip {\n padding: 0;\n padding-block: 0;\n}\n\n\n.conversation-history > button {\n height: 25px;\n}\n\n.skip-title {\n font-size: larger;\n margin-bottom: 5px;\n height: 20px;\n margin-top: 5px;\n}\n\n.conversation-list {\n margin-top: 5px;\n padding-top: 5px;\n \n border: 0;\n background-color: #f9f9f9;\n}\n\n\n\n/* Center the welcome message vertically and horizontally */\n.welcome-wrapper {\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n height: 100%;\n width: 100%;\n overflow: hidden;\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n z-index: 5;\n}\n\n.welcome-message {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n text-align: center;\n overflow: hidden;\n height: 100%;\n padding-bottom: 100px; /* Push the content up a bit */\n}\n\n.embedded-conversations {\n margin-left: 3px;\n margin-top: 5px;\n font-size: 10pt;\n color: rgb(48, 48, 235);\n}\n.embedded-conversations > span {\n margin-top: 4px;\n margin-left: 5px;\n cursor: pointer;\n}\n.conversation-item-linked {\n color: rgb(48, 48, 235);\n}\n\n.welcome-message img {\n width: 120px;\n height: 50px;\n margin-bottom: 20px; /* Adds some space between the image and the text below */\n position: relative;\n z-index: 10;\n}\n\n.welcome-header-text {\n font-size: larger;\n font-weight: bold;\n}\n\n/* Position the welcome-suggested-questions at the bottom of its container */\n.welcome-suggested-questions {\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-content: center;\n margin-top: 30px; /* Push questions down for spacing */\n}\n.welcome-suggested-questions-col {\n display: flex;\n margin-bottom: 10px; /* Space between rows */\n}\n\n/* Flex layout for questions, two per row */\n.welcome-question {\n display: flex;\n flex-direction: column; /* Stack the header and text vertically */\n align-items: left;;\n width: 300px; \n justify-content: space-between;\n margin: 5px; /* Adds some space around each question */\n border: solid 1px rgba(41, 28, 28, 0.08);\n border-radius: 15px;\n padding: 10px;\n cursor: pointer;\n}\n\n.welcome-question:hover {\n background-color: rgba(0, 0, 0, 0.05);\n}\n\n\n.welcome-question-header {\n font-size: 12pt;\n font-weight: bold;\n display: block; /* Ensures the header is on its own line */\n}\n\n/* Non-bold text for the content below the header */\n.welcome-question span:not(.welcome-question-header) {\n font-weight: normal;\n font-size: 10pt;\n}\n\n\n.messages {\n overflow-y: auto !important; /* enable scrolling if the content overflows */\n overflow-x: hidden !important; /* hide horizontal scrollbar */\n /* border: solid 1px rgba(0, 0, 0, 0.08); */\n margin-bottom: 5px;\n\n margin-top: 2px; /* align it with the top of converation history exactly*/\n\n background-color: #f9f9f9;\n flex: 1 1 auto; /* Take up available space but don't push parent beyond size */\n height: calc(100% - 50px); /* Ensure messages container has a height */\n max-height: 100%; /* Don't exceed parent height */\n scrollbar-width: thin; /* For Firefox */\n scrollbar-color: #d3d3d3 #f8f9fa; /* Thumb color and track color */\n position: relative; /* For proper positioning of scroll icon */\n}\n\n/* For WebKit-based browsers (Chrome, Edge, Safari) */\n.messages::-webkit-scrollbar {\n width: 8px; /* Narrower scrollbar */\n background-color: #f8f9fa; /* Scrollbar track color */\n}\n\n.messages::-webkit-scrollbar-thumb {\n background-color: #d3d3d3; /* Lighter gray scrollbar thumb */\n border-radius: 4px; /* Rounded corners for the thumb */\n}\n\n.messages::-webkit-scrollbar-thumb:hover {\n background-color: #c0c0c0; /* Slightly darker gray on hover */\n}\n\n.messages::-webkit-scrollbar-track {\n background-color: #f8f9fa; /* Background of the scrollbar track */\n}\n\n/* Class for the messages container */\n.messages-container {\n min-height: 20px; /* Ensure container takes space even when empty */\n}\n\n\n\n.new-chat-area {\n display: flex;\n justify-content: space-between; /* Aligns children (img and button) to each end */\n align-items: center; /* Centers children vertically */\n}\n.avatar {\n max-height: 24px;\n margin-right: 10px;\n margin-left: 5px;\n margin-bottom: 3px;\n /* Ensure the image aligns to the left */\n margin-right: auto; /* Pushes everything else to the right */\n}\n\n.conversation-item {\n margin-left: 5px;\n margin-right: 5px;\n padding-top: 10px;\n padding-bottom: 10px;\n padding-left: 5px;\n padding-right: 5px;\n border-radius: 5px;\n cursor: pointer;\n overflow: hidden;\n max-height: 150px;\n font-size: 14px;\n\n display: flex;\n align-items: flex-start; /* Align items to the top */\n\n flex-wrap: wrap; /* Allow items to wrap to the next line */\n}\n\n.text-container {\n flex: 1; /* Take up remaining space */\n display: flex;\n flex-direction: column; /* Stack children vertically */\n}\n\n.text-container textarea {\n resize: none; /* Disable resizing */\n /* Add more styles for the textarea if needed */\n}\n\n.conversation-item > .conversation-icon {\n margin-top: 3px;\n}\n\n.conversation-item span {\n display: inline-block;\n white-space: pre-wrap; /* Allow text to wrap */\n overflow: auto;\n word-wrap: break-word;\n margin-left: 3px; /* Move the text to the right */\n}\n\n.conversation-item:hover {\n background-color: rgba(0, 0, 0, 0.05);\n}\n.conversation-item-selected {\n background-color: rgba(0, 0, 0, 0.15);\n}\n\n\n.conversation-item > .conversation-icon {\n margin-right: 11px;\n}\n.edit-conversation-panel {\n display: flex;\n justify-content: flex-end; /* Align icons to the right */\n margin-top: 2px; /* litle buffer on top */\n margin-right: 2px; /* litle buffer to the right */\n}\n.edit-conversation-panel > .k-icon {\n margin-left: 5px;\n cursor: pointer;\n}\n.edit-conversation-panel > .k-icon:hover {\n color: #ff0000;\n}\n\n\n\n.input-area {\n min-height: 35px;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-bottom: 15px;\n position: sticky;\n bottom: 0;\n background-color: #f9f9f9;\n z-index: 10;\n}\n\n.input-area > .button-area {\n vertical-align: top;\n margin-top: 3px;\n margin-left: -65px;\n}\n/*all buttons in the button area within the input area*/\n.button-area > button {\n width: 30px;\n height: 30px;\n border-radius: 12px;\n margin-left: 3px;\n}\n\n/* Stop button styling */\n.button-area > button.stop-button {\n background-color: #dc3545;\n color: white;\n}\n\n.button-area > button.stop-button:hover {\n background-color: #c82333;\n}\n/* .input-area > button:first-of-type {\n margin-left: -40px;\n}\n.input-area > button:last-child {\n margin-left: -65px;\n}\n.input-area > .share-button {\n margin-left: 10px;\n} */\n\n.text-area-wrapper {\n padding: 3px;\n border: solid 1px rgba(0, 0, 0, 0.08) ;\n border-radius: 15px;\n\n margin-top: 4px;\n margin-right: -1px;\n min-height: 42px;\n max-height: 100%; /* Prevent it from growing beyond the container */\n\n overflow: hidden; \n align-items: center;\n\n /*combined width and padding is 800*/\n width: 710px; \n padding-right: 90px\n} \n.text-area-wrapper > textarea {\n border: 0;\n outline: 0;\n resize: none;\n\n min-height: 20px; /* Initial height */\n\n width: 100%;\n overflow-y: hidden; /* Hide scrollbar */\n\n font-family: S\u00F6hne, ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", Roboto, Ubuntu, Cantarell, \"Noto Sans\", sans-serif, \"Helvetica Neue\", Arial, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n font-size: 1rem;\n\n margin-left: 7px;\n margin-top: 7px;\n margin-bottom: 5px;\n\n background-color: #f9f9f9;\n}\n\n/* .text-area-wrapper > textarea:disabled {\n background-color: white;\n} */\n\n.input-wrapper {\n flex-grow: 1; /* This will make the input-wrapper take the remaining space */\n height: 100%;\n}\n\n.waiting-for-ai {\n position: absolute;\n display: flex; /* Use flexbox layout */\n bottom: 100px;\n z-index: 999;\n left: 10px; \n}\n \n.scroll-to-bottom-icon {\n position: fixed; /* Fixed positioning to float over content */\n bottom: 120px; /* Position relative to the viewport */\n /* left position will be set dynamically via inline style */\n transform: translateX(-50%); /* Shift it back by half its width to center it */\n z-index: 1000; /* Ensure it stays on top */\n background-color: white; /* Circle background color */\n color: black; /* Icon color */\n border-radius: 50%; /* Makes the background a circle */\n width: 40px; /* Circle size */\n height: 40px; /* Circle size */\n display: flex;\n justify-content: center;\n align-items: center;\n box-shadow: 0px 0px 5px rgba(0,0,0,0.3); /* Subtle shadow for better visibility */\n cursor: pointer;\n opacity: 0.9; /* Slightly transparent */\n}\n\n.loading-convo-messages-wrapper {\n display: flex;\n justify-content: center;\n align-items: center;\n height: 100%;\n width: 100%;\n position: absolute;\n z-index: 1000;\n}\n\n@media (min-width: 600px) {\n .welcome-suggested-questions {\n display: flex;\n flex-wrap: wrap; /* Allows questions to wrap to the next line */\n align-content: flex-end; /* Aligns the content to the bottom */\n }\n}\n \n"] }]
|