@twentyhq/call-recorder 1.0.1 → 1.0.2
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/manifest.json +297 -0
- package/package.json +2 -2
- package/src/front-components/calendar-event-recording.front-component.mjs +269 -0
- package/src/front-components/calendar-event-recording.front-component.mjs.map +7 -0
- package/src/logic-functions/process-recall-webhook.mjs +1744 -0
- package/src/logic-functions/process-recall-webhook.mjs.map +7 -0
- package/src/logic-functions/recall-webhook.mjs +391 -0
- package/src/logic-functions/recall-webhook.mjs.map +7 -0
- package/src/logic-functions/reconcile-call-recorder-calendar-event.mjs +1602 -0
- package/src/logic-functions/reconcile-call-recorder-calendar-event.mjs.map +7 -0
- package/src/logic-functions/reconcile-stale-bot-state.mjs +2268 -0
- package/src/logic-functions/reconcile-stale-bot-state.mjs.map +7 -0
- package/.env.example +0 -5
- package/.nvmrc +0 -1
- package/.oxlintrc.json +0 -20
- package/AGENTS.md +0 -67
- package/CLAUDE.md +0 -67
- package/README.md +0 -24
- package/SETUP.md +0 -95
- package/src/__tests__/global-setup.ts +0 -100
- package/src/__tests__/schema.integration-test.ts +0 -104
- package/src/application-config.ts +0 -96
- package/src/constants/__tests__/call-recording-field-universal-identifiers.test.ts +0 -19
- package/src/constants/app-description.ts +0 -2
- package/src/constants/app-display-name.ts +0 -1
- package/src/constants/application-universal-identifier.ts +0 -2
- package/src/constants/calendar-event-reconciliation-logic-function-universal-identifier.ts +0 -2
- package/src/constants/calendar-event-record-page-layout-universal-identifier.ts +0 -2
- package/src/constants/calendar-event-recording-front-component-universal-identifier.ts +0 -2
- package/src/constants/calendar-event-recording-page-layout-tab-universal-identifier.ts +0 -2
- package/src/constants/calendar-event-recording-page-layout-widget-universal-identifier.ts +0 -2
- package/src/constants/call-recorder-everyone-left-timeout-seconds-app-variable-universal-identifier.ts +0 -2
- package/src/constants/call-recorder-failure-reason-on-call-recording-field-universal-identifier.ts +0 -2
- package/src/constants/call-recorder-join-early-minutes-app-variable-universal-identifier.ts +0 -2
- package/src/constants/call-recorder-name-app-variable-universal-identifier.ts +0 -2
- package/src/constants/call-recorder-noone-joined-timeout-seconds-app-variable-universal-identifier.ts +0 -2
- package/src/constants/call-recorder-preference-off-option-id.ts +0 -2
- package/src/constants/call-recorder-preference-on-calendar-event-field-universal-identifier.ts +0 -2
- package/src/constants/call-recorder-preference-on-calendar-event-view-field-universal-identifier.ts +0 -2
- package/src/constants/call-recorder-preference-on-option-id.ts +0 -2
- package/src/constants/call-recorder-preference.ts +0 -4
- package/src/constants/call-recorder-waiting-room-timeout-seconds-app-variable-universal-identifier.ts +0 -2
- package/src/constants/call-recording-audio-field-universal-identifier.ts +0 -2
- package/src/constants/call-recording-video-field-universal-identifier.ts +0 -2
- package/src/constants/default-role-universal-identifier.ts +0 -2
- package/src/constants/process-recall-webhook-logic-function-universal-identifier.ts +0 -2
- package/src/constants/recall-webhook-logic-function-universal-identifier.ts +0 -2
- package/src/constants/stale-bot-state-logic-function-universal-identifier.ts +0 -2
- package/src/default-role.ts +0 -69
- package/src/fields/call-recorder-failure-reason-on-call-recording.field.ts +0 -22
- package/src/fields/call-recorder-preference-on-calendar-event.field.ts +0 -41
- package/src/front-components/calendar-event-recording.front-component.tsx +0 -13
- package/src/front-components/components/CalendarEventRecording.tsx +0 -39
- package/src/front-components/components/CalendarEventRecordingBody.tsx +0 -96
- package/src/front-components/components/CalendarEventRecordingContent.tsx +0 -111
- package/src/front-components/components/RecordingTranscript.tsx +0 -92
- package/src/front-components/components/RecordingVideoPlayer.tsx +0 -52
- package/src/front-components/components/TranscriptEntryList.tsx +0 -61
- package/src/front-components/components/TranscriptEntryListItem.tsx +0 -115
- package/src/front-components/components/TranscriptErrorBox.tsx +0 -48
- package/src/front-components/components/TranscriptSpeakerAvatar.tsx +0 -141
- package/src/front-components/components/TranscriptSpeakerChip.tsx +0 -51
- package/src/front-components/constants/recording-theme-css-variables.ts +0 -40
- package/src/front-components/hooks/use-calendar-event-participants.ts +0 -172
- package/src/front-components/hooks/use-calendar-event-recording.ts +0 -155
- package/src/front-components/types/calendar-event-participant-by-speaker-name.type.ts +0 -6
- package/src/front-components/types/calendar-event-recording-participant.type.ts +0 -7
- package/src/front-components/types/transcript-entry.type.ts +0 -13
- package/src/front-components/utils/__tests__/find-active-transcript-entry-index.test.ts +0 -66
- package/src/front-components/utils/__tests__/format-transcript-timestamp.test.ts +0 -29
- package/src/front-components/utils/__tests__/get-speaker-name-match-keys.test.ts +0 -22
- package/src/front-components/utils/__tests__/parse-transcript-entries.test.ts +0 -162
- package/src/front-components/utils/build-calendar-event-participant-by-speaker-name.util.ts +0 -45
- package/src/front-components/utils/find-active-transcript-entry-index.util.ts +0 -77
- package/src/front-components/utils/format-transcript-timestamp.util.ts +0 -16
- package/src/front-components/utils/get-absolute-avatar-url.util.ts +0 -48
- package/src/front-components/utils/get-calendar-event-participant-for-speaker-name.util.ts +0 -24
- package/src/front-components/utils/get-speaker-name-match-keys.util.ts +0 -64
- package/src/front-components/utils/get-video-file-extension.util.ts +0 -23
- package/src/front-components/utils/parse-transcript-entries.util.ts +0 -85
- package/src/logic-functions/__tests__/process-recall-webhook.test.ts +0 -62
- package/src/logic-functions/__tests__/recall-webhook.test.ts +0 -180
- package/src/logic-functions/constants/call-recorder-everyone-left-timeout-seconds-env-var-name.ts +0 -2
- package/src/logic-functions/constants/call-recorder-everyone-left-timeout-seconds.ts +0 -1
- package/src/logic-functions/constants/call-recorder-join-early-minutes-env-var-name.ts +0 -2
- package/src/logic-functions/constants/call-recorder-name-env-var-name.ts +0 -1
- package/src/logic-functions/constants/call-recorder-noone-joined-timeout-seconds-env-var-name.ts +0 -2
- package/src/logic-functions/constants/call-recorder-noone-joined-timeout-seconds.ts +0 -1
- package/src/logic-functions/constants/call-recorder-recording-retention-hours-env-var-name.ts +0 -2
- package/src/logic-functions/constants/call-recorder-waiting-room-timeout-seconds-env-var-name.ts +0 -2
- package/src/logic-functions/constants/call-recorder-waiting-room-timeout-seconds.ts +0 -1
- package/src/logic-functions/constants/call-recording-micro-credits-per-hour.ts +0 -1
- package/src/logic-functions/constants/call-recording-request-status.ts +0 -5
- package/src/logic-functions/constants/call-recording-status.ts +0 -9
- package/src/logic-functions/constants/default-call-recorder-join-early-minutes.ts +0 -1
- package/src/logic-functions/constants/default-call-recorder-name.ts +0 -1
- package/src/logic-functions/constants/default-call-recorder-recording-retention-hours.ts +0 -2
- package/src/logic-functions/constants/default-recall-region.ts +0 -1
- package/src/logic-functions/constants/milliseconds-per-minute.ts +0 -1
- package/src/logic-functions/constants/non-terminal-call-recording-statuses.ts +0 -8
- package/src/logic-functions/constants/recall-api-key-env-var-name.ts +0 -1
- package/src/logic-functions/constants/recall-api-max-attempts.ts +0 -1
- package/src/logic-functions/constants/recall-api-retry-delay-ms.ts +0 -1
- package/src/logic-functions/constants/recall-bot-automatic-leave.ts +0 -74
- package/src/logic-functions/constants/recall-bot-everyone-left-min-activate-after-seconds.ts +0 -1
- package/src/logic-functions/constants/recall-bot-recording-config.ts +0 -34
- package/src/logic-functions/constants/recall-region-env-var-name.ts +0 -1
- package/src/logic-functions/constants/recall-webhook-secret-env-var-name.ts +0 -1
- package/src/logic-functions/constants/restricted-field-placeholder.ts +0 -3
- package/src/logic-functions/constants/stale-bot-state-cron-pattern.ts +0 -1
- package/src/logic-functions/constants/twenty-page-size.ts +0 -1
- package/src/logic-functions/data/__tests__/complete-call-recording-ingestion.test.ts +0 -55
- package/src/logic-functions/data/__tests__/fetch-all-nodes.test.ts +0 -43
- package/src/logic-functions/data/__tests__/get-current-workspace-id.test.ts +0 -38
- package/src/logic-functions/data/__tests__/strip-restricted-field-value.test.ts +0 -22
- package/src/logic-functions/data/complete-call-recording-ingestion.util.ts +0 -24
- package/src/logic-functions/data/create-call-recording.util.ts +0 -41
- package/src/logic-functions/data/fetch-all-nodes.util.ts +0 -44
- package/src/logic-functions/data/fetch-calendar-events-by-filter.util.ts +0 -80
- package/src/logic-functions/data/fetch-calendar-events-by-ids.util.ts +0 -20
- package/src/logic-functions/data/fetch-calendar-events-by-starts-at-values.util.ts +0 -19
- package/src/logic-functions/data/find-call-recordings-by-calendar-event-ids.util.ts +0 -17
- package/src/logic-functions/data/find-call-recordings-by-filter.util.ts +0 -102
- package/src/logic-functions/data/find-call-recordings-by-ids.util.ts +0 -17
- package/src/logic-functions/data/find-open-scheduled-call-recordings.util.ts +0 -14
- package/src/logic-functions/data/get-current-workspace-id.util.ts +0 -36
- package/src/logic-functions/data/strip-restricted-field-value.util.ts +0 -6
- package/src/logic-functions/data/update-call-recording.util.ts +0 -24
- package/src/logic-functions/domain/__tests__/build-call-recorder-policy-result.test.ts +0 -47
- package/src/logic-functions/domain/__tests__/compute-call-recording-charge.test.ts +0 -71
- package/src/logic-functions/domain/__tests__/compute-call-recording-id-for-meeting.test.ts +0 -37
- package/src/logic-functions/domain/__tests__/compute-real-meeting-key.test.ts +0 -88
- package/src/logic-functions/domain/__tests__/is-call-recording-ingestion-complete.test.ts +0 -59
- package/src/logic-functions/domain/__tests__/is-call-recording-status-downgrade.test.ts +0 -37
- package/src/logic-functions/domain/__tests__/resolve-call-recorder-policy-result.test.ts +0 -120
- package/src/logic-functions/domain/__tests__/should-complete-call-recording-ingestion.test.ts +0 -102
- package/src/logic-functions/domain/aggregate-call-recorder-policy-results-by-meeting.util.ts +0 -42
- package/src/logic-functions/domain/build-call-recorder-policy-result.util.ts +0 -53
- package/src/logic-functions/domain/build-failed-transcript-marker.util.ts +0 -13
- package/src/logic-functions/domain/build-pending-transcript-marker.util.ts +0 -13
- package/src/logic-functions/domain/build-recall-routing-metadata.util.ts +0 -12
- package/src/logic-functions/domain/build-transcript-failure-reason.util.ts +0 -7
- package/src/logic-functions/domain/compute-call-recording-charge.util.ts +0 -41
- package/src/logic-functions/domain/compute-call-recording-id-for-meeting.util.ts +0 -16
- package/src/logic-functions/domain/compute-real-meeting-key.util.ts +0 -48
- package/src/logic-functions/domain/compute-recall-bot-join-at.util.ts +0 -34
- package/src/logic-functions/domain/is-call-recording-ingestion-complete.util.ts +0 -19
- package/src/logic-functions/domain/is-call-recording-status-downgrade.util.ts +0 -37
- package/src/logic-functions/domain/is-recall-recording-done-signal.util.ts +0 -13
- package/src/logic-functions/domain/map-recall-status-code-to-call-recording-status.util.ts +0 -26
- package/src/logic-functions/domain/parse-transcript-marker.util.ts +0 -29
- package/src/logic-functions/domain/resolve-call-recorder-policy-result.util.ts +0 -72
- package/src/logic-functions/domain/should-complete-call-recording-ingestion.util.ts +0 -32
- package/src/logic-functions/flows/__tests__/charge-completed-call-recording.test.ts +0 -45
- package/src/logic-functions/flows/__tests__/complete-and-charge-call-recording.test.ts +0 -61
- package/src/logic-functions/flows/__tests__/converge-diverged-call-recordings.test.ts +0 -727
- package/src/logic-functions/flows/__tests__/download-transcript.test.ts +0 -74
- package/src/logic-functions/flows/__tests__/handle-recall-webhook.test.ts +0 -1301
- package/src/logic-functions/flows/__tests__/heal-call-recordings-missing-bot.test.ts +0 -225
- package/src/logic-functions/flows/__tests__/ingest-call-recording-media.test.ts +0 -153
- package/src/logic-functions/flows/__tests__/reap-orphaned-call-recorders.test.ts +0 -425
- package/src/logic-functions/flows/__tests__/reconcile-call-recorder.test.ts +0 -1007
- package/src/logic-functions/flows/cancel-call-recording-request.util.ts +0 -46
- package/src/logic-functions/flows/charge-completed-call-recording.util.ts +0 -31
- package/src/logic-functions/flows/complete-and-charge-call-recording.util.ts +0 -29
- package/src/logic-functions/flows/converge-diverged-call-recordings-result.type.ts +0 -8
- package/src/logic-functions/flows/converge-diverged-call-recordings.util.ts +0 -447
- package/src/logic-functions/flows/download-transcript.util.ts +0 -67
- package/src/logic-functions/flows/ensure-call-recorder.util.ts +0 -73
- package/src/logic-functions/flows/handle-recall-webhook.util.ts +0 -672
- package/src/logic-functions/flows/heal-call-recordings-missing-bot.util.ts +0 -82
- package/src/logic-functions/flows/ingest-call-recording-media.util.ts +0 -128
- package/src/logic-functions/flows/persist-call-recording-progress.util.ts +0 -58
- package/src/logic-functions/flows/reap-orphaned-call-recorders.util.ts +0 -183
- package/src/logic-functions/flows/reconcile-call-recorder.util.ts +0 -495
- package/src/logic-functions/flows/reconcile-call-recording-transcript-artifact-result.type.ts +0 -11
- package/src/logic-functions/flows/reconcile-call-recording-transcript-artifact.util.ts +0 -182
- package/src/logic-functions/flows/reschedule-call-recording-bot.util.ts +0 -69
- package/src/logic-functions/process-recall-webhook.ts +0 -23
- package/src/logic-functions/recall-api/__tests__/extract-recall-bot-convergence.test.ts +0 -153
- package/src/logic-functions/recall-api/__tests__/extract-recall-media-urls.test.ts +0 -67
- package/src/logic-functions/recall-api/__tests__/recall-bot-api.test.ts +0 -744
- package/src/logic-functions/recall-api/__tests__/verify-recall-webhook-signature.test.ts +0 -122
- package/src/logic-functions/recall-api/cancel-recall-bot.util.ts +0 -28
- package/src/logic-functions/recall-api/create-async-recall-transcript.util.ts +0 -47
- package/src/logic-functions/recall-api/eject-recall-bot.util.ts +0 -28
- package/src/logic-functions/recall-api/extract-recall-bot-convergence.util.ts +0 -149
- package/src/logic-functions/recall-api/extract-recall-bot-id.util.ts +0 -10
- package/src/logic-functions/recall-api/extract-recall-media-urls.util.ts +0 -30
- package/src/logic-functions/recall-api/extract-twenty-workspace-id-from-recall-webhook.util.ts +0 -8
- package/src/logic-functions/recall-api/get-recall-api-config.util.ts +0 -59
- package/src/logic-functions/recall-api/get-recall-bot.util.ts +0 -42
- package/src/logic-functions/recall-api/get-recall-recording.util.ts +0 -31
- package/src/logic-functions/recall-api/get-recall-webhook-bot-metadata.util.ts +0 -18
- package/src/logic-functions/recall-api/list-recall-transcripts.util.ts +0 -141
- package/src/logic-functions/recall-api/list-scheduled-recall-bots.util.ts +0 -106
- package/src/logic-functions/recall-api/normalize-recall-timestamp.util.ts +0 -14
- package/src/logic-functions/recall-api/parse-recall-webhook-event.util.ts +0 -88
- package/src/logic-functions/recall-api/recall-bot-api-request.util.ts +0 -165
- package/src/logic-functions/recall-api/recall-transcript-summary.type.ts +0 -5
- package/src/logic-functions/recall-api/reschedule-recall-bot.util.ts +0 -56
- package/src/logic-functions/recall-api/retrieve-recall-transcript.util.ts +0 -71
- package/src/logic-functions/recall-api/schedule-recall-bot.util.ts +0 -68
- package/src/logic-functions/recall-api/verify-recall-webhook-signature.util.ts +0 -109
- package/src/logic-functions/recall-webhook.ts +0 -90
- package/src/logic-functions/reconcile-call-recorder-calendar-event.ts +0 -178
- package/src/logic-functions/reconcile-stale-bot-state.ts +0 -106
- package/src/logic-functions/types/calendar-event-record.type.ts +0 -5
- package/src/logic-functions/types/call-recorder-policy-calendar-event-input.type.ts +0 -10
- package/src/logic-functions/types/call-recorder-policy-input.type.ts +0 -9
- package/src/logic-functions/types/call-recorder-policy-not-required-reason.type.ts +0 -5
- package/src/logic-functions/types/call-recorder-policy-required-reason.type.ts +0 -1
- package/src/logic-functions/types/call-recorder-policy-result-for-calendar-event.type.ts +0 -9
- package/src/logic-functions/types/call-recorder-policy-result-for-meeting.type.ts +0 -6
- package/src/logic-functions/types/call-recorder-policy-result.type.ts +0 -12
- package/src/logic-functions/types/call-recorder-reconciliation-result.type.ts +0 -16
- package/src/logic-functions/types/call-recording-media-file.type.ts +0 -1
- package/src/logic-functions/types/call-recording-record.type.ts +0 -15
- package/src/logic-functions/types/call-recording-update-fields.type.ts +0 -20
- package/src/logic-functions/types/files-field-value.type.ts +0 -1
- package/src/logic-functions/types/meeting-recording.type.ts +0 -7
- package/src/logic-functions/types/recall-bot-operation-result.type.ts +0 -19
- package/src/logic-functions/types/recall-routing-metadata.type.ts +0 -4
- package/src/logic-functions/types/removed-call-recorder-occurrence.type.ts +0 -6
- package/src/logic-functions/types/transcript-marker.type.ts +0 -6
- package/src/logic-functions/utils/as-record.util.ts +0 -6
- package/src/logic-functions/utils/get-application-variable-value.util.ts +0 -3
- package/src/logic-functions/utils/get-record-at-path.util.ts +0 -10
- package/src/logic-functions/utils/get-string.util.ts +0 -4
- package/src/logic-functions/utils/get-unique-sorted-ids.util.ts +0 -8
- package/src/logic-functions/utils/is-non-empty-string.util.ts +0 -5
- package/src/page-layouts/calendar-event-recording-tab.ts +0 -33
- package/src/view-fields/call-recorder-preference-on-calendar-event.view-field.ts +0 -27
- package/tsconfig.json +0 -42
- package/tsconfig.spec.json +0 -9
- package/vitest.config.ts +0 -31
- package/vitest.unit.config.ts +0 -14
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import { getSpeakerNameMatchKeys } from 'src/front-components/utils/get-speaker-name-match-keys.util';
|
|
4
|
-
|
|
5
|
-
describe('getSpeakerNameMatchKeys', () => {
|
|
6
|
-
it('matches transcript full names to compact calendar aliases', () => {
|
|
7
|
-
expect(getSpeakerNameMatchKeys('Martin Muller')).toContain('martmull');
|
|
8
|
-
expect(getSpeakerNameMatchKeys('Martmull92')).toContain('martmull');
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it('keeps exact normalized full names available for regular participant names', () => {
|
|
12
|
-
expect(getSpeakerNameMatchKeys('Nitin Koche')).toEqual([
|
|
13
|
-
'nitin koche',
|
|
14
|
-
'nitinkoche',
|
|
15
|
-
'nitikoch',
|
|
16
|
-
]);
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it('folds accents before generating compact match keys', () => {
|
|
20
|
-
expect(getSpeakerNameMatchKeys('Martin Müller')).toContain('martmull');
|
|
21
|
-
});
|
|
22
|
-
});
|
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import { parseTranscriptEntries } from 'src/front-components/utils/parse-transcript-entries.util';
|
|
4
|
-
|
|
5
|
-
describe('parseTranscriptEntries', () => {
|
|
6
|
-
it('parses diarized entries into speaker, start time, and joined text', () => {
|
|
7
|
-
expect(
|
|
8
|
-
parseTranscriptEntries([
|
|
9
|
-
{
|
|
10
|
-
participant: { id: 100, name: 'Ada Lovelace' },
|
|
11
|
-
words: [
|
|
12
|
-
{
|
|
13
|
-
text: 'Hello',
|
|
14
|
-
start_timestamp: {
|
|
15
|
-
relative: 1.2,
|
|
16
|
-
absolute: '2026-06-12T10:00:01Z',
|
|
17
|
-
},
|
|
18
|
-
end_timestamp: {
|
|
19
|
-
relative: 1.6,
|
|
20
|
-
absolute: '2026-06-12T10:00:01Z',
|
|
21
|
-
},
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
text: 'there',
|
|
25
|
-
start_timestamp: {
|
|
26
|
-
relative: 1.7,
|
|
27
|
-
absolute: '2026-06-12T10:00:02Z',
|
|
28
|
-
},
|
|
29
|
-
end_timestamp: {
|
|
30
|
-
relative: 2.1,
|
|
31
|
-
absolute: '2026-06-12T10:00:02Z',
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
],
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
participant: { id: 101, name: 'Grace Hopper' },
|
|
38
|
-
words: [
|
|
39
|
-
{
|
|
40
|
-
text: 'Hi',
|
|
41
|
-
start_timestamp: {
|
|
42
|
-
relative: 3.4,
|
|
43
|
-
absolute: '2026-06-12T10:00:03Z',
|
|
44
|
-
},
|
|
45
|
-
},
|
|
46
|
-
],
|
|
47
|
-
},
|
|
48
|
-
]),
|
|
49
|
-
).toEqual([
|
|
50
|
-
{
|
|
51
|
-
speakerName: 'Ada Lovelace',
|
|
52
|
-
startSeconds: 1.2,
|
|
53
|
-
endSeconds: 2.1,
|
|
54
|
-
text: 'Hello there',
|
|
55
|
-
words: [
|
|
56
|
-
{ text: 'Hello', startSeconds: 1.2, endSeconds: 1.6 },
|
|
57
|
-
{ text: 'there', startSeconds: 1.7, endSeconds: 2.1 },
|
|
58
|
-
],
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
speakerName: 'Grace Hopper',
|
|
62
|
-
startSeconds: 3.4,
|
|
63
|
-
endSeconds: undefined,
|
|
64
|
-
text: 'Hi',
|
|
65
|
-
words: [{ text: 'Hi', startSeconds: 3.4, endSeconds: undefined }],
|
|
66
|
-
},
|
|
67
|
-
]);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('falls back to an unknown speaker when the participant has no name', () => {
|
|
71
|
-
expect(
|
|
72
|
-
parseTranscriptEntries([
|
|
73
|
-
{ participant: { id: 100, name: null }, words: [{ text: 'Hello' }] },
|
|
74
|
-
{ words: [{ text: 'Hi' }] },
|
|
75
|
-
]),
|
|
76
|
-
).toEqual([
|
|
77
|
-
{
|
|
78
|
-
speakerName: 'Unknown speaker',
|
|
79
|
-
startSeconds: undefined,
|
|
80
|
-
endSeconds: undefined,
|
|
81
|
-
text: 'Hello',
|
|
82
|
-
words: [
|
|
83
|
-
{ text: 'Hello', startSeconds: undefined, endSeconds: undefined },
|
|
84
|
-
],
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
speakerName: 'Unknown speaker',
|
|
88
|
-
startSeconds: undefined,
|
|
89
|
-
endSeconds: undefined,
|
|
90
|
-
text: 'Hi',
|
|
91
|
-
words: [{ text: 'Hi', startSeconds: undefined, endSeconds: undefined }],
|
|
92
|
-
},
|
|
93
|
-
]);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it('returns an undefined start time when the first word has no relative timestamp', () => {
|
|
97
|
-
expect(
|
|
98
|
-
parseTranscriptEntries([
|
|
99
|
-
{
|
|
100
|
-
participant: { name: 'Ada Lovelace' },
|
|
101
|
-
words: [
|
|
102
|
-
{
|
|
103
|
-
text: 'Hello',
|
|
104
|
-
start_timestamp: { absolute: '2026-06-12T10:00:01Z' },
|
|
105
|
-
},
|
|
106
|
-
],
|
|
107
|
-
},
|
|
108
|
-
]),
|
|
109
|
-
).toEqual([
|
|
110
|
-
{
|
|
111
|
-
speakerName: 'Ada Lovelace',
|
|
112
|
-
startSeconds: undefined,
|
|
113
|
-
endSeconds: undefined,
|
|
114
|
-
text: 'Hello',
|
|
115
|
-
words: [
|
|
116
|
-
{ text: 'Hello', startSeconds: undefined, endSeconds: undefined },
|
|
117
|
-
],
|
|
118
|
-
},
|
|
119
|
-
]);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it('skips entries without usable words instead of failing the whole transcript', () => {
|
|
123
|
-
expect(
|
|
124
|
-
parseTranscriptEntries([
|
|
125
|
-
{ participant: { name: 'Ada Lovelace' }, words: [] },
|
|
126
|
-
{ participant: { name: 'Grace Hopper' } },
|
|
127
|
-
{
|
|
128
|
-
participant: { name: 'Alan Turing' },
|
|
129
|
-
words: [{ text: ' ' }, 42, null],
|
|
130
|
-
},
|
|
131
|
-
{ participant: { name: 'Joan Clarke' }, words: [{ text: 'Kept' }] },
|
|
132
|
-
'not an entry',
|
|
133
|
-
]),
|
|
134
|
-
).toEqual([
|
|
135
|
-
{
|
|
136
|
-
speakerName: 'Joan Clarke',
|
|
137
|
-
startSeconds: undefined,
|
|
138
|
-
endSeconds: undefined,
|
|
139
|
-
text: 'Kept',
|
|
140
|
-
words: [
|
|
141
|
-
{ text: 'Kept', startSeconds: undefined, endSeconds: undefined },
|
|
142
|
-
],
|
|
143
|
-
},
|
|
144
|
-
]);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it('returns an empty list for an empty transcript array', () => {
|
|
148
|
-
expect(parseTranscriptEntries([])).toEqual([]);
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
it('returns undefined for values that are not a diarized transcript array', () => {
|
|
152
|
-
expect(parseTranscriptEntries(null)).toBeUndefined();
|
|
153
|
-
expect(parseTranscriptEntries(undefined)).toBeUndefined();
|
|
154
|
-
expect(parseTranscriptEntries('transcript text')).toBeUndefined();
|
|
155
|
-
expect(
|
|
156
|
-
parseTranscriptEntries({
|
|
157
|
-
recallTranscriptId: 'recall-transcript-1',
|
|
158
|
-
status: 'PENDING',
|
|
159
|
-
}),
|
|
160
|
-
).toBeUndefined();
|
|
161
|
-
});
|
|
162
|
-
});
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { isUndefined } from '@sniptt/guards';
|
|
2
|
-
|
|
3
|
-
import { type CalendarEventParticipantBySpeakerName } from 'src/front-components/types/calendar-event-participant-by-speaker-name.type';
|
|
4
|
-
import { type CalendarEventRecordingParticipant } from 'src/front-components/types/calendar-event-recording-participant.type';
|
|
5
|
-
import { getSpeakerNameMatchKeys } from 'src/front-components/utils/get-speaker-name-match-keys.util';
|
|
6
|
-
|
|
7
|
-
export const buildCalendarEventParticipantBySpeakerName = (
|
|
8
|
-
calendarEventParticipants: CalendarEventRecordingParticipant[],
|
|
9
|
-
): CalendarEventParticipantBySpeakerName => {
|
|
10
|
-
const calendarEventParticipantBySpeakerName: CalendarEventParticipantBySpeakerName =
|
|
11
|
-
new Map();
|
|
12
|
-
const ambiguousSpeakerNameMatchKeys = new Set<string>();
|
|
13
|
-
|
|
14
|
-
for (const calendarEventParticipant of calendarEventParticipants) {
|
|
15
|
-
for (const nameCandidate of calendarEventParticipant.nameCandidates) {
|
|
16
|
-
const speakerNameMatchKeys = getSpeakerNameMatchKeys(nameCandidate);
|
|
17
|
-
|
|
18
|
-
for (const speakerNameMatchKey of speakerNameMatchKeys) {
|
|
19
|
-
const matchingCalendarEventParticipant =
|
|
20
|
-
calendarEventParticipantBySpeakerName.get(speakerNameMatchKey);
|
|
21
|
-
|
|
22
|
-
if (ambiguousSpeakerNameMatchKeys.has(speakerNameMatchKey)) {
|
|
23
|
-
continue;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (isUndefined(matchingCalendarEventParticipant)) {
|
|
27
|
-
calendarEventParticipantBySpeakerName.set(
|
|
28
|
-
speakerNameMatchKey,
|
|
29
|
-
calendarEventParticipant,
|
|
30
|
-
);
|
|
31
|
-
continue;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (
|
|
35
|
-
matchingCalendarEventParticipant.id !== calendarEventParticipant.id
|
|
36
|
-
) {
|
|
37
|
-
calendarEventParticipantBySpeakerName.delete(speakerNameMatchKey);
|
|
38
|
-
ambiguousSpeakerNameMatchKeys.add(speakerNameMatchKey);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return calendarEventParticipantBySpeakerName;
|
|
45
|
-
};
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { isUndefined } from '@sniptt/guards';
|
|
2
|
-
|
|
3
|
-
import { type TranscriptEntry } from 'src/front-components/types/transcript-entry.type';
|
|
4
|
-
|
|
5
|
-
export const findActiveTranscriptEntryIndex = (
|
|
6
|
-
entries: TranscriptEntry[],
|
|
7
|
-
currentTimeSeconds: number,
|
|
8
|
-
): number => {
|
|
9
|
-
for (let entryIndex = entries.length - 1; entryIndex >= 0; entryIndex--) {
|
|
10
|
-
const entry = entries[entryIndex];
|
|
11
|
-
|
|
12
|
-
if (
|
|
13
|
-
isTranscriptEntryActive({
|
|
14
|
-
entries,
|
|
15
|
-
entry,
|
|
16
|
-
entryIndex,
|
|
17
|
-
currentTimeSeconds,
|
|
18
|
-
})
|
|
19
|
-
) {
|
|
20
|
-
return entryIndex;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return -1;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
const isTranscriptEntryActive = ({
|
|
28
|
-
entries,
|
|
29
|
-
entry,
|
|
30
|
-
entryIndex,
|
|
31
|
-
currentTimeSeconds,
|
|
32
|
-
}: {
|
|
33
|
-
entries: TranscriptEntry[];
|
|
34
|
-
entry: TranscriptEntry;
|
|
35
|
-
entryIndex: number;
|
|
36
|
-
currentTimeSeconds: number;
|
|
37
|
-
}): boolean => {
|
|
38
|
-
if (
|
|
39
|
-
isUndefined(entry.startSeconds) ||
|
|
40
|
-
currentTimeSeconds < entry.startSeconds
|
|
41
|
-
) {
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (!isUndefined(entry.endSeconds)) {
|
|
46
|
-
return currentTimeSeconds <= entry.endSeconds;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const nextTranscriptEntryStartSeconds = findNextTranscriptEntryStartSeconds(
|
|
50
|
-
entries,
|
|
51
|
-
entryIndex,
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
return isUndefined(nextTranscriptEntryStartSeconds)
|
|
55
|
-
? true
|
|
56
|
-
: currentTimeSeconds < nextTranscriptEntryStartSeconds;
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
const findNextTranscriptEntryStartSeconds = (
|
|
60
|
-
entries: TranscriptEntry[],
|
|
61
|
-
entryIndex: number,
|
|
62
|
-
): number | undefined => {
|
|
63
|
-
for (
|
|
64
|
-
let nextEntryIndex = entryIndex + 1;
|
|
65
|
-
nextEntryIndex < entries.length;
|
|
66
|
-
nextEntryIndex++
|
|
67
|
-
) {
|
|
68
|
-
const nextTranscriptEntryStartSeconds =
|
|
69
|
-
entries[nextEntryIndex].startSeconds;
|
|
70
|
-
|
|
71
|
-
if (!isUndefined(nextTranscriptEntryStartSeconds)) {
|
|
72
|
-
return nextTranscriptEntryStartSeconds;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return undefined;
|
|
77
|
-
};
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
export const formatTranscriptTimestamp = (totalSeconds: number): string => {
|
|
2
|
-
const safeSeconds = Number.isFinite(totalSeconds)
|
|
3
|
-
? Math.max(0, Math.floor(totalSeconds))
|
|
4
|
-
: 0;
|
|
5
|
-
|
|
6
|
-
const hours = Math.floor(safeSeconds / 3600);
|
|
7
|
-
const minutes = Math.floor((safeSeconds % 3600) / 60);
|
|
8
|
-
const seconds = safeSeconds % 60;
|
|
9
|
-
const paddedSeconds = String(seconds).padStart(2, '0');
|
|
10
|
-
|
|
11
|
-
if (hours > 0) {
|
|
12
|
-
return `${hours}:${String(minutes).padStart(2, '0')}:${paddedSeconds}`;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
return `${minutes}:${paddedSeconds}`;
|
|
16
|
-
};
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
// Duplicates minimal front image URL logic for this app.
|
|
2
|
-
// Remove once shared front utilities can be imported safely in front components.
|
|
3
|
-
import { isNonEmptyString } from 'src/logic-functions/utils/is-non-empty-string.util';
|
|
4
|
-
|
|
5
|
-
type GetImageAbsoluteUrlArgs = {
|
|
6
|
-
imageUrl: string;
|
|
7
|
-
baseUrl: string;
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
const getImageAbsoluteUrl = ({
|
|
11
|
-
imageUrl,
|
|
12
|
-
baseUrl,
|
|
13
|
-
}: GetImageAbsoluteUrlArgs): string => {
|
|
14
|
-
const lowerCaseImageUrl = imageUrl.toLowerCase();
|
|
15
|
-
const isAlreadyAbsoluteUrl =
|
|
16
|
-
['http:', 'https:', 'data:', 'blob:'].some((scheme) =>
|
|
17
|
-
lowerCaseImageUrl.startsWith(scheme),
|
|
18
|
-
) || imageUrl.startsWith('//');
|
|
19
|
-
|
|
20
|
-
if (isAlreadyAbsoluteUrl) {
|
|
21
|
-
return imageUrl;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
if (imageUrl.startsWith('/')) {
|
|
25
|
-
return new URL(`/files${imageUrl}`, baseUrl).toString();
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return new URL(`/files/${imageUrl}`, baseUrl).toString();
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
export const getAbsoluteAvatarUrl = (
|
|
32
|
-
avatarUrl: string | null | undefined,
|
|
33
|
-
): string | undefined => {
|
|
34
|
-
if (!isNonEmptyString(avatarUrl)) {
|
|
35
|
-
return undefined;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const apiBaseUrl = process.env.TWENTY_API_URL;
|
|
39
|
-
|
|
40
|
-
if (!isNonEmptyString(apiBaseUrl)) {
|
|
41
|
-
return avatarUrl.trim();
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return getImageAbsoluteUrl({
|
|
45
|
-
imageUrl: avatarUrl.trim(),
|
|
46
|
-
baseUrl: apiBaseUrl,
|
|
47
|
-
});
|
|
48
|
-
};
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { isUndefined } from '@sniptt/guards';
|
|
2
|
-
|
|
3
|
-
import { type CalendarEventParticipantBySpeakerName } from 'src/front-components/types/calendar-event-participant-by-speaker-name.type';
|
|
4
|
-
import { type CalendarEventRecordingParticipant } from 'src/front-components/types/calendar-event-recording-participant.type';
|
|
5
|
-
import { getSpeakerNameMatchKeys } from 'src/front-components/utils/get-speaker-name-match-keys.util';
|
|
6
|
-
|
|
7
|
-
export const getCalendarEventParticipantForSpeakerName = ({
|
|
8
|
-
speakerName,
|
|
9
|
-
calendarEventParticipantBySpeakerName,
|
|
10
|
-
}: {
|
|
11
|
-
speakerName: string;
|
|
12
|
-
calendarEventParticipantBySpeakerName: CalendarEventParticipantBySpeakerName;
|
|
13
|
-
}): CalendarEventRecordingParticipant | undefined => {
|
|
14
|
-
for (const speakerNameMatchKey of getSpeakerNameMatchKeys(speakerName)) {
|
|
15
|
-
const calendarEventParticipant =
|
|
16
|
-
calendarEventParticipantBySpeakerName.get(speakerNameMatchKey);
|
|
17
|
-
|
|
18
|
-
if (!isUndefined(calendarEventParticipant)) {
|
|
19
|
-
return calendarEventParticipant;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
return undefined;
|
|
24
|
-
};
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { isNonEmptyString } from 'src/logic-functions/utils/is-non-empty-string.util';
|
|
2
|
-
|
|
3
|
-
const MINIMUM_FUZZY_MATCH_KEY_LENGTH = 5;
|
|
4
|
-
|
|
5
|
-
export const getSpeakerNameMatchKeys = (speakerName: string): string[] => {
|
|
6
|
-
const normalizedSpeakerName = normalizeSpeakerName(speakerName);
|
|
7
|
-
const compactSpeakerName = getCompactSpeakerName(normalizedSpeakerName);
|
|
8
|
-
const compactSpeakerNameWithoutDigits = compactSpeakerName.replace(/\d/g, '');
|
|
9
|
-
const abbreviatedSpeakerNameMatchKey = getAbbreviatedSpeakerNameMatchKey(
|
|
10
|
-
normalizedSpeakerName,
|
|
11
|
-
);
|
|
12
|
-
|
|
13
|
-
return [
|
|
14
|
-
...new Set(
|
|
15
|
-
[
|
|
16
|
-
normalizedSpeakerName,
|
|
17
|
-
compactSpeakerName,
|
|
18
|
-
compactSpeakerNameWithoutDigits,
|
|
19
|
-
abbreviatedSpeakerNameMatchKey,
|
|
20
|
-
].filter(isSpeakerNameMatchKey),
|
|
21
|
-
),
|
|
22
|
-
];
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
const normalizeSpeakerName = (speakerName: string): string =>
|
|
26
|
-
speakerName
|
|
27
|
-
.trim()
|
|
28
|
-
.normalize('NFD')
|
|
29
|
-
.replace(/[\u0300-\u036f]/g, '')
|
|
30
|
-
.toLocaleLowerCase();
|
|
31
|
-
|
|
32
|
-
const getCompactSpeakerName = (speakerName: string): string =>
|
|
33
|
-
normalizeSpeakerName(speakerName).replace(/[^a-z0-9]/g, '');
|
|
34
|
-
|
|
35
|
-
const getAbbreviatedSpeakerNameMatchKey = (
|
|
36
|
-
speakerName: string,
|
|
37
|
-
): string | undefined => {
|
|
38
|
-
const speakerNameParts = normalizeSpeakerName(speakerName)
|
|
39
|
-
.split(/\s+/)
|
|
40
|
-
.map(getCompactSpeakerName)
|
|
41
|
-
.filter(isNonEmptyString);
|
|
42
|
-
|
|
43
|
-
if (speakerNameParts.length < 2) {
|
|
44
|
-
return undefined;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const firstSpeakerNamePart = speakerNameParts[0];
|
|
48
|
-
const lastSpeakerNamePart = speakerNameParts[speakerNameParts.length - 1];
|
|
49
|
-
const abbreviatedSpeakerNameMatchKey = `${firstSpeakerNamePart.slice(
|
|
50
|
-
0,
|
|
51
|
-
4,
|
|
52
|
-
)}${lastSpeakerNamePart.slice(0, 4)}`;
|
|
53
|
-
|
|
54
|
-
return abbreviatedSpeakerNameMatchKey.length >= MINIMUM_FUZZY_MATCH_KEY_LENGTH
|
|
55
|
-
? abbreviatedSpeakerNameMatchKey
|
|
56
|
-
: undefined;
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
const isSpeakerNameMatchKey = (
|
|
60
|
-
speakerNameMatchKey: string | undefined,
|
|
61
|
-
): speakerNameMatchKey is string =>
|
|
62
|
-
isNonEmptyString(speakerNameMatchKey) &&
|
|
63
|
-
(speakerNameMatchKey.includes(' ') ||
|
|
64
|
-
speakerNameMatchKey.length >= MINIMUM_FUZZY_MATCH_KEY_LENGTH);
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { isUndefined } from '@sniptt/guards';
|
|
2
|
-
|
|
3
|
-
import { isNonEmptyString } from 'src/logic-functions/utils/is-non-empty-string.util';
|
|
4
|
-
|
|
5
|
-
export const getVideoFileExtension = ({
|
|
6
|
-
extension,
|
|
7
|
-
label,
|
|
8
|
-
}: {
|
|
9
|
-
extension: string | null;
|
|
10
|
-
label: string | null;
|
|
11
|
-
}): string | undefined => {
|
|
12
|
-
const labelParts = label?.split('.');
|
|
13
|
-
const videoFileExtension =
|
|
14
|
-
extension ??
|
|
15
|
-
(isUndefined(labelParts) ? undefined : labelParts[labelParts.length - 1]);
|
|
16
|
-
const normalizedVideoFileExtension = videoFileExtension
|
|
17
|
-
?.toLowerCase()
|
|
18
|
-
.replace(/^\./, '');
|
|
19
|
-
|
|
20
|
-
return isNonEmptyString(normalizedVideoFileExtension)
|
|
21
|
-
? normalizedVideoFileExtension
|
|
22
|
-
: undefined;
|
|
23
|
-
};
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { isArray, isNumber, isUndefined } from '@sniptt/guards';
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
type TranscriptEntry,
|
|
5
|
-
type TranscriptWord,
|
|
6
|
-
} from 'src/front-components/types/transcript-entry.type';
|
|
7
|
-
import { asRecord } from 'src/logic-functions/utils/as-record.util';
|
|
8
|
-
import { isNonEmptyString } from 'src/logic-functions/utils/is-non-empty-string.util';
|
|
9
|
-
|
|
10
|
-
type TranscriptRecord = NonNullable<ReturnType<typeof asRecord>>;
|
|
11
|
-
|
|
12
|
-
const isTranscriptRecord = (
|
|
13
|
-
candidate: TranscriptRecord | undefined,
|
|
14
|
-
): candidate is TranscriptRecord => !isUndefined(candidate);
|
|
15
|
-
|
|
16
|
-
const readRelativeTimestamp = (
|
|
17
|
-
timestamp: TranscriptRecord | undefined,
|
|
18
|
-
): number | undefined => {
|
|
19
|
-
const relativeTimestamp = timestamp?.relative;
|
|
20
|
-
|
|
21
|
-
return isNumber(relativeTimestamp) && Number.isFinite(relativeTimestamp)
|
|
22
|
-
? relativeTimestamp
|
|
23
|
-
: undefined;
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
const readTranscriptWord = (
|
|
27
|
-
candidate: TranscriptRecord,
|
|
28
|
-
): TranscriptWord | undefined => {
|
|
29
|
-
if (!isNonEmptyString(candidate.text)) {
|
|
30
|
-
return undefined;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return {
|
|
34
|
-
text: candidate.text.trim(),
|
|
35
|
-
startSeconds: readRelativeTimestamp(asRecord(candidate.start_timestamp)),
|
|
36
|
-
endSeconds: readRelativeTimestamp(asRecord(candidate.end_timestamp)),
|
|
37
|
-
};
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
const readSpeakerName = (participant: TranscriptRecord | undefined): string => {
|
|
41
|
-
const name = participant?.name;
|
|
42
|
-
|
|
43
|
-
return isNonEmptyString(name) ? name.trim() : 'Unknown speaker';
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
const readTranscriptEntry = (
|
|
47
|
-
candidate: TranscriptRecord,
|
|
48
|
-
): TranscriptEntry | undefined => {
|
|
49
|
-
if (!isArray(candidate.words)) {
|
|
50
|
-
return undefined;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const words = candidate.words
|
|
54
|
-
.map(asRecord)
|
|
55
|
-
.filter(isTranscriptRecord)
|
|
56
|
-
.map(readTranscriptWord)
|
|
57
|
-
.filter((word): word is TranscriptWord => !isUndefined(word));
|
|
58
|
-
|
|
59
|
-
if (words.length === 0) {
|
|
60
|
-
return undefined;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return {
|
|
64
|
-
speakerName: readSpeakerName(asRecord(candidate.participant)),
|
|
65
|
-
startSeconds: words[0].startSeconds,
|
|
66
|
-
endSeconds: words[words.length - 1].endSeconds,
|
|
67
|
-
text: words.map((word) => word.text).join(' '),
|
|
68
|
-
words,
|
|
69
|
-
};
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
// Undefined means the value is not a diarized transcript; malformed entries are skipped, not fatal.
|
|
73
|
-
export const parseTranscriptEntries = (
|
|
74
|
-
transcript: unknown,
|
|
75
|
-
): TranscriptEntry[] | undefined => {
|
|
76
|
-
if (!isArray(transcript)) {
|
|
77
|
-
return undefined;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return transcript
|
|
81
|
-
.map(asRecord)
|
|
82
|
-
.filter(isTranscriptRecord)
|
|
83
|
-
.map(readTranscriptEntry)
|
|
84
|
-
.filter((entry): entry is TranscriptEntry => !isUndefined(entry));
|
|
85
|
-
};
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import processRecallWebhookLogicFunction, {
|
|
4
|
-
processRecallWebhookHandler,
|
|
5
|
-
} from 'src/logic-functions/process-recall-webhook';
|
|
6
|
-
|
|
7
|
-
const handleRecallWebhookMock = vi.hoisted(() => vi.fn());
|
|
8
|
-
const coreApiClientMock = vi.hoisted(() => vi.fn());
|
|
9
|
-
|
|
10
|
-
vi.mock('src/logic-functions/flows/handle-recall-webhook.util', () => ({
|
|
11
|
-
handleRecallWebhook: handleRecallWebhookMock,
|
|
12
|
-
}));
|
|
13
|
-
|
|
14
|
-
vi.mock('twenty-client-sdk/core', () => ({
|
|
15
|
-
CoreApiClient: coreApiClientMock,
|
|
16
|
-
}));
|
|
17
|
-
|
|
18
|
-
const buildRecordingDoneWebhookBody = () => ({
|
|
19
|
-
event: 'recording.done',
|
|
20
|
-
data: {
|
|
21
|
-
bot: {
|
|
22
|
-
id: 'recall-bot-1',
|
|
23
|
-
metadata: {
|
|
24
|
-
twentyWorkspaceId: '123e4567-e89b-12d3-a456-426614174000',
|
|
25
|
-
twentyCallRecordingId: 'call-recording-1',
|
|
26
|
-
},
|
|
27
|
-
},
|
|
28
|
-
recording: { id: 'recall-recording-1' },
|
|
29
|
-
},
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
describe('process-recall-webhook', () => {
|
|
33
|
-
beforeEach(() => {
|
|
34
|
-
handleRecallWebhookMock.mockReset();
|
|
35
|
-
handleRecallWebhookMock.mockResolvedValue({ status: 'updated' });
|
|
36
|
-
coreApiClientMock.mockReset();
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('declares no external trigger so it only runs when dispatched by the resolver', () => {
|
|
40
|
-
expect(processRecallWebhookLogicFunction.success).toBe(true);
|
|
41
|
-
expect(
|
|
42
|
-
'serverRouteTriggerSettings' in processRecallWebhookLogicFunction.config,
|
|
43
|
-
).toBe(false);
|
|
44
|
-
expect(
|
|
45
|
-
processRecallWebhookLogicFunction.config.httpRouteTriggerSettings,
|
|
46
|
-
).toBeUndefined();
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('forwards the resolved payload to handleRecallWebhook with a workspace-scoped client', async () => {
|
|
50
|
-
const body = buildRecordingDoneWebhookBody();
|
|
51
|
-
|
|
52
|
-
const result = await processRecallWebhookHandler(body);
|
|
53
|
-
|
|
54
|
-
expect(coreApiClientMock).toHaveBeenCalledTimes(1);
|
|
55
|
-
expect(handleRecallWebhookMock).toHaveBeenCalledTimes(1);
|
|
56
|
-
expect(handleRecallWebhookMock).toHaveBeenCalledWith({
|
|
57
|
-
client: coreApiClientMock.mock.instances[0],
|
|
58
|
-
body,
|
|
59
|
-
});
|
|
60
|
-
expect(result).toEqual({ status: 'updated' });
|
|
61
|
-
});
|
|
62
|
-
});
|