()
allExistingFathomShapes.forEach(s => {
const noteId = (s as any).props?.noteId || ''
const match = noteId.match(/fathom-(?:summary|transcript|actions)-(.+)/)
if (match) {
meetingIds.add(match[1])
}
})
const meetingRowIndex = Array.from(meetingIds).indexOf(currentMeetingId)
const actualMeetingRowIndex = meetingRowIndex >= 0 ? meetingRowIndex : meetingIds.size
// Shape dimensions - all shapes are the same size
const shapeWidth = 500
const shapeHeight = 600
const horizontalSpacing = 20
const verticalSpacing = 30 // Space between meeting rows
const shapesToCreate: any[] = []
// Calculate Y position for this meeting's shapes
// If this meeting already has shapes, use the Y position of the first existing shape
// Otherwise, calculate based on meeting row index
let baseY: number
if (existingShapesForThisMeeting.length > 0) {
// Use the Y position of existing shapes for this meeting to ensure they're on the same line
const firstExistingShapeBounds = this.editor.getShapePageBounds(existingShapesForThisMeeting[0].id)
baseY = firstExistingShapeBounds ? firstExistingShapeBounds.y : startY + actualMeetingRowIndex * (shapeHeight + verticalSpacing)
} else {
// New meeting row - calculate position based on row index
baseY = startY + actualMeetingRowIndex * (shapeHeight + verticalSpacing)
}
// Calculate horizontal positions for this meeting's shapes
// Summary, Transcript, Action Items will be side by side on the same horizontal line
// Each meeting row is positioned below the previous one
let currentX = startX
// If this meeting already has shapes, position new shapes after the existing ones
if (existingShapesForThisMeeting.length > 0) {
// Find the rightmost existing shape for this meeting
let rightmostX = startX
existingShapesForThisMeeting.forEach(s => {
const bounds = this.editor.getShapePageBounds(s.id)
if (bounds) {
const shapeRight = bounds.x + bounds.w
if (shapeRight > rightmostX) {
rightmostX = shapeRight
}
}
})
// Start new shapes after the rightmost existing shape
currentX = rightmostX + horizontalSpacing
}
// Create shapes for each selected data type in button order: Summary, Transcript, Action Items
// Position shapes horizontally for the same meeting, vertically for different meetings
// Blue shades match button colors: Summary (#3b82f6), Transcript (#2563eb), Actions (#1d4ed8)
if (options.summary) {
// Check for summary in various possible formats from Fathom API
const summaryText = fullMeeting.default_summary?.markdown_formatted ||
fullMeeting.default_summary?.text ||
fullMeeting.summary?.markdown_formatted ||
fullMeeting.summary?.text ||
fullMeeting.summary ||
''
if (summaryText) {
const xPos = currentX
const yPos = baseY
// Create Fathom note shape for summary with lightest blue (#3b82f6)
// Format: date in top right, title in content
const contentWithHeader = meetingDate
? `
${meetingName}: Fathom Summary
${meetingDate}
\n\n${summaryText}`
: `# ${meetingName}: Fathom Summary\n\n${summaryText}`
const noteShape = FathomNoteShape.createFromData(
{
id: `fathom-summary-${meetingRecordingId}`,
title: 'Fathom Meeting Object: Summary',
content: contentWithHeader,
tags: ['fathom', 'summary'],
primaryColor: '#3b82f6', // Lightest blue - matches Summary button
},
xPos,
yPos
)
// Update the shape dimensions - all shapes same size
const updatedNoteShape = {
...noteShape,
props: {
...noteShape.props,
w: shapeWidth,
h: shapeHeight,
}
}
shapesToCreate.push(updatedNoteShape)
currentX += shapeWidth + horizontalSpacing
} else {
console.warn('Summary requested but no summary data found in meeting response')
}
}
if (options.transcript) {
// Check for transcript data
const transcript = fullMeeting.transcript || []
if (transcript.length > 0) {
const xPos = currentX
const yPos = baseY
// Create Fathom note shape for transcript with medium blue (#2563eb)
const transcriptText = transcript.map((entry: any) => {
const speaker = entry.speaker?.display_name || 'Unknown'
const text = entry.text || ''
const timestamp = entry.timestamp || ''
return timestamp ? `**${speaker}** (${timestamp}): ${text}` : `**${speaker}**: ${text}`
}).join('\n\n')
// Format: date in top right, title in content
const contentWithHeader = meetingDate
? `
${meetingName}: Fathom Transcript
${meetingDate}
\n\n${transcriptText}`
: `# ${meetingName}: Fathom Transcript\n\n${transcriptText}`
const noteShape = FathomNoteShape.createFromData(
{
id: `fathom-transcript-${meetingRecordingId}`,
title: 'Fathom Meeting Object: Transcript',
content: contentWithHeader,
tags: ['fathom', 'transcript'],
primaryColor: '#2563eb', // Medium blue - matches Transcript button
},
xPos,
yPos
)
// Update the shape dimensions - same size as others
const updatedNoteShape = {
...noteShape,
props: {
...noteShape.props,
w: shapeWidth,
h: shapeHeight,
}
}
shapesToCreate.push(updatedNoteShape)
currentX += shapeWidth + horizontalSpacing
} else {
console.warn('Transcript requested but no transcript data found in meeting response')
}
}
if (options.actionItems) {
// Check for action items in various possible formats from Fathom API
const actionItems = fullMeeting.action_items || fullMeeting.actionItems || []
if (actionItems.length > 0) {
const xPos = currentX
const yPos = baseY
// Create Fathom note shape for action items with darker blue (#1d4ed8)
const actionItemsText = actionItems.map((item: any) => {
const description = item.description || item.text || item.title || ''
const assignee = item.assignee?.name || item.assignee || item.owner?.name || item.owner || ''
const dueDate = item.due_date || item.dueDate || item.due || ''
let itemText = `- [ ] ${description}`
if (assignee) itemText += ` (@${assignee})`
if (dueDate) itemText += ` - Due: ${dueDate}`
return itemText
}).join('\n')
// Format: date in top right, title in content
const contentWithHeader = meetingDate
? `
${meetingName}: Fathom Action Items
${meetingDate}
\n\n${actionItemsText}`
: `# ${meetingName}: Fathom Action Items\n\n${actionItemsText}`
const noteShape = FathomNoteShape.createFromData(
{
id: `fathom-actions-${meetingRecordingId}`,
title: 'Fathom Meeting Object: Action Items',
content: contentWithHeader,
tags: ['fathom', 'action-items'],
primaryColor: '#1d4ed8', // Darker blue - matches Action Items button
},
xPos,
yPos
)
// Update the shape dimensions - same size as others
const updatedNoteShape = {
...noteShape,
props: {
...noteShape.props,
w: shapeWidth,
h: shapeHeight,
}
}
shapesToCreate.push(updatedNoteShape)
currentX += shapeWidth + horizontalSpacing
} else {
console.warn('Action items requested but no action items found in meeting response')
}
}
if (options.video) {
// Open Fathom video URL directly in a new tab instead of creating a note shape
// Try multiple sources for the correct video URL
// The Fathom API may provide url, share_url, or we may need to construct from call_id or id
const callId = fullMeeting.call_id ||
fullMeeting.id ||
fullMeeting.recording_id ||
meeting.call_id ||
meeting.id ||
meeting.recording_id
// Check if URL fields contain valid meeting URLs (contain /calls/)
const isValidMeetingUrl = (url: string) => url && url.includes('/calls/')
// Prioritize valid meeting URLs, then construct from call ID
const videoUrl = (fullMeeting.url && isValidMeetingUrl(fullMeeting.url)) ? fullMeeting.url :
(fullMeeting.share_url && isValidMeetingUrl(fullMeeting.share_url)) ? fullMeeting.share_url :
(meeting.url && isValidMeetingUrl(meeting.url)) ? meeting.url :
(meeting.share_url && isValidMeetingUrl(meeting.share_url)) ? meeting.share_url :
(callId ? `https://fathom.video/calls/${callId}` : null)
if (videoUrl) {
console.log('Opening Fathom video URL:', videoUrl, 'for meeting:', { callId, recording_id: meeting.recording_id })
window.open(videoUrl, '_blank', 'noopener,noreferrer')
} else {
console.error('Could not determine Fathom video URL for meeting:', { meeting, fullMeeting })
}
}
// Create all shapes at once
if (shapesToCreate.length > 0) {
this.editor.createShapes(shapesToCreate)
// Animate camera to the first created note
// Animate camera to show the note
setTimeout(() => {
const firstShapeId = shapesToCreate[0].id
// getShapePageBounds works with raw ID, setSelectedShapes needs "shape:" prefix
const rawShapeId = firstShapeId.startsWith('shape:') ? firstShapeId.replace('shape:', '') : firstShapeId
const shapeIdWithPrefix = `shape:${rawShapeId}`
const firstShapeBounds = this.editor.getShapePageBounds(rawShapeId)
if (firstShapeBounds) {
let boundsToShow = firstShapeBounds
if (browserShapeBounds) {
const minX = Math.min(browserShapeBounds.x, firstShapeBounds.x)
const maxX = Math.max(browserShapeBounds.x + browserShapeBounds.w, firstShapeBounds.x + firstShapeBounds.w)
const minY = Math.min(browserShapeBounds.y, firstShapeBounds.y)
const maxY = Math.max(browserShapeBounds.y + browserShapeBounds.h, firstShapeBounds.y + firstShapeBounds.h)
boundsToShow = Box.Common([browserShapeBounds, firstShapeBounds])
}
this.editor.zoomToBounds(boundsToShow, {
inset: 50,
animation: {
duration: 500,
easing: (t) => t * (2 - t),
},
})
}
this.editor.setSelectedShapes([shapeIdWithPrefix] as any)
this.editor.setCurrentTool('select')
}, 50)
}
} catch (error) {
console.error('Error creating Fathom meeting shapes:', error)
}
}
if (!isOpen) {
return null
}
return (
{
this.editor.updateShape({
id: shape.id,
type: 'FathomMeetingsBrowser',
props: {
...shape.props,
tags: newTags,
}
})
}}
tagsEditable={true}
>
)
}
return
}
indicator(shape: IFathomMeetingsBrowser) {
return
}
}