mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-05-25 18:55:19 +02:00
Render background task output with rich markdown
Add a read-only TipTap-backed RichMarkdownViewer and use it for Background Tasks output so rendered index.md files can display the same rich fenced blocks as notes, including email, calendar, chart, table, image, embed, transcript, and Mermaid blocks. Keep the existing Source/Rendered toggle for raw markdown inspection, and hide editor-only delete controls in read-only output. Move the rich block format examples out of the LiveNote-only prompt and into the shared knowledge note style guide. This gives both LiveNote and Background Task agents the same canonical renderer contract, including exact fenced-code schemas for rich Markdown blocks and the rule to avoid emitting task blocks as agent output. Verified with: - npm run build in apps/x/apps/renderer - npm run build in apps/x/packages/core
This commit is contained in:
parent
65f8e9d678
commit
fe5e67f810
5 changed files with 322 additions and 166 deletions
|
|
@ -18,6 +18,7 @@ import { toast } from '@/lib/toast'
|
|||
import type { ConversationItem } from '@/lib/chat-conversation'
|
||||
import { runLogToConversation } from '@/lib/run-to-conversation'
|
||||
import { CompactConversation } from '@/components/compact-conversation'
|
||||
import { RichMarkdownViewer } from '@/components/rich-markdown-viewer'
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Trigger helpers (inlined; extract to shared <TriggersEditor> as a follow-up)
|
||||
|
|
@ -560,9 +561,7 @@ function OutputPane({ slug, taskName, refreshKey }: { slug: string; taskName: st
|
|||
) : viewSource ? (
|
||||
<pre className="overflow-x-auto whitespace-pre-wrap font-mono text-[13px] leading-relaxed">{body}</pre>
|
||||
) : (
|
||||
<Streamdown className="prose dark:prose-invert max-w-none [&>*:first-child]:mt-0 [&>*:last-child]:mb-0">
|
||||
{body}
|
||||
</Streamdown>
|
||||
<RichMarkdownViewer content={body} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
106
apps/x/apps/renderer/src/components/rich-markdown-viewer.tsx
Normal file
106
apps/x/apps/renderer/src/components/rich-markdown-viewer.tsx
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
import { useEffect } from 'react'
|
||||
import { EditorContent, useEditor } from '@tiptap/react'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import Link from '@tiptap/extension-link'
|
||||
import Image from '@tiptap/extension-image'
|
||||
import TaskList from '@tiptap/extension-task-list'
|
||||
import TaskItem from '@tiptap/extension-task-item'
|
||||
import { TableKit } from '@tiptap/extension-table'
|
||||
import { Markdown } from 'tiptap-markdown'
|
||||
import { TaskBlockExtension } from '@/extensions/task-block'
|
||||
import { PromptBlockExtension } from '@/extensions/prompt-block'
|
||||
import { ImageBlockExtension } from '@/extensions/image-block'
|
||||
import { EmbedBlockExtension } from '@/extensions/embed-block'
|
||||
import { IframeBlockExtension } from '@/extensions/iframe-block'
|
||||
import { ChartBlockExtension } from '@/extensions/chart-block'
|
||||
import { TableBlockExtension } from '@/extensions/table-block'
|
||||
import { CalendarBlockExtension } from '@/extensions/calendar-block'
|
||||
import { EmailBlockExtension, EmailsBlockExtension } from '@/extensions/email-block'
|
||||
import { TranscriptBlockExtension } from '@/extensions/transcript-block'
|
||||
import { MermaidBlockExtension } from '@/extensions/mermaid-block'
|
||||
import { WikiLink } from '@/extensions/wiki-link'
|
||||
import '@/styles/editor.css'
|
||||
|
||||
const BLANK_LINE_MARKER = '\u200B'
|
||||
|
||||
function preprocessMarkdown(markdown: string): string {
|
||||
return markdown.replace(/\n{3,}/g, (match) => {
|
||||
const emptyParagraphs = match.length - 2
|
||||
let result = '\n\n'
|
||||
for (let i = 0; i < emptyParagraphs; i += 1) {
|
||||
result += BLANK_LINE_MARKER + '\n\n'
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
export function RichMarkdownViewer({ content }: { content: string }) {
|
||||
const editor = useEditor({
|
||||
editable: false,
|
||||
extensions: [
|
||||
StarterKit.configure({
|
||||
heading: {
|
||||
levels: [1, 2, 3],
|
||||
},
|
||||
}),
|
||||
Link.configure({
|
||||
openOnClick: true,
|
||||
HTMLAttributes: {
|
||||
rel: 'noopener noreferrer',
|
||||
target: '_blank',
|
||||
},
|
||||
}),
|
||||
Image.configure({
|
||||
inline: false,
|
||||
allowBase64: true,
|
||||
HTMLAttributes: {
|
||||
class: 'editor-image',
|
||||
},
|
||||
}),
|
||||
TaskBlockExtension,
|
||||
PromptBlockExtension,
|
||||
ImageBlockExtension,
|
||||
EmbedBlockExtension,
|
||||
IframeBlockExtension,
|
||||
ChartBlockExtension,
|
||||
TableBlockExtension,
|
||||
CalendarBlockExtension,
|
||||
EmailsBlockExtension,
|
||||
EmailBlockExtension,
|
||||
TranscriptBlockExtension,
|
||||
MermaidBlockExtension,
|
||||
WikiLink,
|
||||
TaskList,
|
||||
TaskItem.configure({
|
||||
nested: true,
|
||||
}),
|
||||
TableKit.configure({
|
||||
table: { resizable: false },
|
||||
}),
|
||||
Markdown.configure({
|
||||
html: true,
|
||||
breaks: true,
|
||||
tightLists: false,
|
||||
transformCopiedText: false,
|
||||
transformPastedText: false,
|
||||
}),
|
||||
],
|
||||
content: preprocessMarkdown(content),
|
||||
editorProps: {
|
||||
attributes: {
|
||||
class: 'prose prose-sm max-w-none focus:outline-none',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (!editor) return
|
||||
editor.chain().setMeta('addToHistory', false).setContent(preprocessMarkdown(content)).run()
|
||||
}, [content, editor])
|
||||
|
||||
return (
|
||||
<div className="tiptap-editor rich-markdown-viewer">
|
||||
<EditorContent editor={editor} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -2020,3 +2020,33 @@
|
|||
.dark .tiptap-editor .ProseMirror p.is-editor-empty:first-child::before {
|
||||
color: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
/* Read-only renderer used by surfaces that need rich blocks without editor chrome. */
|
||||
.rich-markdown-viewer {
|
||||
display: block;
|
||||
overflow: visible;
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
.rich-markdown-viewer .ProseMirror {
|
||||
max-width: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.rich-markdown-viewer .ProseMirror:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.rich-markdown-viewer .ProseMirror .task-block-delete,
|
||||
.rich-markdown-viewer .ProseMirror .image-block-delete,
|
||||
.rich-markdown-viewer .ProseMirror .embed-block-delete,
|
||||
.rich-markdown-viewer .ProseMirror .iframe-block-delete,
|
||||
.rich-markdown-viewer .ProseMirror .chart-block-delete,
|
||||
.rich-markdown-viewer .ProseMirror .table-block-delete,
|
||||
.rich-markdown-viewer .ProseMirror .calendar-block-delete,
|
||||
.rich-markdown-viewer .ProseMirror .email-block-delete,
|
||||
.rich-markdown-viewer .ProseMirror .email-draft-block-delete,
|
||||
.rich-markdown-viewer .ProseMirror .mermaid-block-delete {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue