Verbesserung für Tests von Prompts #80

Merged
Lars merged 2 commits from develop into main 2026-04-12 11:58:07 +02:00
2 changed files with 165 additions and 52 deletions
Showing only changes of commit 08c9cccdcc - Show all commits

View File

@ -1,4 +1,4 @@
import { useState, useEffect, useRef } from 'react' import { useState, useEffect, useRef, useLayoutEffect } from 'react'
import { api } from '../utils/api' import { api } from '../utils/api'
import { X, Plus, Trash2, MoveUp, MoveDown, Code } from 'lucide-react' import { X, Plus, Trash2, MoveUp, MoveDown, Code } from 'lucide-react'
import PlaceholderPicker from './PlaceholderPicker' import PlaceholderPicker from './PlaceholderPicker'
@ -66,6 +66,53 @@ function sanitizeDebugForPanel(dbg, resultType) {
return o 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 (
<div>
<div
ref={wrapRef}
style={{
maxHeight: expanded ? 'none' : `${maxRem}rem`,
overflow: expanded ? 'visible' : 'hidden'
}}
>
{children}
</div>
{showToggle && (
<div style={{ marginTop: 10, textAlign: 'center' }}>
<button
type="button"
className="btn btn-secondary"
style={{ fontSize: 12, padding: '6px 14px' }}
onClick={() => setExpanded((e) => !e)}
>
{expanded ? 'Weniger anzeigen' : 'Mehr anzeigen'}
</button>
</div>
)}
</div>
)
}
function renderTestOutput(testResult) { function renderTestOutput(testResult) {
if (!testResult) return null if (!testResult) return null
if (testResult.error) { if (testResult.error) {
@ -89,7 +136,7 @@ function renderTestOutput(testResult) {
return <span style={{ color: 'var(--text3)' }}>Keine Ausgabe</span> return <span style={{ color: 'var(--text3)' }}>Keine Ausgabe</span>
} }
const proseBox = (content) => ( const proseBox = (content, contentKey) => (
<div <div
style={{ style={{
padding: 16, padding: 16,
@ -102,7 +149,11 @@ function renderTestOutput(testResult) {
textAlign: 'left' textAlign: 'left'
}} }}
> >
{content} {contentKey != null ? (
<ExpandableCollapsible contentKey={contentKey}>{content}</ExpandableCollapsible>
) : (
content
)}
</div> </div>
) )
@ -123,8 +174,12 @@ function renderTestOutput(testResult) {
{key} {key}
</div> </div>
{typeof val === 'string' ? ( {typeof val === 'string' ? (
proseBox(<Markdown text={val} />) proseBox(
<Markdown text={val} />,
`pipe-${key}-${val.length}-${val.slice(0, 120)}`
)
) : ( ) : (
<ExpandableCollapsible contentKey={`pipe-json-${key}-${JSON.stringify(val).slice(0, 200)}`} maxRem={22}>
<pre <pre
style={{ style={{
margin: 0, margin: 0,
@ -134,12 +189,12 @@ function renderTestOutput(testResult) {
border: '1px solid var(--border)', border: '1px solid var(--border)',
fontSize: 12, fontSize: 12,
overflow: 'auto', overflow: 'auto',
maxHeight: 360,
textAlign: 'left' textAlign: 'left'
}} }}
> >
{JSON.stringify(val, null, 2)} {JSON.stringify(val, null, 2)}
</pre> </pre>
</ExpandableCollapsible>
)} )}
</div> </div>
))} ))}
@ -151,7 +206,9 @@ function renderTestOutput(testResult) {
if (fmt === 'json') { if (fmt === 'json') {
try { try {
const parsed = JSON.parse(out) const parsed = JSON.parse(out)
const jsonStr = JSON.stringify(parsed, null, 2)
return ( return (
<ExpandableCollapsible contentKey={`json-out-${jsonStr.length}-${jsonStr.slice(0, 80)}`} maxRem={26}>
<pre <pre
style={{ style={{
margin: 0, margin: 0,
@ -161,22 +218,27 @@ function renderTestOutput(testResult) {
border: '1px solid var(--border)', border: '1px solid var(--border)',
fontSize: 12, fontSize: 12,
overflow: 'auto', overflow: 'auto',
maxHeight: 480,
textAlign: 'left' textAlign: 'left'
}} }}
> >
{JSON.stringify(parsed, null, 2)} {jsonStr}
</pre> </pre>
</ExpandableCollapsible>
) )
} catch { } catch {
return proseBox(<pre style={{ margin: 0, whiteSpace: 'pre-wrap' }}>{out}</pre>) return proseBox(
<pre style={{ margin: 0, whiteSpace: 'pre-wrap' }}>{out}</pre>,
`json-fail-${out.length}-${out.slice(0, 80)}`
)
} }
} }
return proseBox(<Markdown text={out} />) return proseBox(<Markdown text={out} />, `text-out-${out.length}-${out.slice(0, 120)}`)
} }
if (typeof out === 'object') { if (typeof out === 'object') {
const jsonStr = JSON.stringify(out, null, 2)
return ( return (
<ExpandableCollapsible contentKey={`obj-out-${jsonStr.length}-${jsonStr.slice(0, 120)}`} maxRem={26}>
<pre <pre
style={{ style={{
margin: 0, margin: 0,
@ -186,12 +248,12 @@ function renderTestOutput(testResult) {
border: '1px solid var(--border)', border: '1px solid var(--border)',
fontSize: 12, fontSize: 12,
overflow: 'auto', overflow: 'auto',
maxHeight: 480,
textAlign: 'left' textAlign: 'left'
}} }}
> >
{JSON.stringify(out, null, 2)} {jsonStr}
</pre> </pre>
</ExpandableCollapsible>
) )
} }

View File

@ -1,5 +1,5 @@
// Lightweight Markdown renderer handles the subset used by the AI: // 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 }) { export default function Markdown({ text }) {
if (!text) return null if (!text) return null
@ -7,6 +7,19 @@ export default function Markdown({ text }) {
const lines = text.split('\n') const lines = text.split('\n')
const elements = [] const elements = []
let i = 0 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) => { const parseLine = (line) => {
// Parse inline **bold** and *italic* // Parse inline **bold** and *italic*
@ -39,6 +52,44 @@ export default function Markdown({ text }) {
while (i < lines.length) { while (i < lines.length) {
const line = lines[i] 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(
<div key={`code-${blockId++}`} style={{ margin: '10px 0' }}>
{lang && (
<div
style={{
fontSize: 10,
color: 'var(--text3)',
marginBottom: 6,
fontFamily: 'ui-monospace, monospace',
letterSpacing: 0.02
}}
>
{lang}
</div>
)}
<pre style={codeBlockStyle}>
<code style={{ color: 'var(--text1)', whiteSpace: 'pre', display: 'block' }}>{code}</code>
</pre>
</div>
)
continue
}
// Skip empty lines (add spacing) // Skip empty lines (add spacing)
if (line.trim() === '') { if (line.trim() === '') {
elements.push(<div key={i} style={{ height: 8 }} />) elements.push(<div key={i} style={{ height: 8 }} />)