@newheap/platform-ai-chat 0.2.0 → 0.3.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.
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, inject, DestroyRef, Injectable, EnvironmentInjector, runInInjectionContext, signal, computed, Injector, afterNextRender, makeEnvironmentProviders, provideEnvironmentInitializer, input, ChangeDetectionStrategy, Component,
|
|
2
|
+
import { InjectionToken, inject, DestroyRef, Injectable, EnvironmentInjector, runInInjectionContext, signal, computed, Injector, effect, untracked, afterNextRender, makeEnvironmentProviders, provideEnvironmentInitializer, input, ChangeDetectionStrategy, Component, ChangeDetectorRef, Pipe, output, ElementRef, viewChild, HostListener, ViewContainerRef } from '@angular/core';
|
|
3
3
|
import { TranslateService, TranslatePipe } from '@ngx-translate/core';
|
|
4
4
|
import { merge, Observable, firstValueFrom, isObservable, Subscription } from 'rxjs';
|
|
5
5
|
import { BreakpointObserver } from '@angular/cdk/layout';
|
|
@@ -1633,6 +1633,91 @@ function isRecord(value) {
|
|
|
1633
1633
|
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
1634
1634
|
}
|
|
1635
1635
|
|
|
1636
|
+
const emptyState = { agentId: null, conversationId: null, panelOpen: false };
|
|
1637
|
+
const storagePrefix = 'nh-assistant-ui:v1:';
|
|
1638
|
+
/** Browser-only UI pointers. Messages, drafts, tokens and page context are never stored. */
|
|
1639
|
+
class NhAssistantUiState {
|
|
1640
|
+
constructor() {
|
|
1641
|
+
this.config = inject(NH_ASSISTANT_CONFIG);
|
|
1642
|
+
this.injector = inject(EnvironmentInjector);
|
|
1643
|
+
this.key = null;
|
|
1644
|
+
this.state = { ...emptyState };
|
|
1645
|
+
this.activated = false;
|
|
1646
|
+
this.activationRevision = 0;
|
|
1647
|
+
}
|
|
1648
|
+
async activate() {
|
|
1649
|
+
const revision = ++this.activationRevision;
|
|
1650
|
+
let scope = null;
|
|
1651
|
+
try {
|
|
1652
|
+
scope = await runInInjectionContext(this.injector, () => this.config.getStateScope?.() ?? null);
|
|
1653
|
+
}
|
|
1654
|
+
catch {
|
|
1655
|
+
scope = null;
|
|
1656
|
+
}
|
|
1657
|
+
if (revision !== this.activationRevision) {
|
|
1658
|
+
return { changed: false, state: this.state };
|
|
1659
|
+
}
|
|
1660
|
+
const normalized = typeof scope === 'string' ? scope.trim() : '';
|
|
1661
|
+
const key = normalized.length > 0 && normalized.length <= 256
|
|
1662
|
+
? `${storagePrefix}${encodeURIComponent(this.config.apiBaseUrl)}:${encodeURIComponent(normalized)}`
|
|
1663
|
+
: null;
|
|
1664
|
+
if (this.activated && key === this.key) {
|
|
1665
|
+
return { changed: false, state: this.state };
|
|
1666
|
+
}
|
|
1667
|
+
this.activated = true;
|
|
1668
|
+
this.key = key;
|
|
1669
|
+
this.state = this.read();
|
|
1670
|
+
return { changed: true, state: this.state };
|
|
1671
|
+
}
|
|
1672
|
+
deactivate() {
|
|
1673
|
+
this.activationRevision++;
|
|
1674
|
+
this.activated = false;
|
|
1675
|
+
this.key = null;
|
|
1676
|
+
this.state = { ...emptyState };
|
|
1677
|
+
}
|
|
1678
|
+
update(patch) {
|
|
1679
|
+
this.state = { ...this.state, ...patch };
|
|
1680
|
+
if (!this.key) {
|
|
1681
|
+
return;
|
|
1682
|
+
}
|
|
1683
|
+
try {
|
|
1684
|
+
globalThis.localStorage?.setItem(this.key, JSON.stringify(this.state));
|
|
1685
|
+
}
|
|
1686
|
+
catch {
|
|
1687
|
+
// Storage can be unavailable or full; the live assistant remains usable.
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
read() {
|
|
1691
|
+
if (!this.key) {
|
|
1692
|
+
return { ...emptyState };
|
|
1693
|
+
}
|
|
1694
|
+
try {
|
|
1695
|
+
const raw = globalThis.localStorage?.getItem(this.key);
|
|
1696
|
+
const value = raw ? JSON.parse(raw) : null;
|
|
1697
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
1698
|
+
return { ...emptyState };
|
|
1699
|
+
}
|
|
1700
|
+
const saved = value;
|
|
1701
|
+
return {
|
|
1702
|
+
agentId: validId(saved['agentId']),
|
|
1703
|
+
conversationId: validId(saved['conversationId']),
|
|
1704
|
+
panelOpen: saved['panelOpen'] === true
|
|
1705
|
+
};
|
|
1706
|
+
}
|
|
1707
|
+
catch {
|
|
1708
|
+
return { ...emptyState };
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: NhAssistantUiState, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1712
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: NhAssistantUiState }); }
|
|
1713
|
+
}
|
|
1714
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: NhAssistantUiState, decorators: [{
|
|
1715
|
+
type: Injectable
|
|
1716
|
+
}] });
|
|
1717
|
+
function validId(value) {
|
|
1718
|
+
return typeof value === 'string' && value.length > 0 && value.length <= 256 ? value : null;
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1636
1721
|
const conversationPageSize = 50;
|
|
1637
1722
|
const cancelGracePeriodMs = 5_000;
|
|
1638
1723
|
/**
|
|
@@ -1645,6 +1730,7 @@ class NhAssistantStore {
|
|
|
1645
1730
|
this.config = inject(NH_ASSISTANT_CONFIG);
|
|
1646
1731
|
this.injector = inject(EnvironmentInjector);
|
|
1647
1732
|
this.accessPolicy = inject(NH_ASSISTANT_ACCESS_POLICY);
|
|
1733
|
+
this.uiState = inject(NhAssistantUiState);
|
|
1648
1734
|
this.accessGrantedState = signal(null, ...(ngDevMode ? [{ debugName: "accessGrantedState" }] : []));
|
|
1649
1735
|
this.statusState = signal(null, ...(ngDevMode ? [{ debugName: "statusState" }] : []));
|
|
1650
1736
|
this.statusLoadingState = signal(false, ...(ngDevMode ? [{ debugName: "statusLoadingState" }] : []));
|
|
@@ -1661,6 +1747,9 @@ class NhAssistantStore {
|
|
|
1661
1747
|
this.restoredDraftState = signal(null, ...(ngDevMode ? [{ debugName: "restoredDraftState" }] : []));
|
|
1662
1748
|
this.pageContextState = signal(null, ...(ngDevMode ? [{ debugName: "pageContextState" }] : []));
|
|
1663
1749
|
this.pageContextExcludedState = signal(false, ...(ngDevMode ? [{ debugName: "pageContextExcludedState" }] : []));
|
|
1750
|
+
this.restorePanelOpenState = signal(false, ...(ngDevMode ? [{ debugName: "restorePanelOpenState" }] : []));
|
|
1751
|
+
this.statusRevision = 0;
|
|
1752
|
+
this.accountRevision = 0;
|
|
1664
1753
|
/** True when the access policy allows the user and the server reports the assistant as enabled. */
|
|
1665
1754
|
this.enabled = computed(() => this.accessGrantedState() === true && this.statusState()?.enabled === true, ...(ngDevMode ? [{ debugName: "enabled" }] : []));
|
|
1666
1755
|
this.accessGranted = this.accessGrantedState.asReadonly();
|
|
@@ -1687,11 +1776,13 @@ class NhAssistantStore {
|
|
|
1687
1776
|
this.pageContext = this.pageContextState.asReadonly();
|
|
1688
1777
|
/** True when the user left the page context out of the next message. */
|
|
1689
1778
|
this.pageContextExcluded = this.pageContextExcludedState.asReadonly();
|
|
1779
|
+
/** Whether the drawer was open when this account last left the application. */
|
|
1780
|
+
this.restorePanelOpen = this.restorePanelOpenState.asReadonly();
|
|
1690
1781
|
/** True while the user can send: enabled, an agent is chosen and no turn runs or waits. */
|
|
1691
1782
|
this.canSend = computed(() => {
|
|
1692
1783
|
const conversation = this.activeConversationState();
|
|
1693
1784
|
const blocked = conversation !== null && conversation.status !== 'idle';
|
|
1694
|
-
return this.enabled() && this.selectedAgentIdState() !== null && !this.streamingState() && !blocked;
|
|
1785
|
+
return this.enabled() && this.selectedAgentIdState() !== null && !this.streamingState() && !this.conversationLoadingState() && !blocked;
|
|
1695
1786
|
}, ...(ngDevMode ? [{ debugName: "canSend" }] : []));
|
|
1696
1787
|
inject(DestroyRef).onDestroy(() => {
|
|
1697
1788
|
this.accessSubscription?.unsubscribe();
|
|
@@ -1706,39 +1797,95 @@ class NhAssistantStore {
|
|
|
1706
1797
|
}
|
|
1707
1798
|
/** Reloads the server status, for example after the host changed the signed-in user. */
|
|
1708
1799
|
async reloadStatus() {
|
|
1800
|
+
const revision = ++this.statusRevision;
|
|
1709
1801
|
if (this.accessGrantedState() !== true) {
|
|
1802
|
+
this.accountRevision++;
|
|
1803
|
+
this.stopLocalTurn();
|
|
1710
1804
|
this.statusState.set(null);
|
|
1805
|
+
this.activeConversationState.set(null);
|
|
1806
|
+
this.selectedAgentIdState.set(null);
|
|
1807
|
+
this.conversationsState.set([]);
|
|
1808
|
+
this.conversationsTotalState.set(0);
|
|
1809
|
+
this.conversationLoadingState.set(false);
|
|
1810
|
+
this.conversationsLoadingState.set(false);
|
|
1811
|
+
this.pageContextState.set(null);
|
|
1812
|
+
this.pageContextExcludedState.set(false);
|
|
1813
|
+
this.restoredDraftState.set(null);
|
|
1814
|
+
this.errorState.set(null);
|
|
1815
|
+
this.lastUsageState.set(null);
|
|
1816
|
+
this.restorePanelOpenState.set(false);
|
|
1817
|
+
this.uiState.deactivate();
|
|
1711
1818
|
return;
|
|
1712
1819
|
}
|
|
1713
1820
|
this.statusLoadingState.set(true);
|
|
1714
1821
|
try {
|
|
1822
|
+
const restored = await this.uiState.activate();
|
|
1823
|
+
if (revision !== this.statusRevision) {
|
|
1824
|
+
return;
|
|
1825
|
+
}
|
|
1826
|
+
if (restored.changed) {
|
|
1827
|
+
this.accountRevision++;
|
|
1828
|
+
this.stopLocalTurn();
|
|
1829
|
+
this.statusState.set(null);
|
|
1830
|
+
this.activeConversationState.set(null);
|
|
1831
|
+
this.conversationsState.set([]);
|
|
1832
|
+
this.conversationsTotalState.set(0);
|
|
1833
|
+
this.conversationLoadingState.set(false);
|
|
1834
|
+
this.conversationsLoadingState.set(false);
|
|
1835
|
+
this.pageContextState.set(null);
|
|
1836
|
+
this.pageContextExcludedState.set(false);
|
|
1837
|
+
this.restoredDraftState.set(null);
|
|
1838
|
+
this.errorState.set(null);
|
|
1839
|
+
this.lastUsageState.set(null);
|
|
1840
|
+
this.selectedAgentIdState.set(restored.state.agentId);
|
|
1841
|
+
this.restorePanelOpenState.set(restored.state.panelOpen);
|
|
1842
|
+
}
|
|
1715
1843
|
const status = await firstValueFrom(this.api.status());
|
|
1844
|
+
if (revision !== this.statusRevision) {
|
|
1845
|
+
return;
|
|
1846
|
+
}
|
|
1716
1847
|
this.statusState.set(status);
|
|
1717
|
-
|
|
1848
|
+
if (status.enabled) {
|
|
1849
|
+
this.selectInitialAgent(status.agents);
|
|
1850
|
+
if (!this.activeConversationState() && restored.state.conversationId) {
|
|
1851
|
+
await this.loadConversation(restored.state.conversationId, true);
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1718
1854
|
}
|
|
1719
1855
|
catch {
|
|
1720
1856
|
// An unavailable assistant endpoint hides the assistant instead of showing an error.
|
|
1721
|
-
this.
|
|
1857
|
+
if (revision === this.statusRevision) {
|
|
1858
|
+
this.statusState.set(null);
|
|
1859
|
+
}
|
|
1722
1860
|
}
|
|
1723
1861
|
finally {
|
|
1724
|
-
this.
|
|
1862
|
+
if (revision === this.statusRevision) {
|
|
1863
|
+
this.statusLoadingState.set(false);
|
|
1864
|
+
}
|
|
1725
1865
|
}
|
|
1726
1866
|
}
|
|
1727
1867
|
async refreshConversations() {
|
|
1728
1868
|
if (!this.enabled()) {
|
|
1729
1869
|
return;
|
|
1730
1870
|
}
|
|
1871
|
+
const revision = this.accountRevision;
|
|
1731
1872
|
this.conversationsLoadingState.set(true);
|
|
1732
1873
|
try {
|
|
1733
1874
|
const page = await firstValueFrom(this.api.listConversations(1, conversationPageSize));
|
|
1734
|
-
this.
|
|
1735
|
-
|
|
1875
|
+
if (revision === this.accountRevision) {
|
|
1876
|
+
this.conversationsState.set(page.items);
|
|
1877
|
+
this.conversationsTotalState.set(page.total);
|
|
1878
|
+
}
|
|
1736
1879
|
}
|
|
1737
1880
|
catch (error) {
|
|
1738
|
-
this.
|
|
1881
|
+
if (revision === this.accountRevision) {
|
|
1882
|
+
this.setError(error);
|
|
1883
|
+
}
|
|
1739
1884
|
}
|
|
1740
1885
|
finally {
|
|
1741
|
-
this.
|
|
1886
|
+
if (revision === this.accountRevision) {
|
|
1887
|
+
this.conversationsLoadingState.set(false);
|
|
1888
|
+
}
|
|
1742
1889
|
}
|
|
1743
1890
|
}
|
|
1744
1891
|
/** Chooses the agent for the next new conversation. Switching away from the active conversation's agent starts a new one. */
|
|
@@ -1747,6 +1894,7 @@ class NhAssistantStore {
|
|
|
1747
1894
|
return;
|
|
1748
1895
|
}
|
|
1749
1896
|
this.selectedAgentIdState.set(agentId);
|
|
1897
|
+
this.uiState.update({ agentId });
|
|
1750
1898
|
const active = this.activeConversationState();
|
|
1751
1899
|
if (active && active.agentId !== agentId && !this.streamingState()) {
|
|
1752
1900
|
this.startNewConversation();
|
|
@@ -1758,43 +1906,71 @@ class NhAssistantStore {
|
|
|
1758
1906
|
return;
|
|
1759
1907
|
}
|
|
1760
1908
|
this.activeConversationState.set(null);
|
|
1909
|
+
this.uiState.update({ conversationId: null });
|
|
1761
1910
|
this.errorState.set(null);
|
|
1762
1911
|
this.lastUsageState.set(null);
|
|
1763
1912
|
}
|
|
1764
1913
|
async openConversation(conversationId) {
|
|
1914
|
+
await this.loadConversation(conversationId, false);
|
|
1915
|
+
}
|
|
1916
|
+
async loadConversation(conversationId, restoring) {
|
|
1765
1917
|
if (this.streamingState() || this.activeConversationState()?.id === conversationId) {
|
|
1766
1918
|
return;
|
|
1767
1919
|
}
|
|
1920
|
+
const revision = this.accountRevision;
|
|
1768
1921
|
this.conversationLoadingState.set(true);
|
|
1769
1922
|
this.errorState.set(null);
|
|
1770
1923
|
try {
|
|
1771
1924
|
const conversation = await firstValueFrom(this.api.getConversation(conversationId));
|
|
1772
|
-
this.
|
|
1773
|
-
|
|
1774
|
-
|
|
1925
|
+
if (revision !== this.accountRevision) {
|
|
1926
|
+
return;
|
|
1927
|
+
}
|
|
1928
|
+
if (!this.agents().some(agent => agent.id === conversation.agentId)) {
|
|
1929
|
+
this.uiState.update({ conversationId: null });
|
|
1930
|
+
return;
|
|
1775
1931
|
}
|
|
1932
|
+
this.activeConversationState.set(conversation);
|
|
1933
|
+
this.selectedAgentIdState.set(conversation.agentId);
|
|
1934
|
+
this.uiState.update({ conversationId, agentId: conversation.agentId });
|
|
1776
1935
|
}
|
|
1777
1936
|
catch (error) {
|
|
1778
|
-
this.
|
|
1937
|
+
if (revision !== this.accountRevision) {
|
|
1938
|
+
return;
|
|
1939
|
+
}
|
|
1940
|
+
if (error instanceof NhAssistantApiError && (error.status === 403 || error.status === 404)) {
|
|
1941
|
+
this.uiState.update({ conversationId: null });
|
|
1942
|
+
}
|
|
1943
|
+
if (!restoring) {
|
|
1944
|
+
this.setError(error);
|
|
1945
|
+
}
|
|
1779
1946
|
}
|
|
1780
1947
|
finally {
|
|
1781
|
-
this.
|
|
1948
|
+
if (revision === this.accountRevision) {
|
|
1949
|
+
this.conversationLoadingState.set(false);
|
|
1950
|
+
}
|
|
1782
1951
|
}
|
|
1783
1952
|
}
|
|
1784
1953
|
async deleteConversation(conversationId) {
|
|
1785
1954
|
if (this.streamingState() && this.activeConversationState()?.id === conversationId) {
|
|
1786
1955
|
return;
|
|
1787
1956
|
}
|
|
1957
|
+
const revision = this.accountRevision;
|
|
1788
1958
|
try {
|
|
1789
1959
|
await firstValueFrom(this.api.deleteConversation(conversationId), { defaultValue: undefined });
|
|
1960
|
+
if (revision !== this.accountRevision) {
|
|
1961
|
+
return;
|
|
1962
|
+
}
|
|
1790
1963
|
this.conversationsState.update(items => items.filter(item => item.id !== conversationId));
|
|
1791
1964
|
this.conversationsTotalState.update(total => Math.max(0, total - 1));
|
|
1792
1965
|
if (this.activeConversationState()?.id === conversationId) {
|
|
1793
1966
|
this.activeConversationState.set(null);
|
|
1967
|
+
this.uiState.update({ conversationId: null });
|
|
1794
1968
|
}
|
|
1795
1969
|
}
|
|
1796
1970
|
catch (error) {
|
|
1797
|
-
this.
|
|
1971
|
+
if (revision === this.accountRevision) {
|
|
1972
|
+
this.setError(error);
|
|
1973
|
+
}
|
|
1798
1974
|
}
|
|
1799
1975
|
}
|
|
1800
1976
|
/**
|
|
@@ -1804,6 +1980,8 @@ class NhAssistantStore {
|
|
|
1804
1980
|
*/
|
|
1805
1981
|
async send(text) {
|
|
1806
1982
|
const trimmed = text.trim();
|
|
1983
|
+
await this.initialize();
|
|
1984
|
+
await this.reloadStatus();
|
|
1807
1985
|
const maxChars = this.limits()?.maxMessageChars ?? Number.MAX_SAFE_INTEGER;
|
|
1808
1986
|
if (trimmed.length === 0 || trimmed.length > maxChars || !this.canSend()) {
|
|
1809
1987
|
return false;
|
|
@@ -1811,16 +1989,22 @@ class NhAssistantStore {
|
|
|
1811
1989
|
this.errorState.set(null);
|
|
1812
1990
|
this.restoredDraftState.set(null);
|
|
1813
1991
|
this.streamingState.set(true);
|
|
1992
|
+
const accountRevision = this.accountRevision;
|
|
1814
1993
|
let conversation = this.activeConversationState();
|
|
1815
1994
|
if (!conversation) {
|
|
1816
1995
|
conversation = await this.createConversation();
|
|
1817
1996
|
if (!conversation) {
|
|
1818
|
-
this.
|
|
1819
|
-
|
|
1997
|
+
if (accountRevision === this.accountRevision) {
|
|
1998
|
+
this.streamingState.set(false);
|
|
1999
|
+
this.restoredDraftState.set(text);
|
|
2000
|
+
}
|
|
1820
2001
|
return false;
|
|
1821
2002
|
}
|
|
1822
2003
|
}
|
|
1823
2004
|
const clientContext = await this.contextForMessage();
|
|
2005
|
+
if (accountRevision !== this.accountRevision) {
|
|
2006
|
+
return false;
|
|
2007
|
+
}
|
|
1824
2008
|
const clientMessageId = createClientMessageId();
|
|
1825
2009
|
const previousStatus = conversation.status;
|
|
1826
2010
|
this.activeConversationState.set({
|
|
@@ -1833,6 +2017,7 @@ class NhAssistantStore {
|
|
|
1833
2017
|
});
|
|
1834
2018
|
const conversationId = conversation.id;
|
|
1835
2019
|
return new Promise(resolve => {
|
|
2020
|
+
this.pendingSendResolve = resolve;
|
|
1836
2021
|
let started = false;
|
|
1837
2022
|
let failedAfterStart = false;
|
|
1838
2023
|
this.runStream(this.api.sendMessage(conversationId, {
|
|
@@ -1858,6 +2043,7 @@ class NhAssistantStore {
|
|
|
1858
2043
|
this.applyEvent(event, clientMessageId);
|
|
1859
2044
|
},
|
|
1860
2045
|
complete: () => {
|
|
2046
|
+
this.pendingSendResolve = undefined;
|
|
1861
2047
|
resolve(started);
|
|
1862
2048
|
this.afterTurn(conversationId, failedAfterStart);
|
|
1863
2049
|
}
|
|
@@ -1904,11 +2090,17 @@ class NhAssistantStore {
|
|
|
1904
2090
|
if (!conversation || (conversation.status === 'idle' && !this.streamingState())) {
|
|
1905
2091
|
return;
|
|
1906
2092
|
}
|
|
2093
|
+
const revision = this.accountRevision;
|
|
1907
2094
|
try {
|
|
1908
2095
|
await firstValueFrom(this.api.cancel(conversation.id), { defaultValue: undefined });
|
|
1909
2096
|
}
|
|
1910
2097
|
catch (error) {
|
|
1911
|
-
this.
|
|
2098
|
+
if (revision === this.accountRevision) {
|
|
2099
|
+
this.setError(error);
|
|
2100
|
+
}
|
|
2101
|
+
return;
|
|
2102
|
+
}
|
|
2103
|
+
if (revision !== this.accountRevision) {
|
|
1912
2104
|
return;
|
|
1913
2105
|
}
|
|
1914
2106
|
if (!this.streamingState()) {
|
|
@@ -1929,9 +2121,18 @@ class NhAssistantStore {
|
|
|
1929
2121
|
clearError() {
|
|
1930
2122
|
this.errorState.set(null);
|
|
1931
2123
|
}
|
|
2124
|
+
/** Persists only drawer visibility, never the page context or a message draft. */
|
|
2125
|
+
setPanelOpen(open) {
|
|
2126
|
+
this.restorePanelOpenState.set(open);
|
|
2127
|
+
this.uiState.update({ panelOpen: open });
|
|
2128
|
+
}
|
|
1932
2129
|
/** Reads the host's page context again so the chip shows what the next message sends. */
|
|
1933
2130
|
async refreshPageContext() {
|
|
1934
|
-
|
|
2131
|
+
const revision = this.accountRevision;
|
|
2132
|
+
const context = await this.readPageContext();
|
|
2133
|
+
if (revision === this.accountRevision) {
|
|
2134
|
+
this.pageContextState.set(context ?? null);
|
|
2135
|
+
}
|
|
1935
2136
|
}
|
|
1936
2137
|
/** Leaves the page context out of the next message only, or includes it again. */
|
|
1937
2138
|
setPageContextExcluded(excluded) {
|
|
@@ -1955,8 +2156,11 @@ class NhAssistantStore {
|
|
|
1955
2156
|
this.pageContextExcludedState.set(false);
|
|
1956
2157
|
return null;
|
|
1957
2158
|
}
|
|
2159
|
+
const revision = this.accountRevision;
|
|
1958
2160
|
const context = await this.readPageContext();
|
|
1959
|
-
|
|
2161
|
+
if (revision === this.accountRevision) {
|
|
2162
|
+
this.pageContextState.set(context ?? null);
|
|
2163
|
+
}
|
|
1960
2164
|
return context;
|
|
1961
2165
|
}
|
|
1962
2166
|
async readPageContext() {
|
|
@@ -1986,14 +2190,8 @@ class NhAssistantStore {
|
|
|
1986
2190
|
await new Promise(resolve => {
|
|
1987
2191
|
this.accessSubscription = decision.subscribe({
|
|
1988
2192
|
next: granted => {
|
|
1989
|
-
const changed = this.accessGrantedState() !== granted;
|
|
1990
2193
|
this.accessGrantedState.set(granted);
|
|
1991
|
-
|
|
1992
|
-
void this.reloadStatus().finally(() => resolve());
|
|
1993
|
-
}
|
|
1994
|
-
else {
|
|
1995
|
-
resolve();
|
|
1996
|
-
}
|
|
2194
|
+
void this.reloadStatus().finally(() => resolve());
|
|
1997
2195
|
},
|
|
1998
2196
|
error: () => {
|
|
1999
2197
|
this.accessGrantedState.set(false);
|
|
@@ -2008,15 +2206,22 @@ class NhAssistantStore {
|
|
|
2008
2206
|
if (!agentId) {
|
|
2009
2207
|
return null;
|
|
2010
2208
|
}
|
|
2209
|
+
const revision = this.accountRevision;
|
|
2011
2210
|
try {
|
|
2012
2211
|
const conversation = await firstValueFrom(this.api.createConversation({ agentId }));
|
|
2212
|
+
if (revision !== this.accountRevision) {
|
|
2213
|
+
return null;
|
|
2214
|
+
}
|
|
2013
2215
|
this.activeConversationState.set(conversation);
|
|
2216
|
+
this.uiState.update({ conversationId: conversation.id });
|
|
2014
2217
|
this.conversationsState.update(items => [toSummary(conversation), ...items.filter(item => item.id !== conversation.id)]);
|
|
2015
2218
|
this.conversationsTotalState.update(total => total + 1);
|
|
2016
2219
|
return conversation;
|
|
2017
2220
|
}
|
|
2018
2221
|
catch (error) {
|
|
2019
|
-
this.
|
|
2222
|
+
if (revision === this.accountRevision) {
|
|
2223
|
+
this.setError(error);
|
|
2224
|
+
}
|
|
2020
2225
|
return null;
|
|
2021
2226
|
}
|
|
2022
2227
|
}
|
|
@@ -2037,6 +2242,14 @@ class NhAssistantStore {
|
|
|
2037
2242
|
complete: finish
|
|
2038
2243
|
});
|
|
2039
2244
|
}
|
|
2245
|
+
stopLocalTurn() {
|
|
2246
|
+
this.streamSubscription?.unsubscribe();
|
|
2247
|
+
this.pendingSendResolve?.(false);
|
|
2248
|
+
this.pendingSendResolve = undefined;
|
|
2249
|
+
clearTimeout(this.cancelTimer);
|
|
2250
|
+
this.streamingState.set(false);
|
|
2251
|
+
this.decidingState.set(false);
|
|
2252
|
+
}
|
|
2040
2253
|
applyEvent(event, clientMessageId) {
|
|
2041
2254
|
this.updateActive(conversation => applyNhAssistantEvent(conversation, event, clientMessageId));
|
|
2042
2255
|
if (event.type === 'error') {
|
|
@@ -2063,9 +2276,10 @@ class NhAssistantStore {
|
|
|
2063
2276
|
}
|
|
2064
2277
|
}
|
|
2065
2278
|
async reloadActiveConversation(conversationId) {
|
|
2279
|
+
const revision = this.accountRevision;
|
|
2066
2280
|
try {
|
|
2067
2281
|
const conversation = await firstValueFrom(this.api.getConversation(conversationId));
|
|
2068
|
-
if (this.activeConversationState()?.id === conversationId && !this.streamingState()) {
|
|
2282
|
+
if (revision === this.accountRevision && this.activeConversationState()?.id === conversationId && !this.streamingState()) {
|
|
2069
2283
|
this.activeConversationState.set(conversation);
|
|
2070
2284
|
}
|
|
2071
2285
|
}
|
|
@@ -2093,6 +2307,7 @@ class NhAssistantStore {
|
|
|
2093
2307
|
}
|
|
2094
2308
|
const preferred = agents.find(agent => agent.id === this.config.defaultAgentId) ?? agents[0] ?? null;
|
|
2095
2309
|
this.selectedAgentIdState.set(preferred?.id ?? null);
|
|
2310
|
+
this.uiState.update({ agentId: preferred?.id ?? null });
|
|
2096
2311
|
}
|
|
2097
2312
|
setError(error) {
|
|
2098
2313
|
if (error instanceof NhAssistantApiError) {
|
|
@@ -2161,12 +2376,31 @@ class NhAssistantPanelService {
|
|
|
2161
2376
|
this.returnFocusTo = null;
|
|
2162
2377
|
this.isOpen = this.openState.asReadonly();
|
|
2163
2378
|
inject(DestroyRef).onDestroy(() => this.dispose());
|
|
2379
|
+
effect(() => {
|
|
2380
|
+
if (this.store.enabled() && this.store.restorePanelOpen() && this.portal && !this.openState()) {
|
|
2381
|
+
untracked(() => queueMicrotask(() => {
|
|
2382
|
+
if (this.portal && this.store.enabled() && this.store.restorePanelOpen() && !this.openState()) {
|
|
2383
|
+
this.showPanel();
|
|
2384
|
+
}
|
|
2385
|
+
}));
|
|
2386
|
+
}
|
|
2387
|
+
});
|
|
2164
2388
|
}
|
|
2165
2389
|
open(conversationId) {
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
void this.store.openConversation(conversationId);
|
|
2390
|
+
if (this.openState() && !conversationId) {
|
|
2391
|
+
return;
|
|
2169
2392
|
}
|
|
2393
|
+
this.showPanel();
|
|
2394
|
+
void this.store.initialize().then(() => this.store.reloadStatus()).then(() => {
|
|
2395
|
+
if (this.openState()) {
|
|
2396
|
+
this.store.setPanelOpen(true);
|
|
2397
|
+
}
|
|
2398
|
+
if (conversationId) {
|
|
2399
|
+
void this.store.openConversation(conversationId);
|
|
2400
|
+
}
|
|
2401
|
+
});
|
|
2402
|
+
}
|
|
2403
|
+
showPanel() {
|
|
2170
2404
|
if (this.openState()) {
|
|
2171
2405
|
return;
|
|
2172
2406
|
}
|
|
@@ -2180,6 +2414,7 @@ class NhAssistantPanelService {
|
|
|
2180
2414
|
return;
|
|
2181
2415
|
}
|
|
2182
2416
|
this.openState.set(false);
|
|
2417
|
+
this.store.setPanelOpen(false);
|
|
2183
2418
|
this.overlayRef?.detach();
|
|
2184
2419
|
const target = this.returnFocusTo;
|
|
2185
2420
|
this.returnFocusTo = null;
|
|
@@ -2201,6 +2436,13 @@ class NhAssistantPanelService {
|
|
|
2201
2436
|
if (this.openState()) {
|
|
2202
2437
|
this.attach();
|
|
2203
2438
|
}
|
|
2439
|
+
else {
|
|
2440
|
+
void this.store.initialize().then(() => {
|
|
2441
|
+
if (this.portal === portal && this.store.enabled() && this.store.restorePanelOpen()) {
|
|
2442
|
+
this.showPanel();
|
|
2443
|
+
}
|
|
2444
|
+
});
|
|
2445
|
+
}
|
|
2204
2446
|
}
|
|
2205
2447
|
unregisterPanel(portal) {
|
|
2206
2448
|
if (this.portal !== portal) {
|
|
@@ -2274,6 +2516,7 @@ function provideNhAssistant(config) {
|
|
|
2274
2516
|
NhAssistantApiService,
|
|
2275
2517
|
NhAssistantAdminApiService,
|
|
2276
2518
|
NhAssistantStore,
|
|
2519
|
+
NhAssistantUiState,
|
|
2277
2520
|
NhAssistantPanelService,
|
|
2278
2521
|
...(config.translations === 'host'
|
|
2279
2522
|
? []
|