diff --git a/frontend/src/components/UnifiedPromptModal.jsx b/frontend/src/components/UnifiedPromptModal.jsx
index 3052f06..4281866 100644
--- a/frontend/src/components/UnifiedPromptModal.jsx
+++ b/frontend/src/components/UnifiedPromptModal.jsx
@@ -1,4 +1,4 @@
-import { useState, useEffect, useRef } from 'react'
+import { useState, useEffect, useRef, useLayoutEffect } from 'react'
import { api } from '../utils/api'
import { X, Plus, Trash2, MoveUp, MoveDown, Code } from 'lucide-react'
import PlaceholderPicker from './PlaceholderPicker'
@@ -66,6 +66,53 @@ function sanitizeDebugForPanel(dbg, resultType) {
return o
}
+/** Langer Inhalt: zunächst auf maxRem eingeklappt, „Mehr/Weniger anzeigen“ */
+function ExpandableCollapsible({ children, contentKey, maxRem = 28 }) {
+ const [expanded, setExpanded] = useState(false)
+ const wrapRef = useRef(null)
+ const [showToggle, setShowToggle] = useState(false)
+
+ useEffect(() => {
+ setExpanded(false)
+ }, [contentKey])
+
+ useLayoutEffect(() => {
+ const el = wrapRef.current
+ if (!el) return
+ if (expanded) {
+ setShowToggle(true)
+ return
+ }
+ setShowToggle(el.scrollHeight > el.clientHeight + 2)
+ }, [contentKey, expanded])
+
+ return (
+
+
+ {children}
+
+ {showToggle && (
+
+
+
+ )}
+
+ )
+}
+
function renderTestOutput(testResult) {
if (!testResult) return null
if (testResult.error) {
@@ -89,7 +136,7 @@ function renderTestOutput(testResult) {
return Keine Ausgabe
}
- const proseBox = (content) => (
+ const proseBox = (content, contentKey) => (
- {content}
+ {contentKey != null ? (
+ {content}
+ ) : (
+ content
+ )}
)
@@ -123,23 +174,27 @@ function renderTestOutput(testResult) {
{key}
{typeof val === 'string' ? (
- proseBox()
+ proseBox(
+ ,
+ `pipe-${key}-${val.length}-${val.slice(0, 120)}`
+ )
) : (
-
- {JSON.stringify(val, null, 2)}
-
+
+
+ {JSON.stringify(val, null, 2)}
+
+
)}
))}
@@ -151,47 +206,54 @@ function renderTestOutput(testResult) {
if (fmt === 'json') {
try {
const parsed = JSON.parse(out)
+ const jsonStr = JSON.stringify(parsed, null, 2)
return (
-
- {JSON.stringify(parsed, null, 2)}
-
+
+
+ {jsonStr}
+
+
)
} catch {
- return proseBox({out})
+ return proseBox(
+ {out},
+ `json-fail-${out.length}-${out.slice(0, 80)}`
+ )
}
}
- return proseBox()
+ return proseBox(, `text-out-${out.length}-${out.slice(0, 120)}`)
}
if (typeof out === 'object') {
+ const jsonStr = JSON.stringify(out, null, 2)
return (
-
- {JSON.stringify(out, null, 2)}
-
+
+
+ {jsonStr}
+
+
)
}
diff --git a/frontend/src/utils/Markdown.jsx b/frontend/src/utils/Markdown.jsx
index 328fa8b..ece6419 100644
--- a/frontend/src/utils/Markdown.jsx
+++ b/frontend/src/utils/Markdown.jsx
@@ -1,5 +1,5 @@
// Lightweight Markdown renderer – handles the subset used by the AI:
-// ## Headings, **bold**, bullet lists, numbered lists, line breaks
+// ## Headings, **bold**, bullet lists, numbered lists, fenced ``` code ```, line breaks
export default function Markdown({ text }) {
if (!text) return null
@@ -7,6 +7,19 @@ export default function Markdown({ text }) {
const lines = text.split('\n')
const elements = []
let i = 0
+ let blockId = 0
+
+ const codeBlockStyle = {
+ margin: '10px 0',
+ padding: 12,
+ background: 'var(--surface2)',
+ borderRadius: 8,
+ border: '1px solid var(--border)',
+ overflow: 'auto',
+ fontSize: 12,
+ lineHeight: 1.5,
+ fontFamily: 'ui-monospace, Consolas, monospace'
+ }
const parseLine = (line) => {
// Parse inline **bold** and *italic*
@@ -39,6 +52,44 @@ export default function Markdown({ text }) {
while (i < lines.length) {
const line = lines[i]
+ // Fenced code block: ``` or ```lang
+ const trimmedStart = line.trimStart()
+ if (trimmedStart.startsWith('```')) {
+ const lang = trimmedStart.slice(3).trim() || null
+ i++
+ const codeLines = []
+ while (i < lines.length) {
+ if (lines[i].trim().startsWith('```')) {
+ i++
+ break
+ }
+ codeLines.push(lines[i])
+ i++
+ }
+ const code = codeLines.join('\n')
+ elements.push(
+
+ {lang && (
+
+ {lang}
+
+ )}
+
+ {code}
+
+
+ )
+ continue
+ }
+
// Skip empty lines (add spacing)
if (line.trim() === '') {
elements.push()