diff --git a/web/packages/agenta-entity-ui/src/testcase/TestcaseDataEditor.tsx b/web/packages/agenta-entity-ui/src/testcase/TestcaseDataEditor.tsx index ce60ddb663..d0e18dae46 100644 --- a/web/packages/agenta-entity-ui/src/testcase/TestcaseDataEditor.tsx +++ b/web/packages/agenta-entity-ui/src/testcase/TestcaseDataEditor.tsx @@ -52,8 +52,18 @@ const MESSAGES_VIEW_OPTIONS: FieldViewModeOption[] = [ {value: "yaml", label: "YAML"}, ] -const getTestcaseViewOptions = ({dataType}: {dataType: DataType}): FieldViewModeOption[] => - dataType === "messages" ? MESSAGES_VIEW_OPTIONS : TESTCASE_VIEW_OPTIONS +// JSON/YAML only — for number and boolean fields +const CODE_ONLY_VIEW_OPTIONS: FieldViewModeOption[] = [ + {value: "json", label: "JSON"}, + {value: "yaml", label: "YAML"}, +] + +const getTestcaseViewOptions = ({dataType}: {dataType: DataType}): FieldViewModeOption[] => { + if (dataType === "messages") return MESSAGES_VIEW_OPTIONS + if (dataType === "number" || dataType === "boolean") return CODE_ONLY_VIEW_OPTIONS + return TESTCASE_VIEW_OPTIONS // strings, null → Text/Markdown/JSON/YAML +} + const renderTestcaseTypeChip = (value: unknown) => function FullPayloadCodeEditor({ diff --git a/web/packages/agenta-entity-ui/src/testcase/TestcaseDrillInFieldRenderer.tsx b/web/packages/agenta-entity-ui/src/testcase/TestcaseDrillInFieldRenderer.tsx index ed7a6144f1..776355a966 100644 --- a/web/packages/agenta-entity-ui/src/testcase/TestcaseDrillInFieldRenderer.tsx +++ b/web/packages/agenta-entity-ui/src/testcase/TestcaseDrillInFieldRenderer.tsx @@ -1,6 +1,8 @@ import {useCallback, useEffect, useMemo} from "react" import { + detectDataType, + type DataType, JsonEditorWithLocalState, MessagesField, type CoreFieldRendererProps, @@ -31,6 +33,8 @@ function CodeEditor({ viewMode, onChange, readOnly, + lockedType, + onLockType, }: { editorId: string value: unknown @@ -38,13 +42,25 @@ function CodeEditor({ viewMode?: ViewMode onChange: (value: unknown) => void readOnly?: boolean + lockedType?: DataType + onLockType?: (type: DataType) => void }) { if (viewMode !== "yaml") { return ( onChange(parseCodeEditorValue(nextValue, value))} + onValidChange={(nextRaw) => { + const parsed = parseCodeEditorValue(nextRaw, value) + onChange(parsed) + // Dynamic type casting: if the parsed type changed, update metadata + if (onLockType) { + const newType = detectDataType(parsed, "native") + if (newType !== lockedType) { + onLockType(newType) + } + } + }} readOnly={readOnly} /> ) @@ -63,11 +79,16 @@ function CodeEditor({ id={editorId} initialValue={displayValue} value={displayValue} - handleChange={ - readOnly - ? undefined - : (nextValue) => onChange(parseCodeEditorValue(nextValue, value, viewMode)) - } + handleChange={(nextValue) => { + const parsed = parseCodeEditorValue(nextValue, value, viewMode) + onChange(parsed) + if (onLockType) { + const newType = detectDataType(parsed, "native") + if (newType !== lockedType) { + onLockType(newType) + } + } + }} editorType="border" className="min-h-[60px] overflow-hidden" disableDebounce @@ -111,6 +132,7 @@ function TextEditor({ markdown, onChange, readOnly, + lockedType, }: { editorId: string value: unknown @@ -118,14 +140,30 @@ function TextEditor({ markdown?: boolean onChange: (value: unknown) => void readOnly?: boolean + lockedType?: DataType }) { // Auto-infer native types from typed text so number / boolean values // stop getting stored as strings. Anything that doesn't look exactly // like a clean number or boolean literal stays a string — see // inferPrimitiveFromText for the precise rules. const handleChange = useCallback( - (next: string) => onChange(inferPrimitiveFromText(next)), - [onChange], + (next: string) => { + const inferred = inferPrimitiveFromText(next) + if (lockedType) { + const inferredType = + typeof inferred === "boolean" + ? "boolean" + : typeof inferred === "number" + ? "number" + : "string" + // Reject if type doesn't match — do NOT call onChange + if (inferredType !== lockedType) { + return + } + } + onChange(inferred) + }, + [onChange, lockedType], ) return ( @@ -173,6 +211,8 @@ export function TestcaseDrillInFieldRenderer({ dataType, isRawMode, viewMode, + lockedType, + onLockType, }: CoreFieldRendererProps) { const displayValue = useMemo(() => toDisplayString(value, viewMode), [value, viewMode]) const editorId = `testcase-field-${fullPathKey}` @@ -200,6 +240,8 @@ export function TestcaseDrillInFieldRenderer({ viewMode={viewMode} onChange={onChange} readOnly={!editable} + lockedType={lockedType} + onLockType={onLockType} /> ) } @@ -250,6 +292,7 @@ export function TestcaseDrillInFieldRenderer({ markdown onChange={onChange} readOnly={!editable} + lockedType={lockedType} /> ) } @@ -262,6 +305,7 @@ export function TestcaseDrillInFieldRenderer({ displayValue={displayValue} onChange={onChange} readOnly={!editable} + lockedType={lockedType} /> ) } @@ -276,6 +320,8 @@ export function TestcaseDrillInFieldRenderer({ viewMode={viewMode} onChange={onChange} readOnly={!editable} + lockedType={lockedType} + onLockType={onLockType} /> ) } @@ -287,6 +333,7 @@ export function TestcaseDrillInFieldRenderer({ displayValue={displayValue} onChange={onChange} readOnly={!editable} + lockedType={lockedType} /> ) }