fix(canvas): Fix IntrospectionPanel to handle new NX introspection API response format
- Handle expressions as object with user/internal arrays (new format) or direct array (old) - Add useMemo for expression filtering - Make extractors_available, dependent_files, warnings optional with safe access - Support both 'unit' and 'units' field names
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
* Introspection Panel - Shows discovered expressions and extractors from NX model
|
* Introspection Panel - Shows discovered expressions and extractors from NX model
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback, useMemo } from 'react';
|
||||||
import {
|
import {
|
||||||
X,
|
X,
|
||||||
Search,
|
Search,
|
||||||
@@ -27,9 +27,11 @@ interface IntrospectionPanelProps {
|
|||||||
interface Expression {
|
interface Expression {
|
||||||
name: string;
|
name: string;
|
||||||
value: number;
|
value: number;
|
||||||
|
rhs?: string;
|
||||||
min?: number;
|
min?: number;
|
||||||
max?: number;
|
max?: number;
|
||||||
unit: string;
|
unit?: string;
|
||||||
|
units?: string; // API returns 'units' not 'unit'
|
||||||
type: string;
|
type: string;
|
||||||
source?: string;
|
source?: string;
|
||||||
}
|
}
|
||||||
@@ -47,14 +49,34 @@ interface DependentFile {
|
|||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The API returns expressions in a nested structure
|
||||||
|
interface ExpressionsResult {
|
||||||
|
user: Expression[];
|
||||||
|
internal: Expression[];
|
||||||
|
total_count: number;
|
||||||
|
user_count: number;
|
||||||
|
}
|
||||||
|
|
||||||
interface IntrospectionResult {
|
interface IntrospectionResult {
|
||||||
file_path: string;
|
part_file?: string;
|
||||||
file_type: string;
|
part_path?: string;
|
||||||
expressions: Expression[];
|
file_path?: string;
|
||||||
solver_type: string | null;
|
file_type?: string;
|
||||||
dependent_files: DependentFile[];
|
success?: boolean;
|
||||||
extractors_available: Extractor[];
|
error?: string | null;
|
||||||
warnings: string[];
|
// Expressions can be either an array (old format) or object with user/internal (new format)
|
||||||
|
expressions: Expression[] | ExpressionsResult;
|
||||||
|
solver_type?: string | null;
|
||||||
|
dependent_files?: DependentFile[];
|
||||||
|
extractors_available?: Extractor[];
|
||||||
|
warnings?: string[];
|
||||||
|
// Additional fields from NX introspection
|
||||||
|
mass_properties?: Record<string, unknown>;
|
||||||
|
materials?: Record<string, unknown>;
|
||||||
|
bodies?: Record<string, unknown>;
|
||||||
|
attributes?: Array<{ title: string; value: string }>;
|
||||||
|
units?: Record<string, unknown>;
|
||||||
|
linked_parts?: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function IntrospectionPanel({ filePath, studyId, onClose }: IntrospectionPanelProps) {
|
export function IntrospectionPanel({ filePath, studyId, onClose }: IntrospectionPanelProps) {
|
||||||
@@ -161,10 +183,23 @@ export function IntrospectionPanel({ filePath, studyId, onClose }: Introspection
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const filteredExpressions =
|
// Handle both array format (old) and object format (new API)
|
||||||
result?.expressions.filter((e) =>
|
const allExpressions: Expression[] = useMemo(() => {
|
||||||
e.name.toLowerCase().includes(searchTerm.toLowerCase())
|
if (!result?.expressions) return [];
|
||||||
) || [];
|
|
||||||
|
// Check if expressions is an array (old format) or object (new format)
|
||||||
|
if (Array.isArray(result.expressions)) {
|
||||||
|
return result.expressions;
|
||||||
|
}
|
||||||
|
|
||||||
|
// New format: { user: [...], internal: [...] }
|
||||||
|
const exprObj = result.expressions as ExpressionsResult;
|
||||||
|
return [...(exprObj.user || []), ...(exprObj.internal || [])];
|
||||||
|
}, [result?.expressions]);
|
||||||
|
|
||||||
|
const filteredExpressions = allExpressions.filter((e) =>
|
||||||
|
e.name.toLowerCase().includes(searchTerm.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-dark-850 border border-dark-700 rounded-xl w-80 max-h-[70vh] flex flex-col shadow-xl">
|
<div className="bg-dark-850 border border-dark-700 rounded-xl w-80 max-h-[70vh] flex flex-col shadow-xl">
|
||||||
@@ -260,7 +295,7 @@ export function IntrospectionPanel({ filePath, studyId, onClose }: Introspection
|
|||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<p className="text-sm text-white truncate">{expr.name}</p>
|
<p className="text-sm text-white truncate">{expr.name}</p>
|
||||||
<p className="text-xs text-dark-500">
|
<p className="text-xs text-dark-500">
|
||||||
{expr.value} {expr.unit}
|
{expr.value} {expr.units || expr.unit || ''}
|
||||||
{expr.source === 'inferred' && (
|
{expr.source === 'inferred' && (
|
||||||
<span className="ml-1 text-amber-500">(inferred)</span>
|
<span className="ml-1 text-amber-500">(inferred)</span>
|
||||||
)}
|
)}
|
||||||
@@ -281,7 +316,8 @@ export function IntrospectionPanel({ filePath, studyId, onClose }: Introspection
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Extractors Section */}
|
{/* Extractors Section - only show if available */}
|
||||||
|
{(result.extractors_available?.length ?? 0) > 0 && (
|
||||||
<div className="border border-dark-700 rounded-lg overflow-hidden">
|
<div className="border border-dark-700 rounded-lg overflow-hidden">
|
||||||
<button
|
<button
|
||||||
onClick={() => toggleSection('extractors')}
|
onClick={() => toggleSection('extractors')}
|
||||||
@@ -290,7 +326,7 @@ export function IntrospectionPanel({ filePath, studyId, onClose }: Introspection
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<FlaskConical size={14} className="text-cyan-400" />
|
<FlaskConical size={14} className="text-cyan-400" />
|
||||||
<span className="text-sm font-medium text-white">
|
<span className="text-sm font-medium text-white">
|
||||||
Available Extractors ({result.extractors_available.length})
|
Available Extractors ({result.extractors_available?.length || 0})
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{expandedSections.has('extractors') ? (
|
{expandedSections.has('extractors') ? (
|
||||||
@@ -302,7 +338,7 @@ export function IntrospectionPanel({ filePath, studyId, onClose }: Introspection
|
|||||||
|
|
||||||
{expandedSections.has('extractors') && (
|
{expandedSections.has('extractors') && (
|
||||||
<div className="p-2 space-y-1 max-h-48 overflow-y-auto">
|
<div className="p-2 space-y-1 max-h-48 overflow-y-auto">
|
||||||
{result.extractors_available.map((ext) => (
|
{(result.extractors_available || []).map((ext) => (
|
||||||
<div
|
<div
|
||||||
key={ext.id}
|
key={ext.id}
|
||||||
className="flex items-center justify-between p-2 bg-dark-850 rounded hover:bg-dark-750 group transition-colors"
|
className="flex items-center justify-between p-2 bg-dark-850 rounded hover:bg-dark-750 group transition-colors"
|
||||||
@@ -327,9 +363,10 @@ export function IntrospectionPanel({ filePath, studyId, onClose }: Introspection
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Dependent Files */}
|
{/* Dependent Files */}
|
||||||
{result.dependent_files.length > 0 && (
|
{(result.dependent_files?.length ?? 0) > 0 && (
|
||||||
<div className="border border-dark-700 rounded-lg overflow-hidden">
|
<div className="border border-dark-700 rounded-lg overflow-hidden">
|
||||||
<button
|
<button
|
||||||
onClick={() => toggleSection('files')}
|
onClick={() => toggleSection('files')}
|
||||||
@@ -338,7 +375,7 @@ export function IntrospectionPanel({ filePath, studyId, onClose }: Introspection
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<FileBox size={14} className="text-amber-400" />
|
<FileBox size={14} className="text-amber-400" />
|
||||||
<span className="text-sm font-medium text-white">
|
<span className="text-sm font-medium text-white">
|
||||||
Dependent Files ({result.dependent_files.length})
|
Dependent Files ({result.dependent_files?.length || 0})
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{expandedSections.has('files') ? (
|
{expandedSections.has('files') ? (
|
||||||
@@ -350,7 +387,7 @@ export function IntrospectionPanel({ filePath, studyId, onClose }: Introspection
|
|||||||
|
|
||||||
{expandedSections.has('files') && (
|
{expandedSections.has('files') && (
|
||||||
<div className="p-2 space-y-1 max-h-32 overflow-y-auto">
|
<div className="p-2 space-y-1 max-h-32 overflow-y-auto">
|
||||||
{result.dependent_files.map((file) => (
|
{(result.dependent_files || []).map((file) => (
|
||||||
<div
|
<div
|
||||||
key={file.path}
|
key={file.path}
|
||||||
className="flex items-center gap-2 p-2 bg-dark-850 rounded"
|
className="flex items-center gap-2 p-2 bg-dark-850 rounded"
|
||||||
@@ -368,13 +405,13 @@ export function IntrospectionPanel({ filePath, studyId, onClose }: Introspection
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Warnings */}
|
{/* Warnings */}
|
||||||
{result.warnings.length > 0 && (
|
{(result.warnings?.length ?? 0) > 0 && (
|
||||||
<div className="p-2 bg-amber-500/10 border border-amber-500/30 rounded-lg">
|
<div className="p-2 bg-amber-500/10 border border-amber-500/30 rounded-lg">
|
||||||
<div className="flex items-center gap-1.5 mb-1">
|
<div className="flex items-center gap-1.5 mb-1">
|
||||||
<AlertTriangle size={12} className="text-amber-400" />
|
<AlertTriangle size={12} className="text-amber-400" />
|
||||||
<p className="text-xs text-amber-400 font-medium">Warnings</p>
|
<p className="text-xs text-amber-400 font-medium">Warnings</p>
|
||||||
</div>
|
</div>
|
||||||
{result.warnings.map((w, i) => (
|
{(result.warnings || []).map((w, i) => (
|
||||||
<p key={i} className="text-xs text-amber-300">
|
<p key={i} className="text-xs text-amber-300">
|
||||||
{w}
|
{w}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
Reference in New Issue
Block a user