rename twenty meeting bot to call recorder (#22093)
<!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22093?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: martmull <martmull@hotmail.fr>
This commit is contained in:
+18
-18
@@ -1,8 +1,8 @@
|
||||
# Twenty Meeting Bot
|
||||
# Call Recorder
|
||||
|
||||
Record your meetings automatically and keep every call inside your CRM. Twenty
|
||||
Meeting Bot sends a recording bot to your team's calendar meetings, then stores
|
||||
the video, audio, and a speaker-attributed transcript on the meeting's record —
|
||||
Record your meetings automatically and keep every call inside your CRM. Call
|
||||
Recorder sends a recording bot to your team's calendar meetings, then stores the
|
||||
video, audio, and a speaker-attributed transcript on the meeting's record —
|
||||
searchable, in context, and ready for people, AI agents, and workflows to act
|
||||
on.
|
||||
|
||||
@@ -11,7 +11,7 @@ on.
|
||||
1. A teammate has an upcoming meeting on a synced calendar with a video
|
||||
conference link (Zoom, Google Meet, Microsoft Teams — anything Recall.ai
|
||||
supports).
|
||||
2. Because recording is on by default, the app schedules a meeting bot to join
|
||||
2. Because recording is on by default, the app schedules a call recorder to join
|
||||
that event shortly before it starts.
|
||||
3. The bot joins under the configured display name and records the meeting's
|
||||
audio and video for its duration.
|
||||
@@ -34,7 +34,7 @@ on.
|
||||
**CallRecording**: the mixed audio (MP3) and video (MP4), the transcript, the
|
||||
call's actual start and end times, and a lifecycle status (`SCHEDULED` →
|
||||
`JOINING` → `RECORDING` → `PROCESSING` → `COMPLETED`, or `FAILED`), with a
|
||||
Meeting Bot Failure Reason when failure details are available.
|
||||
Call Recorder Failure Reason when failure details are available.
|
||||
- **A default role.** A scoped application role that reads calendar events,
|
||||
participants, people, and workspace members to decide attendance, and writes
|
||||
the resulting CallRecording records, uploads recording media, and fills
|
||||
@@ -71,7 +71,7 @@ the bot never recorded (opted out, canceled, or no one showed) is not charged.
|
||||
## Installing
|
||||
|
||||
1. Open **Settings → Applications** in your Twenty workspace.
|
||||
2. Find **Twenty Meeting Bot** and click **Install**.
|
||||
2. Find **Call Recorder** and click **Install**.
|
||||
3. A server admin completes the one-time
|
||||
[Self-hosting setup](#self-hosting-setup-admin-only) below to wire up the
|
||||
Recall.ai API key and webhook. On Twenty Cloud these may already be
|
||||
@@ -108,7 +108,7 @@ What this app intentionally does **not** do in v1:
|
||||
| Transcript empty, or marked pending/failed | Recall hasn't finished async transcription yet, or transcription failed for that call | Wait for the reconciliation job to ingest the transcript; a persistent failure leaves a marker in the transcript |
|
||||
| Webhook rejected with `401` (Recall keeps retrying) | `RECALL_WEBHOOK_SECRET` doesn't match the Recall endpoint's signing secret | Re-copy the `whsec_…` secret from the Recall webhook endpoint into the `RECALL_WEBHOOK_SECRET` server variable |
|
||||
| Webhook rejected with `500` about the secret | `RECALL_WEBHOOK_SECRET` is not set | Admin: set it on the application registration |
|
||||
| Bot left almost immediately | No one was admitted before the lobby / no-one-joined timeout, or everyone left | Adjust `MEETING_BOT_WAITING_ROOM_TIMEOUT_SECONDS` / `MEETING_BOT_NOONE_JOINED_TIMEOUT_SECONDS` if too aggressive |
|
||||
| Bot left almost immediately | No one was admitted before the lobby / no-one-joined timeout, or everyone left | Adjust `CALL_RECORDER_WAITING_ROOM_TIMEOUT_SECONDS` / `CALL_RECORDER_NOONE_JOINED_TIMEOUT_SECONDS` if too aggressive |
|
||||
| Bot joined a meeting you didn't want recorded | Recording is on by default | Set the event's **Recording Bot** field to Off; the scheduled bot is canceled |
|
||||
|
||||
---
|
||||
@@ -121,13 +121,13 @@ credentials may already be configured.
|
||||
### Server variables
|
||||
|
||||
Set these on the application registration after installing (Settings →
|
||||
Applications → Twenty Meeting Bot):
|
||||
Applications → Call Recorder):
|
||||
|
||||
| Server variable | Required | Purpose |
|
||||
|---|---|---|
|
||||
| `RECALL_API_KEY` | Yes | Recall.ai API key for the configured region; used to schedule, update, and cancel bots. |
|
||||
| `RECALL_REGION` | No | Recall.ai region for API requests. Defaults to `eu-central-1` (Europe / Frankfurt). |
|
||||
| `MEETING_BOT_RECORDING_RETENTION_HOURS` | No | How long Recall.ai retains the source media after processing. Defaults to `166` hours (6 days 22 hours), just under Recall's 168-hour free-storage window. Values above `168` may incur Recall storage charges. Twenty's ingested copy is unaffected. |
|
||||
| `CALL_RECORDER_RECORDING_RETENTION_HOURS` | No | How long Recall.ai retains the source media after processing. Defaults to `166` hours (6 days 22 hours), just under Recall's 168-hour free-storage window. Values above `168` may incur Recall storage charges. Twenty's ingested copy is unaffected. |
|
||||
| `RECALL_WEBHOOK_SECRET` | Yes | Svix signing secret (`whsec_…`) used to verify incoming Recall webhooks. |
|
||||
|
||||
### Application variables
|
||||
@@ -136,11 +136,11 @@ A workspace admin can tune bot behavior through application variables:
|
||||
|
||||
| Application variable | Default | Purpose |
|
||||
|---|---|---|
|
||||
| `MEETING_BOT_NAME` | `Twenty Meeting Bot` | Display name the bot uses when it joins a call. |
|
||||
| `MEETING_BOT_JOIN_EARLY_MINUTES` | `1` | Minutes before the start time the bot joins. Set to `0` to join at the scheduled start. |
|
||||
| `MEETING_BOT_WAITING_ROOM_TIMEOUT_SECONDS` | `1200` | Seconds the bot waits in the lobby before giving up and leaving. |
|
||||
| `MEETING_BOT_NOONE_JOINED_TIMEOUT_SECONDS` | `1200` | Seconds the bot stays in an empty meeting when no one else ever joins. |
|
||||
| `MEETING_BOT_EVERYONE_LEFT_TIMEOUT_SECONDS` | `2` | Seconds the bot keeps recording after everyone else leaves. |
|
||||
| `CALL_RECORDER_NAME` | `Twenty.com` | Display name the bot uses when it joins a call. |
|
||||
| `CALL_RECORDER_JOIN_EARLY_MINUTES` | `1` | Minutes before the start time the bot joins. Set to `0` to join at the scheduled start. |
|
||||
| `CALL_RECORDER_WAITING_ROOM_TIMEOUT_SECONDS` | `1200` | Seconds the bot waits in the lobby before giving up and leaving. |
|
||||
| `CALL_RECORDER_NOONE_JOINED_TIMEOUT_SECONDS` | `1200` | Seconds the bot stays in an empty meeting when no one else ever joins. |
|
||||
| `CALL_RECORDER_EVERYONE_LEFT_TIMEOUT_SECONDS` | `2` | Seconds the bot keeps recording after everyone else leaves. |
|
||||
|
||||
### Configuring the Recall webhook
|
||||
|
||||
@@ -157,7 +157,7 @@ Use this URL on your deployment, replacing only the host:
|
||||
https://<your-twenty-host>/webhooks/server/8da4b8b5-5edf-4880-b51f-ab6e679ec617/9215afe6-1497-4149-a49d-e608e239bbaf
|
||||
```
|
||||
|
||||
The first ID is the **Twenty Meeting Bot application registration**. The second
|
||||
The first ID is the **Call Recorder application registration**. The second
|
||||
ID is the **Recall webhook logic function**.
|
||||
|
||||
1. In the Recall.ai dashboard, create a webhook endpoint pointing at your
|
||||
@@ -168,6 +168,6 @@ ID is the **Recall webhook logic function**.
|
||||
transcript events trigger media and transcript ingestion. Subscribing to
|
||||
status changes alone leaves ingestion to the reconciliation backstop.
|
||||
2. Copy the endpoint's signing secret — it starts with `whsec_`.
|
||||
3. Set it as the `RECALL_WEBHOOK_SECRET` server variable on the **Twenty
|
||||
Meeting Bot** application registration.
|
||||
3. Set it as the `RECALL_WEBHOOK_SECRET` server variable on the
|
||||
**Call Recorder** application registration.
|
||||
4. Set `RECALL_API_KEY` (and optionally `RECALL_REGION`) the same way.
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "twenty-meeting-bot",
|
||||
"name": "@twentyhq/call-recorder",
|
||||
"version": "0.1.4",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
+2
-3
@@ -115,9 +115,8 @@ type GeneratedCoreSchemaRuntime = {
|
||||
};
|
||||
|
||||
const getServerCallRecordingStatuses = async (): Promise<string[]> => {
|
||||
const generatedCoreSchema = (await import(
|
||||
'twenty-client-sdk/core'
|
||||
)) as unknown as GeneratedCoreSchemaRuntime;
|
||||
const generatedCoreSchema =
|
||||
(await import('twenty-client-sdk/core')) as unknown as GeneratedCoreSchemaRuntime;
|
||||
|
||||
return Object.values(generatedCoreSchema.enumCallRecordingStatusEnum);
|
||||
};
|
||||
@@ -0,0 +1,96 @@
|
||||
import { defineApplication } from 'twenty-sdk/define';
|
||||
|
||||
import { APP_DESCRIPTION } from 'src/constants/app-description';
|
||||
import { APP_DISPLAY_NAME } from 'src/constants/app-display-name';
|
||||
import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/constants/application-universal-identifier';
|
||||
import { CALL_RECORDER_EVERYONE_LEFT_TIMEOUT_SECONDS_APP_VARIABLE_UNIVERSAL_IDENTIFIER } from 'src/constants/call-recorder-everyone-left-timeout-seconds-app-variable-universal-identifier';
|
||||
import { CALL_RECORDER_JOIN_EARLY_MINUTES_APP_VARIABLE_UNIVERSAL_IDENTIFIER } from 'src/constants/call-recorder-join-early-minutes-app-variable-universal-identifier';
|
||||
import { CALL_RECORDER_NAME_APP_VARIABLE_UNIVERSAL_IDENTIFIER } from 'src/constants/call-recorder-name-app-variable-universal-identifier';
|
||||
import { CALL_RECORDER_NOONE_JOINED_TIMEOUT_SECONDS_APP_VARIABLE_UNIVERSAL_IDENTIFIER } from 'src/constants/call-recorder-noone-joined-timeout-seconds-app-variable-universal-identifier';
|
||||
import { CALL_RECORDER_WAITING_ROOM_TIMEOUT_SECONDS_APP_VARIABLE_UNIVERSAL_IDENTIFIER } from 'src/constants/call-recorder-waiting-room-timeout-seconds-app-variable-universal-identifier';
|
||||
import { CALL_RECORDER_EVERYONE_LEFT_TIMEOUT_SECONDS } from 'src/logic-functions/constants/call-recorder-everyone-left-timeout-seconds';
|
||||
import { CALL_RECORDER_EVERYONE_LEFT_TIMEOUT_SECONDS_ENV_VAR_NAME } from 'src/logic-functions/constants/call-recorder-everyone-left-timeout-seconds-env-var-name';
|
||||
import { CALL_RECORDER_JOIN_EARLY_MINUTES_ENV_VAR_NAME } from 'src/logic-functions/constants/call-recorder-join-early-minutes-env-var-name';
|
||||
import { CALL_RECORDER_NAME_ENV_VAR_NAME } from 'src/logic-functions/constants/call-recorder-name-env-var-name';
|
||||
import { CALL_RECORDER_NOONE_JOINED_TIMEOUT_SECONDS } from 'src/logic-functions/constants/call-recorder-noone-joined-timeout-seconds';
|
||||
import { CALL_RECORDER_NOONE_JOINED_TIMEOUT_SECONDS_ENV_VAR_NAME } from 'src/logic-functions/constants/call-recorder-noone-joined-timeout-seconds-env-var-name';
|
||||
import { CALL_RECORDER_RECORDING_RETENTION_HOURS_ENV_VAR_NAME } from 'src/logic-functions/constants/call-recorder-recording-retention-hours-env-var-name';
|
||||
import { CALL_RECORDER_WAITING_ROOM_TIMEOUT_SECONDS } from 'src/logic-functions/constants/call-recorder-waiting-room-timeout-seconds';
|
||||
import { CALL_RECORDER_WAITING_ROOM_TIMEOUT_SECONDS_ENV_VAR_NAME } from 'src/logic-functions/constants/call-recorder-waiting-room-timeout-seconds-env-var-name';
|
||||
import { DEFAULT_CALL_RECORDER_JOIN_EARLY_MINUTES } from 'src/logic-functions/constants/default-call-recorder-join-early-minutes';
|
||||
import { DEFAULT_CALL_RECORDER_NAME } from 'src/logic-functions/constants/default-call-recorder-name';
|
||||
import { DEFAULT_CALL_RECORDER_RECORDING_RETENTION_HOURS } from 'src/logic-functions/constants/default-call-recorder-recording-retention-hours';
|
||||
import { DEFAULT_RECALL_REGION } from 'src/logic-functions/constants/default-recall-region';
|
||||
import { RECALL_API_KEY_ENV_VAR_NAME } from 'src/logic-functions/constants/recall-api-key-env-var-name';
|
||||
import { RECALL_REGION_ENV_VAR_NAME } from 'src/logic-functions/constants/recall-region-env-var-name';
|
||||
import { RECALL_WEBHOOK_SECRET_ENV_VAR_NAME } from 'src/logic-functions/constants/recall-webhook-secret-env-var-name';
|
||||
|
||||
export default defineApplication({
|
||||
universalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
|
||||
displayName: APP_DISPLAY_NAME,
|
||||
description: APP_DESCRIPTION,
|
||||
logoUrl: 'public/logo.svg',
|
||||
screenshots: ['public/gallery/call-recorder-cover.png'],
|
||||
applicationVariables: {
|
||||
[CALL_RECORDER_NAME_ENV_VAR_NAME]: {
|
||||
universalIdentifier: CALL_RECORDER_NAME_APP_VARIABLE_UNIVERSAL_IDENTIFIER,
|
||||
description: 'Display name the call recorder uses when it joins a call.',
|
||||
isSecret: false,
|
||||
value: DEFAULT_CALL_RECORDER_NAME,
|
||||
},
|
||||
[CALL_RECORDER_JOIN_EARLY_MINUTES_ENV_VAR_NAME]: {
|
||||
universalIdentifier:
|
||||
CALL_RECORDER_JOIN_EARLY_MINUTES_APP_VARIABLE_UNIVERSAL_IDENTIFIER,
|
||||
description:
|
||||
'How many minutes before the meeting start time the bot should join. Set to 0 to join at the scheduled start time.',
|
||||
isSecret: false,
|
||||
value: String(DEFAULT_CALL_RECORDER_JOIN_EARLY_MINUTES),
|
||||
},
|
||||
[CALL_RECORDER_WAITING_ROOM_TIMEOUT_SECONDS_ENV_VAR_NAME]: {
|
||||
universalIdentifier:
|
||||
CALL_RECORDER_WAITING_ROOM_TIMEOUT_SECONDS_APP_VARIABLE_UNIVERSAL_IDENTIFIER,
|
||||
description:
|
||||
'How many seconds the bot waits in a meeting lobby before giving up and leaving.',
|
||||
isSecret: false,
|
||||
value: String(CALL_RECORDER_WAITING_ROOM_TIMEOUT_SECONDS),
|
||||
},
|
||||
[CALL_RECORDER_NOONE_JOINED_TIMEOUT_SECONDS_ENV_VAR_NAME]: {
|
||||
universalIdentifier:
|
||||
CALL_RECORDER_NOONE_JOINED_TIMEOUT_SECONDS_APP_VARIABLE_UNIVERSAL_IDENTIFIER,
|
||||
description:
|
||||
'How many seconds the bot stays in an empty meeting when no one else ever joins.',
|
||||
isSecret: false,
|
||||
value: String(CALL_RECORDER_NOONE_JOINED_TIMEOUT_SECONDS),
|
||||
},
|
||||
[CALL_RECORDER_EVERYONE_LEFT_TIMEOUT_SECONDS_ENV_VAR_NAME]: {
|
||||
universalIdentifier:
|
||||
CALL_RECORDER_EVERYONE_LEFT_TIMEOUT_SECONDS_APP_VARIABLE_UNIVERSAL_IDENTIFIER,
|
||||
description:
|
||||
'How many seconds the bot keeps recording after everyone else leaves the meeting.',
|
||||
isSecret: false,
|
||||
value: String(CALL_RECORDER_EVERYONE_LEFT_TIMEOUT_SECONDS),
|
||||
},
|
||||
},
|
||||
serverVariables: {
|
||||
[RECALL_API_KEY_ENV_VAR_NAME]: {
|
||||
description:
|
||||
'Recall.ai API key for the configured region. Set by the server admin on this registration after installation; used to create, update, and cancel scheduled recording bots.',
|
||||
isSecret: true,
|
||||
isRequired: true,
|
||||
},
|
||||
[RECALL_REGION_ENV_VAR_NAME]: {
|
||||
description: `Recall.ai region used for API requests. Defaults to ${DEFAULT_RECALL_REGION} when unset. Europe Frankfurt is eu-central-1.`,
|
||||
isSecret: false,
|
||||
},
|
||||
[CALL_RECORDER_RECORDING_RETENTION_HOURS_ENV_VAR_NAME]: {
|
||||
description: `How many hours Recall.ai retains recording media after processing. Defaults to ${DEFAULT_CALL_RECORDER_RECORDING_RETENTION_HOURS} hours (6 days and 22 hours) to stay below Recall.ai's 7-day free storage window. Values above 168 hours may incur Recall.ai storage charges.`,
|
||||
isSecret: false,
|
||||
},
|
||||
[RECALL_WEBHOOK_SECRET_ENV_VAR_NAME]: {
|
||||
description:
|
||||
'Recall.ai webhook signing secret (whsec_...). Set by the server admin from the Recall webhook endpoint settings; used to verify the Svix signature of incoming Recall webhook deliveries.',
|
||||
isSecret: true,
|
||||
isRequired: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,2 @@
|
||||
export const APP_DESCRIPTION =
|
||||
'Capture every customer conversation automatically. A call recorder joins eligible meetings and records calls for you.';
|
||||
@@ -0,0 +1 @@
|
||||
export const APP_DISPLAY_NAME = 'Call Recorder';
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export const CALL_RECORDER_EVERYONE_LEFT_TIMEOUT_SECONDS_APP_VARIABLE_UNIVERSAL_IDENTIFIER =
|
||||
'c866ddd4-fb7b-4cb4-8ad1-5599755e495c';
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export const CALL_RECORDER_FAILURE_REASON_ON_CALL_RECORDING_FIELD_UNIVERSAL_IDENTIFIER =
|
||||
'33a577e4-02f5-48bd-8bed-d365949caa72';
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export const CALL_RECORDER_JOIN_EARLY_MINUTES_APP_VARIABLE_UNIVERSAL_IDENTIFIER =
|
||||
'0568ebb2-3f64-47de-8c0d-d367dfbb7462';
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export const CALL_RECORDER_NAME_APP_VARIABLE_UNIVERSAL_IDENTIFIER =
|
||||
'c54cbacd-ad10-40b4-9056-7aaf23846d64';
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export const CALL_RECORDER_NOONE_JOINED_TIMEOUT_SECONDS_APP_VARIABLE_UNIVERSAL_IDENTIFIER =
|
||||
'241180e8-d864-4160-ad02-db44a9e8d395';
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export const CALL_RECORDER_PREFERENCE_OFF_OPTION_ID =
|
||||
'cc7de62a-08b6-46c8-aa69-f8117e7dd722';
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export const CALL_RECORDER_PREFERENCE_ON_CALENDAR_EVENT_FIELD_UNIVERSAL_IDENTIFIER =
|
||||
'8ee9444a-2437-4def-8e61-6e493862a4fd';
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export const CALL_RECORDER_PREFERENCE_ON_CALENDAR_EVENT_VIEW_FIELD_UNIVERSAL_IDENTIFIER =
|
||||
'e8c7e9c5-2b4f-4a75-ae46-ea331809106c';
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export const CALL_RECORDER_PREFERENCE_ON_OPTION_ID =
|
||||
'72431216-49c4-47c8-99af-de4c3831b0be';
|
||||
@@ -0,0 +1,4 @@
|
||||
export enum CallRecorderPreference {
|
||||
ON = 'ON',
|
||||
OFF = 'OFF',
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export const CALL_RECORDER_WAITING_ROOM_TIMEOUT_SECONDS_APP_VARIABLE_UNIVERSAL_IDENTIFIER =
|
||||
'12e4e14d-d539-4d07-b477-4773539dd20b';
|
||||
+1
-1
@@ -11,7 +11,7 @@ export default defineApplicationRole({
|
||||
universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
label: `${APP_DISPLAY_NAME} default role`,
|
||||
description:
|
||||
'Reads calendar events to decide whether the meeting bot should attend a meeting; writes the resulting CallRecording records, uploads recording media, and fills transcripts.',
|
||||
'Reads calendar events to decide whether the call recorder should attend a meeting; writes the resulting CallRecording records, uploads recording media, and fills transcripts.',
|
||||
canReadAllObjectRecords: false,
|
||||
canUpdateAllObjectRecords: false,
|
||||
canSoftDeleteAllObjectRecords: false,
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
import {
|
||||
defineField,
|
||||
FieldType,
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
|
||||
} from 'twenty-sdk/define';
|
||||
|
||||
import { CALL_RECORDER_FAILURE_REASON_ON_CALL_RECORDING_FIELD_UNIVERSAL_IDENTIFIER } from 'src/constants/call-recorder-failure-reason-on-call-recording-field-universal-identifier';
|
||||
|
||||
export default defineField({
|
||||
universalIdentifier:
|
||||
CALL_RECORDER_FAILURE_REASON_ON_CALL_RECORDING_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
objectUniversalIdentifier:
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.callRecording.universalIdentifier,
|
||||
type: FieldType.TEXT,
|
||||
name: 'callRecorderFailureReason',
|
||||
label: 'Call Recorder Failure Reason',
|
||||
description:
|
||||
'Provider-specific reason the call recorder could not produce a recording.',
|
||||
icon: 'IconAlertTriangle',
|
||||
isNullable: true,
|
||||
isUIEditable: false,
|
||||
});
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
import {
|
||||
defineField,
|
||||
FieldType,
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
|
||||
} from 'twenty-sdk/define';
|
||||
|
||||
import { CallRecorderPreference } from 'src/constants/call-recorder-preference';
|
||||
import { CALL_RECORDER_PREFERENCE_OFF_OPTION_ID } from 'src/constants/call-recorder-preference-off-option-id';
|
||||
import { CALL_RECORDER_PREFERENCE_ON_OPTION_ID } from 'src/constants/call-recorder-preference-on-option-id';
|
||||
import { CALL_RECORDER_PREFERENCE_ON_CALENDAR_EVENT_FIELD_UNIVERSAL_IDENTIFIER } from 'src/constants/call-recorder-preference-on-calendar-event-field-universal-identifier';
|
||||
|
||||
export default defineField({
|
||||
universalIdentifier:
|
||||
CALL_RECORDER_PREFERENCE_ON_CALENDAR_EVENT_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
objectUniversalIdentifier:
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.calendarEvent.universalIdentifier,
|
||||
type: FieldType.SELECT,
|
||||
name: 'callRecorderPreference',
|
||||
label: 'Recording Bot',
|
||||
description:
|
||||
'Call recording is on by default when the app is installed. Turn it off for this event when needed.',
|
||||
icon: 'IconRobot',
|
||||
isNullable: false,
|
||||
defaultValue: `'${CallRecorderPreference.ON}'`,
|
||||
options: [
|
||||
{
|
||||
id: CALL_RECORDER_PREFERENCE_ON_OPTION_ID,
|
||||
value: CallRecorderPreference.ON,
|
||||
label: 'On',
|
||||
position: 0,
|
||||
color: 'green',
|
||||
},
|
||||
{
|
||||
id: CALL_RECORDER_PREFERENCE_OFF_OPTION_ID,
|
||||
value: CallRecorderPreference.OFF,
|
||||
label: 'Off',
|
||||
position: 1,
|
||||
color: 'red',
|
||||
},
|
||||
],
|
||||
});
|
||||
+3
-3
@@ -74,9 +74,9 @@ export const TranscriptSpeakerAvatar = ({
|
||||
avatarUrl,
|
||||
placeholderColorSeed,
|
||||
}: TranscriptSpeakerAvatarProps) => {
|
||||
const [erroredAvatarUrl, setErroredAvatarUrl] = useState<
|
||||
string | undefined
|
||||
>(undefined);
|
||||
const [erroredAvatarUrl, setErroredAvatarUrl] = useState<string | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
const shouldShowAvatarImage =
|
||||
isNonEmptyString(avatarUrl) && erroredAvatarUrl !== avatarUrl;
|
||||
+1
-3
@@ -98,9 +98,7 @@ export const useCalendarEventParticipants = (
|
||||
|
||||
setCalendarEventParticipants(
|
||||
calendarEventParticipantEdges.map((calendarEventParticipantEdge) =>
|
||||
mapCalendarEventParticipantNode(
|
||||
calendarEventParticipantEdge.node,
|
||||
),
|
||||
mapCalendarEventParticipantNode(calendarEventParticipantEdge.node),
|
||||
),
|
||||
);
|
||||
} catch {
|
||||
+2
-3
@@ -93,9 +93,8 @@ export const useCalendarEventRecording = (
|
||||
const callRecordingNodes = callRecordingEdges.map(
|
||||
(callRecordingEdge) => callRecordingEdge.node,
|
||||
);
|
||||
const callRecordingNode = selectCalendarEventRecording(
|
||||
callRecordingNodes,
|
||||
);
|
||||
const callRecordingNode =
|
||||
selectCalendarEventRecording(callRecordingNodes);
|
||||
|
||||
setState({
|
||||
transcript: callRecordingNode?.transcript ?? undefined,
|
||||
+4
-4
@@ -6,8 +6,9 @@ export const getSpeakerNameMatchKeys = (speakerName: string): string[] => {
|
||||
const normalizedSpeakerName = normalizeSpeakerName(speakerName);
|
||||
const compactSpeakerName = getCompactSpeakerName(normalizedSpeakerName);
|
||||
const compactSpeakerNameWithoutDigits = compactSpeakerName.replace(/\d/g, '');
|
||||
const abbreviatedSpeakerNameMatchKey =
|
||||
getAbbreviatedSpeakerNameMatchKey(normalizedSpeakerName);
|
||||
const abbreviatedSpeakerNameMatchKey = getAbbreviatedSpeakerNameMatchKey(
|
||||
normalizedSpeakerName,
|
||||
);
|
||||
|
||||
return [
|
||||
...new Set(
|
||||
@@ -50,8 +51,7 @@ const getAbbreviatedSpeakerNameMatchKey = (
|
||||
4,
|
||||
)}${lastSpeakerNamePart.slice(0, 4)}`;
|
||||
|
||||
return abbreviatedSpeakerNameMatchKey.length >=
|
||||
MINIMUM_FUZZY_MATCH_KEY_LENGTH
|
||||
return abbreviatedSpeakerNameMatchKey.length >= MINIMUM_FUZZY_MATCH_KEY_LENGTH
|
||||
? abbreviatedSpeakerNameMatchKey
|
||||
: undefined;
|
||||
};
|
||||
+1
-3
@@ -37,9 +37,7 @@ const readTranscriptWord = (
|
||||
};
|
||||
};
|
||||
|
||||
const readSpeakerName = (
|
||||
participant: TranscriptRecord | undefined,
|
||||
): string => {
|
||||
const readSpeakerName = (participant: TranscriptRecord | undefined): string => {
|
||||
const name = participant?.name;
|
||||
|
||||
return isNonEmptyString(name) ? name.trim() : 'Unknown speaker';
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export const CALL_RECORDER_EVERYONE_LEFT_TIMEOUT_SECONDS_ENV_VAR_NAME =
|
||||
'CALL_RECORDER_EVERYONE_LEFT_TIMEOUT_SECONDS';
|
||||
+1
@@ -0,0 +1 @@
|
||||
export const CALL_RECORDER_EVERYONE_LEFT_TIMEOUT_SECONDS = 2;
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export const CALL_RECORDER_JOIN_EARLY_MINUTES_ENV_VAR_NAME =
|
||||
'CALL_RECORDER_JOIN_EARLY_MINUTES';
|
||||
+1
@@ -0,0 +1 @@
|
||||
export const CALL_RECORDER_NAME_ENV_VAR_NAME = 'CALL_RECORDER_NAME';
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export const CALL_RECORDER_NOONE_JOINED_TIMEOUT_SECONDS_ENV_VAR_NAME =
|
||||
'CALL_RECORDER_NOONE_JOINED_TIMEOUT_SECONDS';
|
||||
+1
@@ -0,0 +1 @@
|
||||
export const CALL_RECORDER_NOONE_JOINED_TIMEOUT_SECONDS = 20 * 60;
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export const CALL_RECORDER_RECORDING_RETENTION_HOURS_ENV_VAR_NAME =
|
||||
'CALL_RECORDER_RECORDING_RETENTION_HOURS';
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export const CALL_RECORDER_WAITING_ROOM_TIMEOUT_SECONDS_ENV_VAR_NAME =
|
||||
'CALL_RECORDER_WAITING_ROOM_TIMEOUT_SECONDS';
|
||||
+1
@@ -0,0 +1 @@
|
||||
export const CALL_RECORDER_WAITING_ROOM_TIMEOUT_SECONDS = 1200;
|
||||
+1
@@ -0,0 +1 @@
|
||||
export const DEFAULT_CALL_RECORDER_JOIN_EARLY_MINUTES = 1;
|
||||
+1
@@ -0,0 +1 @@
|
||||
export const DEFAULT_CALL_RECORDER_NAME = 'Twenty.com';
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
// Twenty stores ingested recording artifacts, so Recall.ai media is temporary. Keep the default below Recall.ai's 168-hour free storage window.
|
||||
export const DEFAULT_MEETING_BOT_RECORDING_RETENTION_HOURS = 166;
|
||||
export const DEFAULT_CALL_RECORDER_RECORDING_RETENTION_HOURS = 166;
|
||||
+6
-6
@@ -1,8 +1,8 @@
|
||||
import { isUndefined } from '@sniptt/guards';
|
||||
|
||||
import { MEETING_BOT_EVERYONE_LEFT_TIMEOUT_SECONDS_ENV_VAR_NAME } from 'src/logic-functions/constants/meeting-bot-everyone-left-timeout-seconds-env-var-name';
|
||||
import { MEETING_BOT_NOONE_JOINED_TIMEOUT_SECONDS_ENV_VAR_NAME } from 'src/logic-functions/constants/meeting-bot-noone-joined-timeout-seconds-env-var-name';
|
||||
import { MEETING_BOT_WAITING_ROOM_TIMEOUT_SECONDS_ENV_VAR_NAME } from 'src/logic-functions/constants/meeting-bot-waiting-room-timeout-seconds-env-var-name';
|
||||
import { CALL_RECORDER_EVERYONE_LEFT_TIMEOUT_SECONDS_ENV_VAR_NAME } from 'src/logic-functions/constants/call-recorder-everyone-left-timeout-seconds-env-var-name';
|
||||
import { CALL_RECORDER_NOONE_JOINED_TIMEOUT_SECONDS_ENV_VAR_NAME } from 'src/logic-functions/constants/call-recorder-noone-joined-timeout-seconds-env-var-name';
|
||||
import { CALL_RECORDER_WAITING_ROOM_TIMEOUT_SECONDS_ENV_VAR_NAME } from 'src/logic-functions/constants/call-recorder-waiting-room-timeout-seconds-env-var-name';
|
||||
import { RECALL_BOT_EVERYONE_LEFT_MIN_ACTIVATE_AFTER_SECONDS } from 'src/logic-functions/constants/recall-bot-everyone-left-min-activate-after-seconds';
|
||||
import { getApplicationVariableValue } from 'src/logic-functions/utils/get-application-variable-value.util';
|
||||
import { isNonEmptyString } from 'src/logic-functions/utils/is-non-empty-string.util';
|
||||
@@ -20,13 +20,13 @@ export const getRecallBotAutomaticLeave = ():
|
||||
| RecallBotAutomaticLeave
|
||||
| undefined => {
|
||||
const waitingRoomTimeoutSeconds = getOptionalPositiveIntegerVariable(
|
||||
MEETING_BOT_WAITING_ROOM_TIMEOUT_SECONDS_ENV_VAR_NAME,
|
||||
CALL_RECORDER_WAITING_ROOM_TIMEOUT_SECONDS_ENV_VAR_NAME,
|
||||
);
|
||||
const nooneJoinedTimeoutSeconds = getOptionalPositiveIntegerVariable(
|
||||
MEETING_BOT_NOONE_JOINED_TIMEOUT_SECONDS_ENV_VAR_NAME,
|
||||
CALL_RECORDER_NOONE_JOINED_TIMEOUT_SECONDS_ENV_VAR_NAME,
|
||||
);
|
||||
const everyoneLeftTimeoutSeconds = getOptionalPositiveIntegerVariable(
|
||||
MEETING_BOT_EVERYONE_LEFT_TIMEOUT_SECONDS_ENV_VAR_NAME,
|
||||
CALL_RECORDER_EVERYONE_LEFT_TIMEOUT_SECONDS_ENV_VAR_NAME,
|
||||
);
|
||||
|
||||
const automaticLeave: RecallBotAutomaticLeave = {};
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
import { DEFAULT_MEETING_BOT_RECORDING_RETENTION_HOURS } from 'src/logic-functions/constants/default-meeting-bot-recording-retention-hours';
|
||||
import { MEETING_BOT_RECORDING_RETENTION_HOURS_ENV_VAR_NAME } from 'src/logic-functions/constants/meeting-bot-recording-retention-hours-env-var-name';
|
||||
import { CALL_RECORDER_RECORDING_RETENTION_HOURS_ENV_VAR_NAME } from 'src/logic-functions/constants/call-recorder-recording-retention-hours-env-var-name';
|
||||
import { DEFAULT_CALL_RECORDER_RECORDING_RETENTION_HOURS } from 'src/logic-functions/constants/default-call-recorder-recording-retention-hours';
|
||||
import { getApplicationVariableValue } from 'src/logic-functions/utils/get-application-variable-value.util';
|
||||
import { isNonEmptyString } from 'src/logic-functions/utils/is-non-empty-string.util';
|
||||
|
||||
@@ -12,7 +12,7 @@ type RecallBotRecordingConfig = {
|
||||
// Recall only produces artifacts declared at bot creation; both gate COMPLETED.
|
||||
export const getRecallBotRecordingConfig = (): RecallBotRecordingConfig => {
|
||||
const configuredRecordingRetentionHours = getApplicationVariableValue(
|
||||
MEETING_BOT_RECORDING_RETENTION_HOURS_ENV_VAR_NAME,
|
||||
CALL_RECORDER_RECORDING_RETENTION_HOURS_ENV_VAR_NAME,
|
||||
);
|
||||
|
||||
const recordingRetentionHours = isNonEmptyString(
|
||||
@@ -24,7 +24,7 @@ export const getRecallBotRecordingConfig = (): RecallBotRecordingConfig => {
|
||||
const resolvedRecordingRetentionHours =
|
||||
Number.isInteger(recordingRetentionHours) && recordingRetentionHours > 0
|
||||
? recordingRetentionHours
|
||||
: DEFAULT_MEETING_BOT_RECORDING_RETENTION_HOURS;
|
||||
: DEFAULT_CALL_RECORDER_RECORDING_RETENTION_HOURS;
|
||||
|
||||
return {
|
||||
video_mixed_mp4: {},
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user