@@ -1508,12 +1508,37 @@ private static object SetComponentPropertiesInternal(
15081508 {
15091509 if ( ! SetProperty ( targetComponent , propName , propValue ) )
15101510 {
1511- // Log warning if property could not be set
1512- Debug . LogWarning (
1513- $ "[ManageGameObject] Could not set property '{ propName } ' on component '{ compName } ' ('{ targetComponent . GetType ( ) . Name } '). Property might not exist, be read-only, or type mismatch."
1514- ) ;
1515- // Optionally return an error here instead of just logging
1516- // return Response.Error($"Could not set property '{propName}' on component '{compName}'.");
1511+ // Get available properties and AI suggestions for better error messages
1512+ var availableProperties = ComponentResolver . GetAllComponentProperties ( targetComponent . GetType ( ) ) ;
1513+ var suggestions = ComponentResolver . GetAIPropertySuggestions ( propName , availableProperties ) ;
1514+
1515+ var errorMessage = $ "[ManageGameObject] Could not set property '{ propName } ' on component '{ compName } ' ('{ targetComponent . GetType ( ) . Name } ').";
1516+
1517+ if ( suggestions . Any ( ) )
1518+ {
1519+ errorMessage += $ " Did you mean: { string . Join ( ", " , suggestions ) } ?";
1520+ }
1521+
1522+ errorMessage += $ " Available properties: [{ string . Join ( ", " , availableProperties ) } ]";
1523+
1524+ Debug . LogWarning ( errorMessage ) ;
1525+
1526+ // Return enhanced error with suggestions for better UX
1527+ if ( suggestions . Any ( ) )
1528+ {
1529+ return Response . Error (
1530+ $ "Property '{ propName } ' not found on { compName } . " +
1531+ $ "Did you mean: { string . Join ( ", " , suggestions ) } ? " +
1532+ $ "Available properties: [{ string . Join ( ", " , availableProperties ) } ]"
1533+ ) ;
1534+ }
1535+ else
1536+ {
1537+ return Response . Error (
1538+ $ "Property '{ propName } ' not found on { compName } . " +
1539+ $ "Available properties: [{ string . Join ( ", " , availableProperties ) } ]"
1540+ ) ;
1541+ }
15171542 }
15181543 }
15191544 catch ( Exception e )
@@ -2132,6 +2157,14 @@ internal static class ComponentResolver
21322157 public static bool TryResolve ( string nameOrFullName , out Type type , out string error )
21332158 {
21342159 error = string . Empty ;
2160+ type = null ! ;
2161+
2162+ // Handle null/empty input
2163+ if ( string . IsNullOrWhiteSpace ( nameOrFullName ) )
2164+ {
2165+ error = "Component name cannot be null or empty" ;
2166+ return false ;
2167+ }
21352168
21362169 // 1) Exact FQN via Type.GetType
21372170 if ( CacheByFqn . TryGetValue ( nameOrFullName , out type ) ) return true ;
@@ -2228,6 +2261,144 @@ private static string Ambiguity(string query, IEnumerable<Type> cands)
22282261 "\n Provide a fully qualified type name to disambiguate." ;
22292262 }
22302263
2264+ /// <summary>
2265+ /// Gets all accessible property and field names from a component type.
2266+ /// </summary>
2267+ public static List < string > GetAllComponentProperties ( Type componentType )
2268+ {
2269+ if ( componentType == null ) return new List < string > ( ) ;
2270+
2271+ var properties = componentType . GetProperties ( BindingFlags . Public | BindingFlags . Instance )
2272+ . Where ( p => p . CanRead && p . CanWrite )
2273+ . Select ( p => p . Name ) ;
2274+
2275+ var fields = componentType . GetFields ( BindingFlags . Public | BindingFlags . Instance )
2276+ . Where ( f => ! f . IsInitOnly && ! f . IsLiteral )
2277+ . Select ( f => f . Name ) ;
2278+
2279+ // Also include SerializeField private fields (common in Unity)
2280+ var serializeFields = componentType . GetFields ( BindingFlags . NonPublic | BindingFlags . Instance )
2281+ . Where ( f => f . GetCustomAttribute < SerializeField > ( ) != null )
2282+ . Select ( f => f . Name ) ;
2283+
2284+ return properties . Concat ( fields ) . Concat ( serializeFields ) . Distinct ( ) . OrderBy ( x => x ) . ToList ( ) ;
2285+ }
2286+
2287+ /// <summary>
2288+ /// Uses AI to suggest the most likely property matches for a user's input.
2289+ /// </summary>
2290+ public static List < string > GetAIPropertySuggestions ( string userInput , List < string > availableProperties )
2291+ {
2292+ if ( string . IsNullOrWhiteSpace ( userInput ) || ! availableProperties . Any ( ) )
2293+ return new List < string > ( ) ;
2294+
2295+ // Simple caching to avoid repeated AI calls for the same input
2296+ var cacheKey = $ "{ userInput . ToLowerInvariant ( ) } :{ string . Join ( "," , availableProperties ) } ";
2297+ if ( PropertySuggestionCache . TryGetValue ( cacheKey , out var cached ) )
2298+ return cached ;
2299+
2300+ try
2301+ {
2302+ var prompt = $ "A Unity developer is trying to set a component property but used an incorrect name.\n \n " +
2303+ $ "User requested: \" { userInput } \" \n " +
2304+ $ "Available properties: [{ string . Join ( ", " , availableProperties ) } ]\n \n " +
2305+ $ "Find 1-3 most likely matches considering:\n " +
2306+ $ "- Unity Inspector display names vs actual field names (e.g., \" Max Reach Distance\" → \" maxReachDistance\" )\n " +
2307+ $ "- camelCase vs PascalCase vs spaces\n " +
2308+ $ "- Similar meaning/semantics\n " +
2309+ $ "- Common Unity naming patterns\n \n " +
2310+ $ "Return ONLY the matching property names, comma-separated, no quotes or explanation.\n " +
2311+ $ "If confidence is low (<70%), return empty string.\n \n " +
2312+ $ "Examples:\n " +
2313+ $ "- \" Max Reach Distance\" → \" maxReachDistance\" \n " +
2314+ $ "- \" Health Points\" → \" healthPoints, hp\" \n " +
2315+ $ "- \" Move Speed\" → \" moveSpeed, movementSpeed\" ";
2316+
2317+ // For now, we'll use a simple rule-based approach that mimics AI behavior
2318+ // This can be replaced with actual AI calls later
2319+ var suggestions = GetRuleBasedSuggestions ( userInput , availableProperties ) ;
2320+
2321+ PropertySuggestionCache [ cacheKey ] = suggestions ;
2322+ return suggestions ;
2323+ }
2324+ catch ( Exception ex )
2325+ {
2326+ Debug . LogWarning ( $ "[AI Property Matching] Error getting suggestions for '{ userInput } ': { ex . Message } ") ;
2327+ return new List < string > ( ) ;
2328+ }
2329+ }
2330+
2331+ private static readonly Dictionary < string , List < string > > PropertySuggestionCache = new ( ) ;
2332+
2333+ /// <summary>
2334+ /// Rule-based suggestions that mimic AI behavior for property matching.
2335+ /// This provides immediate value while we could add real AI integration later.
2336+ /// </summary>
2337+ private static List < string > GetRuleBasedSuggestions ( string userInput , List < string > availableProperties )
2338+ {
2339+ var suggestions = new List < string > ( ) ;
2340+ var cleanedInput = userInput . ToLowerInvariant ( ) . Replace ( " " , "" ) . Replace ( "-" , "" ) . Replace ( "_" , "" ) ;
2341+
2342+ foreach ( var property in availableProperties )
2343+ {
2344+ var cleanedProperty = property . ToLowerInvariant ( ) ;
2345+
2346+ // Exact match after cleaning
2347+ if ( cleanedProperty == cleanedInput )
2348+ {
2349+ suggestions . Add ( property ) ;
2350+ continue ;
2351+ }
2352+
2353+ // Check if property contains all words from input
2354+ var inputWords = userInput . ToLowerInvariant ( ) . Split ( new [ ] { ' ' , '-' , '_' } , StringSplitOptions . RemoveEmptyEntries ) ;
2355+ if ( inputWords . All ( word => cleanedProperty . Contains ( word . ToLowerInvariant ( ) ) ) )
2356+ {
2357+ suggestions . Add ( property ) ;
2358+ continue ;
2359+ }
2360+
2361+ // Levenshtein distance for close matches
2362+ if ( LevenshteinDistance ( cleanedInput , cleanedProperty ) <= Math . Max ( 2 , cleanedInput . Length / 4 ) )
2363+ {
2364+ suggestions . Add ( property ) ;
2365+ }
2366+ }
2367+
2368+ // Prioritize exact matches, then by similarity
2369+ return suggestions . OrderBy ( s => LevenshteinDistance ( cleanedInput , s . ToLowerInvariant ( ) . Replace ( " " , "" ) ) )
2370+ . Take ( 3 )
2371+ . ToList ( ) ;
2372+ }
2373+
2374+ /// <summary>
2375+ /// Calculates Levenshtein distance between two strings for similarity matching.
2376+ /// </summary>
2377+ private static int LevenshteinDistance ( string s1 , string s2 )
2378+ {
2379+ if ( string . IsNullOrEmpty ( s1 ) ) return s2 ? . Length ?? 0 ;
2380+ if ( string . IsNullOrEmpty ( s2 ) ) return s1 . Length ;
2381+
2382+ var matrix = new int [ s1 . Length + 1 , s2 . Length + 1 ] ;
2383+
2384+ for ( int i = 0 ; i <= s1 . Length ; i ++ ) matrix [ i , 0 ] = i ;
2385+ for ( int j = 0 ; j <= s2 . Length ; j ++ ) matrix [ 0 , j ] = j ;
2386+
2387+ for ( int i = 1 ; i <= s1 . Length ; i ++ )
2388+ {
2389+ for ( int j = 1 ; j <= s2 . Length ; j ++ )
2390+ {
2391+ int cost = ( s2 [ j - 1 ] == s1 [ i - 1 ] ) ? 0 : 1 ;
2392+ matrix [ i , j ] = Math . Min ( Math . Min (
2393+ matrix [ i - 1 , j ] + 1 , // deletion
2394+ matrix [ i , j - 1 ] + 1 ) , // insertion
2395+ matrix [ i - 1 , j - 1 ] + cost ) ; // substitution
2396+ }
2397+ }
2398+
2399+ return matrix [ s1 . Length , s2 . Length ] ;
2400+ }
2401+
22312402 /// <summary>
22322403 /// Parses a JArray like [x, y, z] into a Vector3.
22332404 /// </summary>
0 commit comments