diff --git a/app/constants/json_retries.go b/app/constants/json_retries.go new file mode 100644 index 00000000..8856bd7d --- /dev/null +++ b/app/constants/json_retries.go @@ -0,0 +1,5 @@ +package constants + +const ( + MAX_JSON_RETRIES = 5 +) \ No newline at end of file diff --git a/app/models/types/errors.go b/app/models/types/errors.go index d9d1d669..e0f1a2dd 100644 --- a/app/models/types/errors.go +++ b/app/models/types/errors.go @@ -11,3 +11,5 @@ var ErrInvalidStory = errors.New("invalid story") var ErrInvalidStoryStatusTransition = errors.New("invalid story status transition") var ErrAnotherStoryAlreadyInProgress = errors.New("another story already in progress") + +var ErrJsonParsingRetriesExceeded = errors.New("json parsing retries exceeded") \ No newline at end of file diff --git a/app/prompts/nextjs/ai_frontend_developer_edit_code_retry.txt b/app/prompts/nextjs/ai_frontend_developer_edit_code_retry.txt new file mode 100644 index 00000000..8d1a4486 --- /dev/null +++ b/app/prompts/nextjs/ai_frontend_developer_edit_code_retry.txt @@ -0,0 +1,73 @@ +You are tasked with updating the frontend code to resolve build errors. You will be given information about a specific file, the error description, the current code, and the project's directory structure. Your job is to propose a solution by either editing existing code or inserting new code. + +Here's the file you need to work on: + +{{FILE_NAME}} + + +The build error description is as follows: + +{{ERROR_DESCRIPTION}} + + +Here's the current code in the file: + +{{CURRENT_CODE}} + + +The directory structure of the project is: + +{{DIRECTORY_STRUCTURE}} + + +Based on the error description and the current code, you must choose ONE of the following actions: + +1. Edit code: + Replace the code in the given file path from `start_line` to `end_line` with `new_code`. + +2. Add code: + Insert the code in the given file path below the `line_number`. + +Provide your response in JSON format, following one of these structures: + +For editing code: +{ + "type": "edit", + "start_line": Starting Line number, + "end_line": Ending Line number, + "new_code": Code snippet +} + +For inserting code: +{ + "type": "insert", + "line_number": Line number, + "new_code": Code snippet +} + +Guidelines for code editing and inserting: +- When editing, provide the code snippet from several lines before and after the actual edit to ensure proper context. Aim to edit complete sections of the code, covering at least 20-30 lines. +- If multiple edits are needed, choose line numbers that cover all necessary changes. In extreme cases, you may edit the entire file by setting start_line = 1 and end_line = last line of code. +- Do not include ```json and ``` in your response. +- Ensure that the proposed code is syntactically correct. +- Beware of any unescaped single quotes (') in the JSX code. Do not have unescaped single quotes in the code, use ' or " or other appropriate methods instead. + +Remember to provide only one edit or insert at a time. Your primary goal is to resolve the build error while maintaining the integrity of the code. + +Present your solution in the JSON format described above, without any additional explanation or commentary. + +The previous response was not valid JSON. Please provide the edit instructions in the specified JSON format. Here is the format: +For editing code: +{ + "type": "edit", + "start_line": Starting Line number, + "end_line": Ending Line number, + "new_code": Code snippet +} + +For inserting code: +{ + "type": "insert", + "line_number": Line number, + "new_code": Code snippet +} \ No newline at end of file diff --git a/app/prompts/nextjs/next_js_build_checker_retry.txt b/app/prompts/nextjs/next_js_build_checker_retry.txt new file mode 100644 index 00000000..9999321b --- /dev/null +++ b/app/prompts/nextjs/next_js_build_checker_retry.txt @@ -0,0 +1,102 @@ +You are an AI assistant tasked with analyzing the build logs of a website's frontend and determining if the build was successful. If not, you'll need to suggest an action to resolve the issue. You'll be provided with the following information: + + +{{BUILD_LOGS}} + + + +{{DIRECTORY_STRUCTURE}} + + +Your task is to: +1. Analyze the build logs carefully. +2. Determine if the build was successful or not. +3. If the build was not successful, suggest one of the following actions: + a. Create a new file + b. Edit an existing file + +To determine if the build was successful: +- Look for phrases like "Build successful", "Build completed", or similar indications of success. +- Check for error messages, warnings, or failed steps in the build process. +- Pay attention to the final lines of the build logs, as they often contain the build status. + +If the build was not successful: +1. Identify the root cause of the failure based on the error messages in the build logs. +2. Consider the logs and directory structure when suggesting a solution. +3. Choose the most appropriate action (create, or edit) to resolve the issue. +4. Provide detailed information about the action, including file paths (relative to the root directory) or terminal commands. +5. Include line numbers from the build logs in your description if available. + +Provide your response in the following JSON format: + +For a successful build: +{ + "build_successful": "Yes" +} + +For an unsuccessful build: +{ + "build_successful": "No", + "action": { + "type": "create|edit", + "file_path": "path/to/file", + "description": "Detailed description of the action and why it's needed" + } +} +Examples to clarify: +{ + "build_successful": "No", + "action": { + "type": "create", + "file_path": "path/to/file", + "description": "Detailed description of the action and why it's needed" + } +} + +{ + "build_successful": "No", + "action": { + "type": "edit", + "file_path": "path/to/file", + "description": "Detailed description of the action and why it's needed" + } +} + + +Remember to analyze the build logs thoroughly and provide a clear, concise explanation of the issue and the proposed solution in the "description" field. Do not include the ```json and ``` markers in your response. + +The previous response was not valid JSON. Please provide the edit instructions in the specified JSON format. Here is the format: +Provide your response in the following JSON format: + +For a successful build: +{ + "build_successful": "Yes" +} + +For an unsuccessful build: +{ + "build_successful": "No", + "action": { + "type": "create|edit", + "file_path": "path/to/file", + "description": "Detailed description of the action and why it's needed" + } +} +Examples to clarify: +{ + "build_successful": "No", + "action": { + "type": "create", + "file_path": "path/to/file", + "description": "Detailed description of the action and why it's needed" + } +} + +{ + "build_successful": "No", + "action": { + "type": "edit", + "file_path": "path/to/file", + "description": "Detailed description of the action and why it's needed" + } +} diff --git a/app/workflow_executors/step_executors/impl/next_js_server_test_executor.go b/app/workflow_executors/step_executors/impl/next_js_server_test_executor.go index e95df65e..0a43cf20 100644 --- a/app/workflow_executors/step_executors/impl/next_js_server_test_executor.go +++ b/app/workflow_executors/step_executors/impl/next_js_server_test_executor.go @@ -14,8 +14,8 @@ import ( "os/exec" "path/filepath" "strings" - "go.uber.org/zap" + "ai-developer/app/monitoring" ) type NextJsServerStartTestExecutor struct { @@ -26,6 +26,7 @@ type NextJsServerStartTestExecutor struct { llmAPIKeyService *services.LLMAPIKeyService storyService *services.StoryService projectService *services.ProjectService + slackAlert *monitoring.SlackAlert } func NewNextJsServerStartTestExecutor( @@ -36,6 +37,7 @@ func NewNextJsServerStartTestExecutor( executionService *services.ExecutionService, storyService *services.StoryService, projectService *services.ProjectService, + slackAlert *monitoring.SlackAlert, ) *NextJsServerStartTestExecutor { return &NextJsServerStartTestExecutor{ executionStepService: executionStepService, @@ -45,6 +47,7 @@ func NewNextJsServerStartTestExecutor( executionService: executionService, storyService: storyService, projectService: projectService, + slackAlert: slackAlert, } } @@ -170,46 +173,71 @@ func (e NextJsServerStartTestExecutor) Execute(step steps.ServerStartTestStep) e func (e NextJsServerStartTestExecutor) AnalyseBuildLogs(buildLogs, directoryPlan, apiKey string, step steps.ServerStartTestStep) (bool, map[string]interface{}, error) { e.logger.Info("____Analyzing build logs____ ", zap.String("buildLogs", buildLogs)) - messages, err := e.CreateMessage(buildLogs, directoryPlan) - if err != nil { - return false, nil, err - } - claudeClient:= llms.NewClaudeClient(apiKey) - response, err := claudeClient.ChatCompletion(messages) - if err != nil { - settingsUrl := config.Get("app.url").(string) + "/settings" - err = e.activityLogService.CreateActivityLog( - step.Execution.ID, - step.ExecutionStep.ID, - "INFO", - fmt.Sprintf("Action required: There's an issue with your LLM API Key. Ensure your API Key for %s is correct. Settings", constants.CLAUDE_3, settingsUrl, "blue", "underline"), - ) - if err != nil { - fmt.Printf("Error creating activity log: %s\n", err.Error()) - return false, nil, err + claudeClient := llms.NewClaudeClient(apiKey) + var jsonResponse map[string]interface{} + var response string + + for retryCount := 1; retryCount <= constants.MAX_JSON_RETRIES; retryCount++ { + messages, err := e.CreateMessage(buildLogs, directoryPlan, retryCount) + if err != nil{ + e.logger.Error("failed to create messages for llm") + return false, nil, err } - //Update Execution Status and Story Status - if err = e.storyService.UpdateStoryStatus(int(step.Story.ID), constants.InReviewLLMKeyNotFound); err != nil { - fmt.Printf("Error updating story status: %s\n", err.Error()) - return false, nil, err + response, err = claudeClient.ChatCompletion(messages) + if err != nil { + settingsUrl := config.Get("app.url").(string) + "/settings" + err = e.activityLogService.CreateActivityLog( + step.Execution.ID, + step.ExecutionStep.ID, + "INFO", + fmt.Sprintf("Action required: There's an issue with your LLM API Key. Ensure your API Key for %s is correct. Settings", constants.CLAUDE_3, settingsUrl, "blue", "underline"), + ) + if err != nil { + e.logger.Error("failed to create activity log for llm", zap.Error(err)) + return false, nil, err + } + //Update Execution Status and Story Status + if err = e.storyService.UpdateStoryStatus(int(step.Story.ID), constants.InReviewLLMKeyNotFound); err != nil { + fmt.Printf("Error updating story status: %s\n", err.Error()) + return false, nil, err + } + if err = e.executionService.UpdateExecutionStatus(step.Execution.ID, constants.InReviewLLMKeyNotFound); err != nil { + e.logger.Error("Error updating execution status", zap.Error(err)) + return false, nil, err + } + e.logger.Error("failed to generate code from llm") + if retryCount == constants.MAX_JSON_RETRIES { + return false, nil, fmt.Errorf("failed to generate code from llm after 5 attempts: %w", err) + } + continue } - if err = e.executionService.UpdateExecutionStatus(step.Execution.ID, constants.InReviewLLMKeyNotFound); err != nil { - fmt.Printf("Error updating execution step: %s\n", err.Error()) - return false, nil, err + if err = json.Unmarshal([]byte(response), &jsonResponse); err != nil { + e.logger.Error("error decoding build logs response from Claude API", zap.Error(err)) + e.logger.Error("failed to unmarshal response from Claude API, retrying...") + err := e.slackAlert.SendAlert( + "error occurred while parsing build logs JSON response", + map[string]string{ + "story_id": fmt.Sprintf("%d", int64(step.Story.ID)), + "execution_id": fmt.Sprintf("%d", int64(step.Execution.ID)), + "execution_step_id": fmt.Sprintf("%d", int64(step.ExecutionStep.ID)), + "is_re_execution": fmt.Sprintf("%t", step.Execution.ReExecution), + "error": err.Error(), + "attempt": fmt.Sprintf("%d", int64(retryCount)), + }) + if err != nil { + e.logger.Error("error sending slack alert", zap.Error(err)) + return false, nil, err + } + if retryCount == 5 { + return false, nil, fmt.Errorf("failed to unmarshal response from Claude API after 5 attempts: %w", err) + } + continue } - fmt.Println("failed to generate code from llm") - return false, nil, fmt.Errorf("failed to generate code from llm: %w", err) - } - var jsonResponse map[string]interface{} - if err = json.Unmarshal([]byte(response), &jsonResponse); err != nil { - fmt.Println("failed to unmarshal response from Claude API, Failed to parse response as JSON on attempt.") - return false, nil, fmt.Errorf("failed to unmarshal response from Claude API: %w", err) + break } - fmt.Println("Response after extracting JSON: ", jsonResponse) + buildResponse, action := e.CheckBuildResponse(jsonResponse) - fmt.Println("Build Logs Check Response") return buildResponse, action, nil - } func (e NextJsServerStartTestExecutor) CheckBuildResponse(response map[string]interface{}) (bool, map[string]interface{}) { @@ -230,8 +258,20 @@ func (e NextJsServerStartTestExecutor) CheckBuildResponse(response map[string]in return false, action } -func (e NextJsServerStartTestExecutor) CreateMessage(buildLogs string, directoryPlan string) ([]llms.ClaudeChatCompletionMessage, error) { - content, err := os.ReadFile("/go/prompts/nextjs/next_js_build_checker.txt") +func (e NextJsServerStartTestExecutor) CreateMessage(buildLogs string, directoryPlan string, attempts int) ([]llms.ClaudeChatCompletionMessage, error) { + var content []byte + var err error + if attempts > 1 { + content, err = os.ReadFile("/go/prompts/nextjs/next_js_build_checker_retry.txt") + if err!= nil { + return nil, fmt.Errorf("failed to load system prompt: %w", err) + } + } else { + content, err = os.ReadFile("/go/prompts/nextjs/next_js_build_checker.txt") + if err!= nil { + return nil, fmt.Errorf("failed to load system prompt: %w", err) + } + } modifiedContent := strings.Replace(string(content), "{{BUILD_LOGS}}", buildLogs, -1) modifiedContent = strings.Replace(string(modifiedContent), "{{DIRECTORY_STRUCTURE}}", directoryPlan, -1) if err != nil { diff --git a/app/workflow_executors/step_executors/impl/open_ai_next_js_code_generation_executor.go b/app/workflow_executors/step_executors/impl/open_ai_next_js_code_generation_executor.go index 9456bb67..848119f1 100644 --- a/app/workflow_executors/step_executors/impl/open_ai_next_js_code_generation_executor.go +++ b/app/workflow_executors/step_executors/impl/open_ai_next_js_code_generation_executor.go @@ -5,16 +5,20 @@ import ( "ai-developer/app/constants" "ai-developer/app/llms" "ai-developer/app/models" + "ai-developer/app/models/types" + "ai-developer/app/monitoring" "ai-developer/app/services" "ai-developer/app/services/filestore" "ai-developer/app/utils" "ai-developer/app/workflow_executors/step_executors/steps" + "encoding/json" "errors" "fmt" "os" "os/exec" "path/filepath" "strings" + "time" "go.uber.org/zap" ) @@ -28,6 +32,7 @@ type OpenAiNextJsCodeGenerator struct { designReviewService *services.DesignStoryReviewService llmAPIKeyService *services.LLMAPIKeyService logger *zap.Logger + slackAlert *monitoring.SlackAlert fileStore filestore.FileStore } @@ -40,6 +45,7 @@ func NewOpenAINextJsCodeGenerationExecutor( designReviewService *services.DesignStoryReviewService, llmAPIKeyService *services.LLMAPIKeyService, logger *zap.Logger, + slackAlert *monitoring.SlackAlert, fileStore filestore.FileStore, ) *OpenAiNextJsCodeGenerator { return &OpenAiNextJsCodeGenerator{ @@ -51,6 +57,7 @@ func NewOpenAINextJsCodeGenerationExecutor( designReviewService: designReviewService, llmAPIKeyService: llmAPIKeyService, logger: logger, + slackAlert: slackAlert, fileStore: fileStore, } } @@ -80,6 +87,18 @@ func (openAiCodeGenerator OpenAiNextJsCodeGenerator) Execute(step steps.Generate if count > step.MaxLoopIterations { fmt.Println("Max retry limit reached for LLM steps") + err = openAiCodeGenerator.slackAlert.SendAlert( + "Max retry limit reached", + map[string]string{ + "story_id": fmt.Sprintf("%d", int64(step.Story.ID)), + "execution_id": fmt.Sprintf("%d", int64(step.Execution.ID)), + "execution_step_id": fmt.Sprintf("%d", int64(step.ExecutionStep.ID)), + "is_re_execution": fmt.Sprintf("%t", step.Execution.ReExecution), + }) + if err != nil { + fmt.Printf("Error sending slack alert: %s\n", err.Error()) + return err + } //Update story status to MAX_LOOP_ITERATION_REACHED if err = openAiCodeGenerator.storyService.UpdateStoryStatus(int(step.Story.ID), constants.MaxLoopIterationReached); err != nil { fmt.Printf("Error updating story status: %s\n", err.Error()) @@ -161,27 +180,63 @@ func (openAiCodeGenerator OpenAiNextJsCodeGenerator) Execute(step steps.Generate code, err := openAiCodeGenerator.GenerateCode(step, finalInstructionForGeneration, storyDir, apiKey) if err != nil { fmt.Println("____ERROR OCCURRED WHILE GENERATING CODE: ______", err) - settingsUrl := config.Get("app.url").(string) + "/settings" - err = openAiCodeGenerator.activityLogService.CreateActivityLog( - step.Execution.ID, - step.ExecutionStep.ID, - "INFO", - fmt.Sprintf("Action required: There's an issue with your LLM API Key. Ensure your API Key for %s is correct. Settings", constants.CLAUDE_3, settingsUrl, "blue", "underline"), - ) - if err != nil { - fmt.Printf("Error creating activity log: %s\n", err.Error()) - return err - } - //Update Execution Status and Story Status - if err = openAiCodeGenerator.storyService.UpdateStoryStatus(int(step.Story.ID), constants.InReviewLLMKeyNotFound); err != nil { - fmt.Printf("Error updating story status: %s\n", err.Error()) + if err == types.ErrJsonParsingRetriesExceeded { + err = openAiCodeGenerator.slackAlert.SendAlert( + "Max retry limit reached while parsing JSON, error occurred while parsing the generated edit code", + map[string]string{ + "story_id": fmt.Sprintf("%d", int64(step.Story.ID)), + "execution_id": fmt.Sprintf("%d", int64(step.Execution.ID)), + "execution_step_id": fmt.Sprintf("%d", int64(step.ExecutionStep.ID)), + "is_re_execution": fmt.Sprintf("%t", step.Execution.ReExecution), + "model_name": constants.CLAUDE_3, + }) + if err != nil { + fmt.Printf("Error sending slack alert: %s\n", err.Error()) + return err + } + err = openAiCodeGenerator.activityLogService.CreateActivityLog( + step.Execution.ID, + step.ExecutionStep.ID, + "INFO", + fmt.Sprintf("Json parsing retries exceeded for code generation!"), + ) + if err != nil { + fmt.Printf("Error creating activity log: %s\n", err.Error()) + return err + } + //Update Execution Status and Story Status + if err = openAiCodeGenerator.storyService.UpdateStoryStatus(int(step.Story.ID), constants.InReview); err != nil { + fmt.Printf("Error updating story status: %s\n", err.Error()) + return err + } + if err = openAiCodeGenerator.executionService.UpdateExecutionStatus(step.Execution.ID, constants.InReview); err != nil { + fmt.Printf("Error updating execution step: %s\n", err.Error()) + return err + } return err - } - if err = openAiCodeGenerator.executionService.UpdateExecutionStatus(step.Execution.ID, constants.InReviewLLMKeyNotFound); err != nil { - fmt.Printf("Error updating execution step: %s\n", err.Error()) + } else { + settingsUrl := config.Get("app.url").(string) + "/settings" + err = openAiCodeGenerator.activityLogService.CreateActivityLog( + step.Execution.ID, + step.ExecutionStep.ID, + "INFO", + fmt.Sprintf("Action required: There's an issue with your LLM API Key. Ensure your API Key for %s is correct. Settings", constants.CLAUDE_3, settingsUrl, "blue", "underline"), + ) + if err != nil { + fmt.Printf("Error creating activity log: %s\n", err.Error()) + return err + } + //Update Execution Status and Story Status + if err = openAiCodeGenerator.storyService.UpdateStoryStatus(int(step.Story.ID), constants.InReviewLLMKeyNotFound); err != nil { + fmt.Printf("Error updating story status: %s\n", err.Error()) + return err + } + if err = openAiCodeGenerator.executionService.UpdateExecutionStatus(step.Execution.ID, constants.InReviewLLMKeyNotFound); err != nil { + fmt.Printf("Error updating execution step: %s\n", err.Error()) + return err + } return err } - return err } if err = openAiCodeGenerator.executionStepService.UpdateExecutionStepResponse( @@ -407,10 +462,10 @@ func (openAiCodeGenerator *OpenAiNextJsCodeGenerator) buildInstructionOnReExecut func (openAiCodeGenerator *OpenAiNextJsCodeGenerator) GenerateCode(step steps.GenerateCodeStep, instruction map[string]string, storyDir string, apiKey string) (string, error) { if step.Retry { - response, err := openAiCodeGenerator.GenerateCodeOnRetry(step.ExecutionStep, instruction, storyDir, apiKey) + response, err := openAiCodeGenerator.GenerateCodeOnRetry(step.ExecutionStep, instruction, storyDir, apiKey, step) if err != nil { fmt.Println("Error generating code on retry") - return "", fmt.Errorf("failed to generate retry code from Claude API: %w", err) + return "", err } return response, nil } else { @@ -450,7 +505,7 @@ func (openAiCodeGenerator *OpenAiNextJsCodeGenerator) ProcessMessageResponse(mes } -func (openAiCodeGenerator *OpenAiNextJsCodeGenerator) GenerateCodeOnRetry(executionStep *models.ExecutionStep, instruction map[string]string, storyDir string, apiKey string) (string, error) { +func (openAiCodeGenerator *OpenAiNextJsCodeGenerator) GenerateCodeOnRetry(executionStep *models.ExecutionStep, instruction map[string]string, storyDir string, apiKey string, step steps.GenerateCodeStep) (string, error) { switch instruction["actionType"] { case "create": filePath := storyDir + instruction["fileName"] @@ -478,7 +533,7 @@ func (openAiCodeGenerator *OpenAiNextJsCodeGenerator) GenerateCodeOnRetry(execut } return "", nil case "edit": - response, err := openAiCodeGenerator.EditCodeOnRetry(instruction, storyDir, executionStep, apiKey) + response, err := openAiCodeGenerator.EditCodeOnRetry(instruction, storyDir, executionStep, apiKey, step) if err != nil { return "", err } @@ -489,30 +544,80 @@ func (openAiCodeGenerator *OpenAiNextJsCodeGenerator) GenerateCodeOnRetry(execut } } -func (openAiCodeGenerator *OpenAiNextJsCodeGenerator) EditCodeOnRetry(instruction map[string]string, storyDir string, executionStep *models.ExecutionStep, apiKey string) (string, error) { - generationPlan, err := openAiCodeGenerator.GetCodeGenerationPlan(storyDir) - if err != nil { - return "", err - } - systemPrompt, err := openAiCodeGenerator.GetRetrySystemPrompt(instruction, generationPlan) - if err != nil { - return "", err - } - messages := openAiCodeGenerator.GetMessagesOnRetry(systemPrompt, instruction["description"]) - err = openAiCodeGenerator.executionStepService.UpdateExecutionStepRequest( - executionStep, - map[string]interface{}{ - "final_instruction": instruction, - "llm_request": messages, - }, - "IN_PROGRESS", - ) - claudeClient := llms.NewClaudeClient(apiKey) - response, err := claudeClient.ChatCompletion(messages) - if err != nil { - return "", fmt.Errorf("failed to generate code from llm: %w", err) - } - return response, nil +func (openAiCodeGenerator *OpenAiNextJsCodeGenerator) EditCodeOnRetry(instruction map[string]string, storyDir string, executionStep *models.ExecutionStep, apiKey string, step steps.GenerateCodeStep) (string, error) { + var response string + var err error + var jsonErr error + + for attempt := 1; attempt <= constants.MAX_JSON_RETRIES; attempt++ { + response, err = openAiCodeGenerator.attemptEditCode(instruction, storyDir, executionStep, apiKey, attempt) + if err !=nil { + return "", err + } + jsonErr = openAiCodeGenerator.checkJsonValidity(response) + if jsonErr == nil { + return response, nil + } + + if attempt <= constants.MAX_JSON_RETRIES { + err = openAiCodeGenerator.slackAlert.SendAlert( + "error occurred while parsing edit code JSON response", + map[string]string{ + "story_id": fmt.Sprintf("%d", int64(step.Story.ID)), + "execution_id": fmt.Sprintf("%d", int64(step.Execution.ID)), + "execution_step_id": fmt.Sprintf("%d", int64(step.ExecutionStep.ID)), + "is_re_execution": fmt.Sprintf("%t", step.Execution.ReExecution), + "error": jsonErr.Error(), + "attempt": fmt.Sprintf("%d", int64(attempt)), + }) + if err != nil { + fmt.Printf("Error sending slack alert: %s\n", err.Error()) + return "", err + } + fmt.Printf("Attempt %d failed: %v. Retrying...\n", attempt, jsonErr) + time.Sleep(time.Second * time.Duration(attempt)) + } + } + + return "", types.ErrJsonParsingRetriesExceeded +} + +func (openAiCodeGenerator *OpenAiNextJsCodeGenerator) attemptEditCode(instruction map[string]string, storyDir string, executionStep *models.ExecutionStep, apiKey string, attempts int) (string, error) { + generationPlan, err := openAiCodeGenerator.GetCodeGenerationPlan(storyDir) + if err != nil { + return "", err + } + systemPrompt, err := openAiCodeGenerator.GetRetrySystemPrompt(instruction, generationPlan, attempts) + if err != nil { + return "", err + } + messages := openAiCodeGenerator.GetMessagesOnRetry(systemPrompt, instruction["description"]) + err = openAiCodeGenerator.executionStepService.UpdateExecutionStepRequest( + executionStep, + map[string]interface{}{ + "final_instruction": instruction, + "llm_request": messages, + }, + "IN_PROGRESS", + ) + if err != nil { + return "", err + } + claudeClient := llms.NewClaudeClient(apiKey) + response, err := claudeClient.ChatCompletion(messages) + if err != nil { + return "", fmt.Errorf("failed to generate code from llm: %w", err) + } + return response, nil +} + +func (openAiCodeGenerator *OpenAiNextJsCodeGenerator) checkJsonValidity(input string) error { + fmt.Println("_____input to json validator_____:", input) + var llmResponse map[string]interface{} + if err := json.Unmarshal([]byte(input), &llmResponse); err != nil { + return fmt.Errorf("failed to unmarshal edit response: %w", err) + } + return nil } func (openAiCodeGenerator *OpenAiNextJsCodeGenerator) GenerateMessages(instruction map[string]string, storyDir string, step steps.GenerateCodeStep) ([]llms.ClaudeChatCompletionMessage, error) { @@ -589,11 +694,19 @@ func (openAiCodeGenerator *OpenAiNextJsCodeGenerator) getSystemPrompt(instructio return systemPrompt, nil } -func (openAiCodeGenerator *OpenAiNextJsCodeGenerator) GetRetrySystemPrompt(instruction map[string]string, directoryStructure string) (string, error) { - content, err := os.ReadFile("/go/prompts/nextjs/ai_frontend_developer_edit_code.txt") - if err != nil { - panic(fmt.Sprintf("failed to read system prompt: %v", err)) - return "", err +func (openAiCodeGenerator *OpenAiNextJsCodeGenerator) GetRetrySystemPrompt(instruction map[string]string, directoryStructure string, attempts int) (string, error) { + var content []byte + var err error + if attempts > 1 { + content, err = os.ReadFile("/go/prompts/nextjs/ai_frontend_developer_edit_code_retry.txt") + if err != nil { + panic(fmt.Sprintf("failed to read system prompt: %v", err)) + } + } else { + content, err = os.ReadFile("/go/prompts/nextjs/ai_frontend_developer_edit_code.txt") + if err != nil { + panic(fmt.Sprintf("failed to read system prompt: %v", err)) + } } modifiedContent := strings.Replace(string(content), "{{FILE_NAME}}", instruction["fileName"], -1) modifiedContent = strings.Replace(string(modifiedContent), "{{ERROR_DESCRIPTION}}", instruction["description"], -1) diff --git a/app/workflow_executors/step_executors/impl/open_ai_next_js_update_code_file_executor.go b/app/workflow_executors/step_executors/impl/open_ai_next_js_update_code_file_executor.go index d8779e99..3890b545 100644 --- a/app/workflow_executors/step_executors/impl/open_ai_next_js_update_code_file_executor.go +++ b/app/workflow_executors/step_executors/impl/open_ai_next_js_update_code_file_executor.go @@ -98,11 +98,24 @@ func (e *NextJsUpdateCodeFileExecutor) UpdateReGeneratedCodeFile(response Respon } err := json.Unmarshal([]byte(response.LLMResponse), &llmResponse) if err != nil { - return nil + e.logger.Error("___error occurred while parsing json response_____", zap.Any("error", err)) + return err } switch llmResponse["type"].(string) { case "edit", "update": - newCode := llmResponse["new_code"].(string) + var newCode string + switch nc := llmResponse["new_code"].(type) { + case string: + newCode = nc + case map[string]interface{}: + newCodeJson, err := json.Marshal(nc) + if err != nil { + fmt.Printf("Error marshaling new_code: %v\n", err) + } + newCode = string(newCodeJson) + default: + fmt.Printf("Unexpected type for new_code: %T\n", nc) + } var startLine int switch startLineVal := llmResponse["start_line"].(type) { case float64: @@ -144,9 +157,7 @@ func (e *NextJsUpdateCodeFileExecutor) UpdateReGeneratedCodeFile(response Respon } func (e *NextJsUpdateCodeFileExecutor) EditCode(filePath string, startLine, endLine int, newCode string) error { - fmt.Printf("____Editing file %s_____", filePath) - fmt.Println("Start Line:", startLine) - fmt.Println("End Line:", endLine) + e.logger.Info("___Editing file____", zap.Any("filePath", filePath)) file, err := os.Open(filePath) if err != nil { fmt.Println("Error opening file", filePath, err.Error()) @@ -169,7 +180,6 @@ func (e *NextJsUpdateCodeFileExecutor) EditCode(filePath string, startLine, endL endLine-- newLines := strings.Split(newCode, "\n") - fmt.Println("New Lines:", newLines) // Edge case handling when startLine and endLine are 0 if startLine < 0 { @@ -250,4 +260,4 @@ func (e NextJsUpdateCodeFileExecutor) UpdateCodeFile(llmResponse, fileName strin return err } return nil -} +} \ No newline at end of file diff --git a/gui/src/components/SyntaxDisplay/SyntaxDisplay.tsx b/gui/src/components/SyntaxDisplay/SyntaxDisplay.tsx index e33d9fab..cf620eb9 100644 --- a/gui/src/components/SyntaxDisplay/SyntaxDisplay.tsx +++ b/gui/src/components/SyntaxDisplay/SyntaxDisplay.tsx @@ -11,18 +11,29 @@ interface SyntaxDisplayProps { const SyntaxDisplay: React.FC = ({ type, msg }) => { let title = ''; - let content = msg; + let content = ''; + + const parseMessage = (message: string) => { + const titleMatch = message.match(/"title":\s*"(.*?)"/); + const contentMatch = message.match(/"content":\s*"([\s\S]*?)"/); + + return { + title: titleMatch ? titleMatch[1] : '', + content: contentMatch ? contentMatch[1].replace(/\\n/g, '\n').replace(/\\"/g, '"') : '' + }; + }; try { - const parsedMsg = JSON.parse(msg || ''); - if (parsedMsg.title && parsedMsg.content) { - title = parsedMsg.title; - content = parsedMsg.content; - } + const parsedMsg = parseMessage(msg || ''); + title = parsedMsg.title; + content = parsedMsg.content; } catch (e) { - // If parsing fails, it's not a valid JSON, so we'll use the original msg + console.error('Failed to parse activity log message:', e); + content = msg || ''; } + const command = title.replace('Running command: ', ''); + return ( <> {type === ActivityLogType.ERROR && ( @@ -32,7 +43,7 @@ const SyntaxDisplay: React.FC = ({ type, msg }) => { )} {type === ActivityLogType.CODE && title && ( - Running command: {title.replace('Running command: ', '')} + Running command: {command} )}