@newheap/platform-ai-chat 0.4.0 → 0.6.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/fesm2022/newheap-platform-ai-chat-admin.mjs +21 -21
- package/fesm2022/newheap-platform-ai-chat-testing.mjs +323 -18
- package/fesm2022/newheap-platform-ai-chat-testing.mjs.map +1 -1
- package/fesm2022/newheap-platform-ai-chat.mjs +1977 -364
- package/fesm2022/newheap-platform-ai-chat.mjs.map +1 -1
- package/i18n/en.json +92 -9
- package/i18n/nl.json +92 -9
- package/index.d.ts +547 -38
- package/package.json +5 -1
- package/push/nh-assistant-push-worker.js +70 -0
- package/testing/index.d.ts +102 -11
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, inject, signal, Injectable, makeEnvironmentProviders } from '@angular/core';
|
|
3
|
-
import { NH_ASSISTANT_CONFIG, normalizeNhAssistantClientContext, applyNhAssistantApprovalDecision,
|
|
2
|
+
import { InjectionToken, inject, signal, Injectable, DestroyRef, makeEnvironmentProviders } from '@angular/core';
|
|
3
|
+
import { NH_ASSISTANT_CONFIG, applyNhAssistantEvent, normalizeNhAssistantClientContext, applyNhAssistantApprovalDecision, NH_ASSISTANT_FETCH, NhAssistantLiveService } from '@newheap/platform-ai-chat';
|
|
4
|
+
import { Subject } from 'rxjs';
|
|
4
5
|
|
|
5
6
|
const dashCase = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
6
7
|
const styles = ['default', 'direct', 'personal', 'detailed'];
|
|
@@ -484,7 +485,10 @@ function hashText(text) {
|
|
|
484
485
|
}
|
|
485
486
|
|
|
486
487
|
const NH_ASSISTANT_MOCK_SCENARIO = new InjectionToken('NH_ASSISTANT_MOCK_SCENARIO');
|
|
488
|
+
/** Path the mock reports as hub; the mock live service answers it in memory. */
|
|
489
|
+
const NH_ASSISTANT_MOCK_HUB_PATH = '/hub/assistant-mock';
|
|
487
490
|
const defaultLimits = { maxMessageChars: 4_000, maxToolCallsPerTurn: 8 };
|
|
491
|
+
const defaultUser = { actorId: 'mock-user', displayName: 'You' };
|
|
488
492
|
class RunStopped extends Error {
|
|
489
493
|
}
|
|
490
494
|
/**
|
|
@@ -504,11 +508,20 @@ class NhAssistantMockBackend {
|
|
|
504
508
|
this.enabledState = signal(this.scenario.enabled ?? true, ...(ngDevMode ? [{ debugName: "enabledState" }] : []));
|
|
505
509
|
this.admin = new NhAssistantMockAdmin(this.scenario, () => new Date().toISOString());
|
|
506
510
|
this.canAdministerState = signal(this.admin.administers, ...(ngDevMode ? [{ debugName: "canAdministerState" }] : []));
|
|
511
|
+
this.currentUser = this.scenario.collaboration?.currentUser ?? defaultUser;
|
|
512
|
+
this.members = new Map();
|
|
513
|
+
this.readPositions = new Map();
|
|
514
|
+
this.shareTokens = new Map();
|
|
515
|
+
this.activeActors = new Map();
|
|
516
|
+
this.liveSubject = new Subject();
|
|
517
|
+
this.pushEnabled = true;
|
|
507
518
|
this.sequence = 0;
|
|
508
519
|
/** Requests received so far, oldest first. */
|
|
509
520
|
this.requests = [];
|
|
510
521
|
this.enabled = this.enabledState.asReadonly();
|
|
511
522
|
this.canAdminister = this.canAdministerState.asReadonly();
|
|
523
|
+
/** Live updates for the mock live service, in the order the hub would send them. */
|
|
524
|
+
this.live$ = this.liveSubject.asObservable();
|
|
512
525
|
/** A `fetch` implementation that answers the assistant endpoints below `apiBaseUrl`. */
|
|
513
526
|
this.fetch = async (input, init) => {
|
|
514
527
|
const method = (init.method ?? 'GET').toUpperCase();
|
|
@@ -526,9 +539,49 @@ class NhAssistantMockBackend {
|
|
|
526
539
|
return this.route(method, path, url.searchParams, body);
|
|
527
540
|
};
|
|
528
541
|
for (const conversation of this.scenario.conversations ?? []) {
|
|
529
|
-
|
|
542
|
+
const copy = withSequences(structuredClone(conversation));
|
|
543
|
+
this.conversations.set(copy.id, copy);
|
|
544
|
+
this.readPositions.set(copy.id, lastSequence(copy));
|
|
530
545
|
}
|
|
531
546
|
}
|
|
547
|
+
/**
|
|
548
|
+
* Plays a scripted turn as another person in the conversation, as if a colleague with an
|
|
549
|
+
* invitation sent a message: the conversation becomes shared, and the message and the
|
|
550
|
+
* answer arrive as live updates. Resolves when the turn ends.
|
|
551
|
+
*/
|
|
552
|
+
async simulateParticipantTurn(conversationId, person, text) {
|
|
553
|
+
const conversation = this.conversations.get(conversationId);
|
|
554
|
+
if (!conversation || conversation.status !== 'idle') {
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
this.addMember(conversationId, person, 'participant');
|
|
558
|
+
const userMessage = {
|
|
559
|
+
id: this.nextId(),
|
|
560
|
+
role: 'user',
|
|
561
|
+
createdAt: new Date().toISOString(),
|
|
562
|
+
parts: [{ type: 'text', text }],
|
|
563
|
+
authorActorId: person.actorId
|
|
564
|
+
};
|
|
565
|
+
this.activeActors.set(conversationId, person.actorId);
|
|
566
|
+
this.update(conversationId, current => ({ ...current, status: 'running', messages: [...current.messages, userMessage] }));
|
|
567
|
+
const stored = this.conversations.get(conversationId).messages.find(message => message.id === userMessage.id);
|
|
568
|
+
this.emitLive({ kind: 'event', data: { conversationId, actorId: person.actorId, type: 'message.created', data: stored } });
|
|
569
|
+
const timing = this.scenario.timing ?? {};
|
|
570
|
+
const assistantMessageId = this.nextId();
|
|
571
|
+
const emit = async (event) => {
|
|
572
|
+
await delay(timing.eventDelayMs ?? 40);
|
|
573
|
+
this.update(conversationId, current => applyNhAssistantEvent(current, event));
|
|
574
|
+
if (event.type === 'turn.completed' && event.data.status !== 'waiting-for-approval') {
|
|
575
|
+
this.activeActors.delete(conversationId);
|
|
576
|
+
this.emitChanged(conversationId);
|
|
577
|
+
}
|
|
578
|
+
this.emitLive({ kind: 'event', data: { conversationId, actorId: person.actorId, type: event.type, data: event.data } });
|
|
579
|
+
};
|
|
580
|
+
await delay(timing.firstEventDelayMs ?? 200);
|
|
581
|
+
await emit({ type: 'turn.started', data: { turnId: this.nextId(), userMessageId: userMessage.id, assistantMessageId } });
|
|
582
|
+
const turn = this.findTurn(text, conversation.agentId, null);
|
|
583
|
+
await this.playSteps(conversationId, assistantMessageId, turn?.steps ?? [], emit);
|
|
584
|
+
}
|
|
532
585
|
/** Switches the simulated `NewHeap:AI:Assistant:Enabled` flag. */
|
|
533
586
|
setEnabled(enabled) {
|
|
534
587
|
this.enabledState.set(enabled);
|
|
@@ -546,8 +599,14 @@ class NhAssistantMockBackend {
|
|
|
546
599
|
const segments = path.split('/').map(segment => decodeURIComponent(segment));
|
|
547
600
|
if (method === 'GET' && path === 'status') {
|
|
548
601
|
const limits = { ...defaultLimits, ...this.scenario.limits };
|
|
602
|
+
const collaboration = {
|
|
603
|
+
directory: (this.scenario.collaboration?.directory?.length ?? 0) > 0,
|
|
604
|
+
hubPath: NH_ASSISTANT_MOCK_HUB_PATH,
|
|
605
|
+
push: false,
|
|
606
|
+
maxParticipants: this.maxParticipants()
|
|
607
|
+
};
|
|
549
608
|
return this.json(200, this.enabledState()
|
|
550
|
-
? { enabled: true, agents: this.admin.chatAgents(), limits, canAdminister: this.admin.administers }
|
|
609
|
+
? { enabled: true, agents: this.admin.chatAgents(), limits, canAdminister: this.admin.administers, collaboration }
|
|
551
610
|
: { enabled: false, agents: [], limits, canAdminister: false });
|
|
552
611
|
}
|
|
553
612
|
if (!this.enabledState()) {
|
|
@@ -559,6 +618,15 @@ class NhAssistantMockBackend {
|
|
|
559
618
|
if (path === 'preferences') {
|
|
560
619
|
return this.result(this.admin.handlePreferences(method, body));
|
|
561
620
|
}
|
|
621
|
+
if (path === 'notifications') {
|
|
622
|
+
if (method === 'PUT') {
|
|
623
|
+
this.pushEnabled = body?.pushEnabled === true;
|
|
624
|
+
}
|
|
625
|
+
return this.json(200, { pushEnabled: this.pushEnabled, pushAvailable: false, publicKey: null });
|
|
626
|
+
}
|
|
627
|
+
if (path === 'notifications/push-subscription') {
|
|
628
|
+
return this.error(404, 'assistant-push-unavailable');
|
|
629
|
+
}
|
|
562
630
|
if (segments[0] === 'admin') {
|
|
563
631
|
return this.result(this.admin.handleAdmin(method, segments, body));
|
|
564
632
|
}
|
|
@@ -580,15 +648,46 @@ class NhAssistantMockBackend {
|
|
|
580
648
|
}
|
|
581
649
|
if (segments.length === 2) {
|
|
582
650
|
if (method === 'GET') {
|
|
583
|
-
return this.json(200, conversation);
|
|
651
|
+
return this.json(200, this.view(conversation));
|
|
584
652
|
}
|
|
585
653
|
if (method === 'DELETE') {
|
|
586
654
|
this.conversations.delete(conversation.id);
|
|
587
655
|
this.pendingApprovals.delete(conversation.id);
|
|
656
|
+
this.emitLive({ kind: 'removed', data: { conversationId: conversation.id } });
|
|
588
657
|
return this.empty(204);
|
|
589
658
|
}
|
|
590
659
|
return this.empty(405);
|
|
591
660
|
}
|
|
661
|
+
if (method === 'POST' && segments.length === 3 && segments[2] === 'read') {
|
|
662
|
+
return this.markRead(conversation, body?.sequence);
|
|
663
|
+
}
|
|
664
|
+
if (segments.length === 3 && segments[2] === 'share-link') {
|
|
665
|
+
if (method === 'POST') {
|
|
666
|
+
const token = this.nextId().replace(/-/g, '');
|
|
667
|
+
this.shareTokens.set(conversation.id, token);
|
|
668
|
+
return this.json(200, { token });
|
|
669
|
+
}
|
|
670
|
+
if (method === 'DELETE') {
|
|
671
|
+
this.shareTokens.delete(conversation.id);
|
|
672
|
+
return this.empty(204);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
if (method === 'POST' && segments.length === 3 && segments[2] === 'join') {
|
|
676
|
+
const token = body?.token;
|
|
677
|
+
return token && this.shareTokens.get(conversation.id) === token
|
|
678
|
+
? this.json(200, this.view(conversation))
|
|
679
|
+
: this.error(404, 'assistant-share-link-invalid');
|
|
680
|
+
}
|
|
681
|
+
if (method === 'GET' && segments.length === 3 && segments[2] === 'participant-candidates') {
|
|
682
|
+
return this.candidates(conversation, query.get('query') ?? '');
|
|
683
|
+
}
|
|
684
|
+
if (method === 'POST' && segments.length === 3 && segments[2] === 'participants') {
|
|
685
|
+
return this.invite(conversation, body?.actorId);
|
|
686
|
+
}
|
|
687
|
+
if (method === 'DELETE' && segments.length === 4 && segments[2] === 'participants') {
|
|
688
|
+
const removed = this.removeMember(conversation.id, segments[3]);
|
|
689
|
+
return removed ? this.empty(204) : this.error(404, 'assistant-participant-not-found');
|
|
690
|
+
}
|
|
592
691
|
if (method === 'POST' && segments.length === 3 && segments[2] === 'messages') {
|
|
593
692
|
return this.sendMessage(conversation, body);
|
|
594
693
|
}
|
|
@@ -605,7 +704,7 @@ class NhAssistantMockBackend {
|
|
|
605
704
|
const itemsPerPage = Math.max(1, Number(query.get('itemsPerPage') ?? 20));
|
|
606
705
|
const all = [...this.conversations.values()]
|
|
607
706
|
.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt))
|
|
608
|
-
.map(
|
|
707
|
+
.map(conversation => this.summary(conversation));
|
|
609
708
|
return { items: all.slice((page - 1) * itemsPerPage, page * itemsPerPage), total: all.length };
|
|
610
709
|
}
|
|
611
710
|
create(request) {
|
|
@@ -626,7 +725,8 @@ class NhAssistantMockBackend {
|
|
|
626
725
|
pendingApproval: null
|
|
627
726
|
};
|
|
628
727
|
this.conversations.set(conversation.id, conversation);
|
|
629
|
-
|
|
728
|
+
this.readPositions.set(conversation.id, 0);
|
|
729
|
+
return this.json(201, this.view(conversation));
|
|
630
730
|
}
|
|
631
731
|
sendMessage(conversation, request) {
|
|
632
732
|
const text = request?.text?.trim() ?? '';
|
|
@@ -643,12 +743,18 @@ class NhAssistantMockBackend {
|
|
|
643
743
|
const turn = this.findTurn(text, conversation.agentId, pageContext);
|
|
644
744
|
const userMessageId = this.nextId();
|
|
645
745
|
const assistantMessageId = this.nextId();
|
|
746
|
+
this.activeActors.set(conversation.id, this.currentUser.actorId);
|
|
646
747
|
this.update(conversation.id, current => ({
|
|
647
748
|
...current,
|
|
648
749
|
title: current.title ?? text.slice(0, 60),
|
|
649
750
|
status: 'running',
|
|
650
|
-
messages: [
|
|
751
|
+
messages: [
|
|
752
|
+
...current.messages,
|
|
753
|
+
{ id: userMessageId, role: 'user', createdAt: new Date().toISOString(), parts: [{ type: 'text', text }], authorActorId: this.currentUser.actorId }
|
|
754
|
+
]
|
|
651
755
|
}));
|
|
756
|
+
// The sender has read their own message.
|
|
757
|
+
this.readPositions.set(conversation.id, lastSequence(this.conversations.get(conversation.id)));
|
|
652
758
|
return this.stream(conversation.id, async (emit) => {
|
|
653
759
|
await emit({ type: 'turn.started', data: { turnId: this.nextId(), userMessageId, assistantMessageId } });
|
|
654
760
|
await this.playSteps(conversation.id, assistantMessageId, turn?.steps ?? [], emit);
|
|
@@ -659,6 +765,10 @@ class NhAssistantMockBackend {
|
|
|
659
765
|
if (!pending || pending.approval.approvalId !== approvalId) {
|
|
660
766
|
return this.empty(404);
|
|
661
767
|
}
|
|
768
|
+
const decider = this.activeActors.get(conversation.id);
|
|
769
|
+
if (decider && decider !== this.currentUser.actorId) {
|
|
770
|
+
return this.error(403, 'assistant-approval-forbidden');
|
|
771
|
+
}
|
|
662
772
|
if (request?.expectedProposalHash !== pending.approval.proposalHash) {
|
|
663
773
|
return this.empty(409);
|
|
664
774
|
}
|
|
@@ -857,10 +967,133 @@ class NhAssistantMockBackend {
|
|
|
857
967
|
}
|
|
858
968
|
update(conversationId, change) {
|
|
859
969
|
const conversation = this.conversations.get(conversationId);
|
|
860
|
-
if (conversation) {
|
|
861
|
-
|
|
970
|
+
if (!conversation) {
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
const updated = withSequences({ ...change(conversation), updatedAt: new Date().toISOString() });
|
|
974
|
+
if (updated.status !== 'running' && updated.status !== 'waiting-for-approval') {
|
|
975
|
+
this.activeActors.delete(conversationId);
|
|
976
|
+
}
|
|
977
|
+
this.conversations.set(conversationId, updated);
|
|
978
|
+
if (updated.status !== conversation.status || updated.title !== conversation.title) {
|
|
979
|
+
this.emitChanged(conversationId);
|
|
862
980
|
}
|
|
863
981
|
}
|
|
982
|
+
/** The conversation as the signed-in user sees it. */
|
|
983
|
+
view(conversation) {
|
|
984
|
+
const members = this.members.get(conversation.id) ?? [];
|
|
985
|
+
return {
|
|
986
|
+
...conversation,
|
|
987
|
+
...this.summary(conversation),
|
|
988
|
+
members,
|
|
989
|
+
shareToken: this.shareTokens.get(conversation.id) ?? null,
|
|
990
|
+
currentActorId: this.currentUser.actorId
|
|
991
|
+
};
|
|
992
|
+
}
|
|
993
|
+
summary(conversation) {
|
|
994
|
+
const latest = lastSequence(conversation);
|
|
995
|
+
const members = this.members.get(conversation.id) ?? [];
|
|
996
|
+
return {
|
|
997
|
+
id: conversation.id,
|
|
998
|
+
agentId: conversation.agentId,
|
|
999
|
+
title: conversation.title,
|
|
1000
|
+
status: conversation.status,
|
|
1001
|
+
createdAt: conversation.createdAt,
|
|
1002
|
+
updatedAt: conversation.updatedAt,
|
|
1003
|
+
role: 'owner',
|
|
1004
|
+
participantCount: Math.max(0, members.length - 1),
|
|
1005
|
+
lastMessageSequence: latest,
|
|
1006
|
+
lastReadSequence: Math.min(this.readPositions.get(conversation.id) ?? latest, latest),
|
|
1007
|
+
activeActorId: this.activeActors.get(conversation.id) ?? null
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
markRead(conversation, sequence) {
|
|
1011
|
+
const latest = lastSequence(conversation);
|
|
1012
|
+
const target = Math.min(sequence ?? latest, latest);
|
|
1013
|
+
if (target > (this.readPositions.get(conversation.id) ?? 0)) {
|
|
1014
|
+
this.readPositions.set(conversation.id, target);
|
|
1015
|
+
this.emitLive({ kind: 'read', data: { conversationId: conversation.id, lastReadSequence: target } });
|
|
1016
|
+
}
|
|
1017
|
+
return this.empty(204);
|
|
1018
|
+
}
|
|
1019
|
+
candidates(conversation, query) {
|
|
1020
|
+
const directory = this.scenario.collaboration?.directory ?? [];
|
|
1021
|
+
if (directory.length === 0) {
|
|
1022
|
+
return this.error(404, 'assistant-directory-unavailable');
|
|
1023
|
+
}
|
|
1024
|
+
const text = query.trim().toLowerCase();
|
|
1025
|
+
if (text.length < 2) {
|
|
1026
|
+
return this.error(400, 'assistant-validation');
|
|
1027
|
+
}
|
|
1028
|
+
const members = new Set((this.members.get(conversation.id) ?? []).map(member => member.actorId));
|
|
1029
|
+
return this.json(200, directory
|
|
1030
|
+
.filter(person => person.actorId !== this.currentUser.actorId && !members.has(person.actorId))
|
|
1031
|
+
.filter(person => person.displayName.toLowerCase().includes(text))
|
|
1032
|
+
.map(person => ({ actorId: person.actorId, displayName: person.displayName, detail: person.detail ?? null })));
|
|
1033
|
+
}
|
|
1034
|
+
invite(conversation, actorId) {
|
|
1035
|
+
const person = (this.scenario.collaboration?.directory ?? []).find(item => item.actorId === actorId);
|
|
1036
|
+
if (!person) {
|
|
1037
|
+
return this.error(404, 'assistant-participant-not-found');
|
|
1038
|
+
}
|
|
1039
|
+
const participants = (this.members.get(conversation.id) ?? []).filter(member => member.role === 'participant');
|
|
1040
|
+
if (participants.length >= this.maxParticipants()) {
|
|
1041
|
+
return this.error(409, 'assistant-participant-limit-reached');
|
|
1042
|
+
}
|
|
1043
|
+
this.addMember(conversation.id, person, 'participant');
|
|
1044
|
+
return this.empty(204);
|
|
1045
|
+
}
|
|
1046
|
+
addMember(conversationId, person, role) {
|
|
1047
|
+
const members = this.members.get(conversationId) ?? [];
|
|
1048
|
+
if (members.some(member => member.actorId === person.actorId)) {
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
const joinedAt = new Date().toISOString();
|
|
1052
|
+
const owner = members.length === 0
|
|
1053
|
+
? [{ actorId: this.currentUser.actorId, displayName: this.currentUser.displayName, role: 'owner', joinedAt }]
|
|
1054
|
+
: [];
|
|
1055
|
+
this.members.set(conversationId, [...members, ...owner, { actorId: person.actorId, displayName: person.displayName, role, joinedAt }]);
|
|
1056
|
+
this.emitChanged(conversationId);
|
|
1057
|
+
}
|
|
1058
|
+
removeMember(conversationId, actorId) {
|
|
1059
|
+
const members = this.members.get(conversationId) ?? [];
|
|
1060
|
+
if (!members.some(member => member.actorId === actorId && member.role === 'participant')) {
|
|
1061
|
+
return false;
|
|
1062
|
+
}
|
|
1063
|
+
const remaining = members.filter(member => member.actorId !== actorId);
|
|
1064
|
+
this.members.set(conversationId, remaining.length > 1 ? remaining : []);
|
|
1065
|
+
this.emitChanged(conversationId);
|
|
1066
|
+
return true;
|
|
1067
|
+
}
|
|
1068
|
+
emitChanged(conversationId) {
|
|
1069
|
+
const conversation = this.conversations.get(conversationId);
|
|
1070
|
+
if (!conversation) {
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
const summary = this.summary(conversation);
|
|
1074
|
+
this.emitLive({
|
|
1075
|
+
kind: 'changed',
|
|
1076
|
+
data: {
|
|
1077
|
+
conversationId,
|
|
1078
|
+
status: summary.status,
|
|
1079
|
+
title: summary.title,
|
|
1080
|
+
updatedAt: summary.updatedAt,
|
|
1081
|
+
activeActorId: summary.activeActorId ?? null,
|
|
1082
|
+
lastMessageSequence: summary.lastMessageSequence ?? 0,
|
|
1083
|
+
participantCount: summary.participantCount ?? 0
|
|
1084
|
+
}
|
|
1085
|
+
});
|
|
1086
|
+
}
|
|
1087
|
+
emitLive(message) {
|
|
1088
|
+
// Delivered after the current request, as a hub message would arrive.
|
|
1089
|
+
queueMicrotask(() => this.liveSubject.next(message));
|
|
1090
|
+
}
|
|
1091
|
+
maxParticipants() {
|
|
1092
|
+
return this.scenario.collaboration?.maxParticipants ?? 20;
|
|
1093
|
+
}
|
|
1094
|
+
error(status, code) {
|
|
1095
|
+
return this.json(status, { code, messageKey: `nh-assistant.errors.${code}` });
|
|
1096
|
+
}
|
|
864
1097
|
nextId() {
|
|
865
1098
|
this.sequence++;
|
|
866
1099
|
const suffix = this.sequence.toString(16).padStart(12, '0');
|
|
@@ -875,28 +1108,100 @@ class NhAssistantMockBackend {
|
|
|
875
1108
|
empty(status) {
|
|
876
1109
|
return new Response(null, { status });
|
|
877
1110
|
}
|
|
878
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
879
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
1111
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantMockBackend, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1112
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantMockBackend }); }
|
|
880
1113
|
}
|
|
881
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
1114
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantMockBackend, decorators: [{
|
|
882
1115
|
type: Injectable
|
|
883
1116
|
}], ctorParameters: () => [] });
|
|
884
1117
|
function delay(milliseconds) {
|
|
885
1118
|
return new Promise(resolve => setTimeout(resolve, milliseconds));
|
|
886
1119
|
}
|
|
1120
|
+
/** Numbers messages in order, as the server does, keeping numbers that are already set. */
|
|
1121
|
+
function withSequences(conversation) {
|
|
1122
|
+
let next = lastSequence(conversation);
|
|
1123
|
+
if (conversation.messages.every(message => message.sequence !== undefined)) {
|
|
1124
|
+
return conversation;
|
|
1125
|
+
}
|
|
1126
|
+
return {
|
|
1127
|
+
...conversation,
|
|
1128
|
+
messages: conversation.messages.map(message => message.sequence === undefined ? { ...message, sequence: ++next } : message)
|
|
1129
|
+
};
|
|
1130
|
+
}
|
|
1131
|
+
function lastSequence(conversation) {
|
|
1132
|
+
return conversation.messages.reduce((maximum, message) => Math.max(maximum, message.sequence ?? 0), 0);
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
/**
|
|
1136
|
+
* Stands in for the SignalR live service: it delivers the mock back-end's live updates in
|
|
1137
|
+
* memory, so shared conversations, other tabs and parallel turns behave as with a real hub.
|
|
1138
|
+
*/
|
|
1139
|
+
class NhAssistantMockLiveService {
|
|
1140
|
+
constructor() {
|
|
1141
|
+
this.backend = inject(NhAssistantMockBackend);
|
|
1142
|
+
this.changedSubject = new Subject();
|
|
1143
|
+
this.readSubject = new Subject();
|
|
1144
|
+
this.removedSubject = new Subject();
|
|
1145
|
+
this.eventSubject = new Subject();
|
|
1146
|
+
this.resyncSubject = new Subject();
|
|
1147
|
+
this.stateSignal = signal('off', ...(ngDevMode ? [{ debugName: "stateSignal" }] : []));
|
|
1148
|
+
this.changed$ = this.changedSubject.asObservable();
|
|
1149
|
+
this.read$ = this.readSubject.asObservable();
|
|
1150
|
+
this.removed$ = this.removedSubject.asObservable();
|
|
1151
|
+
this.events$ = this.eventSubject.asObservable();
|
|
1152
|
+
this.resync$ = this.resyncSubject.asObservable();
|
|
1153
|
+
this.state = this.stateSignal.asReadonly();
|
|
1154
|
+
inject(DestroyRef).onDestroy(() => this.subscription?.unsubscribe());
|
|
1155
|
+
}
|
|
1156
|
+
async connect(hubPath) {
|
|
1157
|
+
await this.stop();
|
|
1158
|
+
if (!hubPath) {
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
this.subscription = this.backend.live$.subscribe(message => {
|
|
1162
|
+
switch (message.kind) {
|
|
1163
|
+
case 'changed':
|
|
1164
|
+
this.changedSubject.next(message.data);
|
|
1165
|
+
break;
|
|
1166
|
+
case 'read':
|
|
1167
|
+
this.readSubject.next(message.data);
|
|
1168
|
+
break;
|
|
1169
|
+
case 'removed':
|
|
1170
|
+
this.removedSubject.next(message.data);
|
|
1171
|
+
break;
|
|
1172
|
+
case 'event':
|
|
1173
|
+
this.eventSubject.next(message.data);
|
|
1174
|
+
break;
|
|
1175
|
+
}
|
|
1176
|
+
});
|
|
1177
|
+
this.stateSignal.set('connected');
|
|
1178
|
+
this.resyncSubject.next();
|
|
1179
|
+
}
|
|
1180
|
+
async stop() {
|
|
1181
|
+
this.subscription?.unsubscribe();
|
|
1182
|
+
this.subscription = undefined;
|
|
1183
|
+
this.stateSignal.set('off');
|
|
1184
|
+
}
|
|
1185
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantMockLiveService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1186
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantMockLiveService }); }
|
|
1187
|
+
}
|
|
1188
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantMockLiveService, decorators: [{
|
|
1189
|
+
type: Injectable
|
|
1190
|
+
}], ctorParameters: () => [] });
|
|
887
1191
|
|
|
888
1192
|
/**
|
|
889
1193
|
* Replaces the assistant transport with an in-memory back-end that plays `script`.
|
|
890
1194
|
* Register it after `provideNhAssistant(...)` in the same injector. Every client feature
|
|
891
|
-
* (status, agents, conversations, streamed turns, approvals, cancellation
|
|
892
|
-
* without a server. Inject `NhAssistantMockBackend` to
|
|
893
|
-
* inspect the received requests.
|
|
1195
|
+
* (status, agents, conversations, streamed turns, approvals, cancellation, sharing, read
|
|
1196
|
+
* state and live updates) then works without a server. Inject `NhAssistantMockBackend` to
|
|
1197
|
+
* switch the feature flag, simulate a colleague or inspect the received requests.
|
|
894
1198
|
*/
|
|
895
1199
|
function provideNhAssistantMockApi(script) {
|
|
896
1200
|
return makeEnvironmentProviders([
|
|
897
1201
|
{ provide: NH_ASSISTANT_MOCK_SCENARIO, useValue: script },
|
|
898
1202
|
NhAssistantMockBackend,
|
|
899
|
-
{ provide: NH_ASSISTANT_FETCH, useFactory: () => inject(NhAssistantMockBackend).fetch }
|
|
1203
|
+
{ provide: NH_ASSISTANT_FETCH, useFactory: () => inject(NhAssistantMockBackend).fetch },
|
|
1204
|
+
{ provide: NhAssistantLiveService, useClass: NhAssistantMockLiveService }
|
|
900
1205
|
]);
|
|
901
1206
|
}
|
|
902
1207
|
|
|
@@ -908,5 +1213,5 @@ function provideNhAssistantMockApi(script) {
|
|
|
908
1213
|
* Generated bundle index. Do not edit.
|
|
909
1214
|
*/
|
|
910
1215
|
|
|
911
|
-
export { NH_ASSISTANT_MOCK_SCENARIO, NhAssistantMockBackend, provideNhAssistantMockApi };
|
|
1216
|
+
export { NH_ASSISTANT_MOCK_HUB_PATH, NH_ASSISTANT_MOCK_SCENARIO, NhAssistantMockBackend, NhAssistantMockLiveService, provideNhAssistantMockApi };
|
|
912
1217
|
//# sourceMappingURL=newheap-platform-ai-chat-testing.mjs.map
|