All files / src/visual-editor visual-editor.controller.ts

40% Statements 14/35
0% Branches 0/10
50% Functions 1/2
36.36% Lines 12/33

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 1096x               6x 6x 6x 6x 6x                                   6x 6x     6x 6x 6x                                     6x                                                                                                          
import {
  Controller,
  Post,
  Body,
  HttpCode,
  HttpStatus,
  Logger,
} from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
import { SessionsService } from '../sessions/sessions.service';
import { SessionInputsService } from '../session-inputs/session-inputs.service';
import { ApplicationStateService } from '../application-state/application-state.service';
import { SubmitVisualPromptDto } from './visual-editor.dto';
 
/**
 * Visual Editor endpoints handle prompts from the visual selection mode.
 *
 * The visual editor proxy allows selecting UI elements in a running application
 * and sending their component context as part of a prompt. This enables
 * "point and click" AI modifications to React components.
 *
 * Workflow:
 * 1. User clicks visual selector button in the target app
 * 2. Selects a UI element to modify
 * 3. Component path and context are sent to this endpoint
 * 4. Backend creates a session input with the component files as context
 * 5. LLM generates modifications for the selected component
 */
@ApiTags('visual-editor')
@Controller('visual-editor')
export class VisualEditorController {
  private readonly logger = new Logger(VisualEditorController.name);
 
  constructor(
    private readonly sessionsService: SessionsService,
    private readonly sessionInputsService: SessionInputsService,
    private readonly applicationStateService: ApplicationStateService,
  ) {}
 
  @Post('submit')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({
    summary: 'Submit visual editor prompt',
    description:
      'Submit a prompt from the visual editor with selected component context. Creates a session input with the component files included as context.',
  })
  @ApiResponse({
    status: 200,
    description: 'Prompt submitted successfully',
    schema: {
      properties: {
        message: { type: 'string', example: 'Prompt submitted successfully' },
      },
    },
  })
  async submitVisualPrompt(@Body() submitDto: SubmitVisualPromptDto) {
    this.logger.log('Received prompt from visual editor');
 
    let sessionId = await this.applicationStateService.getActiveSessionId();
 
    if (!sessionId) {
      this.logger.log('No active session found. Creating a new one.');
      const newSession = await this.sessionsService.create({
        session_title: 'New Visual Editor Session',
      });
      sessionId = newSession.id;
      this.logger.log(`Created new session with ID: ${sessionId}`);
    } else {
      this.logger.log(`Using active session ID: ${sessionId}`);
    }
 
    const adHocFiles = [];
    Iif (submitDto.componentPath) {
      adHocFiles.push(submitDto.componentPath);
    }
    Iif (submitDto.parentComponentPath) {
      adHocFiles.push(submitDto.parentComponentPath);
    }
 
    const ad_hoc_context_definition = JSON.stringify({
      files: adHocFiles,
      folders: [],
      command_outputs: [],
    });
 
    // We fetch the session details to correctly determine the context template.
    const sessionDetails = await this.sessionsService.findOne(sessionId);
    const existingInputsCount = sessionDetails.sessionInputs?.length || 0;
    const isFirstPrompt = existingInputsCount === 0;
 
    const templateId = isFirstPrompt
      ? sessionDetails.default_initial_context_template_id
      : sessionDetails.default_followup_context_template_id;
 
    await this.sessionInputsService.create(sessionId, {
      user_prompt: submitDto.prompt,
      execution_strategy: 'apply_revert',
      context_template_id: templateId || null,
      ad_hoc_context_definition: ad_hoc_context_definition,
    });
 
    this.logger.log(
      `Successfully created new session input in session ${sessionId}`,
    );
 
    return { message: 'Prompt submitted successfully' };
  }
}