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(
)