mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-06-12 19:55:19 +02:00
transcript block
This commit is contained in:
parent
066ac81791
commit
cf33e78243
5 changed files with 168 additions and 4 deletions
|
|
@ -3476,7 +3476,8 @@ function App() {
|
|||
const bodyWithoutTitle = transcriptBody.replace(/^#\s+.+\s*\n*/, '')
|
||||
// Also strip any title/heading the LLM may have generated
|
||||
const cleanedNotes = notes.replace(/^#{1,2}\s+.+\n+/, '')
|
||||
const newBody = `# ${noteTitle}\n\n` + cleanedNotes + '\n\n---\n\n## Raw transcript\n\n' + bodyWithoutTitle
|
||||
const transcriptData = JSON.stringify({ transcript: bodyWithoutTitle.trim() })
|
||||
const newBody = `# ${noteTitle}\n\n` + cleanedNotes + '\n\n```transcript\n' + transcriptData + '\n```'
|
||||
const newContent = fm ? `${fm}\n${newBody}` : newBody
|
||||
await window.ipc.invoke('workspace:writeFile', {
|
||||
path: notePath,
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import { ChartBlockExtension } from '@/extensions/chart-block'
|
|||
import { TableBlockExtension } from '@/extensions/table-block'
|
||||
import { CalendarBlockExtension } from '@/extensions/calendar-block'
|
||||
import { EmailBlockExtension } from '@/extensions/email-block'
|
||||
import { TranscriptBlockExtension } from '@/extensions/transcript-block'
|
||||
import { Markdown } from 'tiptap-markdown'
|
||||
import { useEffect, useCallback, useMemo, useRef, useState } from 'react'
|
||||
import { Calendar, ChevronDown, ExternalLink } from 'lucide-react'
|
||||
|
|
@ -155,6 +156,8 @@ function getMarkdownWithBlankLines(editor: Editor): string {
|
|||
blocks.push('```calendar\n' + (node.attrs?.data as string || '{}') + '\n```')
|
||||
} else if (node.type === 'emailBlock') {
|
||||
blocks.push('```email\n' + (node.attrs?.data as string || '{}') + '\n```')
|
||||
} else if (node.type === 'transcriptBlock') {
|
||||
blocks.push('```transcript\n' + (node.attrs?.data as string || '{}') + '\n```')
|
||||
} else if (node.type === 'codeBlock') {
|
||||
const lang = (node.attrs?.language as string) || ''
|
||||
blocks.push('```' + lang + '\n' + nodeToText(node) + '\n```')
|
||||
|
|
@ -567,6 +570,7 @@ export function MarkdownEditor({
|
|||
TableBlockExtension,
|
||||
CalendarBlockExtension,
|
||||
EmailBlockExtension,
|
||||
TranscriptBlockExtension,
|
||||
WikiLink.configure({
|
||||
onCreate: wikiLinks?.onCreate
|
||||
? (path) => {
|
||||
|
|
|
|||
102
apps/x/apps/renderer/src/extensions/transcript-block.tsx
Normal file
102
apps/x/apps/renderer/src/extensions/transcript-block.tsx
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
import { mergeAttributes, Node } from '@tiptap/react'
|
||||
import { ReactNodeViewRenderer, NodeViewWrapper } from '@tiptap/react'
|
||||
import { ChevronDown, FileText } from 'lucide-react'
|
||||
import { blocks } from '@x/shared'
|
||||
import { useState } from 'react'
|
||||
|
||||
function TranscriptBlockView({ node }: {
|
||||
node: { attrs: Record<string, unknown> }
|
||||
}) {
|
||||
const raw = node.attrs.data as string
|
||||
let config: blocks.TranscriptBlock | null = null
|
||||
|
||||
try {
|
||||
config = blocks.TranscriptBlockSchema.parse(JSON.parse(raw))
|
||||
} catch {
|
||||
// fallback below
|
||||
}
|
||||
|
||||
const [expanded, setExpanded] = useState(false)
|
||||
|
||||
if (!config) {
|
||||
return (
|
||||
<NodeViewWrapper className="transcript-block-wrapper" data-type="transcript-block">
|
||||
<div className="transcript-block-card transcript-block-error">
|
||||
<FileText size={16} />
|
||||
<span>Invalid transcript block</span>
|
||||
</div>
|
||||
</NodeViewWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<NodeViewWrapper className="transcript-block-wrapper" data-type="transcript-block">
|
||||
<div className="transcript-block-card" onMouseDown={(e) => e.stopPropagation()}>
|
||||
<button
|
||||
className="transcript-block-toggle"
|
||||
onClick={(e) => { e.stopPropagation(); setExpanded(!expanded) }}
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
>
|
||||
<ChevronDown size={14} className={`transcript-block-chevron ${expanded ? 'transcript-block-chevron-open' : ''}`} />
|
||||
<FileText size={14} />
|
||||
<span>Raw transcript</span>
|
||||
</button>
|
||||
{expanded && (
|
||||
<div className="transcript-block-content">
|
||||
{config.transcript}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</NodeViewWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export const TranscriptBlockExtension = Node.create({
|
||||
name: 'transcriptBlock',
|
||||
group: 'block',
|
||||
atom: true,
|
||||
selectable: true,
|
||||
draggable: false,
|
||||
|
||||
addAttributes() {
|
||||
return {
|
||||
data: { default: '{}' },
|
||||
}
|
||||
},
|
||||
|
||||
parseHTML() {
|
||||
return [{
|
||||
tag: 'pre',
|
||||
priority: 60,
|
||||
getAttrs(element) {
|
||||
const code = element.querySelector('code')
|
||||
if (!code) return false
|
||||
const cls = code.className || ''
|
||||
if (cls.includes('language-transcript')) {
|
||||
return { data: code.textContent || '{}' }
|
||||
}
|
||||
return false
|
||||
},
|
||||
}]
|
||||
},
|
||||
|
||||
renderHTML({ HTMLAttributes }: { HTMLAttributes: Record<string, unknown> }) {
|
||||
return ['div', mergeAttributes(HTMLAttributes, { 'data-type': 'transcript-block' })]
|
||||
},
|
||||
|
||||
addNodeView() {
|
||||
return ReactNodeViewRenderer(TranscriptBlockView)
|
||||
},
|
||||
|
||||
addStorage() {
|
||||
return {
|
||||
markdown: {
|
||||
serialize(state: { write: (text: string) => void; closeBlock: (node: unknown) => void }, node: { attrs: { data: string } }) {
|
||||
state.write('```transcript\n' + node.attrs.data + '\n```')
|
||||
state.closeBlock(node)
|
||||
},
|
||||
parse: {},
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
|
@ -618,7 +618,8 @@
|
|||
.tiptap-editor .ProseMirror .chart-block-wrapper,
|
||||
.tiptap-editor .ProseMirror .table-block-wrapper,
|
||||
.tiptap-editor .ProseMirror .calendar-block-wrapper,
|
||||
.tiptap-editor .ProseMirror .email-block-wrapper {
|
||||
.tiptap-editor .ProseMirror .email-block-wrapper,
|
||||
.tiptap-editor .ProseMirror .transcript-block-wrapper {
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
|
|
@ -628,7 +629,8 @@
|
|||
.tiptap-editor .ProseMirror .table-block-card,
|
||||
.tiptap-editor .ProseMirror .calendar-block-card,
|
||||
.tiptap-editor .ProseMirror .email-block-card,
|
||||
.tiptap-editor .ProseMirror .email-draft-block-card {
|
||||
.tiptap-editor .ProseMirror .email-draft-block-card,
|
||||
.tiptap-editor .ProseMirror .transcript-block-card {
|
||||
position: relative;
|
||||
padding: 12px 14px;
|
||||
border: 1px solid var(--border);
|
||||
|
|
@ -644,7 +646,8 @@
|
|||
.tiptap-editor .ProseMirror .table-block-card:hover,
|
||||
.tiptap-editor .ProseMirror .calendar-block-card:hover,
|
||||
.tiptap-editor .ProseMirror .email-block-card:hover,
|
||||
.tiptap-editor .ProseMirror .email-draft-block-card:hover {
|
||||
.tiptap-editor .ProseMirror .email-draft-block-card:hover,
|
||||
.tiptap-editor .ProseMirror .transcript-block-card:hover {
|
||||
background-color: color-mix(in srgb, var(--muted) 70%, transparent);
|
||||
}
|
||||
|
||||
|
|
@ -1488,6 +1491,54 @@
|
|||
margin-left: 4px;
|
||||
}
|
||||
|
||||
/* Transcript block */
|
||||
.tiptap-editor .ProseMirror .transcript-block-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: color-mix(in srgb, var(--foreground) 60%, transparent);
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: color 0.12s ease;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .transcript-block-toggle:hover {
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .transcript-block-chevron {
|
||||
transition: transform 0.15s ease;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .transcript-block-chevron-open {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .transcript-block-content {
|
||||
margin-top: 10px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid color-mix(in srgb, var(--foreground) 10%, transparent);
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
color: color-mix(in srgb, var(--foreground) 70%, transparent);
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .transcript-block-error {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
color: color-mix(in srgb, var(--foreground) 55%, transparent);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* Meeting event banner */
|
||||
.meeting-event-banner {
|
||||
position: relative;
|
||||
|
|
|
|||
|
|
@ -74,3 +74,9 @@ export const EmailBlockSchema = z.object({
|
|||
});
|
||||
|
||||
export type EmailBlock = z.infer<typeof EmailBlockSchema>;
|
||||
|
||||
export const TranscriptBlockSchema = z.object({
|
||||
transcript: z.string(),
|
||||
});
|
||||
|
||||
export type TranscriptBlock = z.infer<typeof TranscriptBlockSchema>;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue