Skip to content

AI Code Generation Tutorial

Master Trae's advanced AI code generation capabilities to write code faster and more efficiently.

Overview

Trae's AI code generation goes beyond simple autocomplete. It can:

  • Generate entire functions and classes
  • Create components from descriptions
  • Build complete features
  • Generate tests automatically
  • Create documentation
  • Refactor existing code

Getting Started

Enabling Code Generation

  1. Open Settings: Ctrl+, (Windows/Linux) or Cmd+, (Mac)
  2. Navigate to: AI > Code Generation
  3. Enable features:
    • ✅ Auto-completion
    • ✅ Function generation
    • ✅ Component scaffolding
    • ✅ Test generation

Code Generation Triggers

Method 1: Comments

typescript
// Generate a function to validate email addresses
// AI will generate the function below this comment

Method 2: Function Signatures

typescript
function validateEmail(email: string): boolean {
  // AI will complete the implementation
}

Method 3: AI Chat

  • Open AI chat (Ctrl+Shift+A)
  • Request specific code generation

Method 4: Code Actions

  • Right-click in editor
  • Select "Generate with AI"

Basic Code Generation

1. Function Generation

Input:

typescript
// Create a function to debounce API calls
function debounce

Generated Output:

typescript
function debounce<T extends (...args: any[]) => any>(
  func: T,
  delay: number
): (...args: Parameters<T>) => void {
  let timeoutId: NodeJS.Timeout;
  
  return (...args: Parameters<T>) => {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => func.apply(this, args), delay);
  };
}

// Usage example:
const debouncedSearch = debounce((query: string) => {
  console.log('Searching for:', query);
}, 300);

2. Class Generation

Input:

typescript
// Create a User class with authentication methods
class User {

Generated Output:

typescript
class User {
  private id: string;
  private email: string;
  private passwordHash: string;
  private isActive: boolean;
  private createdAt: Date;

  constructor(email: string, password: string) {
    this.id = this.generateId();
    this.email = email;
    this.passwordHash = this.hashPassword(password);
    this.isActive = true;
    this.createdAt = new Date();
  }

  private generateId(): string {
    return Math.random().toString(36).substr(2, 9);
  }

  private hashPassword(password: string): string {
    // In production, use bcrypt or similar
    return btoa(password + 'salt');
  }

  public authenticate(password: string): boolean {
    return this.passwordHash === this.hashPassword(password);
  }

  public activate(): void {
    this.isActive = true;
  }

  public deactivate(): void {
    this.isActive = false;
  }

  public getProfile(): { id: string; email: string; isActive: boolean } {
    return {
      id: this.id,
      email: this.email,
      isActive: this.isActive
    };
  }
}

React Component Generation

1. Simple Component

Input:

typescript
// Create a loading spinner component
const LoadingSpinner

Generated Output:

typescript
import React from 'react';
import './LoadingSpinner.css';

interface LoadingSpinnerProps {
  size?: 'small' | 'medium' | 'large';
  color?: string;
  message?: string;
}

const LoadingSpinner: React.FC<LoadingSpinnerProps> = ({ 
  size = 'medium', 
  color = '#3498db',
  message = 'Loading...'
}) => {
  const sizeClasses = {
    small: 'spinner-small',
    medium: 'spinner-medium',
    large: 'spinner-large'
  };

  return (
    <div className="loading-container">
      <div 
        className={`spinner ${sizeClasses[size]}`}
        style={{ borderTopColor: color }}
      />
      {message && <p className="loading-message">{message}</p>}
    </div>
  );
};

export default LoadingSpinner;

Generated CSS:

css
/* LoadingSpinner.css */
.loading-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 20px;
}

.spinner {
  border: 4px solid #f3f3f3;
  border-top: 4px solid #3498db;
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

.spinner-small {
  width: 20px;
  height: 20px;
  border-width: 2px;
}

.spinner-medium {
  width: 40px;
  height: 40px;
}

.spinner-large {
  width: 60px;
  height: 60px;
  border-width: 6px;
}

.loading-message {
  margin-top: 10px;
  color: #666;
  font-size: 14px;
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

2. Complex Component with Hooks

Input:

typescript
// Create a search component with debounced input and results
const SearchComponent

Generated Output:

typescript
import React, { useState, useEffect, useMemo } from 'react';
import { debounce } from '../utils/debounce';

interface SearchResult {
  id: string;
  title: string;
  description: string;
  url?: string;
}

interface SearchComponentProps {
  onSearch: (query: string) => Promise<SearchResult[]>;
  placeholder?: string;
  debounceMs?: number;
  maxResults?: number;
}

const SearchComponent: React.FC<SearchComponentProps> = ({
  onSearch,
  placeholder = 'Search...',
  debounceMs = 300,
  maxResults = 10
}) => {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState<SearchResult[]>([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const debouncedSearch = useMemo(
    () => debounce(async (searchQuery: string) => {
      if (!searchQuery.trim()) {
        setResults([]);
        return;
      }

      setLoading(true);
      setError(null);

      try {
        const searchResults = await onSearch(searchQuery);
        setResults(searchResults.slice(0, maxResults));
      } catch (err) {
        setError('Search failed. Please try again.');
        setResults([]);
      } finally {
        setLoading(false);
      }
    }, debounceMs),
    [onSearch, debounceMs, maxResults]
  );

  useEffect(() => {
    debouncedSearch(query);
  }, [query, debouncedSearch]);

  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setQuery(e.target.value);
  };

  const handleResultClick = (result: SearchResult) => {
    if (result.url) {
      window.open(result.url, '_blank');
    }
  };

  return (
    <div className="search-component">
      <div className="search-input-container">
        <input
          type="text"
          value={query}
          onChange={handleInputChange}
          placeholder={placeholder}
          className="search-input"
        />
        {loading && <div className="search-loading">🔍</div>}
      </div>

      {error && (
        <div className="search-error">
          {error}
        </div>
      )}

      {results.length > 0 && (
        <div className="search-results">
          {results.map((result) => (
            <div
              key={result.id}
              className="search-result-item"
              onClick={() => handleResultClick(result)}
            >
              <h4 className="result-title">{result.title}</h4>
              <p className="result-description">{result.description}</p>
            </div>
          ))}
        </div>
      )}

      {query && !loading && results.length === 0 && !error && (
        <div className="no-results">
          No results found for "{query}"
        </div>
      )}
    </div>
  );
};

export default SearchComponent;

Advanced Code Generation

1. API Integration

Input:

typescript
// Create a service class for user management API
class UserService {

Generated Output:

typescript
interface User {
  id: string;
  email: string;
  name: string;
  createdAt: string;
  updatedAt: string;
}

interface CreateUserRequest {
  email: string;
  name: string;
  password: string;
}

interface UpdateUserRequest {
  name?: string;
  email?: string;
}

class UserService {
  private baseUrl: string;
  private apiKey: string;

  constructor(baseUrl: string, apiKey: string) {
    this.baseUrl = baseUrl;
    this.apiKey = apiKey;
  }

  private async request<T>(
    endpoint: string,
    options: RequestInit = {}
  ): Promise<T> {
    const url = `${this.baseUrl}${endpoint}`;
    const config: RequestInit = {
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${this.apiKey}`,
        ...options.headers,
      },
      ...options,
    };

    const response = await fetch(url, config);

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    return response.json();
  }

  async getUsers(): Promise<User[]> {
    return this.request<User[]>('/users');
  }

  async getUserById(id: string): Promise<User> {
    return this.request<User>(`/users/${id}`);
  }

  async createUser(userData: CreateUserRequest): Promise<User> {
    return this.request<User>('/users', {
      method: 'POST',
      body: JSON.stringify(userData),
    });
  }

  async updateUser(id: string, userData: UpdateUserRequest): Promise<User> {
    return this.request<User>(`/users/${id}`, {
      method: 'PUT',
      body: JSON.stringify(userData),
    });
  }

  async deleteUser(id: string): Promise<void> {
    await this.request<void>(`/users/${id}`, {
      method: 'DELETE',
    });
  }

  async searchUsers(query: string): Promise<User[]> {
    const params = new URLSearchParams({ q: query });
    return this.request<User[]>(`/users/search?${params}`);
  }
}

export default UserService;

2. Custom Hooks Generation

Input:

typescript
// Create a custom hook for form validation
function useFormValidation

Generated Output:

typescript
import { useState, useCallback, useMemo } from 'react';

type ValidationRule<T> = {
  required?: boolean;
  minLength?: number;
  maxLength?: number;
  pattern?: RegExp;
  custom?: (value: T) => string | null;
};

type ValidationRules<T> = {
  [K in keyof T]?: ValidationRule<T[K]>;
};

type ValidationErrors<T> = {
  [K in keyof T]?: string;
};

interface UseFormValidationReturn<T> {
  values: T;
  errors: ValidationErrors<T>;
  isValid: boolean;
  isSubmitting: boolean;
  setValue: <K extends keyof T>(field: K, value: T[K]) => void;
  setValues: (values: Partial<T>) => void;
  validateField: <K extends keyof T>(field: K) => boolean;
  validateAll: () => boolean;
  reset: () => void;
  handleSubmit: (onSubmit: (values: T) => Promise<void> | void) => Promise<void>;
}

function useFormValidation<T extends Record<string, any>>(
  initialValues: T,
  validationRules: ValidationRules<T>
): UseFormValidationReturn<T> {
  const [values, setValuesState] = useState<T>(initialValues);
  const [errors, setErrors] = useState<ValidationErrors<T>>({});
  const [isSubmitting, setIsSubmitting] = useState(false);

  const validateField = useCallback(<K extends keyof T>(field: K): boolean => {
    const value = values[field];
    const rules = validationRules[field];
    
    if (!rules) return true;

    let error: string | null = null;

    // Required validation
    if (rules.required && (!value || (typeof value === 'string' && !value.trim()))) {
      error = `${String(field)} is required`;
    }
    
    // String validations
    if (!error && typeof value === 'string') {
      if (rules.minLength && value.length < rules.minLength) {
        error = `${String(field)} must be at least ${rules.minLength} characters`;
      }
      
      if (rules.maxLength && value.length > rules.maxLength) {
        error = `${String(field)} must be no more than ${rules.maxLength} characters`;
      }
      
      if (rules.pattern && !rules.pattern.test(value)) {
        error = `${String(field)} format is invalid`;
      }
    }

    // Custom validation
    if (!error && rules.custom) {
      error = rules.custom(value);
    }

    setErrors(prev => ({
      ...prev,
      [field]: error || undefined
    }));

    return !error;
  }, [values, validationRules]);

  const validateAll = useCallback((): boolean => {
    const fields = Object.keys(validationRules) as (keyof T)[];
    const results = fields.map(field => validateField(field));
    return results.every(Boolean);
  }, [validateField, validationRules]);

  const setValue = useCallback(<K extends keyof T>(field: K, value: T[K]) => {
    setValuesState(prev => ({ ...prev, [field]: value }));
    
    // Clear error when user starts typing
    if (errors[field]) {
      setErrors(prev => ({ ...prev, [field]: undefined }));
    }
  }, [errors]);

  const setValues = useCallback((newValues: Partial<T>) => {
    setValuesState(prev => ({ ...prev, ...newValues }));
  }, []);

  const reset = useCallback(() => {
    setValuesState(initialValues);
    setErrors({});
    setIsSubmitting(false);
  }, [initialValues]);

  const handleSubmit = useCallback(async (onSubmit: (values: T) => Promise<void> | void) => {
    if (isSubmitting) return;
    
    setIsSubmitting(true);
    
    try {
      if (validateAll()) {
        await onSubmit(values);
      }
    } catch (error) {
      console.error('Form submission error:', error);
    } finally {
      setIsSubmitting(false);
    }
  }, [values, validateAll, isSubmitting]);

  const isValid = useMemo(() => {
    return Object.keys(validationRules).every(field => 
      !errors[field as keyof T]
    );
  }, [errors, validationRules]);

  return {
    values,
    errors,
    isValid,
    isSubmitting,
    setValue,
    setValues,
    validateField,
    validateAll,
    reset,
    handleSubmit
  };
}

export default useFormValidation;

// Usage example:
/*
interface LoginForm {
  email: string;
  password: string;
}

const LoginComponent = () => {
  const {
    values,
    errors,
    isValid,
    isSubmitting,
    setValue,
    handleSubmit
  } = useFormValidation<LoginForm>(
    { email: '', password: '' },
    {
      email: {
        required: true,
        pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
        custom: (value) => {
          if (value.includes('test')) {
            return 'Test emails are not allowed';
          }
          return null;
        }
      },
      password: {
        required: true,
        minLength: 8
      }
    }
  );

  const onSubmit = async (formData: LoginForm) => {
    // Handle login
    console.log('Logging in:', formData);
  };

  return (
    <form onSubmit={(e) => {
      e.preventDefault();
      handleSubmit(onSubmit);
    }}>
      <input
        type="email"
        value={values.email}
        onChange={(e) => setValue('email', e.target.value)}
        placeholder="Email"
      />
      {errors.email && <span>{errors.email}</span>}
      
      <input
        type="password"
        value={values.password}
        onChange={(e) => setValue('password', e.target.value)}
        placeholder="Password"
      />
      {errors.password && <span>{errors.password}</span>}
      
      <button type="submit" disabled={!isValid || isSubmitting}>
        {isSubmitting ? 'Logging in...' : 'Login'}
      </button>
    </form>
  );
};
*/

Test Generation

1. Unit Tests

Input:

typescript
// Generate tests for the validateEmail function
// function validateEmail(email: string): boolean

Generated Output:

typescript
import { validateEmail } from './emailValidator';

describe('validateEmail', () => {
  describe('valid emails', () => {
    test('should accept standard email format', () => {
      expect(validateEmail('user@example.com')).toBe(true);
      expect(validateEmail('test.email@domain.org')).toBe(true);
      expect(validateEmail('user+tag@example.co.uk')).toBe(true);
    });

    test('should accept emails with numbers', () => {
      expect(validateEmail('user123@example.com')).toBe(true);
      expect(validateEmail('123user@example.com')).toBe(true);
    });

    test('should accept emails with special characters', () => {
      expect(validateEmail('user.name@example.com')).toBe(true);
      expect(validateEmail('user+tag@example.com')).toBe(true);
      expect(validateEmail('user_name@example.com')).toBe(true);
    });

    test('should accept emails with subdomains', () => {
      expect(validateEmail('user@mail.example.com')).toBe(true);
      expect(validateEmail('user@subdomain.domain.com')).toBe(true);
    });
  });

  describe('invalid emails', () => {
    test('should reject emails without @ symbol', () => {
      expect(validateEmail('userexample.com')).toBe(false);
      expect(validateEmail('user.example.com')).toBe(false);
    });

    test('should reject emails without domain', () => {
      expect(validateEmail('user@')).toBe(false);
      expect(validateEmail('user@.')).toBe(false);
    });

    test('should reject emails without local part', () => {
      expect(validateEmail('@example.com')).toBe(false);
      expect(validateEmail('.@example.com')).toBe(false);
    });

    test('should reject emails with invalid characters', () => {
      expect(validateEmail('user name@example.com')).toBe(false);
      expect(validateEmail('user@exam ple.com')).toBe(false);
      expect(validateEmail('user@example..com')).toBe(false);
    });

    test('should reject empty or null inputs', () => {
      expect(validateEmail('')).toBe(false);
      expect(validateEmail(' ')).toBe(false);
    });

    test('should reject emails that are too long', () => {
      const longEmail = 'a'.repeat(250) + '@example.com';
      expect(validateEmail(longEmail)).toBe(false);
    });

    test('should reject emails with invalid domain formats', () => {
      expect(validateEmail('user@example')).toBe(false);
      expect(validateEmail('user@.com')).toBe(false);
      expect(validateEmail('user@example.')).toBe(false);
    });
  });

  describe('edge cases', () => {
    test('should handle international domain names', () => {
      expect(validateEmail('user@münchen.de')).toBe(true);
    });

    test('should handle quoted local parts', () => {
      expect(validateEmail('"user name"@example.com')).toBe(true);
    });

    test('should handle IP address domains', () => {
      expect(validateEmail('user@[192.168.1.1]')).toBe(true);
    });
  });
});

2. Component Tests

Input:

typescript
// Generate tests for SearchComponent

Generated Output:

typescript
import React from 'react';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import SearchComponent from './SearchComponent';

const mockSearchResults = [
  {
    id: '1',
    title: 'First Result',
    description: 'Description for first result',
    url: 'https://example.com/1'
  },
  {
    id: '2',
    title: 'Second Result',
    description: 'Description for second result',
    url: 'https://example.com/2'
  }
];

const mockOnSearch = jest.fn();

describe('SearchComponent', () => {
  beforeEach(() => {
    jest.clearAllMocks();
    jest.useFakeTimers();
  });

  afterEach(() => {
    jest.useRealTimers();
  });

  test('renders search input with placeholder', () => {
    render(<SearchComponent onSearch={mockOnSearch} placeholder="Search items..." />);
    
    expect(screen.getByPlaceholderText('Search items...')).toBeInTheDocument();
  });

  test('calls onSearch with debounced input', async () => {
    mockOnSearch.mockResolvedValue(mockSearchResults);
    
    render(<SearchComponent onSearch={mockOnSearch} debounceMs={300} />);
    
    const input = screen.getByPlaceholderText('Search...');
    
    await userEvent.type(input, 'test query');
    
    // Should not call immediately
    expect(mockOnSearch).not.toHaveBeenCalled();
    
    // Fast-forward time
    jest.advanceTimersByTime(300);
    
    await waitFor(() => {
      expect(mockOnSearch).toHaveBeenCalledWith('test query');
    });
  });

  test('displays search results', async () => {
    mockOnSearch.mockResolvedValue(mockSearchResults);
    
    render(<SearchComponent onSearch={mockOnSearch} />);
    
    const input = screen.getByPlaceholderText('Search...');
    await userEvent.type(input, 'test');
    
    jest.advanceTimersByTime(300);
    
    await waitFor(() => {
      expect(screen.getByText('First Result')).toBeInTheDocument();
      expect(screen.getByText('Second Result')).toBeInTheDocument();
      expect(screen.getByText('Description for first result')).toBeInTheDocument();
    });
  });

  test('displays loading state during search', async () => {
    mockOnSearch.mockImplementation(() => new Promise(resolve => 
      setTimeout(() => resolve(mockSearchResults), 1000)
    ));
    
    render(<SearchComponent onSearch={mockOnSearch} />);
    
    const input = screen.getByPlaceholderText('Search...');
    await userEvent.type(input, 'test');
    
    jest.advanceTimersByTime(300);
    
    await waitFor(() => {
      expect(screen.getByText('🔍')).toBeInTheDocument();
    });
  });

  test('displays error message on search failure', async () => {
    mockOnSearch.mockRejectedValue(new Error('Search failed'));
    
    render(<SearchComponent onSearch={mockOnSearch} />);
    
    const input = screen.getByPlaceholderText('Search...');
    await userEvent.type(input, 'test');
    
    jest.advanceTimersByTime(300);
    
    await waitFor(() => {
      expect(screen.getByText('Search failed. Please try again.')).toBeInTheDocument();
    });
  });

  test('displays no results message when search returns empty array', async () => {
    mockOnSearch.mockResolvedValue([]);
    
    render(<SearchComponent onSearch={mockOnSearch} />);
    
    const input = screen.getByPlaceholderText('Search...');
    await userEvent.type(input, 'nonexistent');
    
    jest.advanceTimersByTime(300);
    
    await waitFor(() => {
      expect(screen.getByText('No results found for "nonexistent"')).toBeInTheDocument();
    });
  });

  test('handles result click with URL', async () => {
    mockOnSearch.mockResolvedValue(mockSearchResults);
    const originalOpen = window.open;
    window.open = jest.fn();
    
    render(<SearchComponent onSearch={mockOnSearch} />);
    
    const input = screen.getByPlaceholderText('Search...');
    await userEvent.type(input, 'test');
    
    jest.advanceTimersByTime(300);
    
    await waitFor(() => {
      expect(screen.getByText('First Result')).toBeInTheDocument();
    });
    
    fireEvent.click(screen.getByText('First Result'));
    
    expect(window.open).toHaveBeenCalledWith('https://example.com/1', '_blank');
    
    window.open = originalOpen;
  });

  test('respects maxResults prop', async () => {
    const manyResults = Array.from({ length: 20 }, (_, i) => ({
      id: `${i}`,
      title: `Result ${i}`,
      description: `Description ${i}`
    }));
    
    mockOnSearch.mockResolvedValue(manyResults);
    
    render(<SearchComponent onSearch={mockOnSearch} maxResults={5} />);
    
    const input = screen.getByPlaceholderText('Search...');
    await userEvent.type(input, 'test');
    
    jest.advanceTimersByTime(300);
    
    await waitFor(() => {
      expect(screen.getByText('Result 0')).toBeInTheDocument();
      expect(screen.getByText('Result 4')).toBeInTheDocument();
      expect(screen.queryByText('Result 5')).not.toBeInTheDocument();
    });
  });

  test('clears results when input is empty', async () => {
    mockOnSearch.mockResolvedValue(mockSearchResults);
    
    render(<SearchComponent onSearch={mockOnSearch} />);
    
    const input = screen.getByPlaceholderText('Search...');
    
    // Type and get results
    await userEvent.type(input, 'test');
    jest.advanceTimersByTime(300);
    
    await waitFor(() => {
      expect(screen.getByText('First Result')).toBeInTheDocument();
    });
    
    // Clear input
    await userEvent.clear(input);
    jest.advanceTimersByTime(300);
    
    await waitFor(() => {
      expect(screen.queryByText('First Result')).not.toBeInTheDocument();
    });
  });
});

Code Generation Best Practices

1. Write Clear Comments

❌ Vague:

typescript
// Make a function

✅ Specific:

typescript
// Create a function that validates password strength and returns a score from 0-100
// Should check for length, uppercase, lowercase, numbers, and special characters

2. Provide Context

❌ No context:

typescript
function handleSubmit

✅ With context:

typescript
// Handle form submission for user registration
// Should validate data, show loading state, and handle errors
function handleSubmit

3. Specify Types

❌ Generic:

typescript
// Create a hook for API calls

✅ Typed:

typescript
// Create a TypeScript hook for making API calls with loading and error states
// Should return { data: T | null, loading: boolean, error: string | null }

4. Include Examples

typescript
// Create a utility function to format currency
// Example: formatCurrency(1234.56, 'USD') => '$1,234.56'
// Example: formatCurrency(1000, 'EUR') => '€1,000.00'
function formatCurrency

Troubleshooting

Common Issues

Generated code doesn't compile:

  1. Check TypeScript configuration
  2. Ensure all imports are available
  3. Verify type definitions

AI generates outdated patterns:

  1. Specify the framework version
  2. Mention modern best practices
  3. Ask for current approaches

Generated code is too complex:

  1. Break down into smaller requests
  2. Ask for simpler implementations
  3. Specify experience level

Getting Better Results

  1. Be specific about requirements
  2. Include type information for TypeScript
  3. Mention performance requirements
  4. Specify testing needs
  5. Ask for documentation when needed

Advanced Techniques

1. Iterative Refinement

Step 1: "Create a basic todo component"
Step 2: "Add drag and drop functionality"
Step 3: "Add categories and filtering"
Step 4: "Add persistence with localStorage"
Step 5: "Add animations and transitions"

2. Pattern-Based Generation

typescript
// Generate a repository pattern for User entity
// Should include CRUD operations, search, and pagination
// Use TypeScript interfaces and error handling
class UserRepository

3. Architecture Generation

typescript
// Create a complete feature module for user management
// Include: types, service, hooks, components, and tests
// Follow clean architecture principles

Next Steps

Now that you've mastered code generation:

  1. Practice with different types of code
  2. Experiment with various prompting techniques
  3. Combine with other Trae features
  4. Share your generated code with the team

Conclusion

Trae's AI code generation is a powerful tool that can significantly speed up your development process. The key to success is:

  • Writing clear, specific prompts
  • Providing adequate context
  • Iterating and refining
  • Combining AI generation with your expertise

Happy coding! 🚀✨

Your Ultimate AI-Powered IDE Learning Guide