Verbesserung für Tests von Prompts #80
|
|
@ -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 (
|
||||
<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) {
|
||||
if (!testResult) return null
|
||||
if (testResult.error) {
|
||||
|
|
@ -89,7 +136,7 @@ function renderTestOutput(testResult) {
|
|||
return <span style={{ color: 'var(--text3)' }}>Keine Ausgabe</span>
|
||||
}
|
||||
|
||||
const proseBox = (content) => (
|
||||
const proseBox = (content, contentKey) => (
|
||||
<div
|
||||
style={{
|
||||
padding: 16,
|
||||
|
|
@ -102,7 +149,11 @@ function renderTestOutput(testResult) {
|
|||
textAlign: 'left'
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
{contentKey != null ? (
|
||||
<ExpandableCollapsible contentKey={contentKey}>{content}</ExpandableCollapsible>
|
||||
) : (
|
||||
content
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
|
|
@ -123,23 +174,27 @@ function renderTestOutput(testResult) {
|
|||
{key}
|
||||
</div>
|
||||
{typeof val === 'string' ? (
|
||||
proseBox(<Markdown text={val} />)
|
||||
proseBox(
|
||||
<Markdown text={val} />,
|
||||
`pipe-${key}-${val.length}-${val.slice(0, 120)}`
|
||||
)
|
||||
) : (
|
||||
<pre
|
||||
style={{
|
||||
margin: 0,
|
||||
padding: 12,
|
||||
background: 'var(--bg)',
|
||||
borderRadius: 8,
|
||||
border: '1px solid var(--border)',
|
||||
fontSize: 12,
|
||||
overflow: 'auto',
|
||||
maxHeight: 360,
|
||||
textAlign: 'left'
|
||||
}}
|
||||
>
|
||||
{JSON.stringify(val, null, 2)}
|
||||
</pre>
|
||||
<ExpandableCollapsible contentKey={`pipe-json-${key}-${JSON.stringify(val).slice(0, 200)}`} maxRem={22}>
|
||||
<pre
|
||||
style={{
|
||||
margin: 0,
|
||||
padding: 12,
|
||||
background: 'var(--bg)',
|
||||
borderRadius: 8,
|
||||
border: '1px solid var(--border)',
|
||||
fontSize: 12,
|
||||
overflow: 'auto',
|
||||
textAlign: 'left'
|
||||
}}
|
||||
>
|
||||
{JSON.stringify(val, null, 2)}
|
||||
</pre>
|
||||
</ExpandableCollapsible>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
|
|
@ -151,47 +206,54 @@ function renderTestOutput(testResult) {
|
|||
if (fmt === 'json') {
|
||||
try {
|
||||
const parsed = JSON.parse(out)
|
||||
const jsonStr = JSON.stringify(parsed, null, 2)
|
||||
return (
|
||||
<pre
|
||||
style={{
|
||||
margin: 0,
|
||||
padding: 12,
|
||||
background: 'var(--bg)',
|
||||
borderRadius: 8,
|
||||
border: '1px solid var(--border)',
|
||||
fontSize: 12,
|
||||
overflow: 'auto',
|
||||
maxHeight: 480,
|
||||
textAlign: 'left'
|
||||
}}
|
||||
>
|
||||
{JSON.stringify(parsed, null, 2)}
|
||||
</pre>
|
||||
<ExpandableCollapsible contentKey={`json-out-${jsonStr.length}-${jsonStr.slice(0, 80)}`} maxRem={26}>
|
||||
<pre
|
||||
style={{
|
||||
margin: 0,
|
||||
padding: 12,
|
||||
background: 'var(--bg)',
|
||||
borderRadius: 8,
|
||||
border: '1px solid var(--border)',
|
||||
fontSize: 12,
|
||||
overflow: 'auto',
|
||||
textAlign: 'left'
|
||||
}}
|
||||
>
|
||||
{jsonStr}
|
||||
</pre>
|
||||
</ExpandableCollapsible>
|
||||
)
|
||||
} 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') {
|
||||
const jsonStr = JSON.stringify(out, null, 2)
|
||||
return (
|
||||
<pre
|
||||
style={{
|
||||
margin: 0,
|
||||
padding: 12,
|
||||
background: 'var(--bg)',
|
||||
borderRadius: 8,
|
||||
border: '1px solid var(--border)',
|
||||
fontSize: 12,
|
||||
overflow: 'auto',
|
||||
maxHeight: 480,
|
||||
textAlign: 'left'
|
||||
}}
|
||||
>
|
||||
{JSON.stringify(out, null, 2)}
|
||||
</pre>
|
||||
<ExpandableCollapsible contentKey={`obj-out-${jsonStr.length}-${jsonStr.slice(0, 120)}`} maxRem={26}>
|
||||
<pre
|
||||
style={{
|
||||
margin: 0,
|
||||
padding: 12,
|
||||
background: 'var(--bg)',
|
||||
borderRadius: 8,
|
||||
border: '1px solid var(--border)',
|
||||
fontSize: 12,
|
||||
overflow: 'auto',
|
||||
textAlign: 'left'
|
||||
}}
|
||||
>
|
||||
{jsonStr}
|
||||
</pre>
|
||||
</ExpandableCollapsible>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
<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)
|
||||
if (line.trim() === '') {
|
||||
elements.push(<div key={i} style={{ height: 8 }} />)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user