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
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import {
|
||||
X,
|
||||
Search,
|
||||
@@ -27,9 +27,11 @@ interface IntrospectionPanelProps {
|
||||
interface Expression {
|
||||
name: string;
|
||||
value: number;
|
||||
rhs?: string;
|
||||
min?: number;
|
||||
max?: number;
|
||||
unit: string;
|
||||
unit?: string;
|
||||
units?: string; // API returns 'units' not 'unit'
|
||||
type: string;
|
||||
source?: string;
|
||||
}
|
||||
@@ -47,14 +49,34 @@ interface DependentFile {
|
||||
name: string;
|
||||
}
|
||||
|
||||
// The API returns expressions in a nested structure
|
||||
interface ExpressionsResult {
|
||||
user: Expression[];
|
||||
internal: Expression[];
|
||||
total_count: number;
|
||||
user_count: number;
|
||||
}
|
||||
|
||||
interface IntrospectionResult {
|
||||
file_path: string;
|
||||
file_type: string;
|
||||
expressions: Expression[];
|
||||
solver_type: string | null;
|
||||
dependent_files: DependentFile[];
|
||||
extractors_available: Extractor[];
|
||||
warnings: string[];
|
||||
part_file?: string;
|
||||
part_path?: string;
|
||||
file_path?: string;
|
||||
file_type?: string;
|
||||
success?: boolean;
|
||||
error?: string | null;
|
||||
// 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) {
|
||||
@@ -161,10 +183,23 @@ export function IntrospectionPanel({ filePath, studyId, onClose }: Introspection
|
||||
});
|
||||
};
|
||||
|
||||
const filteredExpressions =
|
||||
result?.expressions.filter((e) =>
|
||||
e.name.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
) || [];
|
||||
// Handle both array format (old) and object format (new API)
|
||||
const allExpressions: Expression[] = useMemo(() => {
|
||||
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 (
|
||||
<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">
|
||||
<p className="text-sm text-white truncate">{expr.name}</p>
|
||||
<p className="text-xs text-dark-500">
|
||||
{expr.value} {expr.unit}
|
||||
{expr.value} {expr.units || expr.unit || ''}
|
||||
{expr.source === 'inferred' && (
|
||||
<span className="ml-1 text-amber-500">(inferred)</span>
|
||||
)}
|
||||
@@ -281,7 +316,8 @@ export function IntrospectionPanel({ filePath, studyId, onClose }: Introspection
|
||||
)}
|
||||
</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">
|
||||
<button
|
||||
onClick={() => toggleSection('extractors')}
|
||||
@@ -290,7 +326,7 @@ export function IntrospectionPanel({ filePath, studyId, onClose }: Introspection
|
||||
<div className="flex items-center gap-2">
|
||||
<FlaskConical size={14} className="text-cyan-400" />
|
||||
<span className="text-sm font-medium text-white">
|
||||
Available Extractors ({result.extractors_available.length})
|
||||
Available Extractors ({result.extractors_available?.length || 0})
|
||||
</span>
|
||||
</div>
|
||||
{expandedSections.has('extractors') ? (
|
||||
@@ -302,7 +338,7 @@ export function IntrospectionPanel({ filePath, studyId, onClose }: Introspection
|
||||
|
||||
{expandedSections.has('extractors') && (
|
||||
<div className="p-2 space-y-1 max-h-48 overflow-y-auto">
|
||||
{result.extractors_available.map((ext) => (
|
||||
{(result.extractors_available || []).map((ext) => (
|
||||
<div
|
||||
key={ext.id}
|
||||
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>
|
||||
)}
|
||||
|
||||
{/* Dependent Files */}
|
||||
{result.dependent_files.length > 0 && (
|
||||
{(result.dependent_files?.length ?? 0) > 0 && (
|
||||
<div className="border border-dark-700 rounded-lg overflow-hidden">
|
||||
<button
|
||||
onClick={() => toggleSection('files')}
|
||||
@@ -338,7 +375,7 @@ export function IntrospectionPanel({ filePath, studyId, onClose }: Introspection
|
||||
<div className="flex items-center gap-2">
|
||||
<FileBox size={14} className="text-amber-400" />
|
||||
<span className="text-sm font-medium text-white">
|
||||
Dependent Files ({result.dependent_files.length})
|
||||
Dependent Files ({result.dependent_files?.length || 0})
|
||||
</span>
|
||||
</div>
|
||||
{expandedSections.has('files') ? (
|
||||
@@ -350,7 +387,7 @@ export function IntrospectionPanel({ filePath, studyId, onClose }: Introspection
|
||||
|
||||
{expandedSections.has('files') && (
|
||||
<div className="p-2 space-y-1 max-h-32 overflow-y-auto">
|
||||
{result.dependent_files.map((file) => (
|
||||
{(result.dependent_files || []).map((file) => (
|
||||
<div
|
||||
key={file.path}
|
||||
className="flex items-center gap-2 p-2 bg-dark-850 rounded"
|
||||
@@ -368,13 +405,13 @@ export function IntrospectionPanel({ filePath, studyId, onClose }: Introspection
|
||||
)}
|
||||
|
||||
{/* 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="flex items-center gap-1.5 mb-1">
|
||||
<AlertTriangle size={12} className="text-amber-400" />
|
||||
<p className="text-xs text-amber-400 font-medium">Warnings</p>
|
||||
</div>
|
||||
{result.warnings.map((w, i) => (
|
||||
{(result.warnings || []).map((w, i) => (
|
||||
<p key={i} className="text-xs text-amber-300">
|
||||
{w}
|
||||
</p>
|
||||
|
||||
Reference in New Issue
Block a user