From 545de9947653d823e3c74cab2be44e66862c3cb2 Mon Sep 17 00:00:00 2001 From: nitin <142569587+ehconitin@users.noreply.github.com> Date: Thu, 9 Jul 2026 00:52:20 +0530 Subject: [PATCH] [Twenty Fireflies] Add calendar event summary and transcript UI components (#22667) ## Summary Add comprehensive UI components and hooks for displaying call recording summaries and transcripts on calendar event pages. This includes markdown parsing for summaries, diarized transcript rendering, and data fetching hooks integrated with the Twenty SDK. ## Key Changes ### New Hooks - `useCalendarEventSummary`: Fetches and manages summary markdown for a calendar event's call recordings - `useCalendarEventTranscript`: Fetches and manages transcript data for a calendar event's call recordings ### Summary Components - `CalendarEventSummary`: Top-level component that displays summary for selected calendar event - `CalendarEventSummaryContent`: Container with header and content frame - `CalendarEventSummaryBody`: Handles loading, error, and empty states - `SummaryMarkdown`: Renders parsed markdown with support for headings, lists, and paragraphs - `SummaryInlineSegments`: Renders inline text with bold formatting support ### Transcript Components - `CalendarEventTranscript`: Top-level component that displays transcript for selected calendar event - `CalendarEventTranscriptContent`: Container with header and scrollable content frame - `CalendarEventTranscriptBody`: Handles loading, error, and empty states - `TranscriptEntryList`: Renders list of transcript entries - `TranscriptEntryListItem`: Individual transcript entry with speaker avatar, timestamp, and text - `TranscriptErrorBox`: Styled error state display ### Utilities - `parseSummaryMarkdownBlocks`: Parses markdown into structured blocks (headings, lists, paragraphs) - `parseSummaryInlineSegments`: Parses inline markdown for bold text formatting - `parseTranscriptEntries`: Parses diarized transcript format into structured entries with speaker info and timestamps - `formatSecondsAsClockTimestamp`: Formats seconds into HH:MM:SS or MM:SS format - `asRecord`: Type guard utility for converting values to records ### Page Layout Configuration - `calendar-event-summary-tab.ts`: Defines Summary tab for calendar event page layout - `calendar-event-transcript-tab.ts`: Defines Transcript tab for calendar event page layout - Front component definitions for both summary and transcript ### Types - `TranscriptEntry` and `TranscriptWord`: Structured transcript data types - `SummaryMarkdownBlock`: Block-level markdown structure - `SummaryInlineSegment`: Inline text segment with formatting ## Implementation Details - Uses `CoreApiClient` from Twenty SDK to query call recordings filtered by calendar event ID - Implements proper cleanup with cancellation tokens to prevent state updates on unmounted components - Supports both loading and error states with user-friendly messaging - Markdown parser handles headings (h1-h6), bullet lists, and paragraphs with inline bold formatting - Transcript parser validates diarized format and gracefully handles malformed entries - Styled with emotion and theme constants from twenty-ui for consistent design - Integrates with `useSelectedRecordIds` hook to track selected calendar event https://github.com/user-attachments/assets/0bd63590-bb1e-4f89-8b96-e3fbb659473e Review in cubic --- .../public/twenty-fireflies/package.json | 6 + .../src/constants/universal-identifiers.ts | 22 + ...calendar-event-summary.front-component.tsx | 13 + ...endar-event-transcript.front-component.tsx | 13 + .../components/CalendarEventSummary.tsx | 39 + .../components/CalendarEventSummaryBody.tsx | 53 ++ .../CalendarEventSummaryContent.tsx | 77 ++ .../components/CalendarEventTranscript.tsx | 39 + .../CalendarEventTranscriptBody.tsx | 74 ++ .../CalendarEventTranscriptContent.tsx | 84 ++ .../components/SummaryInlineSegments.tsx | 28 + .../components/SummaryMarkdown.tsx | 87 ++ .../components/TranscriptEntryList.tsx | 25 + .../components/TranscriptEntryListItem.tsx | 76 ++ .../components/TranscriptErrorBox.tsx | 47 + .../hooks/use-calendar-event-summary.ts | 115 +++ .../hooks/use-calendar-event-transcript.ts | 115 +++ .../types/summary-inline-segment.type.ts | 4 + .../types/summary-markdown-block.type.ts | 6 + .../types/transcript-entry.type.ts | 13 + .../parse-summary-inline-segments.test.ts | 33 + .../parse-summary-markdown-blocks.test.ts | 35 + .../parse-transcript-entries.test.ts | 50 ++ .../front-components/utils/as-record.util.ts | 6 + .../format-seconds-as-clock-timestamp.util.ts | 20 + .../utils/is-non-empty-string.util.ts | 5 + .../parse-summary-inline-segments.util.ts | 39 + .../parse-summary-markdown-blocks.util.ts | 58 ++ .../utils/parse-transcript-entries.util.ts | 85 ++ .../calendar-event-summary-tab.ts | 35 + .../calendar-event-transcript-tab.ts | 35 + .../public/twenty-fireflies/yarn.lock | 818 +++++++++++++++++- 32 files changed, 2149 insertions(+), 6 deletions(-) create mode 100644 packages/twenty-apps/public/twenty-fireflies/src/front-components/calendar-event-summary.front-component.tsx create mode 100644 packages/twenty-apps/public/twenty-fireflies/src/front-components/calendar-event-transcript.front-component.tsx create mode 100644 packages/twenty-apps/public/twenty-fireflies/src/front-components/components/CalendarEventSummary.tsx create mode 100644 packages/twenty-apps/public/twenty-fireflies/src/front-components/components/CalendarEventSummaryBody.tsx create mode 100644 packages/twenty-apps/public/twenty-fireflies/src/front-components/components/CalendarEventSummaryContent.tsx create mode 100644 packages/twenty-apps/public/twenty-fireflies/src/front-components/components/CalendarEventTranscript.tsx create mode 100644 packages/twenty-apps/public/twenty-fireflies/src/front-components/components/CalendarEventTranscriptBody.tsx create mode 100644 packages/twenty-apps/public/twenty-fireflies/src/front-components/components/CalendarEventTranscriptContent.tsx create mode 100644 packages/twenty-apps/public/twenty-fireflies/src/front-components/components/SummaryInlineSegments.tsx create mode 100644 packages/twenty-apps/public/twenty-fireflies/src/front-components/components/SummaryMarkdown.tsx create mode 100644 packages/twenty-apps/public/twenty-fireflies/src/front-components/components/TranscriptEntryList.tsx create mode 100644 packages/twenty-apps/public/twenty-fireflies/src/front-components/components/TranscriptEntryListItem.tsx create mode 100644 packages/twenty-apps/public/twenty-fireflies/src/front-components/components/TranscriptErrorBox.tsx create mode 100644 packages/twenty-apps/public/twenty-fireflies/src/front-components/hooks/use-calendar-event-summary.ts create mode 100644 packages/twenty-apps/public/twenty-fireflies/src/front-components/hooks/use-calendar-event-transcript.ts create mode 100644 packages/twenty-apps/public/twenty-fireflies/src/front-components/types/summary-inline-segment.type.ts create mode 100644 packages/twenty-apps/public/twenty-fireflies/src/front-components/types/summary-markdown-block.type.ts create mode 100644 packages/twenty-apps/public/twenty-fireflies/src/front-components/types/transcript-entry.type.ts create mode 100644 packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/__tests__/parse-summary-inline-segments.test.ts create mode 100644 packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/__tests__/parse-summary-markdown-blocks.test.ts create mode 100644 packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/__tests__/parse-transcript-entries.test.ts create mode 100644 packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/as-record.util.ts create mode 100644 packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/format-seconds-as-clock-timestamp.util.ts create mode 100644 packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/is-non-empty-string.util.ts create mode 100644 packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/parse-summary-inline-segments.util.ts create mode 100644 packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/parse-summary-markdown-blocks.util.ts create mode 100644 packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/parse-transcript-entries.util.ts create mode 100644 packages/twenty-apps/public/twenty-fireflies/src/page-layouts/calendar-event-summary-tab.ts create mode 100644 packages/twenty-apps/public/twenty-fireflies/src/page-layouts/calendar-event-transcript-tab.ts diff --git a/packages/twenty-apps/public/twenty-fireflies/package.json b/packages/twenty-apps/public/twenty-fireflies/package.json index b7dcff7a29..9bb2dd528f 100644 --- a/packages/twenty-apps/public/twenty-fireflies/package.json +++ b/packages/twenty-apps/public/twenty-fireflies/package.json @@ -21,7 +21,12 @@ "test:watch": "vitest", "test:unit": "vitest run --config vitest.unit.config.ts" }, + "dependencies": { + "@sniptt/guards": "^0.2.0" + }, "devDependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", "@types/node": "^24.7.2", "@types/react": "^18.2.0", "@typescript/native-preview": "^7.0.0-dev.20260116.1", @@ -30,6 +35,7 @@ "react-dom": "^18.2.0", "twenty-client-sdk": "^2.18.0", "twenty-sdk": "^2.18.0", + "twenty-ui": "^1.0.0-alpha.1", "typescript": "^5.9.3", "vite-tsconfig-paths": "^4.2.1", "vitest": "^4.0.0" diff --git a/packages/twenty-apps/public/twenty-fireflies/src/constants/universal-identifiers.ts b/packages/twenty-apps/public/twenty-fireflies/src/constants/universal-identifiers.ts index a8adca1945..d94354baa6 100644 --- a/packages/twenty-apps/public/twenty-fireflies/src/constants/universal-identifiers.ts +++ b/packages/twenty-apps/public/twenty-fireflies/src/constants/universal-identifiers.ts @@ -21,3 +21,25 @@ export const FIREFLIES_LIST_CALLS_BY_PARTICIPANT_UNIVERSAL_IDENTIFIER = export const FIREFLIES_SEARCH_CALLS_UNIVERSAL_IDENTIFIER = 'cb2dd01d-8dca-4222-acce-1d1dbdef9146'; + +// Shared core CalendarEvent record page layout that apps attach tabs to. +export const CALENDAR_EVENT_RECORD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER = + 'b9b10e40-9ce2-4704-8ac6-c6e92e2563c1'; + +export const CALENDAR_EVENT_SUMMARY_PAGE_LAYOUT_TAB_UNIVERSAL_IDENTIFIER = + 'f4fed96d-1551-40dc-a0c1-0b4d2a782733'; + +export const CALENDAR_EVENT_SUMMARY_PAGE_LAYOUT_WIDGET_UNIVERSAL_IDENTIFIER = + '240925c4-7599-44e1-84ee-4330d450a736'; + +export const CALENDAR_EVENT_SUMMARY_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER = + '64bd540d-f611-4f9e-8b8d-ae8443c310cd'; + +export const CALENDAR_EVENT_TRANSCRIPT_PAGE_LAYOUT_TAB_UNIVERSAL_IDENTIFIER = + '02acda71-77e0-4176-a134-c79f40510419'; + +export const CALENDAR_EVENT_TRANSCRIPT_PAGE_LAYOUT_WIDGET_UNIVERSAL_IDENTIFIER = + 'ac9d7c53-15f8-4c89-85ce-f6451ace2eb2'; + +export const CALENDAR_EVENT_TRANSCRIPT_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER = + '364642a6-addb-4df1-8c57-25228b826259'; diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/calendar-event-summary.front-component.tsx b/packages/twenty-apps/public/twenty-fireflies/src/front-components/calendar-event-summary.front-component.tsx new file mode 100644 index 0000000000..0bc1271c21 --- /dev/null +++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/calendar-event-summary.front-component.tsx @@ -0,0 +1,13 @@ +import { defineFrontComponent } from 'twenty-sdk/define'; + +import { CALENDAR_EVENT_SUMMARY_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers'; +import { CalendarEventSummary } from 'src/front-components/components/CalendarEventSummary'; + +export default defineFrontComponent({ + universalIdentifier: + CALENDAR_EVENT_SUMMARY_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, + name: 'calendar-event-summary', + description: + 'Read-only Fireflies AI summary viewer for the calendar event record page.', + component: CalendarEventSummary, +}); diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/calendar-event-transcript.front-component.tsx b/packages/twenty-apps/public/twenty-fireflies/src/front-components/calendar-event-transcript.front-component.tsx new file mode 100644 index 0000000000..46e8e4abc5 --- /dev/null +++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/calendar-event-transcript.front-component.tsx @@ -0,0 +1,13 @@ +import { defineFrontComponent } from 'twenty-sdk/define'; + +import { CALENDAR_EVENT_TRANSCRIPT_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers'; +import { CalendarEventTranscript } from 'src/front-components/components/CalendarEventTranscript'; + +export default defineFrontComponent({ + universalIdentifier: + CALENDAR_EVENT_TRANSCRIPT_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, + name: 'calendar-event-transcript', + description: + 'Read-only diarized Fireflies transcript viewer for the calendar event record page.', + component: CalendarEventTranscript, +}); diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/CalendarEventSummary.tsx b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/CalendarEventSummary.tsx new file mode 100644 index 0000000000..ffeed13539 --- /dev/null +++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/CalendarEventSummary.tsx @@ -0,0 +1,39 @@ +import styled from '@emotion/styled'; +import { isUndefined } from '@sniptt/guards'; +import { useSelectedRecordIds } from 'twenty-sdk/front-component'; +import { themeCssVariables } from 'twenty-ui/theme-constants'; + +import { CalendarEventSummaryContent } from 'src/front-components/components/CalendarEventSummaryContent'; + +const StyledCenteredState = styled.div` + align-items: center; + box-sizing: border-box; + color: ${() => themeCssVariables.font.color.tertiary}; + display: flex; + font-family: ${() => themeCssVariables.font.family}; + font-size: ${() => themeCssVariables.font.size.sm}; + height: 100%; + justify-content: center; + padding: ${() => themeCssVariables.spacing[4]}; +`; + +export const CalendarEventSummary = () => { + const selectedRecordIds = useSelectedRecordIds(); + const calendarEventId = + selectedRecordIds.length === 1 ? selectedRecordIds[0] : undefined; + + if (isUndefined(calendarEventId)) { + return ( + + Open a calendar event to see its summary. + + ); + } + + return ( + + ); +}; diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/CalendarEventSummaryBody.tsx b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/CalendarEventSummaryBody.tsx new file mode 100644 index 0000000000..4cd4417862 --- /dev/null +++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/CalendarEventSummaryBody.tsx @@ -0,0 +1,53 @@ +import styled from '@emotion/styled'; +import { isUndefined } from '@sniptt/guards'; +import { themeCssVariables } from 'twenty-ui/theme-constants'; + +import { SummaryMarkdown } from 'src/front-components/components/SummaryMarkdown'; +import { TranscriptErrorBox } from 'src/front-components/components/TranscriptErrorBox'; + +const StyledCenteredState = styled.div` + align-items: center; + box-sizing: border-box; + color: ${() => themeCssVariables.font.color.tertiary}; + display: flex; + font-family: ${() => themeCssVariables.font.family}; + font-size: ${() => themeCssVariables.font.size.sm}; + justify-content: center; + min-height: 240px; + padding: ${() => themeCssVariables.spacing[4]}; +`; + +type CalendarEventSummaryBodyProps = { + summaryMarkdown: string | undefined; + isCalendarEventSummaryQueryLoading: boolean; + errorMessage: string | undefined; +}; + +export const CalendarEventSummaryBody = ({ + summaryMarkdown, + isCalendarEventSummaryQueryLoading, + errorMessage, +}: CalendarEventSummaryBodyProps) => { + if (!isUndefined(errorMessage)) { + return ( + + ); + } + + if (isCalendarEventSummaryQueryLoading) { + return Loading summary…; + } + + if (isUndefined(summaryMarkdown)) { + return ( + + No summary for this calendar event yet. + + ); + } + + return ; +}; diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/CalendarEventSummaryContent.tsx b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/CalendarEventSummaryContent.tsx new file mode 100644 index 0000000000..6656ff3067 --- /dev/null +++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/CalendarEventSummaryContent.tsx @@ -0,0 +1,77 @@ +import styled from '@emotion/styled'; +import { themeCssVariables } from 'twenty-ui/theme-constants'; + +import { CalendarEventSummaryBody } from 'src/front-components/components/CalendarEventSummaryBody'; +import { useCalendarEventSummary } from 'src/front-components/hooks/use-calendar-event-summary'; + +const StyledSummaryShell = styled.div` + background: ${() => themeCssVariables.background.primary}; + border: 1px solid transparent; + border-radius: ${() => themeCssVariables.border.radius.md}; + box-sizing: border-box; + font-family: ${() => themeCssVariables.font.family}; + padding: ${() => themeCssVariables.spacing[4]}; + position: relative; + width: 100%; +`; + +const StyledSummaryHeader = styled.div` + align-items: center; + box-sizing: border-box; + display: flex; + height: ${() => themeCssVariables.spacing[6]}; +`; + +const StyledSummaryTitle = styled.h2` + color: ${() => themeCssVariables.font.color.primary}; + flex: 1; + font-size: ${() => themeCssVariables.font.size.md}; + font-weight: ${() => themeCssVariables.font.weight.medium}; + margin: 0; + overflow: hidden; + padding-inline: ${() => themeCssVariables.spacing[1]}; + user-select: none; +`; + +const StyledSummaryBody = styled.div` + box-sizing: border-box; + margin-top: ${() => themeCssVariables.spacing[2]}; +`; + +const StyledSummaryContentFrame = styled.div` + background-color: ${() => themeCssVariables.background.secondary}; + border: 1px solid ${() => themeCssVariables.border.color.medium}; + border-radius: ${() => themeCssVariables.border.radius.md}; + box-sizing: border-box; + padding: ${() => themeCssVariables.spacing[3]}; +`; + +type CalendarEventSummaryContentProps = { + calendarEventId: string; +}; + +export const CalendarEventSummaryContent = ({ + calendarEventId, +}: CalendarEventSummaryContentProps) => { + const { summaryMarkdown, isCalendarEventSummaryQueryLoading, errorMessage } = + useCalendarEventSummary(calendarEventId); + + return ( + + + Summary + + + + + + + + ); +}; diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/CalendarEventTranscript.tsx b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/CalendarEventTranscript.tsx new file mode 100644 index 0000000000..e58f620555 --- /dev/null +++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/CalendarEventTranscript.tsx @@ -0,0 +1,39 @@ +import styled from '@emotion/styled'; +import { isUndefined } from '@sniptt/guards'; +import { useSelectedRecordIds } from 'twenty-sdk/front-component'; +import { themeCssVariables } from 'twenty-ui/theme-constants'; + +import { CalendarEventTranscriptContent } from 'src/front-components/components/CalendarEventTranscriptContent'; + +const StyledCenteredState = styled.div` + align-items: center; + box-sizing: border-box; + color: ${() => themeCssVariables.font.color.tertiary}; + display: flex; + font-family: ${() => themeCssVariables.font.family}; + font-size: ${() => themeCssVariables.font.size.sm}; + height: 100%; + justify-content: center; + padding: ${() => themeCssVariables.spacing[4]}; +`; + +export const CalendarEventTranscript = () => { + const selectedRecordIds = useSelectedRecordIds(); + const calendarEventId = + selectedRecordIds.length === 1 ? selectedRecordIds[0] : undefined; + + if (isUndefined(calendarEventId)) { + return ( + + Open a calendar event to see its transcript. + + ); + } + + return ( + + ); +}; diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/CalendarEventTranscriptBody.tsx b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/CalendarEventTranscriptBody.tsx new file mode 100644 index 0000000000..861e861927 --- /dev/null +++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/CalendarEventTranscriptBody.tsx @@ -0,0 +1,74 @@ +import styled from '@emotion/styled'; +import { isUndefined } from '@sniptt/guards'; +import { useMemo } from 'react'; +import { themeCssVariables } from 'twenty-ui/theme-constants'; + +import { TranscriptEntryList } from 'src/front-components/components/TranscriptEntryList'; +import { TranscriptErrorBox } from 'src/front-components/components/TranscriptErrorBox'; +import { parseTranscriptEntries } from 'src/front-components/utils/parse-transcript-entries.util'; + +const StyledCenteredState = styled.div` + align-items: center; + box-sizing: border-box; + color: ${() => themeCssVariables.font.color.tertiary}; + display: flex; + flex: 1; + font-family: ${() => themeCssVariables.font.family}; + font-size: ${() => themeCssVariables.font.size.sm}; + justify-content: center; + min-height: 240px; + padding: ${() => themeCssVariables.spacing[4]}; +`; + +type CalendarEventTranscriptBodyProps = { + transcript: unknown; + isCalendarEventTranscriptQueryLoading: boolean; + errorMessage: string | undefined; +}; + +export const CalendarEventTranscriptBody = ({ + transcript, + isCalendarEventTranscriptQueryLoading, + errorMessage, +}: CalendarEventTranscriptBodyProps) => { + const entries = useMemo( + () => parseTranscriptEntries(transcript), + [transcript], + ); + + if (!isUndefined(errorMessage)) { + return ( + + ); + } + + if (isCalendarEventTranscriptQueryLoading) { + return Loading transcript…; + } + + if (isUndefined(transcript)) { + return ( + + No transcript for this calendar event yet. + + ); + } + + if (isUndefined(entries)) { + return ( + + ); + } + + if (entries.length === 0) { + return The transcript is empty.; + } + + return ; +}; diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/CalendarEventTranscriptContent.tsx b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/CalendarEventTranscriptContent.tsx new file mode 100644 index 0000000000..db370435dd --- /dev/null +++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/CalendarEventTranscriptContent.tsx @@ -0,0 +1,84 @@ +import styled from '@emotion/styled'; +import { themeCssVariables } from 'twenty-ui/theme-constants'; + +import { CalendarEventTranscriptBody } from 'src/front-components/components/CalendarEventTranscriptBody'; +import { useCalendarEventTranscript } from 'src/front-components/hooks/use-calendar-event-transcript'; + +const StyledTranscriptShell = styled.div` + background: ${() => themeCssVariables.background.primary}; + box-sizing: border-box; + display: flex; + flex-direction: column; + font-family: ${() => themeCssVariables.font.family}; + height: 100%; + padding: ${() => themeCssVariables.spacing[4]}; + width: 100%; +`; + +const StyledTranscriptHeader = styled.div` + align-items: center; + box-sizing: border-box; + display: flex; + height: ${() => themeCssVariables.spacing[6]}; +`; + +const StyledTranscriptTitle = styled.h2` + color: ${() => themeCssVariables.font.color.primary}; + flex: 1; + font-size: ${() => themeCssVariables.font.size.md}; + font-weight: ${() => themeCssVariables.font.weight.medium}; + margin: 0; + overflow: hidden; + padding-inline: ${() => themeCssVariables.spacing[1]}; + user-select: none; +`; + +const StyledTranscriptBody = styled.div` + box-sizing: border-box; + display: flex; + flex: 1; + margin-top: ${() => themeCssVariables.spacing[2]}; + min-height: 0; +`; + +const StyledTranscriptContentFrame = styled.div` + background-color: ${() => themeCssVariables.background.secondary}; + border: 1px solid ${() => themeCssVariables.border.color.medium}; + border-radius: ${() => themeCssVariables.border.radius.md}; + box-sizing: border-box; + display: flex; + flex: 1; + min-height: 0; + overflow-y: auto; + padding: ${() => themeCssVariables.spacing[3]}; +`; + +type CalendarEventTranscriptContentProps = { + calendarEventId: string; +}; + +export const CalendarEventTranscriptContent = ({ + calendarEventId, +}: CalendarEventTranscriptContentProps) => { + const { transcript, isCalendarEventTranscriptQueryLoading, errorMessage } = + useCalendarEventTranscript(calendarEventId); + + return ( + + + Transcript + + + + + + + + ); +}; diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/SummaryInlineSegments.tsx b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/SummaryInlineSegments.tsx new file mode 100644 index 0000000000..4008facb2c --- /dev/null +++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/SummaryInlineSegments.tsx @@ -0,0 +1,28 @@ +import styled from '@emotion/styled'; +import { Fragment } from 'react'; +import { themeCssVariables } from 'twenty-ui/theme-constants'; + +import { type SummaryInlineSegment } from 'src/front-components/types/summary-inline-segment.type'; + +const StyledBold = styled.strong` + color: ${() => themeCssVariables.font.color.primary}; + font-weight: ${() => themeCssVariables.font.weight.medium}; +`; + +type SummaryInlineSegmentsProps = { + segments: SummaryInlineSegment[]; +}; + +export const SummaryInlineSegments = ({ + segments, +}: SummaryInlineSegmentsProps) => ( + <> + {segments.map((segment, index) => + segment.isBold ? ( + {segment.text} + ) : ( + {segment.text} + ), + )} + +); diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/SummaryMarkdown.tsx b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/SummaryMarkdown.tsx new file mode 100644 index 0000000000..a663b22b31 --- /dev/null +++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/SummaryMarkdown.tsx @@ -0,0 +1,87 @@ +import styled from '@emotion/styled'; +import { useMemo } from 'react'; +import { themeCssVariables } from 'twenty-ui/theme-constants'; + +import { SummaryInlineSegments } from 'src/front-components/components/SummaryInlineSegments'; +import { parseSummaryMarkdownBlocks } from 'src/front-components/utils/parse-summary-markdown-blocks.util'; + +const TOP_LEVEL_HEADING_MAX = 2; + +const StyledSummary = styled.div` + color: ${() => themeCssVariables.font.color.secondary}; + display: flex; + flex-direction: column; + font-family: ${() => themeCssVariables.font.family}; + font-size: ${() => themeCssVariables.font.size.sm}; + gap: ${() => themeCssVariables.spacing[2]}; + line-height: 1.5; +`; + +const StyledHeading = styled.h3<{ $isTopLevel: boolean }>` + color: ${() => themeCssVariables.font.color.primary}; + font-size: ${({ $isTopLevel }) => + $isTopLevel + ? themeCssVariables.font.size.md + : themeCssVariables.font.size.sm}; + font-weight: ${() => themeCssVariables.font.weight.medium}; + margin: 0; + margin-top: ${() => themeCssVariables.spacing[1]}; +`; + +const StyledParagraph = styled.p` + margin: 0; +`; + +const StyledList = styled.ul` + display: flex; + flex-direction: column; + gap: ${() => themeCssVariables.spacing[1]}; + margin: 0; + padding-left: ${() => themeCssVariables.spacing[3]}; +`; + +type SummaryMarkdownProps = { + markdown: string; +}; + +export const SummaryMarkdown = ({ markdown }: SummaryMarkdownProps) => { + const blocks = useMemo( + () => parseSummaryMarkdownBlocks(markdown), + [markdown], + ); + + return ( + + {blocks.map((block, index) => { + if (block.type === 'heading') { + return ( + + + + ); + } + + if (block.type === 'list') { + return ( + + {block.items.map((item, itemIndex) => ( +
  • + +
  • + ))} +
    + ); + } + + return ( + + + + ); + })} +
    + ); +}; diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/TranscriptEntryList.tsx b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/TranscriptEntryList.tsx new file mode 100644 index 0000000000..83116e0f7a --- /dev/null +++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/TranscriptEntryList.tsx @@ -0,0 +1,25 @@ +import styled from '@emotion/styled'; +import { themeCssVariables } from 'twenty-ui/theme-constants'; + +import { TranscriptEntryListItem } from 'src/front-components/components/TranscriptEntryListItem'; +import { type TranscriptEntry } from 'src/front-components/types/transcript-entry.type'; + +const StyledTranscriptContainer = styled.div` + display: flex; + flex: 1; + flex-direction: column; + gap: ${() => themeCssVariables.spacing[2]}; + min-height: 0; +`; + +type TranscriptEntryListProps = { + entries: TranscriptEntry[]; +}; + +export const TranscriptEntryList = ({ entries }: TranscriptEntryListProps) => ( + + {entries.map((entry, entryIndex) => ( + + ))} + +); diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/TranscriptEntryListItem.tsx b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/TranscriptEntryListItem.tsx new file mode 100644 index 0000000000..ae4a81e047 --- /dev/null +++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/TranscriptEntryListItem.tsx @@ -0,0 +1,76 @@ +import styled from '@emotion/styled'; +import { isUndefined } from '@sniptt/guards'; +import { Avatar, Chip, ChipVariant } from 'twenty-ui/data-display'; +import { themeCssVariables } from 'twenty-ui/theme-constants'; + +import { type TranscriptEntry } from 'src/front-components/types/transcript-entry.type'; +import { formatSecondsAsClockTimestamp } from 'src/front-components/utils/format-seconds-as-clock-timestamp.util'; + +const StyledEntry = styled.div` + align-items: flex-start; + border-radius: ${() => themeCssVariables.border.radius.sm}; + box-sizing: border-box; + display: flex; + flex-direction: column; + gap: ${() => themeCssVariables.spacing[2]}; + justify-content: center; + padding: ${() => themeCssVariables.spacing[2]}; + width: 100%; +`; + +const StyledEntryHeader = styled.div` + align-items: center; + align-self: stretch; + display: flex; + gap: ${() => themeCssVariables.spacing[2]}; + min-height: ${() => themeCssVariables.spacing[6]}; + min-width: 0; +`; + +const StyledTimestamp = styled.span` + color: ${() => themeCssVariables.font.color.tertiary}; + font-size: ${() => themeCssVariables.font.size.xs}; + line-height: 1.4; +`; + +const StyledEntryText = styled.p` + align-self: stretch; + color: ${() => themeCssVariables.font.color.secondary}; + font-size: ${() => themeCssVariables.font.size.sm}; + line-height: 1.4; + margin: 0; +`; + +type TranscriptEntryListItemProps = { + entry: TranscriptEntry; +}; + +export const TranscriptEntryListItem = ({ + entry, +}: TranscriptEntryListItemProps) => ( + + + + } + /> + {!isUndefined(entry.startSeconds) && ( + + {formatSecondsAsClockTimestamp(entry.startSeconds)} + + )} + + {entry.text} + +); diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/TranscriptErrorBox.tsx b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/TranscriptErrorBox.tsx new file mode 100644 index 0000000000..29020b4bb7 --- /dev/null +++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/components/TranscriptErrorBox.tsx @@ -0,0 +1,47 @@ +import styled from '@emotion/styled'; +import { themeCssVariables } from 'twenty-ui/theme-constants'; + +const StyledStateContainer = styled.div` + box-sizing: border-box; + font-family: ${() => themeCssVariables.font.family}; + height: 100%; + padding: ${() => themeCssVariables.spacing[4]}; +`; + +const StyledErrorBox = styled.div` + background: ${() => themeCssVariables.background.transparent.danger}; + border: 1px solid ${() => themeCssVariables.border.color.danger}; + border-radius: ${() => themeCssVariables.border.radius.md}; + display: flex; + flex-direction: column; + gap: ${() => themeCssVariables.spacing[1]}; + padding: ${() => themeCssVariables.spacing[3]}; +`; + +const StyledErrorTitle = styled.span` + color: ${() => themeCssVariables.font.color.danger}; + font-size: ${() => themeCssVariables.font.size.sm}; + font-weight: ${() => themeCssVariables.font.weight.medium}; +`; + +const StyledErrorDescription = styled.span` + color: ${() => themeCssVariables.font.color.secondary}; + font-size: ${() => themeCssVariables.font.size.sm}; +`; + +type TranscriptErrorBoxProps = { + title: string; + description: string; +}; + +export const TranscriptErrorBox = ({ + title, + description, +}: TranscriptErrorBoxProps) => ( + + + {title} + {description} + + +); diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/hooks/use-calendar-event-summary.ts b/packages/twenty-apps/public/twenty-fireflies/src/front-components/hooks/use-calendar-event-summary.ts new file mode 100644 index 0000000000..1844244f3a --- /dev/null +++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/hooks/use-calendar-event-summary.ts @@ -0,0 +1,115 @@ +import { isNonEmptyString, isUndefined } from '@sniptt/guards'; +import { useEffect, useState } from 'react'; +import { CoreApiClient } from 'twenty-client-sdk/core'; + +type CalendarEventSummaryState = { + summaryMarkdown: string | undefined; + isCalendarEventSummaryQueryLoading: boolean; + errorMessage: string | undefined; +}; + +type CalendarEventSummaryCallRecordingNode = { + id: string; + summary: { markdown: string | null } | null; +}; + +type CalendarEventSummaryCallRecordingEdge = { + node: CalendarEventSummaryCallRecordingNode; +}; + +const CALENDAR_EVENT_SUMMARY_LOOKUP_LIMIT = 10; +const CALENDAR_EVENT_SUMMARY_ERROR_MESSAGE = 'Please try again later.'; + +const selectSummaryMarkdown = ( + callRecordingNodes: CalendarEventSummaryCallRecordingNode[], +): string | undefined => { + const summaryMarkdown = callRecordingNodes.find((callRecordingNode) => + isNonEmptyString(callRecordingNode.summary?.markdown), + )?.summary?.markdown; + + return isNonEmptyString(summaryMarkdown) ? summaryMarkdown : undefined; +}; + +export const useCalendarEventSummary = ( + calendarEventId: string | undefined, +): CalendarEventSummaryState => { + const [state, setState] = useState({ + summaryMarkdown: undefined, + isCalendarEventSummaryQueryLoading: !isUndefined(calendarEventId), + errorMessage: undefined, + }); + + useEffect(() => { + if (isUndefined(calendarEventId)) { + setState({ + summaryMarkdown: undefined, + isCalendarEventSummaryQueryLoading: false, + errorMessage: undefined, + }); + return; + } + + let cancelled = false; + + const fetchSummary = async () => { + setState({ + summaryMarkdown: undefined, + isCalendarEventSummaryQueryLoading: true, + errorMessage: undefined, + }); + + try { + const client = new CoreApiClient(); + const queryResult = await client.query({ + callRecordings: { + __args: { + filter: { calendarEventId: { eq: calendarEventId } }, + orderBy: [{ startedAt: 'DescNullsLast' }], + first: CALENDAR_EVENT_SUMMARY_LOOKUP_LIMIT, + }, + edges: { + node: { + id: true, + summary: { markdown: true }, + }, + }, + }, + }); + + if (cancelled) { + return; + } + + const callRecordingEdges = (queryResult.callRecordings?.edges ?? + []) as CalendarEventSummaryCallRecordingEdge[]; + const callRecordingNodes = callRecordingEdges.map( + (callRecordingEdge) => callRecordingEdge.node, + ); + + setState({ + summaryMarkdown: selectSummaryMarkdown(callRecordingNodes), + isCalendarEventSummaryQueryLoading: false, + errorMessage: undefined, + }); + } catch { + if (cancelled) { + return; + } + + setState({ + summaryMarkdown: undefined, + isCalendarEventSummaryQueryLoading: false, + errorMessage: CALENDAR_EVENT_SUMMARY_ERROR_MESSAGE, + }); + } + }; + + fetchSummary(); + + return () => { + cancelled = true; + }; + }, [calendarEventId]); + + return state; +}; diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/hooks/use-calendar-event-transcript.ts b/packages/twenty-apps/public/twenty-fireflies/src/front-components/hooks/use-calendar-event-transcript.ts new file mode 100644 index 0000000000..6e90efa413 --- /dev/null +++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/hooks/use-calendar-event-transcript.ts @@ -0,0 +1,115 @@ +import { isUndefined } from '@sniptt/guards'; +import { useEffect, useState } from 'react'; +import { CoreApiClient } from 'twenty-client-sdk/core'; + +type CalendarEventTranscriptState = { + transcript: unknown; + isCalendarEventTranscriptQueryLoading: boolean; + errorMessage: string | undefined; +}; + +type CalendarEventTranscriptCallRecordingNode = { + id: string; + transcript: unknown; +}; + +type CalendarEventTranscriptCallRecordingEdge = { + node: CalendarEventTranscriptCallRecordingNode; +}; + +const CALENDAR_EVENT_TRANSCRIPT_LOOKUP_LIMIT = 10; +const CALENDAR_EVENT_TRANSCRIPT_ERROR_MESSAGE = 'Please try again later.'; + +const hasTranscript = ( + callRecordingNode: CalendarEventTranscriptCallRecordingNode, +): boolean => + !isUndefined(callRecordingNode.transcript) && + callRecordingNode.transcript !== null; + +const selectTranscript = ( + callRecordingNodes: CalendarEventTranscriptCallRecordingNode[], +): unknown => callRecordingNodes.find(hasTranscript)?.transcript ?? undefined; + +export const useCalendarEventTranscript = ( + calendarEventId: string | undefined, +): CalendarEventTranscriptState => { + const [state, setState] = useState({ + transcript: undefined, + isCalendarEventTranscriptQueryLoading: !isUndefined(calendarEventId), + errorMessage: undefined, + }); + + useEffect(() => { + if (isUndefined(calendarEventId)) { + setState({ + transcript: undefined, + isCalendarEventTranscriptQueryLoading: false, + errorMessage: undefined, + }); + return; + } + + let cancelled = false; + + const fetchTranscript = async () => { + setState({ + transcript: undefined, + isCalendarEventTranscriptQueryLoading: true, + errorMessage: undefined, + }); + + try { + const client = new CoreApiClient(); + const queryResult = await client.query({ + callRecordings: { + __args: { + filter: { calendarEventId: { eq: calendarEventId } }, + orderBy: [{ startedAt: 'DescNullsLast' }], + first: CALENDAR_EVENT_TRANSCRIPT_LOOKUP_LIMIT, + }, + edges: { + node: { + id: true, + transcript: true, + }, + }, + }, + }); + + if (cancelled) { + return; + } + + const callRecordingEdges = (queryResult.callRecordings?.edges ?? + []) as CalendarEventTranscriptCallRecordingEdge[]; + const callRecordingNodes = callRecordingEdges.map( + (callRecordingEdge) => callRecordingEdge.node, + ); + + setState({ + transcript: selectTranscript(callRecordingNodes), + isCalendarEventTranscriptQueryLoading: false, + errorMessage: undefined, + }); + } catch { + if (cancelled) { + return; + } + + setState({ + transcript: undefined, + isCalendarEventTranscriptQueryLoading: false, + errorMessage: CALENDAR_EVENT_TRANSCRIPT_ERROR_MESSAGE, + }); + } + }; + + fetchTranscript(); + + return () => { + cancelled = true; + }; + }, [calendarEventId]); + + return state; +}; diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/types/summary-inline-segment.type.ts b/packages/twenty-apps/public/twenty-fireflies/src/front-components/types/summary-inline-segment.type.ts new file mode 100644 index 0000000000..474eff8e90 --- /dev/null +++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/types/summary-inline-segment.type.ts @@ -0,0 +1,4 @@ +export type SummaryInlineSegment = { + text: string; + isBold: boolean; +}; diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/types/summary-markdown-block.type.ts b/packages/twenty-apps/public/twenty-fireflies/src/front-components/types/summary-markdown-block.type.ts new file mode 100644 index 0000000000..2ba417b7d7 --- /dev/null +++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/types/summary-markdown-block.type.ts @@ -0,0 +1,6 @@ +import { type SummaryInlineSegment } from 'src/front-components/types/summary-inline-segment.type'; + +export type SummaryMarkdownBlock = + | { type: 'heading'; level: number; segments: SummaryInlineSegment[] } + | { type: 'paragraph'; segments: SummaryInlineSegment[] } + | { type: 'list'; items: SummaryInlineSegment[][] }; diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/types/transcript-entry.type.ts b/packages/twenty-apps/public/twenty-fireflies/src/front-components/types/transcript-entry.type.ts new file mode 100644 index 0000000000..0be2540670 --- /dev/null +++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/types/transcript-entry.type.ts @@ -0,0 +1,13 @@ +export type TranscriptWord = { + text: string; + startSeconds: number | undefined; + endSeconds: number | undefined; +}; + +export type TranscriptEntry = { + speakerName: string; + startSeconds: number | undefined; + endSeconds: number | undefined; + text: string; + words: TranscriptWord[]; +}; diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/__tests__/parse-summary-inline-segments.test.ts b/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/__tests__/parse-summary-inline-segments.test.ts new file mode 100644 index 0000000000..1cf010606c --- /dev/null +++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/__tests__/parse-summary-inline-segments.test.ts @@ -0,0 +1,33 @@ +import { describe, expect, it } from 'vitest'; + +import { parseSummaryInlineSegments } from 'src/front-components/utils/parse-summary-inline-segments.util'; + +describe('parseSummaryInlineSegments', () => { + it('should return a single plain segment for text without markers', () => { + expect(parseSummaryInlineSegments('plain text')).toEqual([ + { text: 'plain text', isBold: false }, + ]); + }); + + it('should parse **bold** runs', () => { + expect(parseSummaryInlineSegments('a **b** c')).toEqual([ + { text: 'a ', isBold: false }, + { text: 'b', isBold: true }, + { text: ' c', isBold: false }, + ]); + }); + + it('should parse __bold__ runs', () => { + expect(parseSummaryInlineSegments('a __b__ c')).toEqual([ + { text: 'a ', isBold: false }, + { text: 'b', isBold: true }, + { text: ' c', isBold: false }, + ]); + }); + + it('should not match mismatched delimiters', () => { + expect(parseSummaryInlineSegments('**b__')).toEqual([ + { text: '**b__', isBold: false }, + ]); + }); +}); diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/__tests__/parse-summary-markdown-blocks.test.ts b/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/__tests__/parse-summary-markdown-blocks.test.ts new file mode 100644 index 0000000000..4a03e558cd --- /dev/null +++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/__tests__/parse-summary-markdown-blocks.test.ts @@ -0,0 +1,35 @@ +import { describe, expect, it } from 'vitest'; + +import { parseSummaryMarkdownBlocks } from 'src/front-components/utils/parse-summary-markdown-blocks.util'; + +describe('parseSummaryMarkdownBlocks', () => { + it('should parse headings, paragraphs and lists preserving order', () => { + expect( + parseSummaryMarkdownBlocks('## Overview\n\nSome text\n\n- first\n- second'), + ).toEqual([ + { + type: 'heading', + level: 2, + segments: [{ text: 'Overview', isBold: false }], + }, + { type: 'paragraph', segments: [{ text: 'Some text', isBold: false }] }, + { + type: 'list', + items: [ + [{ text: 'first', isBold: false }], + [{ text: 'second', isBold: false }], + ], + }, + ]); + }); + + it('should flush a trailing list at end of input', () => { + expect(parseSummaryMarkdownBlocks('- only item')).toEqual([ + { type: 'list', items: [[{ text: 'only item', isBold: false }]] }, + ]); + }); + + it('should return an empty array for empty input', () => { + expect(parseSummaryMarkdownBlocks('')).toEqual([]); + }); +}); diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/__tests__/parse-transcript-entries.test.ts b/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/__tests__/parse-transcript-entries.test.ts new file mode 100644 index 0000000000..855657260f --- /dev/null +++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/__tests__/parse-transcript-entries.test.ts @@ -0,0 +1,50 @@ +import { describe, expect, it } from 'vitest'; + +import { parseTranscriptEntries } from 'src/front-components/utils/parse-transcript-entries.util'; + +describe('parseTranscriptEntries', () => { + it('should parse a diarized entry with speaker and relative timestamps', () => { + expect( + parseTranscriptEntries([ + { + participant: { name: 'Sarah' }, + words: [ + { + text: 'Hello', + start_timestamp: { relative: 1.5 }, + end_timestamp: { relative: 2 }, + }, + ], + }, + ]), + ).toEqual([ + { + speakerName: 'Sarah', + startSeconds: 1.5, + endSeconds: 2, + text: 'Hello', + words: [{ text: 'Hello', startSeconds: 1.5, endSeconds: 2 }], + }, + ]); + }); + + it('should fall back to "Unknown speaker" for a whitespace-only name', () => { + const entries = parseTranscriptEntries([ + { participant: { name: ' ' }, words: [{ text: 'Hi' }] }, + ]); + + expect(entries?.[0]?.speakerName).toBe('Unknown speaker'); + }); + + it('should drop entries whose words are all whitespace', () => { + expect( + parseTranscriptEntries([ + { participant: { name: 'Sarah' }, words: [{ text: ' ' }] }, + ]), + ).toEqual([]); + }); + + it('should return undefined for a non-array transcript', () => { + expect(parseTranscriptEntries({})).toBeUndefined(); + }); +}); diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/as-record.util.ts b/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/as-record.util.ts new file mode 100644 index 0000000000..63e7cfcddd --- /dev/null +++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/as-record.util.ts @@ -0,0 +1,6 @@ +import { isArray, isObject } from '@sniptt/guards'; + +export const asRecord = (value: unknown): Record | undefined => + isObject(value) && !isArray(value) + ? (value as Record) + : undefined; diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/format-seconds-as-clock-timestamp.util.ts b/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/format-seconds-as-clock-timestamp.util.ts new file mode 100644 index 0000000000..c92cae504c --- /dev/null +++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/format-seconds-as-clock-timestamp.util.ts @@ -0,0 +1,20 @@ +const SECONDS_PER_MINUTE = 60; +const SECONDS_PER_HOUR = 3600; + +export const formatSecondsAsClockTimestamp = (totalSeconds: number): string => { + const safeSeconds = Number.isFinite(totalSeconds) + ? Math.max(0, Math.floor(totalSeconds)) + : 0; + const hours = Math.floor(safeSeconds / SECONDS_PER_HOUR); + const minutes = Math.floor( + (safeSeconds % SECONDS_PER_HOUR) / SECONDS_PER_MINUTE, + ); + const seconds = safeSeconds % SECONDS_PER_MINUTE; + const paddedSeconds = String(seconds).padStart(2, '0'); + + if (hours > 0) { + return `${hours}:${String(minutes).padStart(2, '0')}:${paddedSeconds}`; + } + + return `${minutes}:${paddedSeconds}`; +}; diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/is-non-empty-string.util.ts b/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/is-non-empty-string.util.ts new file mode 100644 index 0000000000..175943f383 --- /dev/null +++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/is-non-empty-string.util.ts @@ -0,0 +1,5 @@ +import { isString } from '@sniptt/guards'; + +// Trimming variant of @sniptt/guards isNonEmptyString, for normalizing at read boundaries. +export const isNonEmptyString = (value: unknown): value is string => + isString(value) && value.trim() !== ''; diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/parse-summary-inline-segments.util.ts b/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/parse-summary-inline-segments.util.ts new file mode 100644 index 0000000000..d275f08182 --- /dev/null +++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/parse-summary-inline-segments.util.ts @@ -0,0 +1,39 @@ +import { type SummaryInlineSegment } from 'src/front-components/types/summary-inline-segment.type'; + +const BOLD_PATTERN = /(\*\*|__)(.+?)\1/g; + +// Splits a markdown line into plain and bold (**bold** or __bold__) runs, +// preserving surrounding whitespace so adjacent bold runs don't collapse together. +export const parseSummaryInlineSegments = ( + text: string, +): SummaryInlineSegment[] => { + const boldMatches = [...text.matchAll(BOLD_PATTERN)]; + + const { segments, cursor } = boldMatches.reduce<{ + segments: SummaryInlineSegment[]; + cursor: number; + }>( + (accumulator, match) => { + const matchStart = match.index ?? 0; + const leadingText = text.slice(accumulator.cursor, matchStart); + const leadingSegments = + leadingText.length > 0 ? [{ text: leadingText, isBold: false }] : []; + + return { + segments: [ + ...accumulator.segments, + ...leadingSegments, + { text: match[2], isBold: true }, + ], + cursor: matchStart + match[0].length, + }; + }, + { segments: [], cursor: 0 }, + ); + + const trailingText = text.slice(cursor); + + return trailingText.length > 0 + ? [...segments, { text: trailingText, isBold: false }] + : segments; +}; diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/parse-summary-markdown-blocks.util.ts b/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/parse-summary-markdown-blocks.util.ts new file mode 100644 index 0000000000..131043467e --- /dev/null +++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/parse-summary-markdown-blocks.util.ts @@ -0,0 +1,58 @@ +import { type SummaryInlineSegment } from 'src/front-components/types/summary-inline-segment.type'; +import { type SummaryMarkdownBlock } from 'src/front-components/types/summary-markdown-block.type'; +import { parseSummaryInlineSegments } from 'src/front-components/utils/parse-summary-inline-segments.util'; + +const HEADING_PATTERN = /^(#{1,6})\s+(.+)$/; +const BULLET_PATTERN = /^[-*]\s+(.+)$/; + +export const parseSummaryMarkdownBlocks = ( + markdown: string, +): SummaryMarkdownBlock[] => { + const blocks: SummaryMarkdownBlock[] = []; + let pendingListItems: SummaryInlineSegment[][] = []; + + const flushPendingList = () => { + if (pendingListItems.length > 0) { + blocks.push({ type: 'list', items: pendingListItems }); + pendingListItems = []; + } + }; + + for (const rawLine of markdown.split('\n')) { + const line = rawLine.trim(); + + if (line === '') { + flushPendingList(); + continue; + } + + const headingMatch = HEADING_PATTERN.exec(line); + + if (headingMatch !== null) { + flushPendingList(); + blocks.push({ + type: 'heading', + level: headingMatch[1].length, + segments: parseSummaryInlineSegments(headingMatch[2]), + }); + continue; + } + + const bulletMatch = BULLET_PATTERN.exec(line); + + if (bulletMatch !== null) { + pendingListItems.push(parseSummaryInlineSegments(bulletMatch[1])); + continue; + } + + flushPendingList(); + blocks.push({ + type: 'paragraph', + segments: parseSummaryInlineSegments(line), + }); + } + + flushPendingList(); + + return blocks; +}; diff --git a/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/parse-transcript-entries.util.ts b/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/parse-transcript-entries.util.ts new file mode 100644 index 0000000000..e15fa74c38 --- /dev/null +++ b/packages/twenty-apps/public/twenty-fireflies/src/front-components/utils/parse-transcript-entries.util.ts @@ -0,0 +1,85 @@ +import { isArray, isNumber, isUndefined } from '@sniptt/guards'; + +import { + type TranscriptEntry, + type TranscriptWord, +} from 'src/front-components/types/transcript-entry.type'; +import { asRecord } from 'src/front-components/utils/as-record.util'; +import { isNonEmptyString } from 'src/front-components/utils/is-non-empty-string.util'; + +type TranscriptRecord = NonNullable>; + +const isTranscriptRecord = ( + candidate: TranscriptRecord | undefined, +): candidate is TranscriptRecord => !isUndefined(candidate); + +const readRelativeTimestamp = ( + timestamp: TranscriptRecord | undefined, +): number | undefined => { + const relativeTimestamp = timestamp?.relative; + + return isNumber(relativeTimestamp) && Number.isFinite(relativeTimestamp) + ? relativeTimestamp + : undefined; +}; + +const readTranscriptWord = ( + candidate: TranscriptRecord, +): TranscriptWord | undefined => { + if (!isNonEmptyString(candidate.text)) { + return undefined; + } + + return { + text: candidate.text.trim(), + startSeconds: readRelativeTimestamp(asRecord(candidate.start_timestamp)), + endSeconds: readRelativeTimestamp(asRecord(candidate.end_timestamp)), + }; +}; + +const readSpeakerName = (participant: TranscriptRecord | undefined): string => { + const name = participant?.name; + + return isNonEmptyString(name) ? name.trim() : 'Unknown speaker'; +}; + +const readTranscriptEntry = ( + candidate: TranscriptRecord, +): TranscriptEntry | undefined => { + if (!isArray(candidate.words)) { + return undefined; + } + + const words = candidate.words + .map(asRecord) + .filter(isTranscriptRecord) + .map(readTranscriptWord) + .filter((word): word is TranscriptWord => !isUndefined(word)); + + if (words.length === 0) { + return undefined; + } + + return { + speakerName: readSpeakerName(asRecord(candidate.participant)), + startSeconds: words[0].startSeconds, + endSeconds: words[words.length - 1].endSeconds, + text: words.map((word) => word.text).join(' '), + words, + }; +}; + +// Undefined means the value is not a diarized transcript; malformed entries are skipped, not fatal. +export const parseTranscriptEntries = ( + transcript: unknown, +): TranscriptEntry[] | undefined => { + if (!isArray(transcript)) { + return undefined; + } + + return transcript + .map(asRecord) + .filter(isTranscriptRecord) + .map(readTranscriptEntry) + .filter((entry): entry is TranscriptEntry => !isUndefined(entry)); +}; diff --git a/packages/twenty-apps/public/twenty-fireflies/src/page-layouts/calendar-event-summary-tab.ts b/packages/twenty-apps/public/twenty-fireflies/src/page-layouts/calendar-event-summary-tab.ts new file mode 100644 index 0000000000..675080ebb1 --- /dev/null +++ b/packages/twenty-apps/public/twenty-fireflies/src/page-layouts/calendar-event-summary-tab.ts @@ -0,0 +1,35 @@ +import { + definePageLayoutTab, + PageLayoutTabLayoutMode, +} from 'twenty-sdk/define'; + +import { + CALENDAR_EVENT_RECORD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER, + CALENDAR_EVENT_SUMMARY_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, + CALENDAR_EVENT_SUMMARY_PAGE_LAYOUT_TAB_UNIVERSAL_IDENTIFIER, + CALENDAR_EVENT_SUMMARY_PAGE_LAYOUT_WIDGET_UNIVERSAL_IDENTIFIER, +} from 'src/constants/universal-identifiers'; + +export default definePageLayoutTab({ + universalIdentifier: + CALENDAR_EVENT_SUMMARY_PAGE_LAYOUT_TAB_UNIVERSAL_IDENTIFIER, + title: 'Summary', + position: 16, + icon: 'IconFileText', + layoutMode: PageLayoutTabLayoutMode.CANVAS, + pageLayoutUniversalIdentifier: + CALENDAR_EVENT_RECORD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER, + widgets: [ + { + universalIdentifier: + CALENDAR_EVENT_SUMMARY_PAGE_LAYOUT_WIDGET_UNIVERSAL_IDENTIFIER, + title: 'Summary', + type: 'FRONT_COMPONENT', + configuration: { + configurationType: 'FRONT_COMPONENT', + frontComponentUniversalIdentifier: + CALENDAR_EVENT_SUMMARY_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, + }, + }, + ], +}); diff --git a/packages/twenty-apps/public/twenty-fireflies/src/page-layouts/calendar-event-transcript-tab.ts b/packages/twenty-apps/public/twenty-fireflies/src/page-layouts/calendar-event-transcript-tab.ts new file mode 100644 index 0000000000..f064599c2a --- /dev/null +++ b/packages/twenty-apps/public/twenty-fireflies/src/page-layouts/calendar-event-transcript-tab.ts @@ -0,0 +1,35 @@ +import { + definePageLayoutTab, + PageLayoutTabLayoutMode, +} from 'twenty-sdk/define'; + +import { + CALENDAR_EVENT_RECORD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER, + CALENDAR_EVENT_TRANSCRIPT_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, + CALENDAR_EVENT_TRANSCRIPT_PAGE_LAYOUT_TAB_UNIVERSAL_IDENTIFIER, + CALENDAR_EVENT_TRANSCRIPT_PAGE_LAYOUT_WIDGET_UNIVERSAL_IDENTIFIER, +} from 'src/constants/universal-identifiers'; + +export default definePageLayoutTab({ + universalIdentifier: + CALENDAR_EVENT_TRANSCRIPT_PAGE_LAYOUT_TAB_UNIVERSAL_IDENTIFIER, + title: 'Transcript', + position: 17, + icon: 'IconMicrophone', + layoutMode: PageLayoutTabLayoutMode.CANVAS, + pageLayoutUniversalIdentifier: + CALENDAR_EVENT_RECORD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER, + widgets: [ + { + universalIdentifier: + CALENDAR_EVENT_TRANSCRIPT_PAGE_LAYOUT_WIDGET_UNIVERSAL_IDENTIFIER, + title: 'Transcript', + type: 'FRONT_COMPONENT', + configuration: { + configurationType: 'FRONT_COMPONENT', + frontComponentUniversalIdentifier: + CALENDAR_EVENT_TRANSCRIPT_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, + }, + }, + ], +}); diff --git a/packages/twenty-apps/public/twenty-fireflies/yarn.lock b/packages/twenty-apps/public/twenty-fireflies/yarn.lock index 379ce447ee..e6163e95e7 100644 --- a/packages/twenty-apps/public/twenty-fireflies/yarn.lock +++ b/packages/twenty-apps/public/twenty-fireflies/yarn.lock @@ -15,6 +15,160 @@ __metadata: languageName: node linkType: hard +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/code-frame@npm:7.29.7" + dependencies: + "@babel/helper-validator-identifier": "npm:^7.29.7" + js-tokens: "npm:^4.0.0" + picocolors: "npm:^1.1.1" + checksum: 10c0/169fc2080169a40c1760155eaaaf739bcb882df0bec76a83adbda5493645bc17270a3434b8848c494b1933e96fe1d147370001e3cda09a39f43ae30f08ef2069 + languageName: node + linkType: hard + +"@babel/generator@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/generator@npm:7.29.7" + dependencies: + "@babel/parser": "npm:^7.29.7" + "@babel/types": "npm:^7.29.7" + "@jridgewell/gen-mapping": "npm:^0.3.12" + "@jridgewell/trace-mapping": "npm:^0.3.28" + jsesc: "npm:^3.0.2" + checksum: 10c0/9bf72b01b5bd0ea5b1288a0e37dbd360bff2f2b1ce73342c0d40fb3db2ec3dc004ada5ffa925c5e12939a416eed59e600d562b8ecd938ce0d27dfd0eb6c6c2b7 + languageName: node + linkType: hard + +"@babel/helper-globals@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-globals@npm:7.29.7" + checksum: 10c0/f38417c40b1129a1b2b519ca961b9040c8827d1444fd74068702286b91b77089431dc76b6b9d5c1496e5da2a4f3ad329c6946e688ba3fa0d1d0b3d2b4f34f36a + languageName: node + linkType: hard + +"@babel/helper-module-imports@npm:^7.16.7": + version: 7.29.7 + resolution: "@babel/helper-module-imports@npm:7.29.7" + dependencies: + "@babel/traverse": "npm:^7.29.7" + "@babel/types": "npm:^7.29.7" + checksum: 10c0/6adf60d97356027413342a092f818d9678c4f5caff716a33e3284b5ae14e47a9e88059d421dde4ee4894691260039a12602c0e7becadc175602194b40dfa345d + languageName: node + linkType: hard + +"@babel/helper-string-parser@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-string-parser@npm:7.29.7" + checksum: 10c0/194bc0f1716e396d5ffde56ad6119745fb9557662c98611590e5e454906783a4ccb21ce93056b8eb69a4909044834e45d96e50ac695bbe9e3221648fe033c06c + languageName: node + linkType: hard + +"@babel/helper-validator-identifier@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-validator-identifier@npm:7.29.7" + checksum: 10c0/4795354e7ae0dcafa72de1cd04ec51252dc1498517170beaf019e03effc5b7bf13c6b21a3949a77e07b8125be7f106ed1131350d8ebd4566ae874094a726d62b + languageName: node + linkType: hard + +"@babel/parser@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/parser@npm:7.29.7" + dependencies: + "@babel/types": "npm:^7.29.7" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/65133038f80b54a714d6027cb77cee3f9a6b5c4c6842ce674301e13947cbcbfa8055e63acaf1b84c085d34226a14425b2c2b97b829e0e226d2e8f1299942a51d + languageName: node + linkType: hard + +"@babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.29.2": + version: 7.29.7 + resolution: "@babel/runtime@npm:7.29.7" + checksum: 10c0/ca11572f7146b21e0bde6a9ed4bb6a89eafbee5f0944c7eb54d0d8a2dac962c33638a1d611e14faa71dfbb92b4b5f9236232208568a6b7d5c6f3f39ddb91771e + languageName: node + linkType: hard + +"@babel/template@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/template@npm:7.29.7" + dependencies: + "@babel/code-frame": "npm:^7.29.7" + "@babel/parser": "npm:^7.29.7" + "@babel/types": "npm:^7.29.7" + checksum: 10c0/8bb7f900dcab0e9e1c5ffbc33ca10e0d26b7b2e2ca804becb73ee771b9c4ed6e2908a4ae4a14c08560febb45d2b6b9a173955e42ad404d05f8b04840a14d9c58 + languageName: node + linkType: hard + +"@babel/traverse@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/traverse@npm:7.29.7" + dependencies: + "@babel/code-frame": "npm:^7.29.7" + "@babel/generator": "npm:^7.29.7" + "@babel/helper-globals": "npm:^7.29.7" + "@babel/parser": "npm:^7.29.7" + "@babel/template": "npm:^7.29.7" + "@babel/types": "npm:^7.29.7" + debug: "npm:^4.3.1" + checksum: 10c0/e256a1fbdb956555b76f3c285b1e453f6bedec8b3afb61751d99d933efd11c7d79caf5ddf2493570058a9f7deaa1b48324380d7c1aa1443fd9508becbf56331a + languageName: node + linkType: hard + +"@babel/types@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/types@npm:7.29.7" + dependencies: + "@babel/helper-string-parser": "npm:^7.29.7" + "@babel/helper-validator-identifier": "npm:^7.29.7" + checksum: 10c0/b6623994c69717fa27294f5fa46d59140338e2d86c6c1c13085c84ef7d53086ee357fbf4fe9abe3dd3da75734dc77c4c0df2f90fb29e667558bb3b3fb705e88f + languageName: node + linkType: hard + +"@base-ui/react@npm:^1.5.0": + version: 1.6.0 + resolution: "@base-ui/react@npm:1.6.0" + dependencies: + "@babel/runtime": "npm:^7.29.2" + "@base-ui/utils": "npm:0.3.1" + "@floating-ui/react-dom": "npm:^2.1.8" + "@floating-ui/utils": "npm:^0.2.11" + use-sync-external-store: "npm:^1.6.0" + peerDependencies: + "@date-fns/tz": ^1.2.0 + "@types/react": ^17 || ^18 || ^19 + date-fns: ^4.0.0 + react: ^17 || ^18 || ^19 + react-dom: ^17 || ^18 || ^19 + peerDependenciesMeta: + "@date-fns/tz": + optional: true + "@types/react": + optional: true + date-fns: + optional: true + checksum: 10c0/5e5994dc782710eac900d68cc0691aea70a47fabc6ef8d2ea943103d9aa96c4b44f9fd76ba65d29fa2667a5fbd7ea7a1cc02fe111c4bf639d4e3e65a1c281118 + languageName: node + linkType: hard + +"@base-ui/utils@npm:0.3.1": + version: 0.3.1 + resolution: "@base-ui/utils@npm:0.3.1" + dependencies: + "@babel/runtime": "npm:^7.29.2" + "@floating-ui/utils": "npm:^0.2.11" + reselect: "npm:^5.2.0" + use-sync-external-store: "npm:^1.6.0" + peerDependencies: + "@types/react": ^17 || ^18 || ^19 + react: ^17 || ^18 || ^19 + react-dom: ^17 || ^18 || ^19 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/4de858c3e2c8a4486a549d51c5e887bba6921e5a7f8db54439ba558feaf1656c3592a873928aee8a1711ee9210be3a65ca43b26713e97f649695ed2fa8c3af2c + languageName: node + linkType: hard + "@emnapi/core@npm:1.10.0": version: 1.10.0 resolution: "@emnapi/core@npm:1.10.0" @@ -52,6 +206,152 @@ __metadata: languageName: node linkType: hard +"@emotion/babel-plugin@npm:^11.13.5": + version: 11.13.5 + resolution: "@emotion/babel-plugin@npm:11.13.5" + dependencies: + "@babel/helper-module-imports": "npm:^7.16.7" + "@babel/runtime": "npm:^7.18.3" + "@emotion/hash": "npm:^0.9.2" + "@emotion/memoize": "npm:^0.9.0" + "@emotion/serialize": "npm:^1.3.3" + babel-plugin-macros: "npm:^3.1.0" + convert-source-map: "npm:^1.5.0" + escape-string-regexp: "npm:^4.0.0" + find-root: "npm:^1.1.0" + source-map: "npm:^0.5.7" + stylis: "npm:4.2.0" + checksum: 10c0/8ccbfec7defd0e513cb8a1568fa179eac1e20c35fda18aed767f6c59ea7314363ebf2de3e9d2df66c8ad78928dc3dceeded84e6fa8059087cae5c280090aeeeb + languageName: node + linkType: hard + +"@emotion/cache@npm:^11.14.0": + version: 11.14.0 + resolution: "@emotion/cache@npm:11.14.0" + dependencies: + "@emotion/memoize": "npm:^0.9.0" + "@emotion/sheet": "npm:^1.4.0" + "@emotion/utils": "npm:^1.4.2" + "@emotion/weak-memoize": "npm:^0.4.0" + stylis: "npm:4.2.0" + checksum: 10c0/3fa3e7a431ab6f8a47c67132a00ac8358f428c1b6c8421d4b20de9df7c18e95eec04a5a6ff5a68908f98d3280044f247b4965ac63df8302d2c94dba718769724 + languageName: node + linkType: hard + +"@emotion/hash@npm:^0.9.2": + version: 0.9.2 + resolution: "@emotion/hash@npm:0.9.2" + checksum: 10c0/0dc254561a3cc0a06a10bbce7f6a997883fd240c8c1928b93713f803a2e9153a257a488537012efe89dbe1246f2abfe2add62cdb3471a13d67137fcb808e81c2 + languageName: node + linkType: hard + +"@emotion/is-prop-valid@npm:^1.3.0": + version: 1.4.0 + resolution: "@emotion/is-prop-valid@npm:1.4.0" + dependencies: + "@emotion/memoize": "npm:^0.9.0" + checksum: 10c0/5f857814ec7d8c7e727727346dfb001af6b1fb31d621a3ce9c3edf944a484d8b0d619546c30899ae3ade2f317c76390ba4394449728e9bf628312defc2c41ac3 + languageName: node + linkType: hard + +"@emotion/memoize@npm:^0.9.0": + version: 0.9.0 + resolution: "@emotion/memoize@npm:0.9.0" + checksum: 10c0/13f474a9201c7f88b543e6ea42f55c04fb2fdc05e6c5a3108aced2f7e7aa7eda7794c56bba02985a46d8aaa914fcdde238727a98341a96e2aec750d372dadd15 + languageName: node + linkType: hard + +"@emotion/react@npm:^11.14.0": + version: 11.14.0 + resolution: "@emotion/react@npm:11.14.0" + dependencies: + "@babel/runtime": "npm:^7.18.3" + "@emotion/babel-plugin": "npm:^11.13.5" + "@emotion/cache": "npm:^11.14.0" + "@emotion/serialize": "npm:^1.3.3" + "@emotion/use-insertion-effect-with-fallbacks": "npm:^1.2.0" + "@emotion/utils": "npm:^1.4.2" + "@emotion/weak-memoize": "npm:^0.4.0" + hoist-non-react-statics: "npm:^3.3.1" + peerDependencies: + react: ">=16.8.0" + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/d0864f571a9f99ec643420ef31fde09e2006d3943a6aba079980e4d5f6e9f9fecbcc54b8f617fe003c00092ff9d5241179149ffff2810cb05cf72b4620cfc031 + languageName: node + linkType: hard + +"@emotion/serialize@npm:^1.3.3": + version: 1.3.3 + resolution: "@emotion/serialize@npm:1.3.3" + dependencies: + "@emotion/hash": "npm:^0.9.2" + "@emotion/memoize": "npm:^0.9.0" + "@emotion/unitless": "npm:^0.10.0" + "@emotion/utils": "npm:^1.4.2" + csstype: "npm:^3.0.2" + checksum: 10c0/b28cb7de59de382021de2b26c0c94ebbfb16967a1b969a56fdb6408465a8993df243bfbd66430badaa6800e1834724e84895f5a6a9d97d0d224de3d77852acb4 + languageName: node + linkType: hard + +"@emotion/sheet@npm:^1.4.0": + version: 1.4.0 + resolution: "@emotion/sheet@npm:1.4.0" + checksum: 10c0/3ca72d1650a07d2fbb7e382761b130b4a887dcd04e6574b2d51ce578791240150d7072a9bcb4161933abbcd1e38b243a6fb4464a7fe991d700c17aa66bb5acc7 + languageName: node + linkType: hard + +"@emotion/styled@npm:^11.14.0": + version: 11.14.1 + resolution: "@emotion/styled@npm:11.14.1" + dependencies: + "@babel/runtime": "npm:^7.18.3" + "@emotion/babel-plugin": "npm:^11.13.5" + "@emotion/is-prop-valid": "npm:^1.3.0" + "@emotion/serialize": "npm:^1.3.3" + "@emotion/use-insertion-effect-with-fallbacks": "npm:^1.2.0" + "@emotion/utils": "npm:^1.4.2" + peerDependencies: + "@emotion/react": ^11.0.0-rc.0 + react: ">=16.8.0" + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/2bbf8451df49c967e41fbcf8111a7f6dafe6757f0cc113f2f6e287206c45ac1d54dc8a95a483b7c0cee8614b8a8d08155bded6453d6721de1f8cc8d5b9216963 + languageName: node + linkType: hard + +"@emotion/unitless@npm:^0.10.0": + version: 0.10.0 + resolution: "@emotion/unitless@npm:0.10.0" + checksum: 10c0/150943192727b7650eb9a6851a98034ddb58a8b6958b37546080f794696141c3760966ac695ab9af97efe10178690987aee4791f9f0ad1ff76783cdca83c1d49 + languageName: node + linkType: hard + +"@emotion/use-insertion-effect-with-fallbacks@npm:^1.2.0": + version: 1.2.0 + resolution: "@emotion/use-insertion-effect-with-fallbacks@npm:1.2.0" + peerDependencies: + react: ">=16.8.0" + checksum: 10c0/074dbc92b96bdc09209871070076e3b0351b6b47efefa849a7d9c37ab142130767609ca1831da0055988974e3b895c1de7606e4c421fecaa27c3e56a2afd3b08 + languageName: node + linkType: hard + +"@emotion/utils@npm:^1.4.2": + version: 1.4.2 + resolution: "@emotion/utils@npm:1.4.2" + checksum: 10c0/7d0010bf60a2a8c1a033b6431469de4c80e47aeb8fd856a17c1d1f76bbc3a03161a34aeaa78803566e29681ca551e7bf9994b68e9c5f5c796159923e44f78d9a + languageName: node + linkType: hard + +"@emotion/weak-memoize@npm:^0.4.0": + version: 0.4.0 + resolution: "@emotion/weak-memoize@npm:0.4.0" + checksum: 10c0/64376af11f1266042d03b3305c30b7502e6084868e33327e944b539091a472f089db307af69240f7188f8bc6b319276fd7b141a36613f1160d73d12a60f6ca1a + languageName: node + linkType: hard + "@esbuild/aix-ppc64@npm:0.28.1": version: 0.28.1 resolution: "@esbuild/aix-ppc64@npm:0.28.1" @@ -234,6 +534,44 @@ __metadata: languageName: node linkType: hard +"@floating-ui/core@npm:^1.7.5": + version: 1.7.5 + resolution: "@floating-ui/core@npm:1.7.5" + dependencies: + "@floating-ui/utils": "npm:^0.2.11" + checksum: 10c0/f9c52205e198b231d63a387b09c659aab08c46a1899e0b0bbe147b8b4f048b546f15ba17cb5d2a471da9534f1883d979425e13e5c4ceee67be63e4b0abd4db5d + languageName: node + linkType: hard + +"@floating-ui/dom@npm:^1.7.6": + version: 1.7.6 + resolution: "@floating-ui/dom@npm:1.7.6" + dependencies: + "@floating-ui/core": "npm:^1.7.5" + "@floating-ui/utils": "npm:^0.2.11" + checksum: 10c0/5c098e0d7b58c9bc769f276cca1766994c2c9c70c92d091a61bba8b3e9be53c011e0a79a8457fc2fb2f3d91697a26eb52e0a4962ef936dc963b45f58613c212f + languageName: node + linkType: hard + +"@floating-ui/react-dom@npm:^2.1.8": + version: 2.1.8 + resolution: "@floating-ui/react-dom@npm:2.1.8" + dependencies: + "@floating-ui/dom": "npm:^1.7.6" + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + checksum: 10c0/26260ca4bb23b57c73b824062505abf977a008ce6e0463bdacca74f7e49853c4cd1d2bbf1a77c6caa17fa37dfffda2c6c4cd07a8737ebd7474aaff7818401d75 + languageName: node + linkType: hard + +"@floating-ui/utils@npm:^0.2.11": + version: 0.2.11 + resolution: "@floating-ui/utils@npm:0.2.11" + checksum: 10c0/f4bcea1559bdbb721ecc8e8ead423ac58d6a5b6e70b602cf0810ba6ad4ed1c77211b207faa88b278a9042f0c743133de08a203ed6741c1b6443423332884d5b3 + languageName: node + linkType: hard + "@genql/runtime@npm:^2.10.0": version: 2.10.0 resolution: "@genql/runtime@npm:2.10.0" @@ -731,13 +1069,62 @@ __metadata: languageName: node linkType: hard -"@jridgewell/sourcemap-codec@npm:^1.5.5": +"@jridgewell/gen-mapping@npm:^0.3.12": + version: 0.3.13 + resolution: "@jridgewell/gen-mapping@npm:0.3.13" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.0" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/9a7d65fb13bd9aec1fbab74cda08496839b7e2ceb31f5ab922b323e94d7c481ce0fc4fd7e12e2610915ed8af51178bdc61e168e92a8c8b8303b030b03489b13b + languageName: node + linkType: hard + +"@jridgewell/resolve-uri@npm:^3.1.0": + version: 3.1.2 + resolution: "@jridgewell/resolve-uri@npm:3.1.2" + checksum: 10c0/d502e6fb516b35032331406d4e962c21fe77cdf1cbdb49c6142bcbd9e30507094b18972778a6e27cbad756209cfe34b1a27729e6fa08a2eb92b33943f680cf1e + languageName: node + linkType: hard + +"@jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.5.0, @jridgewell/sourcemap-codec@npm:^1.5.5": version: 1.5.5 resolution: "@jridgewell/sourcemap-codec@npm:1.5.5" checksum: 10c0/f9e538f302b63c0ebc06eecb1dd9918dd4289ed36147a0ddce35d6ea4d7ebbda243cda7b2213b6a5e1d8087a298d5cf630fb2bd39329cdecb82017023f6081a0 languageName: node linkType: hard +"@jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.28": + version: 0.3.31 + resolution: "@jridgewell/trace-mapping@npm:0.3.31" + dependencies: + "@jridgewell/resolve-uri": "npm:^3.1.0" + "@jridgewell/sourcemap-codec": "npm:^1.4.14" + checksum: 10c0/4b30ec8cd56c5fd9a661f088230af01e0c1a3888d11ffb6b47639700f71225be21d1f7e168048d6d4f9449207b978a235c07c8f15c07705685d16dc06280e9d9 + languageName: node + linkType: hard + +"@monaco-editor/loader@npm:^1.5.0": + version: 1.7.0 + resolution: "@monaco-editor/loader@npm:1.7.0" + dependencies: + state-local: "npm:^1.0.6" + checksum: 10c0/1fd3579cb09b669493cf553dfc0723a93483140a44353ce9a739827edfa7b2c6541b254024d15c48176ece749ef666b6d16cce6cf0e4396c7fa1c5a48012d08a + languageName: node + linkType: hard + +"@monaco-editor/react@npm:^4.7.0": + version: 4.7.0 + resolution: "@monaco-editor/react@npm:4.7.0" + dependencies: + "@monaco-editor/loader": "npm:^1.5.0" + peerDependencies: + monaco-editor: ">= 0.25.0 < 1" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10c0/a4e91c6eda1a71f5fd23871bd801de435490ccbc43934d23cba65cefe2be7e9d02c9a57c4ef80fc9fe99e4d4deea51e5db19cb4e0ecf3f51818dda0eb9cdbf12 + languageName: node + linkType: hard + "@napi-rs/wasm-runtime@npm:^1.1.4": version: 1.1.5 resolution: "@napi-rs/wasm-runtime@npm:1.1.5" @@ -813,6 +1200,20 @@ __metadata: languageName: node linkType: hard +"@radix-ui/colors@npm:^3.0.0": + version: 3.0.0 + resolution: "@radix-ui/colors@npm:3.0.0" + checksum: 10c0/612c6bb4efe987f2d49aaca2f785687d08150da59a248328bc2b4e187ece4ef731c1dc5c6a9430fffcac4af9e65cf2f89ceb9806f8809940c9eb238b79d2cf2f + languageName: node + linkType: hard + +"@remix-run/router@npm:1.23.3": + version: 1.23.3 + resolution: "@remix-run/router@npm:1.23.3" + checksum: 10c0/73622465dd9e9a2c5a7ced7d1151ea6e9195a8a979c99b4f70a67093eeff7f339daf03a7c6304709a9c27deb2081a8fff6737663aacb33529c1a12a0a0827a17 + languageName: node + linkType: hard + "@rolldown/binding-android-arm64@npm:1.0.3": version: 1.0.3 resolution: "@rolldown/binding-android-arm64@npm:1.0.3" @@ -943,10 +1344,31 @@ __metadata: languageName: node linkType: hard +"@tabler/icons-react@npm:^3.31.0": + version: 3.44.0 + resolution: "@tabler/icons-react@npm:3.44.0" + dependencies: + "@tabler/icons": "npm:3.44.0" + peerDependencies: + react: ">= 16" + checksum: 10c0/9a4bb6f1121d001049be8bb5757160ce4d5f2430671479ee94763f88ef43a69ab8d89b835d6c6451de22f3071793b779fefeed43db49d7993de1ea65c50145c9 + languageName: node + linkType: hard + +"@tabler/icons@npm:3.44.0": + version: 3.44.0 + resolution: "@tabler/icons@npm:3.44.0" + checksum: 10c0/0d5c1f9d6e68aa04c4a661e96035190e0884e0bc0022d31922efad3cfba210a25b2f013c0b6c0ab6aeb3d5a402a9b85a168966bd11a30ba6087f614f8521ccf3 + languageName: node + linkType: hard + "@twentyhq/twenty-fireflies@workspace:.": version: 0.0.0-use.local resolution: "@twentyhq/twenty-fireflies@workspace:." dependencies: + "@emotion/react": "npm:^11.14.0" + "@emotion/styled": "npm:^11.14.0" + "@sniptt/guards": "npm:^0.2.0" "@types/node": "npm:^24.7.2" "@types/react": "npm:^18.2.0" "@typescript/native-preview": "npm:^7.0.0-dev.20260116.1" @@ -955,6 +1377,7 @@ __metadata: react-dom: "npm:^18.2.0" twenty-client-sdk: "npm:^2.18.0" twenty-sdk: "npm:^2.18.0" + twenty-ui: "npm:^1.0.0-alpha.1" typescript: "npm:^5.9.3" vite-tsconfig-paths: "npm:^4.2.1" vitest: "npm:^4.0.0" @@ -1012,6 +1435,13 @@ __metadata: languageName: node linkType: hard +"@types/parse-json@npm:^4.0.0": + version: 4.0.2 + resolution: "@types/parse-json@npm:4.0.2" + checksum: 10c0/b1b863ac34a2c2172fbe0807a1ec4d5cb684e48d422d15ec95980b81475fac4fdb3768a8b13eef39130203a7c04340fc167bae057c7ebcafd7dec9fe6c36aeb1 + languageName: node + linkType: hard + "@types/prop-types@npm:*": version: 15.7.15 resolution: "@types/prop-types@npm:15.7.15" @@ -1301,6 +1731,17 @@ __metadata: languageName: node linkType: hard +"babel-plugin-macros@npm:^3.1.0": + version: 3.1.0 + resolution: "babel-plugin-macros@npm:3.1.0" + dependencies: + "@babel/runtime": "npm:^7.12.5" + cosmiconfig: "npm:^7.0.0" + resolve: "npm:^1.19.0" + checksum: 10c0/c6dfb15de96f67871d95bd2e8c58b0c81edc08b9b087dc16755e7157f357dc1090a8dc60ebab955e92587a9101f02eba07e730adc253a1e4cf593ca3ebd3839c + languageName: node + linkType: hard + "backo2@npm:^1.0.2": version: 1.0.2 resolution: "backo2@npm:1.0.2" @@ -1318,6 +1759,13 @@ __metadata: languageName: node linkType: hard +"callsites@npm:^3.0.0": + version: 3.1.0 + resolution: "callsites@npm:3.1.0" + checksum: 10c0/fff92277400eb06c3079f9e74f3af120db9f8ea03bad0e84d9aede54bbe2d44a56cccb5f6cf12211f93f52306df87077ecec5b712794c5a9b5dac6d615a3f301 + languageName: node + linkType: hard + "chai@npm:^6.2.2": version: 6.2.2 resolution: "chai@npm:6.2.2" @@ -1388,6 +1836,13 @@ __metadata: languageName: node linkType: hard +"clsx@npm:^2.1.1": + version: 2.1.1 + resolution: "clsx@npm:2.1.1" + checksum: 10c0/c4c8eb865f8c82baab07e71bfa8897c73454881c4f99d6bc81585aecd7c441746c1399d08363dc096c550cceaf97bd4ce1e8854e1771e9998d9f94c4fe075839 + languageName: node + linkType: hard + "code-excerpt@npm:^4.0.0": version: 4.0.0 resolution: "code-excerpt@npm:4.0.0" @@ -1413,6 +1868,13 @@ __metadata: languageName: node linkType: hard +"convert-source-map@npm:^1.5.0": + version: 1.9.0 + resolution: "convert-source-map@npm:1.9.0" + checksum: 10c0/281da55454bf8126cbc6625385928c43479f2060984180c42f3a86c8b8c12720a24eac260624a7d1e090004028d2dee78602330578ceec1a08e27cb8bb0a8a5b + languageName: node + linkType: hard + "convert-source-map@npm:^2.0.0": version: 2.0.0 resolution: "convert-source-map@npm:2.0.0" @@ -1427,14 +1889,34 @@ __metadata: languageName: node linkType: hard -"csstype@npm:^3.2.2": +"cosmiconfig@npm:^7.0.0": + version: 7.1.0 + resolution: "cosmiconfig@npm:7.1.0" + dependencies: + "@types/parse-json": "npm:^4.0.0" + import-fresh: "npm:^3.2.1" + parse-json: "npm:^5.0.0" + path-type: "npm:^4.0.0" + yaml: "npm:^1.10.0" + checksum: 10c0/b923ff6af581638128e5f074a5450ba12c0300b71302398ea38dbeabd33bbcaa0245ca9adbedfcf284a07da50f99ede5658c80bb3e39e2ce770a99d28a21ef03 + languageName: node + linkType: hard + +"css-mediaquery@npm:^0.1.2": + version: 0.1.2 + resolution: "css-mediaquery@npm:0.1.2" + checksum: 10c0/b7825a78f52ce8a8198e004fcad0d7be1d3c9a0463ecd05ba31a0f2c94fb81468ad6f4d7bf715a6ca775696e7a17500c2a339b5216a6d0f789cbf78f9454d048 + languageName: node + linkType: hard + +"csstype@npm:^3.0.2, csstype@npm:^3.2.2": version: 3.2.3 resolution: "csstype@npm:3.2.3" checksum: 10c0/cd29c51e70fa822f1cecd8641a1445bed7063697469d35633b516e60fe8c1bde04b08f6c5b6022136bb669b64c63d4173af54864510fbb4ee23281801841a3ce languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.1.1": +"debug@npm:4, debug@npm:^4.1.1, debug@npm:^4.3.1": version: 4.4.3 resolution: "debug@npm:4.4.3" dependencies: @@ -1492,6 +1974,15 @@ __metadata: languageName: node linkType: hard +"error-ex@npm:^1.3.1": + version: 1.3.4 + resolution: "error-ex@npm:1.3.4" + dependencies: + is-arrayish: "npm:^0.2.1" + checksum: 10c0/b9e34ff4778b8f3b31a8377e1c654456f4c41aeaa3d10a1138c3b7635d8b7b2e03eb2475d46d8ae055c1f180a1063e100bffabf64ea7e7388b37735df5328664 + languageName: node + linkType: hard + "es-define-property@npm:^1.0.1": version: 1.0.1 resolution: "es-define-property@npm:1.0.1" @@ -1642,6 +2133,13 @@ __metadata: languageName: node linkType: hard +"escape-string-regexp@npm:^4.0.0": + version: 4.0.0 + resolution: "escape-string-regexp@npm:4.0.0" + checksum: 10c0/9497d4dd307d845bd7f75180d8188bb17ea8c151c1edbf6b6717c100e104d629dc2dfb687686181b0f4b7d732c7dfdc4d5e7a8ff72de1b0ca283a75bbb3a9cd9 + languageName: node + linkType: hard + "estree-walker@npm:^3.0.3": version: 3.0.3 resolution: "estree-walker@npm:3.0.3" @@ -1709,6 +2207,13 @@ __metadata: languageName: node linkType: hard +"find-root@npm:^1.1.0": + version: 1.1.0 + resolution: "find-root@npm:1.1.0" + checksum: 10c0/1abc7f3bf2f8d78ff26d9e00ce9d0f7b32e5ff6d1da2857bcdf4746134c422282b091c672cde0572cac3840713487e0a7a636af9aa1b74cb11894b447a521efa + languageName: node + linkType: hard + "follow-redirects@npm:^1.16.0": version: 1.16.0 resolution: "follow-redirects@npm:1.16.0" @@ -1872,7 +2377,7 @@ __metadata: languageName: node linkType: hard -"hasown@npm:^2.0.4": +"hasown@npm:^2.0.3, hasown@npm:^2.0.4": version: 2.0.4 resolution: "hasown@npm:2.0.4" dependencies: @@ -1881,6 +2386,15 @@ __metadata: languageName: node linkType: hard +"hoist-non-react-statics@npm:^3.3.1": + version: 3.3.2 + resolution: "hoist-non-react-statics@npm:3.3.2" + dependencies: + react-is: "npm:^16.7.0" + checksum: 10c0/fe0889169e845d738b59b64badf5e55fa3cf20454f9203d1eb088df322d49d4318df774828e789898dcb280e8a5521bb59b3203385662ca5e9218a6ca5820e74 + languageName: node + linkType: hard + "https-proxy-agent@npm:^5.0.1": version: 5.0.1 resolution: "https-proxy-agent@npm:5.0.1" @@ -1891,6 +2405,13 @@ __metadata: languageName: node linkType: hard +"hyphenate-style-name@npm:^1.0.0": + version: 1.1.0 + resolution: "hyphenate-style-name@npm:1.1.0" + checksum: 10c0/bfe88deac2414a41a0d08811e277c8c098f23993d6a1eb17f14a0f11b54c4d42865a63d3cfe1914668eefb9a188e2de58f38b55a179a238fd1fef606893e194f + languageName: node + linkType: hard + "iconv-lite@npm:^0.7.2": version: 0.7.2 resolution: "iconv-lite@npm:0.7.2" @@ -1900,6 +2421,16 @@ __metadata: languageName: node linkType: hard +"import-fresh@npm:^3.2.1": + version: 3.3.1 + resolution: "import-fresh@npm:3.3.1" + dependencies: + parent-module: "npm:^1.0.0" + resolve-from: "npm:^4.0.0" + checksum: 10c0/bf8cc494872fef783249709385ae883b447e3eb09db0ebd15dcead7d9afe7224dad7bd7591c6b73b0b19b3c0f9640eb8ee884f01cfaf2887ab995b0b36a0cbec + languageName: node + linkType: hard + "indent-string@npm:^5.0.0": version: 5.0.0 resolution: "indent-string@npm:5.0.0" @@ -1968,6 +2499,22 @@ __metadata: languageName: node linkType: hard +"is-arrayish@npm:^0.2.1": + version: 0.2.1 + resolution: "is-arrayish@npm:0.2.1" + checksum: 10c0/e7fb686a739068bb70f860b39b67afc62acc62e36bb61c5f965768abce1873b379c563e61dd2adad96ebb7edf6651111b385e490cf508378959b0ed4cac4e729 + languageName: node + linkType: hard + +"is-core-module@npm:^2.16.1": + version: 2.16.2 + resolution: "is-core-module@npm:2.16.2" + dependencies: + hasown: "npm:^2.0.3" + checksum: 10c0/14b4258390283709c15476d023ec173e27458d5d014ccdb8ed39d576e551c3fa45498b7c9fe178f1529c4cb2648ddd58852a6a62107a019f6e349529f277518a + languageName: node + linkType: hard + "is-fullwidth-code-point@npm:^5.0.0, is-fullwidth-code-point@npm:^5.1.0": version: 5.1.0 resolution: "is-fullwidth-code-point@npm:5.1.0" @@ -2010,13 +2557,29 @@ __metadata: languageName: node linkType: hard -"js-tokens@npm:^3.0.0 || ^4.0.0": +"js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": version: 4.0.0 resolution: "js-tokens@npm:4.0.0" checksum: 10c0/e248708d377aa058eacf2037b07ded847790e6de892bbad3dac0abba2e759cb9f121b00099a65195616badcb6eca8d14d975cb3e89eb1cfda644756402c8aeed languageName: node linkType: hard +"jsesc@npm:^3.0.2": + version: 3.1.0 + resolution: "jsesc@npm:3.1.0" + bin: + jsesc: bin/jsesc + checksum: 10c0/531779df5ec94f47e462da26b4cbf05eb88a83d9f08aac2ba04206508fc598527a153d08bd462bae82fc78b3eaa1a908e1a4a79f886e9238641c4cdefaf118b1 + languageName: node + linkType: hard + +"json-parse-even-better-errors@npm:^2.3.0": + version: 2.3.1 + resolution: "json-parse-even-better-errors@npm:2.3.1" + checksum: 10c0/140932564c8f0b88455432e0f33c4cb4086b8868e37524e07e723f4eaedb9425bdc2bafd71bd1d9765bd15fd1e2d126972bc83990f55c467168c228c24d665f3 + languageName: node + linkType: hard + "jsonc-parser@npm:^3.2.0": version: 3.3.1 resolution: "jsonc-parser@npm:3.3.1" @@ -2144,6 +2707,13 @@ __metadata: languageName: node linkType: hard +"lines-and-columns@npm:^1.1.6": + version: 1.2.4 + resolution: "lines-and-columns@npm:1.2.4" + checksum: 10c0/3da6ee62d4cd9f03f5dc90b4df2540fb85b352081bee77fe4bbcd12c9000ead7f35e0a38b8d09a9bb99b13223446dd8689ff3c4959807620726d788701a83d2d + languageName: node + linkType: hard + "lodash@npm:^4.17.20, lodash@npm:^4.17.21": version: 4.18.1 resolution: "lodash@npm:4.18.1" @@ -2151,7 +2721,7 @@ __metadata: languageName: node linkType: hard -"loose-envify@npm:^1.1.0": +"loose-envify@npm:^1.1.0, loose-envify@npm:^1.4.0": version: 1.4.0 resolution: "loose-envify@npm:1.4.0" dependencies: @@ -2171,6 +2741,15 @@ __metadata: languageName: node linkType: hard +"matchmediaquery@npm:^0.3.0": + version: 0.3.1 + resolution: "matchmediaquery@npm:0.3.1" + dependencies: + css-mediaquery: "npm:^0.1.2" + checksum: 10c0/19a254e0a1eb639fcc6aa7428a6e2d36cb5646a165579897e771580653e470b43d7f4a224c522700dbf1d8f19fb7c63a9719e0a6017727ade967155a2ae89fea + languageName: node + linkType: hard + "math-intrinsics@npm:^1.1.0": version: 1.1.0 resolution: "math-intrinsics@npm:1.1.0" @@ -2285,6 +2864,13 @@ __metadata: languageName: node linkType: hard +"object-assign@npm:^4.1.1": + version: 4.1.1 + resolution: "object-assign@npm:4.1.1" + checksum: 10c0/1f4df9945120325d041ccf7b86f31e8bcc14e73d29171e37a7903050e96b81323784ec59f93f102ec635bcf6fa8034ba3ea0a8c7e69fa202b87ae3b6cec5a414 + languageName: node + linkType: hard + "obug@npm:^2.1.1": version: 2.1.2 resolution: "obug@npm:2.1.2" @@ -2337,6 +2923,27 @@ __metadata: languageName: node linkType: hard +"parent-module@npm:^1.0.0": + version: 1.0.1 + resolution: "parent-module@npm:1.0.1" + dependencies: + callsites: "npm:^3.0.0" + checksum: 10c0/c63d6e80000d4babd11978e0d3fee386ca7752a02b035fd2435960ffaa7219dc42146f07069fb65e6e8bf1caef89daf9af7535a39bddf354d78bf50d8294f556 + languageName: node + linkType: hard + +"parse-json@npm:^5.0.0": + version: 5.2.0 + resolution: "parse-json@npm:5.2.0" + dependencies: + "@babel/code-frame": "npm:^7.0.0" + error-ex: "npm:^1.3.1" + json-parse-even-better-errors: "npm:^2.3.0" + lines-and-columns: "npm:^1.1.6" + checksum: 10c0/77947f2253005be7a12d858aedbafa09c9ae39eb4863adf330f7b416ca4f4a08132e453e08de2db46459256fb66afaac5ee758b44fe6541b7cdaf9d252e59585 + languageName: node + linkType: hard + "patch-console@npm:^2.0.0": version: 2.0.0 resolution: "patch-console@npm:2.0.0" @@ -2344,6 +2951,20 @@ __metadata: languageName: node linkType: hard +"path-parse@npm:^1.0.7": + version: 1.0.7 + resolution: "path-parse@npm:1.0.7" + checksum: 10c0/11ce261f9d294cc7a58d6a574b7f1b935842355ec66fba3c3fd79e0f036462eaf07d0aa95bb74ff432f9afef97ce1926c720988c6a7451d8a584930ae7de86e1 + languageName: node + linkType: hard + +"path-type@npm:^4.0.0": + version: 4.0.0 + resolution: "path-type@npm:4.0.0" + checksum: 10c0/666f6973f332f27581371efaf303fd6c272cc43c2057b37aa99e3643158c7e4b2626549555d88626e99ea9e046f82f32e41bbde5f1508547e9a11b149b52387c + languageName: node + linkType: hard + "pathe@npm:^2.0.3": version: 2.0.3 resolution: "pathe@npm:2.0.3" @@ -2399,6 +3020,17 @@ __metadata: languageName: node linkType: hard +"prop-types@npm:^15.6.1": + version: 15.8.1 + resolution: "prop-types@npm:15.8.1" + dependencies: + loose-envify: "npm:^1.4.0" + object-assign: "npm:^4.1.1" + react-is: "npm:^16.13.1" + checksum: 10c0/59ece7ca2fb9838031d73a48d4becb9a7cc1ed10e610517c7d8f19a1e02fa47f7c27d557d8a5702bec3cfeccddc853579832b43f449e54635803f277b1c78077 + languageName: node + linkType: hard + "proxy-from-env@npm:^2.1.0": version: 2.1.0 resolution: "proxy-from-env@npm:2.1.0" @@ -2429,6 +3061,13 @@ __metadata: languageName: node linkType: hard +"react-is@npm:^16.13.1, react-is@npm:^16.7.0": + version: 16.13.1 + resolution: "react-is@npm:16.13.1" + checksum: 10c0/33977da7a5f1a287936a0c85639fec6ca74f4f15ef1e59a6bc20338fc73dc69555381e211f7a3529b8150a1f71e4225525b41b60b52965bda53ce7d47377ada1 + languageName: node + linkType: hard + "react-reconciler@npm:^0.33.0": version: 0.33.0 resolution: "react-reconciler@npm:0.33.0" @@ -2440,6 +3079,44 @@ __metadata: languageName: node linkType: hard +"react-responsive@npm:^9.0.2": + version: 9.0.2 + resolution: "react-responsive@npm:9.0.2" + dependencies: + hyphenate-style-name: "npm:^1.0.0" + matchmediaquery: "npm:^0.3.0" + prop-types: "npm:^15.6.1" + shallow-equal: "npm:^1.2.1" + peerDependencies: + react: ">=16.8.0" + checksum: 10c0/c58b30d8b8ff993edbc73f7de59a2d86d8abd6380b06a9b41667c3bdd5401e5e710fa1d0fdac21368759ab9b958e8b479a111329a06e9b0241ee08f500b37a20 + languageName: node + linkType: hard + +"react-router-dom@npm:^6.4.4": + version: 6.30.4 + resolution: "react-router-dom@npm:6.30.4" + dependencies: + "@remix-run/router": "npm:1.23.3" + react-router: "npm:6.30.4" + peerDependencies: + react: ">=16.8" + react-dom: ">=16.8" + checksum: 10c0/1b25ab26a288da852f7b58eec5c14b3f37919e6773a557cd846d3a05d7a7d890c8d49bda93c0a7f497f240df1df8b0ad50635f990aafb55f2fc545ad8269a822 + languageName: node + linkType: hard + +"react-router@npm:6.30.4": + version: 6.30.4 + resolution: "react-router@npm:6.30.4" + dependencies: + "@remix-run/router": "npm:1.23.3" + peerDependencies: + react: ">=16.8" + checksum: 10c0/fb6de7d1002bcab9ea12c4072d93792eca494f1e4f30cfec334ccb6523756d23ce3e093c7c1241399296cebed94789a3fb89f96ee76004e0e746458a8f6bab33 + languageName: node + linkType: hard + "react@npm:^18.2.0": version: 18.3.1 resolution: "react@npm:18.3.1" @@ -2463,6 +3140,48 @@ __metadata: languageName: node linkType: hard +"reselect@npm:^5.2.0": + version: 5.2.0 + resolution: "reselect@npm:5.2.0" + checksum: 10c0/d0a8497e404b25a769fe792eacae88b9b576c30870450cd243d76ec9427d91a59c4a2cc00d824d283448b444c4c698a8f961d771c8ae39c759313afb31950e2e + languageName: node + linkType: hard + +"resolve-from@npm:^4.0.0": + version: 4.0.0 + resolution: "resolve-from@npm:4.0.0" + checksum: 10c0/8408eec31a3112ef96e3746c37be7d64020cda07c03a920f5024e77290a218ea758b26ca9529fd7b1ad283947f34b2291c1c0f6aa0ed34acfdda9c6014c8d190 + languageName: node + linkType: hard + +"resolve@npm:^1.19.0": + version: 1.22.12 + resolution: "resolve@npm:1.22.12" + dependencies: + es-errors: "npm:^1.3.0" + is-core-module: "npm:^2.16.1" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/b16dc9b537c02e8c3388f7d3dcff9741d3071625f9a97ac1c885f2b0ca51e78df22328fb6d6ef214dd9101fb7cfc19aa2836fe3410402a94f3f7b8639c7149bf + languageName: node + linkType: hard + +"resolve@patch:resolve@npm%3A^1.19.0#optional!builtin": + version: 1.22.12 + resolution: "resolve@patch:resolve@npm%3A1.22.12#optional!builtin::version=1.22.12&hash=c3c19d" + dependencies: + es-errors: "npm:^1.3.0" + is-core-module: "npm:^2.16.1" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/fc6519984ae1f894d877c0060ba8b1f5ba3bc0e85a02f74e141929c118c23d74d9735619a9cc2965397387e514884245c65d72a40731dcb6cfc84c7bcdc8321e + languageName: node + linkType: hard + "restore-cursor@npm:^4.0.0": version: 4.0.0 resolution: "restore-cursor@npm:4.0.0" @@ -2588,6 +3307,13 @@ __metadata: languageName: node linkType: hard +"shallow-equal@npm:^1.2.1": + version: 1.2.1 + resolution: "shallow-equal@npm:1.2.1" + checksum: 10c0/51e03abadd97c9ebe590547d92db9148446962a3f23a3a0fb1ba2fccab80af881eef0ff1f8ccefd3f066c0bc5a4c8ca53706194813b95c8835fa66448a843a26 + languageName: node + linkType: hard + "sharp@npm:^0.34.5": version: 0.34.5 resolution: "sharp@npm:0.34.5" @@ -2710,6 +3436,13 @@ __metadata: languageName: node linkType: hard +"source-map@npm:^0.5.7": + version: 0.5.7 + resolution: "source-map@npm:0.5.7" + checksum: 10c0/904e767bb9c494929be013017380cbba013637da1b28e5943b566031e29df04fba57edf3f093e0914be094648b577372bd8ad247fa98cfba9c600794cd16b599 + languageName: node + linkType: hard + "stack-utils@npm:^2.0.6": version: 2.0.6 resolution: "stack-utils@npm:2.0.6" @@ -2726,6 +3459,13 @@ __metadata: languageName: node linkType: hard +"state-local@npm:^1.0.6": + version: 1.0.7 + resolution: "state-local@npm:1.0.7" + checksum: 10c0/8dc7daeac71844452fafb514a6d6b6f40d7e2b33df398309ea1c7b3948d6110c57f112b7196500a10c54fdde40291488c52c875575670fb5c819602deca48bd9 + languageName: node + linkType: hard + "std-env@npm:^4.0.0-rc.1": version: 4.1.0 resolution: "std-env@npm:4.1.0" @@ -2763,6 +3503,13 @@ __metadata: languageName: node linkType: hard +"stylis@npm:4.2.0": + version: 4.2.0 + resolution: "stylis@npm:4.2.0" + checksum: 10c0/a7128ad5a8ed72652c6eba46bed4f416521bc9745a460ef5741edc725252cebf36ee45e33a8615a7057403c93df0866ab9ee955960792db210bb80abd5ac6543 + languageName: node + linkType: hard + "subscriptions-transport-ws@npm:^0.9.16": version: 0.9.19 resolution: "subscriptions-transport-ws@npm:0.9.19" @@ -2778,6 +3525,13 @@ __metadata: languageName: node linkType: hard +"supports-preserve-symlinks-flag@npm:^1.0.0": + version: 1.0.0 + resolution: "supports-preserve-symlinks-flag@npm:1.0.0" + checksum: 10c0/6c4032340701a9950865f7ae8ef38578d8d7053f5e10518076e6554a9381fa91bd9c6850193695c141f32b21f979c985db07265a758867bac95de05f7d8aeb39 + languageName: node + linkType: hard + "symbol-observable@npm:^1.0.4": version: 1.2.0 resolution: "symbol-observable@npm:1.2.0" @@ -2931,6 +3685,35 @@ __metadata: languageName: node linkType: hard +"twenty-ui@npm:^1.0.0-alpha.1": + version: 1.0.0-alpha.1 + resolution: "twenty-ui@npm:1.0.0-alpha.1" + dependencies: + "@base-ui/react": "npm:^1.5.0" + "@monaco-editor/react": "npm:^4.7.0" + "@radix-ui/colors": "npm:^3.0.0" + "@sniptt/guards": "npm:^0.2.0" + "@tabler/icons-react": "npm:^3.31.0" + clsx: "npm:^2.1.1" + react-responsive: "npm:^9.0.2" + react-router-dom: "npm:^6.4.4" + type-fest: "npm:^4.10.1" + zod: "npm:^4.1.11" + peerDependencies: + monaco-editor: ">= 0.25.0 < 1" + react: ^19.0.0 + react-dom: ^19.0.0 + checksum: 10c0/213582e582949d6ffe90a7a8ab89a1d4e9dc724d34ed1b80bd7d50e834a8cb8539723556d41b39dc658f76f176bd8490ae0da8a3d6a713e79e6573b732084179 + languageName: node + linkType: hard + +"type-fest@npm:^4.10.1": + version: 4.41.0 + resolution: "type-fest@npm:4.41.0" + checksum: 10c0/f5ca697797ed5e88d33ac8f1fec21921839871f808dc59345c9cf67345bfb958ce41bd821165dbf3ae591cedec2bf6fe8882098dfdd8dc54320b859711a2c1e4 + languageName: node + linkType: hard + "type-fest@npm:^5.4.1": version: 5.6.0 resolution: "type-fest@npm:5.6.0" @@ -2988,6 +3771,15 @@ __metadata: languageName: node linkType: hard +"use-sync-external-store@npm:^1.6.0": + version: 1.6.0 + resolution: "use-sync-external-store@npm:1.6.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10c0/35e1179f872a53227bdf8a827f7911da4c37c0f4091c29b76b1e32473d1670ebe7bcd880b808b7549ba9a5605c233350f800ffab963ee4a4ee346ee983b6019b + languageName: node + linkType: hard + "utility-types@npm:^3.10.0": version: 3.11.0 resolution: "utility-types@npm:3.11.0" @@ -3251,6 +4043,13 @@ __metadata: languageName: node linkType: hard +"yaml@npm:^1.10.0": + version: 1.10.3 + resolution: "yaml@npm:1.10.3" + checksum: 10c0/c309ff85a0a569a981d71ab9cf0fef68672a16b9cdf40639d1c3b30034f6cd16ee428602bd6d64ecf006f8c8bee499023cac236538f79898aa99fb5db529a2ed + languageName: node + linkType: hard + "yoga-layout@npm:~3.2.1": version: 3.2.1 resolution: "yoga-layout@npm:3.2.1" @@ -3274,3 +4073,10 @@ __metadata: checksum: 10c0/71cc2f2bbb537300c3f569e25693d37b3bc91f225cefce251a71c30bc6bb3e7f8e9420ca0eb57f2ac9e492b085b8dfa075fd1e8195c40b83c951dd59c6e4fbf8 languageName: node linkType: hard + +"zod@npm:^4.1.11": + version: 4.4.3 + resolution: "zod@npm:4.4.3" + checksum: 10c0/7ea31b558e88f9faf44f31dd185e2e1cbf51fed3081787fb96cc2534749b50c0acfc6da7f0922a7353ed092dd358c7d50c28ea96c94d04af64191bd33152eca3 + languageName: node + linkType: hard