@zeniai/client-epic-state 5.3.18 → 5.3.19
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/lib/entity/tenant/epic/fetchAllTenantsEpic.d.ts +2 -4
- package/lib/entity/tenant/epic/fetchAllTenantsEpic.js +50 -42
- package/lib/entity/tenant/tenantPayload.d.ts +17 -1
- package/lib/entity/tenant/tenantReducer.d.ts +7 -2
- package/lib/entity/tenant/tenantReducer.js +48 -19
- package/lib/entity/tenant/tenantSelector.d.ts +26 -6
- package/lib/entity/tenant/tenantSelector.js +116 -93
- package/lib/entity/tenant/tenantState.d.ts +10 -0
- package/lib/epic.d.ts +2 -1
- package/lib/epic.js +2 -0
- package/lib/esm/entity/tenant/epic/fetchAllTenantsEpic.js +53 -45
- package/lib/esm/entity/tenant/tenantReducer.js +46 -17
- package/lib/esm/entity/tenant/tenantSelector.js +112 -92
- package/lib/esm/epic.js +2 -0
- package/lib/esm/index.js +7 -7
- package/lib/esm/view/aiCfoView/aiCfoViewReducer.js +65 -3
- package/lib/esm/view/aiCfoView/aiCfoViewSelector.js +25 -0
- package/lib/esm/view/aiCfoView/epics/transcribeAudioEpic.js +60 -0
- package/lib/esm/view/dashboard/dashboardEpic.js +0 -4
- package/lib/esm/view/dashboard/dashboardSelector.js +2 -3
- package/lib/esm/view/financeStatement/financeStatementEpic.js +1 -5
- package/lib/esm/view/people/peoplePageEpic.js +0 -4
- package/lib/index.d.ts +8 -8
- package/lib/index.js +56 -46
- package/lib/view/aiCfoView/aiCfoViewPayload.d.ts +20 -0
- package/lib/view/aiCfoView/aiCfoViewReducer.d.ts +2 -2
- package/lib/view/aiCfoView/aiCfoViewReducer.js +66 -4
- package/lib/view/aiCfoView/aiCfoViewSelector.d.ts +8 -1
- package/lib/view/aiCfoView/aiCfoViewSelector.js +27 -0
- package/lib/view/aiCfoView/aiCfoViewState.d.ts +9 -0
- package/lib/view/aiCfoView/epics/transcribeAudioEpic.d.ts +19 -0
- package/lib/view/aiCfoView/epics/transcribeAudioEpic.js +64 -0
- package/lib/view/dashboard/dashboardEpic.js +0 -4
- package/lib/view/dashboard/dashboardSelector.d.ts +0 -2
- package/lib/view/dashboard/dashboardSelector.js +1 -2
- package/lib/view/financeStatement/financeStatementEpic.js +1 -5
- package/lib/view/people/peoplePageEpic.js +0 -4
- package/package.json +1 -1
|
@@ -17,6 +17,7 @@ export const initialState = {
|
|
|
17
17
|
error: undefined,
|
|
18
18
|
accountLocked: false,
|
|
19
19
|
tenantsById: {},
|
|
20
|
+
tenantSummariesById: {},
|
|
20
21
|
onboardingTenantsById: {},
|
|
21
22
|
tenantIds: [],
|
|
22
23
|
signInState: 'Not-Started',
|
|
@@ -107,26 +108,24 @@ const tenant = createSlice({
|
|
|
107
108
|
});
|
|
108
109
|
},
|
|
109
110
|
},
|
|
110
|
-
|
|
111
|
-
const { tenants, user } = action.payload;
|
|
111
|
+
updateTenantSummariesSuccess(draft, action) {
|
|
112
112
|
draft.fetchState = 'Completed';
|
|
113
113
|
draft.error = undefined;
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
114
|
+
const tenantSummariesById = {};
|
|
115
|
+
action.payload.forEach((payload) => {
|
|
116
|
+
tenantSummariesById[payload.tenant_id] =
|
|
117
|
+
mapTenantSummaryPayloadToTenantSummary(payload);
|
|
118
|
+
});
|
|
119
|
+
draft.tenantSummariesById = tenantSummariesById;
|
|
120
|
+
draft.tenantIds = Object.keys(tenantSummariesById);
|
|
121
|
+
// A tenant missing from the list is no longer accessible to the user.
|
|
122
|
+
const accessibleTenantsById = {};
|
|
123
|
+
Object.values(draft.tenantsById).forEach((tenant) => {
|
|
124
|
+
if (tenantSummariesById[tenant.tenantId] != null) {
|
|
125
|
+
accessibleTenantsById[tenant.tenantId] = tenant;
|
|
125
126
|
}
|
|
126
127
|
});
|
|
127
|
-
draft.tenantsById =
|
|
128
|
-
draft.tenantIds = tenantIds;
|
|
129
|
-
draft.loggedInUser = assignWith(draft.loggedInUser ?? {}, firstUser, (objValue, srcValue) => srcValue ?? objValue);
|
|
128
|
+
draft.tenantsById = accessibleTenantsById;
|
|
130
129
|
},
|
|
131
130
|
updateTenantsFailure(draft, action) {
|
|
132
131
|
draft.fetchState = 'Error';
|
|
@@ -134,6 +133,7 @@ const tenant = createSlice({
|
|
|
134
133
|
draft.error = action.payload;
|
|
135
134
|
draft.tenantIds = [];
|
|
136
135
|
draft.tenantsById = {};
|
|
136
|
+
draft.tenantSummariesById = {};
|
|
137
137
|
},
|
|
138
138
|
updateTenantSuccess(draft, action) {
|
|
139
139
|
const { tenants, user } = action.payload;
|
|
@@ -242,6 +242,7 @@ const tenant = createSlice({
|
|
|
242
242
|
draft.signInState = 'Error';
|
|
243
243
|
draft.tenantIds = [];
|
|
244
244
|
draft.tenantsById = {};
|
|
245
|
+
draft.tenantSummariesById = {};
|
|
245
246
|
},
|
|
246
247
|
updateTenantReimbursementInfo(draft, action) {
|
|
247
248
|
const { tenant_id, reimbursement_info } = action.payload;
|
|
@@ -271,6 +272,24 @@ const tenant = createSlice({
|
|
|
271
272
|
updateCurrentTenant(draft, action) {
|
|
272
273
|
draft.currentTenantId = action.payload;
|
|
273
274
|
},
|
|
275
|
+
/**
|
|
276
|
+
* Records a tenant's ids known before the tenants list loads (the app's
|
|
277
|
+
* cached tenant list), so `getCurrentTenantIds` has them from the first
|
|
278
|
+
* render. The list is not changed and its own entries win.
|
|
279
|
+
*/
|
|
280
|
+
seedTenantSummary(draft, action) {
|
|
281
|
+
const { companyId, tenantEmailDomain, tenantId, tenantName } = action.payload;
|
|
282
|
+
if (draft.tenantSummariesById[tenantId] == null) {
|
|
283
|
+
draft.tenantSummariesById[tenantId] = {
|
|
284
|
+
companyId,
|
|
285
|
+
companyName: tenantName ?? '',
|
|
286
|
+
tenantEmailDomain,
|
|
287
|
+
tenantId,
|
|
288
|
+
// Not in `tenantIds`, so it is never ordered or listed.
|
|
289
|
+
userLastCheckInTime: date('1970-01-01'),
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
},
|
|
274
293
|
fetchExcludedResources: {
|
|
275
294
|
prepare(userId, tenantId) {
|
|
276
295
|
return { payload: { userId, tenantId } };
|
|
@@ -934,7 +953,7 @@ const tenant = createSlice({
|
|
|
934
953
|
});
|
|
935
954
|
},
|
|
936
955
|
});
|
|
937
|
-
export const { updateTenants, fetchAllTenants,
|
|
956
|
+
export const { updateTenants, fetchAllTenants, updateTenantSummariesSuccess, seedTenantSummary, updateTenantsFailure, fetchActiveTenant, updateTenantSuccess, updateTenantFailure, updateCurrentTenant, fetchExcludedResources, updateExcludedResourcesSuccess, updateExcludedResourcesFailure, doSignIn, doMagicLinkSignIn, magicLinkSignInSuccess, magicLinkSignInFailure, sendEmailMagicLinkToUser, sendEmailMagicLinkToUserSuccess, sendEmailMagicLinkToUserFailure, updateSignInState, doSignOut, signOutSuccess, sendSessionHeartbeat, sessionHeartbeatSuccess, sessionHeartbeatFailure, updateLoggedInUser, fetchExternalConnections, saveExternalConnection, saveExternalConnectionSuccess, saveExternalConnectionFailure, fetchExternalConnectionsFailure, fetchExternalConnectionsSuccess, saveAPIKeyConnection, saveAPIKeyConnectionSuccess, saveAPIKeyConnectionFailure, saveOAuthConnection, saveOAuthConnectionSuccess, saveOAuthConnectionFailure, saveConnectorCredentials, saveConnectorCredentialsSuccess, saveConnectorCredentialsFailure, initEmailConnectOAuth, initEmailConnectOAuthSuccess, initEmailConnectOAuthFailure, deleteConnection, deleteConnectionSuccess, deleteConnectionFailure, fetchSubscriptionSummaryForTenant, updateSubscriptionSummaryForTenantSuccess, updateSubscriptionSummaryForTenantFailure, updateOnboardingTenants, removeOnboardingTenant, updateTenantReimbursementInfo, updateReferViewedForLoggedInUser, resetSignInState, trigger2FA, updateTenantAccountingClassesEnabled, updateTenantAccountingProjectsEnabled, updateTenantAutoReviewIncomeWithoutCustomer, updateTenantProductSettings, updateTenantCapitalizationOnboardingDismissed, updateTenantCapitalizationAccountOverride, updateTenantMasterTOSInfo, verifyDeviceWithTwoFA, verifyDeviceWithTwoFASuccess, verifyDeviceWithTwoFAFailure, updateTreasuryVideoViewedForLoggedInUser, updateTreasuryPromoRemindMeLaterClickedForLoggedInUser, updateTreasuryPromoIntroClosedByOutsideClickForLoggedInUser, resendVerifyDeviceOTP, resendVerifyDeviceOTPSuccess, resendVerifyDeviceOTPFailure, } = tenant.actions;
|
|
938
957
|
export default tenant.reducer;
|
|
939
958
|
/**
|
|
940
959
|
* Applies treasury promo user fields from the tenants API user block when present.
|
|
@@ -976,6 +995,16 @@ function mapTenantsPayloadToState(tenantsPayload, userTenantPayload) {
|
|
|
976
995
|
const firstUser = mapTenantPayloadToUser(tenantsPayload[0]);
|
|
977
996
|
return [tenantsById, tenantIds, firstUser];
|
|
978
997
|
}
|
|
998
|
+
function mapTenantSummaryPayloadToTenantSummary(payload) {
|
|
999
|
+
return {
|
|
1000
|
+
companyId: payload.company_id,
|
|
1001
|
+
companyName: payload.company_name,
|
|
1002
|
+
tenantEmailDomain: payload.tenant_email_domain,
|
|
1003
|
+
tenantId: payload.tenant_id,
|
|
1004
|
+
// A tenant never opened sorts last, as in `mapTenantPayloadToTenant`.
|
|
1005
|
+
userLastCheckInTime: date(payload.user_last_check_in_time_into_tenant ?? '1970-01-01'),
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
979
1008
|
function mapTenantPayloadToTenant(payload, userTenantPayload) {
|
|
980
1009
|
const tenant = {
|
|
981
1010
|
accountingClassIDs: payload.accounting_class_ids,
|
|
@@ -16,50 +16,75 @@ export function getTenant(tenantState, companyState, tenantId) {
|
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
export function getTenantIdByCompanyId(tenantState, companyId) {
|
|
19
|
-
return Object.values(tenantState.tenantsById).find((tenant) => tenant.companyId === companyId)
|
|
19
|
+
return (Object.values(tenantState.tenantsById).find((tenant) => tenant.companyId === companyId) ??
|
|
20
|
+
Object.values(tenantState.tenantSummariesById).find((tenantSummary) => tenantSummary.companyId === companyId))?.tenantId;
|
|
20
21
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
.map((tenantId) => getTenant(tenantState, companyState, tenantId))
|
|
24
|
-
.filter((value) => value != null);
|
|
25
|
-
const tenantsOrdered = orderBy(tenants, (value) => value.userLastCheckInTime.valueOf(), 'desc');
|
|
26
|
-
return {
|
|
27
|
-
order: 'descending_last_check_in',
|
|
28
|
-
tenantsInOrder: tenantsOrdered,
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
export const getTenantBaseViewForTenantId = createSelector((state) => state.tenantState, (state) => state.companyState, (_state, tenantId) => tenantId, (tenantState, companyState, tenantId) => {
|
|
22
|
+
/** One tenant's entry: its full record when loaded, else its list entry. */
|
|
23
|
+
function getTenantListEntry(tenantState, tenantId) {
|
|
32
24
|
const tenant = tenantState.tenantsById[tenantId];
|
|
33
25
|
if (tenant != null) {
|
|
34
|
-
const companyId = tenant.companyId;
|
|
35
|
-
const companyName = companyState.companiesById[companyId]?.companyInfo.name;
|
|
36
|
-
const tenantName = tenant.companyName ?? companyName;
|
|
37
|
-
const tenantEmailDomain = tenant.tenantEmailDomain;
|
|
38
|
-
const userRole = tenant.userRole;
|
|
39
|
-
const companyLocaleInfo = companyState.companiesById[companyId]?.companyLocaleInfo;
|
|
40
26
|
return {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
tenantEmailDomain,
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
userRole,
|
|
47
|
-
companyLocaleInfo,
|
|
27
|
+
companyId: tenant.companyId,
|
|
28
|
+
companyName: tenant.companyName,
|
|
29
|
+
tenantEmailDomain: tenant.tenantEmailDomain,
|
|
30
|
+
tenantId: tenant.tenantId,
|
|
31
|
+
userLastCheckInTime: tenant.userLastCheckInTime,
|
|
32
|
+
userRole: tenant.userRole,
|
|
48
33
|
};
|
|
49
34
|
}
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
35
|
+
const tenantSummary = tenantState.tenantSummariesById[tenantId];
|
|
36
|
+
return tenantSummary != null ? { ...tenantSummary, userRole: [] } : undefined;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Every tenant in the list, most recently opened first. Roles are known only
|
|
40
|
+
* for tenants whose full record is loaded; the list itself carries none.
|
|
41
|
+
*/
|
|
42
|
+
function getTenantListEntriesByCheckInDate(tenantState) {
|
|
43
|
+
const entries = tenantState.tenantIds
|
|
44
|
+
.map((tenantId) => getTenantListEntry(tenantState, tenantId))
|
|
45
|
+
.filter((entry) => entry != null);
|
|
46
|
+
return orderBy(entries, (entry) => entry.userLastCheckInTime.valueOf(), 'desc');
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The selected tenant's full record. With no tenant selected, or one the user
|
|
50
|
+
* can no longer open, it is the most recently opened loaded tenant. A selected
|
|
51
|
+
* tenant that is in the list but not loaded yet reads as no tenant until its
|
|
52
|
+
* fetch lands, so nothing renders another tenant's record in its place.
|
|
53
|
+
*/
|
|
54
|
+
function getCurrentTenantView(tenantState, companyState) {
|
|
55
|
+
const { currentTenantId } = tenantState;
|
|
55
56
|
if (currentTenantId != null) {
|
|
56
|
-
tenant = getTenant(tenantState, companyState, currentTenantId);
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
tenants.tenantsInOrder.length > 0 ? tenants.tenantsInOrder[0] : undefined;
|
|
57
|
+
const tenant = getTenant(tenantState, companyState, currentTenantId);
|
|
58
|
+
if (tenant != null ||
|
|
59
|
+
tenantState.tenantSummariesById[currentTenantId] != null) {
|
|
60
|
+
return tenant;
|
|
61
|
+
}
|
|
62
62
|
}
|
|
63
|
+
const mostRecentLoadedTenant = getTenantListEntriesByCheckInDate(tenantState).find((entry) => tenantState.tenantsById[entry.tenantId] != null);
|
|
64
|
+
return mostRecentLoadedTenant != null
|
|
65
|
+
? getTenant(tenantState, companyState, mostRecentLoadedTenant.tenantId)
|
|
66
|
+
: undefined;
|
|
67
|
+
}
|
|
68
|
+
/** Whether the tenant's full record is loaded, not just its list entry. */
|
|
69
|
+
export const isTenantLoaded = (state, tenantId) => state.tenantState.tenantsById[tenantId] != null;
|
|
70
|
+
/**
|
|
71
|
+
* Whether the selected tenant's full record is loaded. Screens that act on
|
|
72
|
+
* roles or product settings wait for this rather than for either tenant fetch
|
|
73
|
+
* to complete: the tenants list alone carries neither.
|
|
74
|
+
*/
|
|
75
|
+
export const isCurrentTenantLoaded = (state) => {
|
|
76
|
+
const { currentTenantId } = state.tenantState;
|
|
77
|
+
return currentTenantId != null && isTenantLoaded(state, currentTenantId);
|
|
78
|
+
};
|
|
79
|
+
export const getTenantBaseViewForTenantId = createSelector((state) => state.tenantState, (state) => state.companyState, (_state, tenantId) => tenantId, (tenantState, companyState, tenantId) => {
|
|
80
|
+
const entry = getTenantListEntry(tenantState, tenantId);
|
|
81
|
+
return entry != null
|
|
82
|
+
? getTenantBaseViewForEntry(entry, companyState)
|
|
83
|
+
: undefined;
|
|
84
|
+
});
|
|
85
|
+
const getCurrentTenantHelper = (tenantState, companyState) => {
|
|
86
|
+
const { currentTenantId, excludedResourcesByTenant, subscriptionSummaryByTenant, } = tenantState;
|
|
87
|
+
const tenant = getCurrentTenantView(tenantState, companyState);
|
|
63
88
|
let excludedResources = undefined;
|
|
64
89
|
if (tenant != null) {
|
|
65
90
|
excludedResources = excludedResourcesByTenant[tenant.tenantId];
|
|
@@ -120,9 +145,34 @@ const getCurrentTenantHelper = (tenantState, companyState) => {
|
|
|
120
145
|
export const getCurrentTenant = createSelector((state) => state.tenantState, (state) => state.companyState, (tenantState, companyState) => {
|
|
121
146
|
return getCurrentTenantHelper(tenantState, companyState);
|
|
122
147
|
});
|
|
123
|
-
|
|
124
|
-
|
|
148
|
+
/**
|
|
149
|
+
* The selected tenant's id as soon as it is chosen. Unlike `getCurrentTenant`,
|
|
150
|
+
* it is set before the tenant fetch lands (boot reads it from the cached
|
|
151
|
+
* tenant list).
|
|
152
|
+
*/
|
|
153
|
+
export const getCurrentTenantId = (state) => state.tenantState.currentTenantId;
|
|
154
|
+
const getCurrentTenantIdValues = createSelector((state) => state.tenantState, (state) => state.companyState, (tenantState, companyState) => {
|
|
155
|
+
const tenant = getCurrentTenantView(tenantState, companyState);
|
|
156
|
+
if (tenant != null) {
|
|
157
|
+
return { tenantId: tenant.tenantId, companyId: tenant.companyId };
|
|
158
|
+
}
|
|
159
|
+
const { currentTenantId } = tenantState;
|
|
160
|
+
const tenantSummary = currentTenantId != null
|
|
161
|
+
? tenantState.tenantSummariesById[currentTenantId]
|
|
162
|
+
: undefined;
|
|
163
|
+
return {
|
|
164
|
+
tenantId: tenantSummary?.tenantId,
|
|
165
|
+
companyId: tenantSummary?.companyId,
|
|
166
|
+
};
|
|
125
167
|
});
|
|
168
|
+
/**
|
|
169
|
+
* The current tenant's ids before its record loads: from the record when
|
|
170
|
+
* loaded, otherwise from the tenants list or the entry seeded from the app's
|
|
171
|
+
* cache (`seedTenantSummary`). Providers keyed on them (Statsig, collaboration)
|
|
172
|
+
* then mount once with the final ids, on a reload and on a tenant switch.
|
|
173
|
+
* The result keeps its identity while the ids are unchanged.
|
|
174
|
+
*/
|
|
175
|
+
export const getCurrentTenantIds = createSelector((state) => getCurrentTenantIdValues(state).tenantId, (state) => getCurrentTenantIdValues(state).companyId, (tenantId, companyId) => ({ tenantId, companyId }));
|
|
126
176
|
export function getOnboardingTenantByTenantId(tenantState, tenantId) {
|
|
127
177
|
const tenant = tenantState.onboardingTenantsById[tenantId];
|
|
128
178
|
if (tenant != null) {
|
|
@@ -133,10 +183,7 @@ export function getOnboardingTenantByTenantId(tenantState, tenantId) {
|
|
|
133
183
|
}
|
|
134
184
|
}
|
|
135
185
|
export const getTenantBaseById = createSelector((state) => state.tenantState, (state) => state.companyState, (tenantState, companyState) => {
|
|
136
|
-
return tenantState.
|
|
137
|
-
const tenant = tenantState.tenantsById[tenantId];
|
|
138
|
-
return getTenantBaseViewForTenant(tenant, companyState);
|
|
139
|
-
});
|
|
186
|
+
return getTenantListEntriesByCheckInDate(tenantState).map((entry) => getTenantBaseViewForEntry(entry, companyState));
|
|
140
187
|
});
|
|
141
188
|
export const getTenantsBaseByCheckInDateSelector = createSelector((state) => state.tenantState, (state) => state.companyState, (tenantState, companyState) => {
|
|
142
189
|
return getTenantsBaseByCheckInDate(tenantState, companyState);
|
|
@@ -165,16 +212,8 @@ export const isTenantUsingZeniCOA = (currentTenant) => {
|
|
|
165
212
|
return currentTenant.company?.companyInfo.isZeniCOAInUse ?? true; // defaulting to true
|
|
166
213
|
};
|
|
167
214
|
export const getCurrentTenantExternalConnections = (tenantState, companyState) => {
|
|
168
|
-
const { externalConnectionsByTenant,
|
|
169
|
-
|
|
170
|
-
if (currentTenantId != null) {
|
|
171
|
-
tenant = getTenant(tenantState, companyState, currentTenantId);
|
|
172
|
-
}
|
|
173
|
-
if (tenant == null && tenantIds.length > 0) {
|
|
174
|
-
const tenants = getTenantsByCheckInDate(tenantState, companyState);
|
|
175
|
-
tenant =
|
|
176
|
-
tenants.tenantsInOrder.length > 0 ? tenants.tenantsInOrder[0] : undefined;
|
|
177
|
-
}
|
|
215
|
+
const { externalConnectionsByTenant, currentTenantId } = tenantState;
|
|
216
|
+
const tenant = getCurrentTenantView(tenantState, companyState);
|
|
178
217
|
let externalConnections = undefined;
|
|
179
218
|
if (tenant != null) {
|
|
180
219
|
externalConnections = externalConnectionsByTenant[tenant.tenantId];
|
|
@@ -188,58 +227,39 @@ export const getCurrentTenantExternalConnections = (tenantState, companyState) =
|
|
|
188
227
|
* Helpers
|
|
189
228
|
*/
|
|
190
229
|
function getTenantsBaseByCheckInDate(tenantState, companyState) {
|
|
191
|
-
const tenants = tenantState.tenantIds
|
|
192
|
-
.map((tenantId) => getTenant(tenantState, companyState, tenantId))
|
|
193
|
-
.filter((value) => value != null);
|
|
194
|
-
const tenantsOrdered = orderBy(tenants, (value) => value.userLastCheckInTime.valueOf(), 'desc');
|
|
195
|
-
const tenantsBaseOrdered = tenantsOrdered.map((tenant) => getTenantBaseViewForTenant(tenant, companyState));
|
|
196
230
|
return {
|
|
197
231
|
order: 'descending_last_check_in',
|
|
198
|
-
tenantsBaseInOrder:
|
|
232
|
+
tenantsBaseInOrder: getTenantListEntriesByCheckInDate(tenantState).map((entry) => getTenantBaseViewForEntry(entry, companyState)),
|
|
199
233
|
};
|
|
200
234
|
}
|
|
201
|
-
const
|
|
202
|
-
const
|
|
203
|
-
const companyId = tenant.companyId;
|
|
204
|
-
const companyName = companyState.companiesById[companyId]?.companyInfo.name;
|
|
205
|
-
const tenantName = tenant.companyName ?? companyName ?? tenant.tenantEmailDomain ?? '';
|
|
206
|
-
const tenantEmailDomain = tenant.tenantEmailDomain;
|
|
207
|
-
const userRole = tenant.userRole;
|
|
208
|
-
const companyLocaleInfo = companyState.companiesById[companyId]?.companyLocaleInfo;
|
|
235
|
+
const getTenantBaseViewForEntry = (entry, companyState) => {
|
|
236
|
+
const company = companyState.companiesById[entry.companyId];
|
|
209
237
|
return {
|
|
210
|
-
tenantId,
|
|
211
|
-
tenantName
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
238
|
+
tenantId: entry.tenantId,
|
|
239
|
+
tenantName: entry.companyName ??
|
|
240
|
+
company?.companyInfo.name ??
|
|
241
|
+
entry.tenantEmailDomain ??
|
|
242
|
+
'',
|
|
243
|
+
tenantEmailDomain: entry.tenantEmailDomain,
|
|
244
|
+
companyId: entry.companyId,
|
|
245
|
+
// Companies are loaded only for opened tenants; the list carries the name.
|
|
246
|
+
companyName: company?.companyInfo.name ?? entry.companyName,
|
|
247
|
+
userRole: entry.userRole,
|
|
248
|
+
companyLocaleInfo: company?.companyLocaleInfo,
|
|
217
249
|
};
|
|
218
250
|
};
|
|
219
251
|
function getTenantCoreDetailsByCheckInDate(tenantState, companyState) {
|
|
220
|
-
const tenants = tenantState.tenantIds
|
|
221
|
-
.map((tenantId) => getTenant(tenantState, companyState, tenantId))
|
|
222
|
-
.filter((value) => value != null);
|
|
223
|
-
const tenantsOrdered = orderBy(tenants, (value) => value.userLastCheckInTime.valueOf(), 'desc');
|
|
224
|
-
const tenantsCoreDetailsOrdered = tenantsOrdered.map((tenant) => getTenantCoreDetailsViewForTenant(tenant, companyState));
|
|
225
252
|
return {
|
|
226
253
|
order: 'descending_last_check_in',
|
|
227
|
-
tenantsCoreDetailsInOrder:
|
|
254
|
+
tenantsCoreDetailsInOrder: getTenantListEntriesByCheckInDate(tenantState).map((entry) => ({
|
|
255
|
+
tenantId: entry.tenantId,
|
|
256
|
+
tenantName: entry.companyName ??
|
|
257
|
+
companyState.companiesById[entry.companyId]?.companyInfo.name,
|
|
258
|
+
tenantEmailDomain: entry.tenantEmailDomain,
|
|
259
|
+
companyId: entry.companyId,
|
|
260
|
+
})),
|
|
228
261
|
};
|
|
229
262
|
}
|
|
230
|
-
const getTenantCoreDetailsViewForTenant = (tenant, companyState) => {
|
|
231
|
-
const tenantId = tenant.tenantId;
|
|
232
|
-
const companyId = tenant.companyId;
|
|
233
|
-
const companyName = companyState.companiesById[companyId]?.companyInfo.name;
|
|
234
|
-
const tenantName = tenant.companyName ?? companyName;
|
|
235
|
-
const tenantEmailDomain = tenant.tenantEmailDomain;
|
|
236
|
-
return {
|
|
237
|
-
tenantId,
|
|
238
|
-
tenantName,
|
|
239
|
-
tenantEmailDomain,
|
|
240
|
-
companyId,
|
|
241
|
-
};
|
|
242
|
-
};
|
|
243
263
|
export const toTenantCoreDetailsView = (tenantsBaseView) => {
|
|
244
264
|
return tenantsBaseView.map(({ tenantId, tenantName, tenantEmailDomain, companyId }) => {
|
|
245
265
|
return {
|
package/lib/esm/epic.js
CHANGED
|
@@ -70,6 +70,7 @@ import { stopSubmitEpic, } from './view/aiCfoView/epics/stopSubmitEpic';
|
|
|
70
70
|
import { stopSubmitQuestionEpic, } from './view/aiCfoView/epics/stopSubmitQuestionEpic';
|
|
71
71
|
import { submitFeedbackEpic, } from './view/aiCfoView/epics/submitFeedbackEpic';
|
|
72
72
|
import { submitQuestionEpic, } from './view/aiCfoView/epics/submitQuestionEpic';
|
|
73
|
+
import { transcribeAudioEpic, } from './view/aiCfoView/epics/transcribeAudioEpic';
|
|
73
74
|
import { fetchApAgingDetailEpic, } from './view/apAgingView/apAgingDetail/fetchApAgingDetailEpic';
|
|
74
75
|
import { fetchApAgingEpic, } from './view/apAgingView/apAgingReport/fetchApAgingEpic';
|
|
75
76
|
import { fetchArAgingDetailEpic, } from './view/arAgingView/arAgingDetail/fetchArAgingDetailEpic';
|
|
@@ -1484,6 +1485,7 @@ const ALL_EPICS = [
|
|
|
1484
1485
|
syncInvoicingSubscriptionCouponsEpic,
|
|
1485
1486
|
syncTabsAfterAutomatchEpic,
|
|
1486
1487
|
toggleReportUIOptionForecastModeEpic,
|
|
1488
|
+
transcribeAudioEpic,
|
|
1487
1489
|
transferMoneyEpic,
|
|
1488
1490
|
treasuryTransferMoneyEpic,
|
|
1489
1491
|
triggerAiAccountantJobEpic,
|
package/lib/esm/index.js
CHANGED
|
@@ -72,8 +72,8 @@ import { closeSnackbar, openSnackbar } from './entity/snackbar/snackbarReducer';
|
|
|
72
72
|
import { getSnackbar } from './entity/snackbar/snackbarSelector';
|
|
73
73
|
import { ALL_WEEK_DAYS, toDayOfWeek, toPriorityCodeType, toTaskStatusCodeType, } from './entity/task/taskState';
|
|
74
74
|
import { getTaskGroupById } from './entity/taskGroup/taskGroupSelector';
|
|
75
|
-
import { deleteConnection, doMagicLinkSignIn, doSignIn, doSignOut, fetchActiveTenant, fetchAllTenants, fetchExcludedResources as fetchExcludedResourcesForTenant, fetchExternalConnections as fetchExternalConnectionsForTenant, fetchSubscriptionSummaryForTenant, initEmailConnectOAuth, resendVerifyDeviceOTP, resetSignInState, saveAPIKeyConnection, saveConnectorCredentials, saveExternalConnection as saveExternalConnectionForTenant, saveOAuthConnection, sendEmailMagicLinkToUser, sendSessionHeartbeat, toExternalIntegrationType, updateCurrentTenant, updateSignInState, verifyDeviceWithTwoFA, } from './entity/tenant/tenantReducer';
|
|
76
|
-
import { DEFAULT_RECEIPTS_EMAIL, getCurrentTenant, getIsAccountingClassesEnabled, getIsAccountingProjectsEnabled, getIsAutoReviewIncomeWithoutCustomerEnabled, getReceiptsEmail, getTenantBaseById, getTenantBaseViewForTenantId, getTenantBaseViewForTenantView, getTenantsBaseByCheckInDateSelector,
|
|
75
|
+
import { deleteConnection, doMagicLinkSignIn, doSignIn, doSignOut, fetchActiveTenant, fetchAllTenants, fetchExcludedResources as fetchExcludedResourcesForTenant, fetchExternalConnections as fetchExternalConnectionsForTenant, fetchSubscriptionSummaryForTenant, initEmailConnectOAuth, resendVerifyDeviceOTP, resetSignInState, saveAPIKeyConnection, saveConnectorCredentials, saveExternalConnection as saveExternalConnectionForTenant, saveOAuthConnection, seedTenantSummary, sendEmailMagicLinkToUser, sendSessionHeartbeat, toExternalIntegrationType, updateCurrentTenant, updateSignInState, verifyDeviceWithTwoFA, } from './entity/tenant/tenantReducer';
|
|
76
|
+
import { DEFAULT_RECEIPTS_EMAIL, getCurrentTenant, getCurrentTenantId, getCurrentTenantIds, getIsAccountingClassesEnabled, getIsAccountingProjectsEnabled, getIsAutoReviewIncomeWithoutCustomerEnabled, getReceiptsEmail, getTenantBaseById, getTenantBaseViewForTenantId, getTenantBaseViewForTenantView, getTenantsBaseByCheckInDateSelector, getTenantsCoreDetailsByCheckInDateSelector, isCurrentTenantLoaded, isOtherProductsEnabledForTenant, isTenantBankingOnly, isTenantBookkeepingEnabled, isTenantCardsOnly, isTenantLoaded, isTenantUsingZeniCOA, isZeniDomainTenant, toTenantCoreDetailsView, } from './entity/tenant/tenantSelector';
|
|
77
77
|
import { toExternalSupportedTool } from './entity/tenant/tenantTypes';
|
|
78
78
|
import { pushToastNotification } from './entity/toastNotification/toastNotificationReducer';
|
|
79
79
|
import { getLastNotificationTime, getNotifications, } from './entity/toastNotification/toastNotificationSelector';
|
|
@@ -332,7 +332,7 @@ import { clearInternationalWire, createPaymentInstrument, createPaymentInstrumen
|
|
|
332
332
|
import { getInternationalWireView, } from './view/spendManagement/billPay/internationalWire/internationalWireSelector';
|
|
333
333
|
import { fetchInternationalVerificationForm, submitInternationalVerificationForm, updateVerificationFormLocalData, } from './view/spendManagement/billPay/internationalWireVerification/internationalWireVerificationReducer';
|
|
334
334
|
import { getIntlWireVerificationView, } from './view/spendManagement/billPay/internationalWireVerification/internationalWireVerificationSelector';
|
|
335
|
-
import { fetchBillAttachment, fetchMagicLinkBankNameByRouting, fetchMagicLinkBankNameBySwift, fetchMagicLinkTenant, saveBankAccount, saveMagicLinkAddressInLocalStore, updateMagicLinkBankAccountLocalStoreData, updateMagicLinkInternationalBankAccountLocalStoreData, } from './view/spendManagement/billPay/magicLinkView/magicLinkViewReducer';
|
|
335
|
+
import { fetchBillAttachment, fetchMagicLinkBankNameByRouting, fetchMagicLinkBankNameBySwift, fetchMagicLinkTenant, saveBankAccount, saveMagicLinkAddressInLocalStore, updateMagicLinkBankAccountLocalStoreData, updateMagicLinkInternationalBankAccountLocalStoreData, updateMagicLinkTenantFailure, updateMagicLinkTenantSuccess, } from './view/spendManagement/billPay/magicLinkView/magicLinkViewReducer';
|
|
336
336
|
import { getMagicLinkBankAccountView, getMagicLinkCurrentAddressState, getMagicLinkView, } from './view/spendManagement/billPay/magicLinkView/magicLinkViewSelector';
|
|
337
337
|
import { fetchWiseRedirectStatus } from './view/spendManagement/billPay/wiseRedirectView/wiseRedirectReducer';
|
|
338
338
|
import { getWiseRedirect } from './view/spendManagement/billPay/wiseRedirectView/wiseRedirectSelector';
|
|
@@ -509,7 +509,7 @@ export { toTimeframeTick, mapTimePeriodtoTimeframeTick, toTimePeriod, toAbsolute
|
|
|
509
509
|
export { toVendorSpendTrendFilterTabsTypeStrict, } from './entity/vendorExpense/vendorSpendTrendFilterTabType';
|
|
510
510
|
export { getNumberOfPeriods };
|
|
511
511
|
export { SCHEDULE_DAYS_OF_MONTH, toScheduleDaysOfMonth, toMonth, toMonthStrict, toQuarter, toQuarterStrict, };
|
|
512
|
-
export { getTenantBaseById, getCurrentTenant, getIsAccountingClassesEnabled, getIsAccountingProjectsEnabled, getIsAutoReviewIncomeWithoutCustomerEnabled,
|
|
512
|
+
export { getTenantBaseById, getCurrentTenant, getCurrentTenantId, getCurrentTenantIds, isCurrentTenantLoaded, isTenantLoaded, getIsAccountingClassesEnabled, getIsAccountingProjectsEnabled, getIsAutoReviewIncomeWithoutCustomerEnabled, getTenantsBaseByCheckInDateSelector, getTenantsCoreDetailsByCheckInDateSelector, toTenantCoreDetailsView, getTenantBaseViewForTenantView, getTenantBaseViewForTenantId, isZeniDomainTenant, isTenantUsingZeniCOA, fetchActiveTenant, fetchAllTenants, seedTenantSummary, updateCurrentTenant, clearAll, doMagicLinkSignIn, verifyDeviceWithTwoFA, resendVerifyDeviceOTP, sendEmailMagicLinkToUser, doSignIn, updateSignInState, doSignOut, sendSessionHeartbeat, resetSignInState, fetchExcludedResourcesForTenant, fetchExternalConnectionsForTenant, saveExternalConnectionForTenant, deleteConnection, saveAPIKeyConnection, saveConnectorCredentials, saveOAuthConnection, initEmailConnectOAuth, toExternalIntegrationType, toExternalSupportedTool, fetchSubscriptionSummaryForTenant, isTenantBankingOnly, isTenantCardsOnly, isTenantBookkeepingEnabled, isOtherProductsEnabledForTenant, DEFAULT_RECEIPTS_EMAIL, getReceiptsEmail, };
|
|
513
513
|
export { toAccountType, toAccountGroupType, getAccountGroupKey, getAllAccounts, getTransactionFilterAccountOptions, };
|
|
514
514
|
export { getAllClasses, getClassById, getClassFilterOptions, } from './entity/class/classSelector';
|
|
515
515
|
export { getForecast };
|
|
@@ -661,7 +661,7 @@ export { isZeniClearingAccountReport, ZENI_CLEARING_ACCOUNT, isZeniClearingAccou
|
|
|
661
661
|
export { fetchOnboardingCustomerView, fetchOnboardingCustomerSetupView, getOnboardingPlaidLinkToken, establishOnboardingPlaidConnection, updateOnboardingCustomerViewAccountDetails, updateOnboardingCustomerViewCompleteStatus, updateOnboardingCustomerViewDashboardLoaded, updateOnboardingPaymentAccountLoginStatus, updateOnboardingPaymentAccountStatus, updateOnboardingCustomerView, updateOnboardingCustomerViewLocalStoreData, updateCurrentStep, updateCurrentSubStep, updateOnboardingCustomerViewUIState, saveOnboardingCompnayOfficerPhoneInLocalStore, saveOnboardingCustomerViewDataInLocalStore, clearOnboardingCustomerViewDataInLocalStore, applyKycDocumentAutofillForOnboarding, applyKybDocumentAutofillForOnboarding, clearKycKybAutofillForOnboarding, acknowledgeOnboardingAiActivationViewed, acknowledgeOnboardingAiFinanceTeam, fetchAiAgentsActivationStatus, aiAgentsActivationCountsUpdated, aiAgentsActivationPhaseChanged, getAiAgentsActivationCounts, getAiAgentsActivationPhases, getAiAgentsActivationSubview, getOnboardingCustomerView, getProductSettingsString, ALL_ONBOARDING_STEPS, ALL_ONBOARDING_SUB_STEPS, toOnboardingStepType, toOnboardingSubStepType, };
|
|
662
662
|
export { saveAccountMapping, saveAccountMappingLocalData, clearAccountMappingLocalData, getAccountMappingView, initializeAccountMappingView, };
|
|
663
663
|
export { getMagicLinkView, getMagicLinkBankAccountView, getMagicLinkCurrentAddressState, };
|
|
664
|
-
export { fetchMagicLinkTenant, fetchBillAttachment, saveBankAccount, updateMagicLinkBankAccountLocalStoreData, updateMagicLinkInternationalBankAccountLocalStoreData, fetchMagicLinkBankNameByRouting, fetchMagicLinkBankNameBySwift, saveMagicLinkAddressInLocalStore, };
|
|
664
|
+
export { fetchMagicLinkTenant, updateMagicLinkTenantSuccess, updateMagicLinkTenantFailure, fetchBillAttachment, saveBankAccount, updateMagicLinkBankAccountLocalStoreData, updateMagicLinkInternationalBankAccountLocalStoreData, fetchMagicLinkBankNameByRouting, fetchMagicLinkBankNameBySwift, saveMagicLinkAddressInLocalStore, };
|
|
665
665
|
export { getWiseRedirect, fetchWiseRedirectStatus };
|
|
666
666
|
export { getInternationalWireView, initializeInternationalWireLocalData, updateInternationalWireLocalStoreData, initializeDynamicForm, fetchInternationalWireDynamicForm, createPaymentInstrument, createPaymentInstrumentUpdateStatus, deletePaymentInstrument, clearInternationalWire, };
|
|
667
667
|
export { fetchUserRoleConfig, getUserRoleConfig };
|
|
@@ -705,8 +705,8 @@ export { updateAiAccountantCustomers, updateAiAccountantCustomer, updateAiAccoun
|
|
|
705
705
|
export { getAiAccountantCustomers, getAiAccountantJobsByTenantId, } from './entity/aiAccountantCustomer/aiAccountantCustomerSelector';
|
|
706
706
|
export { cancelAiAccountantOnboarding, fetchAiAccountantCustomers, fetchAiAccountantJobs, setSelectedTenantIdsForJobTrigger, triggerAiAccountantJob, updateAiAccountantUIState, } from './view/aiAccountantView/aiAccountantViewReducer';
|
|
707
707
|
export { getAiAccountantCockpitView, } from './view/aiAccountantView/aiAccountantViewSelector';
|
|
708
|
-
export { acceptMasterTOS, applyAiCfoSidePanelHostPageTransition, clearAiCfoSidePanelHostPageContext, clearCurrentSessionId, clearDeleteChatSessionStatus, clearLastContextMessage, clearSkillDetail, clearSkillMutation, closeRoutineRuns, closeRoutinesBrowser, closeSkillsBrowser, createSession, createSessionAndSubmit, deleteChatSession, deleteRoutine, deleteSkill, fetchChatHistory, fetchChatSessionsForUser, fetchRoutineRuns, fetchRoutines, fetchSkillDetail, fetchSkills, fetchSkillsFailure, fetchSkillsSuccess, fetchSkillVersionBody, fetchSkillVersions, fetchSuggestedQuestions, fetchSuggestedQuestionsFailure, fetchSuggestedQuestionsSuccess, openRoutinesBrowser, openSkillsBrowser, runRoutine, saveRoutine, saveSkill, setRoutineEnabled, setSession, setSkillVisibility, stopSubmit, stopSubmitQuestion, submitFeedback, submitQuestion, updateAiCfoViewScrollPosition, updateCotCollapsedState, updateCurrentInput, updateResponseState, } from './view/aiCfoView/aiCfoViewReducer';
|
|
709
|
-
export { canCreateRoutine, getAiCfoView, getRoutineMutation, getRoutineRuns, getRoutines, getRoutinesEnabled, getSkillDetail, getSkillMutation, getSkills, getSkillsEnabled, getSkillVersionBody, getSkillVersions, getSuggestedQuestionsForPageContext, } from './view/aiCfoView/aiCfoViewSelector';
|
|
708
|
+
export { acceptMasterTOS, applyAiCfoSidePanelHostPageTransition, clearAiCfoSidePanelHostPageContext, clearCurrentSessionId, clearDeleteChatSessionStatus, clearLastContextMessage, clearSkillDetail, clearSkillMutation, closeRoutineRuns, closeRoutinesBrowser, closeSkillsBrowser, createSession, createSessionAndSubmit, deleteChatSession, deleteRoutine, deleteSkill, fetchChatHistory, fetchChatSessionsForUser, fetchRoutineRuns, fetchRoutines, fetchSkillDetail, fetchSkills, fetchSkillsFailure, fetchSkillsSuccess, fetchSkillVersionBody, fetchSkillVersions, fetchSuggestedQuestions, fetchSuggestedQuestionsFailure, fetchSuggestedQuestionsSuccess, openRoutinesBrowser, openSkillsBrowser, runRoutine, saveRoutine, saveSkill, setRoutineEnabled, setSession, setSkillVisibility, stopSubmit, stopSubmitQuestion, submitFeedback, submitQuestion, transcribeAudio, updateAiCfoViewScrollPosition, updateCotCollapsedState, updateCurrentInput, updateResponseState, } from './view/aiCfoView/aiCfoViewReducer';
|
|
709
|
+
export { canCreateRoutine, getAiCfoView, getIsTranscribing, getRoutineMutation, getRoutineRuns, getRoutines, getRoutinesEnabled, getSkillDetail, getSkillMutation, getSkills, getSkillsEnabled, getSkillVersionBody, getSkillVersions, getSuggestedQuestionsForPageContext, getTranscriptionFailed, } from './view/aiCfoView/aiCfoViewSelector';
|
|
710
710
|
export { setNewSession, setSessions, updateAiCfoAnswerCardPolicyWizardPlan, upsertAnswerPayload, upsertOrAddQuestionAnswerPayload, addQuestionPayload, clearSession, toAiCfoExports, toAiCfoVisualization, appendSyntheticAiCfoAnswer, updateSyntheticAiCfoAnswer, removeSyntheticAiCfoAnswer, } from './entity/aiCfo/aiCfoReducer';
|
|
711
711
|
export { getAllQuestionAnswersForChatSession, getQuestionAnswerByIdForChatSession, getAllQuestionsForChatSession, getAiCfoSelectorView, getSyntheticAiCfoAnswersForChatSession, getSyntheticAiCfoAnswerByQuestionAnswerId, } from './entity/aiCfo/aiCfoSelector';
|
|
712
712
|
export { ALL_AI_CFO_CHARTS_TYPES, ALL_AI_CFO_VISUALIZATION_TYPES, ALL_AI_CFO_ANSWER_STATE_TYPES, ALL_AI_CFO_ANSWER_RESPONSE_TYPES, ALL_INTERACTIVE_FORM_TYPES, toAiCfoVisualizationType, toAiCfoVisualizationTypeStrict, toAiCfoChartType, toAiCfoChartTypeStrict, toAiCfoAnswerStateType, toAiCfoAnswerStateTypeStrict, toAiCfoAnswerResponseType, toAiCfoAnswerResponseTypeStrict, toInteractiveFormType, toInteractiveFormTypeStrict, ALL_SYNTHETIC_AI_CFO_ANSWER_KINDS, toMessageSender, toMessageType, } from './entity/aiCfo/aiCfoState';
|
|
@@ -47,6 +47,8 @@ export const initialAiCfoViewState = {
|
|
|
47
47
|
fetchChatSessionHistoryByChatSessionId: {},
|
|
48
48
|
responseStateByChatSessionId: {},
|
|
49
49
|
stopSubmitStateByChatSessionId: {},
|
|
50
|
+
transcriptionState: { fetchState: 'Not-Started', error: undefined },
|
|
51
|
+
transcribingChatSessionId: undefined,
|
|
50
52
|
suggestedQuestionsByPageContext: {},
|
|
51
53
|
skillDetail: {
|
|
52
54
|
fetchState: 'Not-Started',
|
|
@@ -147,6 +149,19 @@ function closeSkillsBrowserState(draft) {
|
|
|
147
149
|
draft.uiState.isSkillsBrowserOpen = false;
|
|
148
150
|
clearOpenSkillState(draft);
|
|
149
151
|
}
|
|
152
|
+
/**
|
|
153
|
+
* Clear a finished dictation attempt when the conversation moves on, so a failed one
|
|
154
|
+
* stops showing "Could not transcribe that recording" in every later chat. A running
|
|
155
|
+
* take keeps its state — the composer is showing a live "Transcribing…" for it.
|
|
156
|
+
*/
|
|
157
|
+
function resetFinishedTranscription(draft) {
|
|
158
|
+
if (draft.transcriptionState.fetchState === 'In-Progress') {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
draft.transcriptionState.fetchState = 'Not-Started';
|
|
162
|
+
draft.transcriptionState.error = undefined;
|
|
163
|
+
draft.transcribingChatSessionId = undefined;
|
|
164
|
+
}
|
|
150
165
|
const aiCfoView = createSlice({
|
|
151
166
|
name: 'aiCfoView',
|
|
152
167
|
initialState: initialAiCfoViewState,
|
|
@@ -154,6 +169,7 @@ const aiCfoView = createSlice({
|
|
|
154
169
|
submitQuestion: {
|
|
155
170
|
reducer(draft, action) {
|
|
156
171
|
const { sessionId } = action.payload;
|
|
172
|
+
resetFinishedTranscription(draft);
|
|
157
173
|
// A turn is being sent, so the chat body has to be what the user sees. Doing this
|
|
158
174
|
// here rather than at each call site is deliberate: the composer, the sample
|
|
159
175
|
// chips, Run, and the `/` menu all submit, and every one of them was a separate
|
|
@@ -299,6 +315,7 @@ const aiCfoView = createSlice({
|
|
|
299
315
|
setSession(draft, action) {
|
|
300
316
|
draft.uiState.currentChatSessionId = action.payload.sessionId;
|
|
301
317
|
draft.uiState.agentId = action.payload.agentId;
|
|
318
|
+
resetFinishedTranscription(draft);
|
|
302
319
|
// Picking a session means wanting to see that chat, so the browser cannot stay over
|
|
303
320
|
// it. In the reducer because there are six dispatch sites across three separate
|
|
304
321
|
// rails, and covering them one at a time missed half of them.
|
|
@@ -351,6 +368,7 @@ const aiCfoView = createSlice({
|
|
|
351
368
|
createSessionAndSubmit: {
|
|
352
369
|
reducer(draft, action) {
|
|
353
370
|
draft.uiState.agentId = action.payload.agentId;
|
|
371
|
+
resetFinishedTranscription(draft);
|
|
354
372
|
draft.createSessionAndSubmitState.fetchState = 'In-Progress';
|
|
355
373
|
draft.createSessionAndSubmitState.error = undefined;
|
|
356
374
|
},
|
|
@@ -551,7 +569,49 @@ const aiCfoView = createSlice({
|
|
|
551
569
|
return { payload: { chatMessageId, error } };
|
|
552
570
|
},
|
|
553
571
|
},
|
|
572
|
+
transcribeAudio: {
|
|
573
|
+
reducer(draft, action) {
|
|
574
|
+
draft.transcriptionState.fetchState = 'In-Progress';
|
|
575
|
+
draft.transcriptionState.error = undefined;
|
|
576
|
+
draft.transcribingChatSessionId = action.payload.chatSessionId;
|
|
577
|
+
},
|
|
578
|
+
prepare(audio, language, chatSessionId) {
|
|
579
|
+
return { payload: { audio, chatSessionId, language } };
|
|
580
|
+
},
|
|
581
|
+
},
|
|
582
|
+
transcribeAudioSuccess: {
|
|
583
|
+
reducer(draft, action) {
|
|
584
|
+
draft.transcriptionState.fetchState = 'Completed';
|
|
585
|
+
draft.transcriptionState.error = undefined;
|
|
586
|
+
// The user moved to another session while this was in flight — dropping it
|
|
587
|
+
// is the only safe option, since appending would put their words in a
|
|
588
|
+
// conversation they did not dictate into.
|
|
589
|
+
if (action.payload.chatSessionId !== draft.uiState.currentChatSessionId) {
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
const transcript = action.payload.transcript.trim();
|
|
593
|
+
if (transcript === '') {
|
|
594
|
+
// Silence, not a failure: leave the composer untouched so the user can
|
|
595
|
+
// simply speak again rather than hunting for what went wrong.
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
// Append to whatever is in the box now rather than to a snapshot taken when
|
|
599
|
+
// recording began — a take produces exactly one result, so there is nothing
|
|
600
|
+
// to reconcile, and anything typed while transcribing survives.
|
|
601
|
+
const existing = draft.uiState.currentInput.trim();
|
|
602
|
+
draft.uiState.currentInput =
|
|
603
|
+
existing === '' ? transcript : `${existing} ${transcript}`;
|
|
604
|
+
},
|
|
605
|
+
prepare(transcript, chatSessionId) {
|
|
606
|
+
return { payload: { chatSessionId, transcript } };
|
|
607
|
+
},
|
|
608
|
+
},
|
|
609
|
+
transcribeAudioFailure(draft, action) {
|
|
610
|
+
draft.transcriptionState.fetchState = 'Error';
|
|
611
|
+
draft.transcriptionState.error = action.payload;
|
|
612
|
+
},
|
|
554
613
|
clearCurrentSessionId(draft) {
|
|
614
|
+
resetFinishedTranscription(draft);
|
|
555
615
|
// New Chat: the empty composer is the point, so the browser cannot stay over it.
|
|
556
616
|
if (draft.uiState.isSkillsBrowserOpen === true) {
|
|
557
617
|
closeSkillsBrowserState(draft);
|
|
@@ -835,7 +895,8 @@ const aiCfoView = createSlice({
|
|
|
835
895
|
// The server has it. Nothing it sends from here on will contain this row,
|
|
836
896
|
// so the tombstone has done its job and holding it any longer is what
|
|
837
897
|
// makes a routine recreated under the same id invisible forever.
|
|
838
|
-
draft.routines.deletedScheduleIds =
|
|
898
|
+
draft.routines.deletedScheduleIds =
|
|
899
|
+
draft.routines.deletedScheduleIds.filter((id) => id !== action.payload.scheduleId);
|
|
839
900
|
draft.routineMutation = {
|
|
840
901
|
fetchState: 'Completed',
|
|
841
902
|
error: undefined,
|
|
@@ -847,7 +908,8 @@ const aiCfoView = createSlice({
|
|
|
847
908
|
// is what lets the refetch put the row back — leaving it hidden would
|
|
848
909
|
// tell the user they had stopped something they had not, which is the
|
|
849
910
|
// worst outcome this list can produce.
|
|
850
|
-
draft.routines.deletedScheduleIds =
|
|
911
|
+
draft.routines.deletedScheduleIds =
|
|
912
|
+
draft.routines.deletedScheduleIds.filter((id) => id !== action.payload.scheduleId);
|
|
851
913
|
// Its own slice: the refetch that puts the row back would otherwise wipe
|
|
852
914
|
// this error, leaving the row silently reappearing with no explanation.
|
|
853
915
|
draft.routineMutation = {
|
|
@@ -1183,5 +1245,5 @@ const aiCfoView = createSlice({
|
|
|
1183
1245
|
});
|
|
1184
1246
|
},
|
|
1185
1247
|
});
|
|
1186
|
-
export const { acceptMasterTOS, acceptMasterTOSFailure, acceptMasterTOSSuccess, applyAiCfoSidePanelHostPageTransition, clearAiCfoSidePanelHostPageContext, clearCurrentSessionId, clearDeleteChatSessionStatus, clearLastContextMessage, clearSkillDetail, clearSkillMutation, closeRoutineRuns, closeRoutinesBrowser, closeSkillsBrowser, createSession, createSessionAndSubmit, createSessionAndSubmitFailure, createSessionAndSubmitSuccess, createSessionFailure, createSessionSuccess, deleteChatSession, deleteChatSessionFailure, deleteChatSessionSuccess, deleteRoutine, deleteRoutineFailure, deleteRoutineSuccess, deleteSkill, deleteSkillFailure, deleteSkillSuccess, fetchChatHistory, fetchChatHistoryFailure, fetchChatHistorySuccess, fetchChatSessionsForUser, fetchChatSessionsForUserFailure, fetchChatSessionsForUserSuccess, fetchRoutineRuns, fetchRoutineRunsFailure, fetchRoutineRunsSuccess, fetchRoutines, fetchRoutinesFailure, fetchRoutinesSuccess, fetchSkillDetail, fetchSkillDetailFailure, fetchSkillDetailSuccess, fetchSkills, fetchSkillsFailure, fetchSkillsSuccess, fetchSkillVersionBody, fetchSkillVersionBodyFailure, fetchSkillVersionBodySuccess, fetchSkillVersions, fetchSkillVersionsFailure, fetchSkillVersionsSuccess, fetchSuggestedQuestions, fetchSuggestedQuestionsFailure, fetchSuggestedQuestionsSuccess, initializeNewSessionState, openRoutinesBrowser, openSkillsBrowser, runRoutine, runRoutineFailure, runRoutineSuccess, saveRoutine, saveRoutineFailure, saveRoutineSuccess, saveSkill, saveSkillFailure, saveSkillSuccess, setRoutineEnabled, setSession, setSkillVisibility, setSkillVisibilityFailure, setSkillVisibilitySuccess, stopSubmit, stopSubmitFailure, stopSubmitQuestion, stopSubmitQuestionFailure, stopSubmitQuestionSuccess, stopSubmitSuccess, submitFeedback, submitFeedbackFailure, submitFeedbackSuccess, submitQuestion, submitQuestionFailure, submitQuestionSuccess, updateAiCfoViewScrollPosition, updateCotCollapsedState, updateCurrentInput, updateResponseState, } = aiCfoView.actions;
|
|
1248
|
+
export const { acceptMasterTOS, acceptMasterTOSFailure, acceptMasterTOSSuccess, applyAiCfoSidePanelHostPageTransition, clearAiCfoSidePanelHostPageContext, clearCurrentSessionId, clearDeleteChatSessionStatus, clearLastContextMessage, clearSkillDetail, clearSkillMutation, closeRoutineRuns, closeRoutinesBrowser, closeSkillsBrowser, createSession, createSessionAndSubmit, createSessionAndSubmitFailure, createSessionAndSubmitSuccess, createSessionFailure, createSessionSuccess, deleteChatSession, deleteChatSessionFailure, deleteChatSessionSuccess, deleteRoutine, deleteRoutineFailure, deleteRoutineSuccess, deleteSkill, deleteSkillFailure, deleteSkillSuccess, fetchChatHistory, fetchChatHistoryFailure, fetchChatHistorySuccess, fetchChatSessionsForUser, fetchChatSessionsForUserFailure, fetchChatSessionsForUserSuccess, fetchRoutineRuns, fetchRoutineRunsFailure, fetchRoutineRunsSuccess, fetchRoutines, fetchRoutinesFailure, fetchRoutinesSuccess, fetchSkillDetail, fetchSkillDetailFailure, fetchSkillDetailSuccess, fetchSkills, fetchSkillsFailure, fetchSkillsSuccess, fetchSkillVersionBody, fetchSkillVersionBodyFailure, fetchSkillVersionBodySuccess, fetchSkillVersions, fetchSkillVersionsFailure, fetchSkillVersionsSuccess, fetchSuggestedQuestions, fetchSuggestedQuestionsFailure, fetchSuggestedQuestionsSuccess, initializeNewSessionState, openRoutinesBrowser, openSkillsBrowser, runRoutine, runRoutineFailure, runRoutineSuccess, saveRoutine, saveRoutineFailure, saveRoutineSuccess, saveSkill, saveSkillFailure, saveSkillSuccess, setRoutineEnabled, setSession, setSkillVisibility, setSkillVisibilityFailure, setSkillVisibilitySuccess, stopSubmit, stopSubmitFailure, stopSubmitQuestion, stopSubmitQuestionFailure, stopSubmitQuestionSuccess, stopSubmitSuccess, submitFeedback, submitFeedbackFailure, submitFeedbackSuccess, submitQuestion, submitQuestionFailure, submitQuestionSuccess, transcribeAudio, transcribeAudioFailure, transcribeAudioSuccess, updateAiCfoViewScrollPosition, updateCotCollapsedState, updateCurrentInput, updateResponseState, } = aiCfoView.actions;
|
|
1187
1249
|
export default aiCfoView.reducer;
|
|
@@ -133,3 +133,28 @@ export function getSkillMutation(state) {
|
|
|
133
133
|
export function getSkillsEnabled(state) {
|
|
134
134
|
return getSkills(state).skillsEnabled;
|
|
135
135
|
}
|
|
136
|
+
/** True while a finished voice recording is being transcribed server-side. */
|
|
137
|
+
/**
|
|
138
|
+
* A take belongs to the chat it was dictated into. A new chat has no session id yet on
|
|
139
|
+
* either side, which is a match rather than a mismatch.
|
|
140
|
+
*/
|
|
141
|
+
function isTranscribingCurrentSession(state) {
|
|
142
|
+
if (state.aiCfoViewState == null) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
return (state.aiCfoViewState.transcribingChatSessionId ===
|
|
146
|
+
state.aiCfoViewState.uiState.currentChatSessionId);
|
|
147
|
+
}
|
|
148
|
+
export function getIsTranscribing(state) {
|
|
149
|
+
return (state.aiCfoViewState?.transcriptionState?.fetchState === 'In-Progress' &&
|
|
150
|
+
isTranscribingCurrentSession(state));
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Set when a transcription request failed. Without surfacing this the composer
|
|
154
|
+
* would just drop the "Transcribing…" label and sit there silently, leaving the
|
|
155
|
+
* user with no sign their recording went nowhere.
|
|
156
|
+
*/
|
|
157
|
+
export function getTranscriptionFailed(state) {
|
|
158
|
+
return (state.aiCfoViewState?.transcriptionState?.fetchState === 'Error' &&
|
|
159
|
+
isTranscribingCurrentSession(state));
|
|
160
|
+
}
|