From 9c74c00dfb986bdcb2629b670f38803268d03c84 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 18 Sep 2025 01:00:03 +0000 Subject: [PATCH 1/6] Initial plan From ae578b75acfb5d5f705c9b25404b7914a2fb2ea0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 18 Sep 2025 01:08:56 +0000 Subject: [PATCH 2/6] Implement coding task generation system with LLM integration Co-authored-by: Anton15K <54205422+Anton15K@users.noreply.github.com> --- .../kotlin/com/gitdiff/CodingTaskService.kt | 45 ++++ src/main/kotlin/com/gitdiff/Models.kt | 14 +- src/main/kotlin/com/quiz/cards/TaskCard.kt | 8 +- src/main/kotlin/com/quiz/pages/QuizPage.kt | 33 ++- .../kotlin/llm_pipeline/CodingTaskAction.kt | 154 ++++++++++++ .../llm_pipeline/CodingTaskGenerator.kt | 225 ++++++++++++++++++ src/main/resources/META-INF/plugin.xml | 8 + 7 files changed, 478 insertions(+), 9 deletions(-) create mode 100644 src/main/kotlin/com/gitdiff/CodingTaskService.kt create mode 100644 src/main/kotlin/llm_pipeline/CodingTaskAction.kt create mode 100644 src/main/kotlin/llm_pipeline/CodingTaskGenerator.kt diff --git a/src/main/kotlin/com/gitdiff/CodingTaskService.kt b/src/main/kotlin/com/gitdiff/CodingTaskService.kt new file mode 100644 index 0000000..33c224f --- /dev/null +++ b/src/main/kotlin/com/gitdiff/CodingTaskService.kt @@ -0,0 +1,45 @@ +package com.gitdiff + +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.service +import com.intellij.openapi.project.Project + +/** + * Project-level service that surfaces the CodingTasks associated with the Guide. + * + * This service intentionally delegates to GuideService to avoid creating a + * separate CodingTask instance and to ensure persistence across the project + * lifecycle. The Guide remains the single source of truth, and we update the + * coding tasks by copying the Guide with new CodingTasks when needed. + */ +@Service(Service.Level.PROJECT) +class CodingTaskService(private val project: Project) { + + private val guideService: GuideService = GuideService.getInstance(project) + + fun getCodingTasks(): CodingTaskList = guideService.getGuide().codingTasks + + fun setCodingTasks(newCodingTasks: CodingTaskList) { + val current = guideService.getGuide() + val updated = current.copy(codingTasks = newCodingTasks) + guideService.setGuide(updated) + } + + // Convenience accessors + fun getTasks(): List = getCodingTasks().tasks + fun getFirstTask(): CodingTask? = getCodingTasks().tasks.firstOrNull() + + /** + * Check if coding tasks have been generated + */ + fun hasCodingTasksContent(): Boolean = getCodingTasks().tasks.isNotEmpty() + + /** + * Get coding tasks count + */ + fun getCodingTasksCount(): Int = getCodingTasks().tasks.size + + companion object { + fun getInstance(project: Project): CodingTaskService = project.service() + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/gitdiff/Models.kt b/src/main/kotlin/com/gitdiff/Models.kt index 5723fe1..08386c3 100644 --- a/src/main/kotlin/com/gitdiff/Models.kt +++ b/src/main/kotlin/com/gitdiff/Models.kt @@ -53,7 +53,8 @@ data class Guide( val commitDiff: CommitDiff, var content: String, val chat: MutableList, - val quiz : Quiz + val quiz : Quiz, + val codingTasks: CodingTaskList = CodingTaskList(emptyList()) ) data class Quiz( @@ -70,3 +71,14 @@ data class QuizOption( val label: String, val isCorrect: Boolean ) + +data class CodingTask( + val id: Int, + val title: String, + val languageId: String, // e.g., "kotlin", "java", "python" + val initialCode: String +) + +data class CodingTaskList( + val tasks: List +) diff --git a/src/main/kotlin/com/quiz/cards/TaskCard.kt b/src/main/kotlin/com/quiz/cards/TaskCard.kt index 4686e41..7ef9a8c 100644 --- a/src/main/kotlin/com/quiz/cards/TaskCard.kt +++ b/src/main/kotlin/com/quiz/cards/TaskCard.kt @@ -30,13 +30,9 @@ import java.security.MessageDigest import javax.swing.* import kotlin.random.Random -// Если у тебя CodingTask внутри QuizBank, импортни её как ниже: -// import com.github.yaroslavmayorov.testingpluginui.quiz.data.QuizBank -// и потом используй QuizBank.CodingTask -// Или, если вынесена модель, просто замени тип ниже на твой data class. class TaskCard( private val project: Project, - private val task: QuizBank.CodingTask + private val task: com.gitdiff.CodingTask ) : JPanel() { private val roundedBorder = RoundedLineBorder(JBColor.border(), arc = JBUI.scale(8), thickness = JBUI.scale(1)) @@ -167,7 +163,7 @@ class TaskCard( // ---------- UI parts ---------- - private fun buildHeader(t: QuizBank.CodingTask): JComponent { + private fun buildHeader(t: com.gitdiff.CodingTask): JComponent { val pnl = JPanel(GridBagLayout()).apply { isOpaque = false } val c = GridBagConstraints().apply { gridy = 0 diff --git a/src/main/kotlin/com/quiz/pages/QuizPage.kt b/src/main/kotlin/com/quiz/pages/QuizPage.kt index add1e14..3bb47e5 100644 --- a/src/main/kotlin/com/quiz/pages/QuizPage.kt +++ b/src/main/kotlin/com/quiz/pages/QuizPage.kt @@ -18,6 +18,7 @@ import javax.swing.ScrollPaneConstants import java.awt.Cursor import javax.swing.JButton import com.gitdiff.QuizService +import com.gitdiff.CodingTaskService class QuizPage( project: Project, @@ -34,6 +35,7 @@ class QuizPage( private val backBtn: JButton private val themeChildren = mutableListOf() private val quizService = QuizService.getInstance(project) + private val codingTaskService = CodingTaskService.getInstance(project) init { border = null @@ -72,8 +74,35 @@ class QuizPage( } content.add(header) - // задачи - QuizBank.codingTasks.forEach { content.add(TaskCard(project, it)) } + // Get coding tasks from service + val codingTasks = codingTaskService.getTasks() + + if (codingTasks.isEmpty()) { + // No coding tasks generated yet, use fallback static tasks or show message + val noCodingTasksLabel = JLabel("
" + + "

No Coding Tasks Available

" + + "

Coding tasks will be generated from the guide content. Please generate a guide first.

" + + "
").apply { + font = JBFont.regular() + foreground = JBColor.gray + border = JBUI.Borders.empty(20) + } + content.add(noCodingTasksLabel) + + // Fallback to static tasks for now + QuizBank.codingTasks.forEach { task -> + val convertedTask = com.gitdiff.CodingTask( + id = task.id, + title = task.title, + languageId = task.languageId, + initialCode = task.initialCode + ) + content.add(TaskCard(project, convertedTask)) + } + } else { + // Use generated coding tasks + codingTasks.forEach { content.add(TaskCard(project, it)) } + } } private fun loadQuizQuestions(questions: List) { diff --git a/src/main/kotlin/llm_pipeline/CodingTaskAction.kt b/src/main/kotlin/llm_pipeline/CodingTaskAction.kt new file mode 100644 index 0000000..c2d460a --- /dev/null +++ b/src/main/kotlin/llm_pipeline/CodingTaskAction.kt @@ -0,0 +1,154 @@ +package llm_pipeline + +import com.gitdiff.CodingTaskList +import com.intellij.openapi.actionSystem.ActionUpdateThread +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.actionSystem.CommonDataKeys +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.openapi.progress.ProgressManager +import com.intellij.openapi.progress.Task +import com.intellij.openapi.ui.DialogWrapper +import com.intellij.openapi.ui.Messages +import java.awt.BorderLayout +import java.awt.Dimension +import javax.swing.* + +/** + * Action to generate coding tasks based on guide content using LLM analysis + */ +class CodingTaskAction : AnAction("Generate Coding Tasks") { + + override fun actionPerformed(e: AnActionEvent) { + val project = e.project ?: return + + ProgressManager.getInstance().run(object : Task.Backgroundable(project, "Generating Coding Tasks", false) { + override fun run(indicator: ProgressIndicator) { + try { + indicator.text = "Checking for existing guide..." + + // Check if guide is already available in GuideService + val guideService = com.gitdiff.GuideService.getInstance(project) + val guide = guideService.getGuide() + + if (guide.content.isBlank()) { + indicator.text = "Generating guide for coding task content..." + + // Generate the guide first if not available + val guideSuccess = SemanticGuideGenerator.generateComprehensiveGuide(project) + + if (guideSuccess == null) { + ApplicationManager.getApplication().invokeLater { + Messages.showErrorDialog(project, "Failed to generate guide. Please ensure you have a valid Git repository with recent commits.", "Coding Task Generation Error") + } + return + } + } + + indicator.text = "Creating practical coding tasks..." + + // Generate coding tasks based on the guide content stored in service + val codingTaskSuccess = CodingTaskGenerator.generateCodingTasksFromGuide(project) + + if (!codingTaskSuccess) { + ApplicationManager.getApplication().invokeLater { + Messages.showErrorDialog(project, "Failed to generate coding tasks from guide content.", "Coding Task Generation Error") + } + return + } + + ApplicationManager.getApplication().invokeLater { + showCodingTaskDialog(project) + } + + } catch (e: Exception) { + ApplicationManager.getApplication().invokeLater { + Messages.showErrorDialog(project, "Failed to generate coding tasks: ${e.message}", "Coding Task Generation Error") + } + } + } + }) + } + + private fun showCodingTaskDialog(project: com.intellij.openapi.project.Project) { + // Get coding tasks from CodingTaskService + val codingTaskService = com.gitdiff.CodingTaskService.getInstance(project) + val codingTasks = codingTaskService.getCodingTasks() + + // Format coding tasks for display + val formattedTasks = formatCodingTasksForDisplay(codingTasks) + + val dialog = CodingTaskDialog(formattedTasks) + dialog.show() + } + + /** + * Format the structured coding tasks for display in the dialog + */ + private fun formatCodingTasksForDisplay(codingTasks: CodingTaskList): String { + if (codingTasks.tasks.isEmpty()) { + return "No coding tasks available. Please generate a guide first." + } + + return buildString { + appendLine("# Practical Coding Tasks") + appendLine() + appendLine("Based on the code changes and concepts in your guide, here are practical programming exercises:") + appendLine() + + codingTasks.tasks.forEachIndexed { index, task -> + appendLine("## Task ${task.id}: ${task.title}") + appendLine() + appendLine("**Language:** ${task.languageId}") + appendLine() + appendLine("```${task.languageId}") + appendLine(task.initialCode) + appendLine("```") + appendLine() + + if (index < codingTasks.tasks.size - 1) { + appendLine("---") + appendLine() + } + } + } + } + + override fun getActionUpdateThread(): ActionUpdateThread { + return ActionUpdateThread.BGT + } +} + +/** + * Dialog to display generated coding tasks + */ +private class CodingTaskDialog(private val content: String) : DialogWrapper(true) { + + init { + title = "Generated Coding Tasks" + setOKButtonText("Close") + setCancelButtonText(null) + init() + } + + override fun createCenterPanel(): JComponent { + val textArea = JTextArea(content).apply { + isEditable = false + font = font.deriveFont(12f) + lineWrap = true + wrapStyleWord = true + background = UIManager.getColor("Panel.background") + } + + val scrollPane = JScrollPane(textArea).apply { + preferredSize = Dimension(800, 600) + verticalScrollBarPolicy = JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED + horizontalScrollBarPolicy = JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED + } + + return JPanel(BorderLayout()).apply { + add(scrollPane, BorderLayout.CENTER) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/llm_pipeline/CodingTaskGenerator.kt b/src/main/kotlin/llm_pipeline/CodingTaskGenerator.kt new file mode 100644 index 0000000..1b0f32c --- /dev/null +++ b/src/main/kotlin/llm_pipeline/CodingTaskGenerator.kt @@ -0,0 +1,225 @@ +package llm_pipeline + +import com.config.GlobalConfig +import com.gitdiff.* +import com.intellij.openapi.project.Project +import org.json.JSONObject +import java.net.URI +import java.net.http.HttpClient +import java.net.http.HttpRequest + +/** + * Generates coding tasks based on guide content using LLM analysis. + * Creates practical programming exercises related to the code changes in the guide. + */ +object CodingTaskGenerator { + + /** + * Generate coding tasks based on the guide content stored in GuideService + * Returns true if successful, false if failed. Stores result in CodingTaskService. + */ + fun generateCodingTasksFromGuide( + project: Project, + modelId: String = GlobalConfig.model_name, + maxTokens: Int = 2000 + ): Boolean { + // Get guide content from GuideService + val guideService = GuideService.getInstance(project) + val guide = guideService.getGuide() + val guideContent = guide.content + + if (guideContent.isBlank()) { + return false + } + + val prompt = buildCodingTaskPrompt(guideContent) + return try { + val raw = callLLMAndSave(project, prompt, "coding_tasks", modelId, maxTokens) + val outer = JSONObject(raw) + if (outer.has("error")) { + false + } else { + val content = outer + .getJSONArray("choices") + .getJSONObject(0) + .getJSONObject("message") + .getString("content") + val tasksContent = stripMarkdownFences(content) + + // Parse and store tasks in CodingTaskService + storeCodingTasksInService(project, tasksContent) + true + } + } catch (t: Throwable) { + false + } + } + + /** + * Store the generated coding tasks in CodingTaskService for later access + */ + private fun storeCodingTasksInService(project: Project, tasksContent: String) { + val codingTaskService = CodingTaskService.getInstance(project) + + // Parse the coding tasks content to extract structured tasks + val tasks = parseCodingTasksContent(tasksContent) + + val codingTaskList = CodingTaskList(tasks = tasks) + codingTaskService.setCodingTasks(codingTaskList) + } + + /** + * Parse generated coding tasks content into structured CodingTask objects + */ + private fun parseCodingTasksContent(content: String): List { + val tasks = mutableListOf() + val lines = content.lines() + + var currentTask: CodingTask? = null + var currentCode = StringBuilder() + var taskId = 1 + var inCodeBlock = false + var currentLanguage = "kotlin" + + for (line in lines) { + when { + line.startsWith("### Task ") -> { + // Save previous task if exists + currentTask?.let { task -> + tasks.add(task.copy(initialCode = currentCode.toString().trim())) + } + + // Start new task + val title = line.removePrefix("### Task $taskId: ").trim() + currentTask = CodingTask( + id = taskId, + title = title, + languageId = currentLanguage, + initialCode = "" + ) + currentCode.clear() + taskId++ + } + line.startsWith("**Language:**") -> { + currentLanguage = line.removePrefix("**Language:**").trim().lowercase() + currentTask = currentTask?.copy(languageId = currentLanguage) + } + line.startsWith("```") -> { + inCodeBlock = !inCodeBlock + if (line.length > 3) { + // Extract language from code fence + val lang = line.removePrefix("```").trim() + if (lang.isNotEmpty()) { + currentLanguage = lang.lowercase() + currentTask = currentTask?.copy(languageId = currentLanguage) + } + } + } + inCodeBlock -> { + currentCode.appendLine(line) + } + } + } + + // Add the last task + currentTask?.let { task -> + tasks.add(task.copy(initialCode = currentCode.toString().trim())) + } + + return tasks + } + + /** + * Build a prompt that generates practical coding tasks based on the guide content + */ + private fun buildCodingTaskPrompt(guideContent: String): String { + return buildString { + appendLine("Based on the comprehensive code guide provided below, create exactly 3 practical coding tasks.") + appendLine() + appendLine("CODING TASK REQUIREMENTS:") + appendLine("- Create hands-on programming exercises that relate to the code changes shown in the guide") + appendLine("- Tasks should help developers practice the concepts, patterns, and techniques discussed") + appendLine("- Include TODO comments and placeholders for students to fill in") + appendLine("- Focus on algorithmic thinking, design patterns, and practical implementation") + appendLine("- Use languages relevant to the codebase (prefer Kotlin, but Java/Python acceptable)") + appendLine("- Make tasks progressively challenging") + appendLine() + appendLine("REQUIRED FORMAT:") + appendLine("### Task 1: [Clear, descriptive title]") + appendLine("**Language:** [programming language]") + appendLine("**Description:** [Brief description of what to implement]") + appendLine("```[language]") + appendLine("// TODO: [Specific instruction]") + appendLine("// Implementation code with TODO placeholders") + appendLine("```") + appendLine() + appendLine("### Task 2: [Clear, descriptive title]") + appendLine("[... same format ...]") + appendLine() + appendLine("### Task 3: [Clear, descriptive title]") + appendLine("[... same format ...]") + appendLine() + appendLine("FOCUS AREAS:") + appendLine("- Data structures and algorithms from the codebase") + appendLine("- Design patterns and architectural concepts") + appendLine("- Code organization and best practices") + appendLine("- Problem-solving techniques demonstrated") + appendLine("- Practical implementation challenges") + appendLine() + appendLine("SEMANTIC HUNKS AND GUIDE CONTENT TO ANALYZE:") + appendLine("=".repeat(50)) + appendLine(guideContent) + appendLine("=".repeat(50)) + appendLine() + appendLine("Generate the coding tasks now using the EXACT format specified above:") + } + } + + /** + * Call LLM using the same approach as other generators + */ + private fun callLLMAndSave( + project: Project, + prompt: String, + filenameBase: String, + modelId: String = GlobalConfig.model_name, + maxTokens: Int = 2000 + ): String { + val quoted = JSONObject.quote(prompt) + val system = "You are an expert software engineering instructor that creates practical coding exercises based on code changes and software guides. Create educational programming tasks that help developers practice the concepts, patterns, and techniques shown in real codebases." + val systemQuoted = JSONObject.quote(system) + val requestBody = """ + { + "model": "$modelId", + "messages": [ + {"role": "system", "content": $systemQuoted}, + {"role": "user", "content": $quoted} + ], + "max_tokens": $maxTokens + } + """.trimIndent() + + val apiKey = com.settings.OpenRouterSettings.getInstance().apiKey ?: throw IllegalStateException("OPENROUTER_API_KEY not set") + val client = HttpClient.newHttpClient() + val request = HttpRequest.newBuilder() + .uri(URI.create("https://openrouter.ai/api/v1/chat/completions")) + .header("Authorization", "Bearer $apiKey") + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(requestBody)) + .build() + + val response = client.send(request, java.net.http.HttpResponse.BodyHandlers.ofString()) + return response.body() + } + + /** + * Remove markdown code fences from the generated content + */ + private fun stripMarkdownFences(content: String): String { + return content + .replace("```markdown\n", "") + .replace("```\n", "") + .replace("```", "") + .trim() + } +} \ No newline at end of file diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 124aa7e..d9dc788 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -85,6 +85,14 @@ description="Generate a concept-focused quiz based on code changes"> + + + + From 27db7b155991438c8ae5d78c5066f6e34f2a9c3b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 18 Sep 2025 01:11:43 +0000 Subject: [PATCH 3/6] Complete coding task generation integration with improved UI messaging Co-authored-by: Anton15K <54205422+Anton15K@users.noreply.github.com> --- src/main/kotlin/com/quiz/pages/QuizPage.kt | 42 +++++++++++++++++----- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/com/quiz/pages/QuizPage.kt b/src/main/kotlin/com/quiz/pages/QuizPage.kt index 3bb47e5..348b795 100644 --- a/src/main/kotlin/com/quiz/pages/QuizPage.kt +++ b/src/main/kotlin/com/quiz/pages/QuizPage.kt @@ -78,18 +78,42 @@ class QuizPage( val codingTasks = codingTaskService.getTasks() if (codingTasks.isEmpty()) { - // No coding tasks generated yet, use fallback static tasks or show message - val noCodingTasksLabel = JLabel("
" + - "

No Coding Tasks Available

" + - "

Coding tasks will be generated from the guide content. Please generate a guide first.

" + - "
").apply { - font = JBFont.regular() + // Check if we have guide content to generate coding tasks + val guideService = com.gitdiff.GuideService.getInstance(project) + val guide = guideService.getGuide() + + if (guide.content.isNotBlank()) { + // Show generating message and try to generate coding tasks in background + val generatingLabel = JLabel("
" + + "

Generating Coding Tasks...

" + + "

Creating practical programming exercises based on your guide content.

" + + "
").apply { + font = JBFont.regular() + foreground = JBColor.gray + border = JBUI.Borders.empty(20) + } + content.add(generatingLabel) + } else { + // No guide content available + val noCodingTasksLabel = JLabel("
" + + "

No Coding Tasks Available

" + + "

Coding tasks will be generated from the guide content. Please generate a guide first using the Tools menu.

" + + "
").apply { + font = JBFont.regular() + foreground = JBColor.gray + border = JBUI.Borders.empty(20) + } + content.add(noCodingTasksLabel) + } + + // Fallback to static tasks for now (as examples) + val fallbackHeader = JLabel("Example Tasks (Static)").apply { + font = JBFont.label().asBold() + border = JBUI.Borders.emptyTop(16) foreground = JBColor.gray - border = JBUI.Borders.empty(20) } - content.add(noCodingTasksLabel) + content.add(fallbackHeader) - // Fallback to static tasks for now QuizBank.codingTasks.forEach { task -> val convertedTask = com.gitdiff.CodingTask( id = task.id, From d0c3a5c2327e4924b5551865ee67a3724a1de819 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 18 Sep 2025 01:20:05 +0000 Subject: [PATCH 4/6] Integrate coding task generation with 'Check Your Knowledge' button and background threading Co-authored-by: Anton15K <54205422+Anton15K@users.noreply.github.com> --- src/main/kotlin/com/quiz/pages/ChatPage.kt | 24 ++++++--- src/main/kotlin/com/quiz/pages/QuizPage.kt | 60 ++++++++++++++++++---- src/main/resources/META-INF/plugin.xml | 8 --- 3 files changed, 66 insertions(+), 26 deletions(-) diff --git a/src/main/kotlin/com/quiz/pages/ChatPage.kt b/src/main/kotlin/com/quiz/pages/ChatPage.kt index cfed901..ceecf6f 100644 --- a/src/main/kotlin/com/quiz/pages/ChatPage.kt +++ b/src/main/kotlin/com/quiz/pages/ChatPage.kt @@ -183,7 +183,7 @@ class ChatPage( }.start() } - // Method to generate quiz when triggered from outside + // Method to generate quiz and coding tasks when triggered from outside fun generateQuiz() { // Check if guide exists first val guideService = com.gitdiff.GuideService.getInstance(project) @@ -197,24 +197,34 @@ class ChatPage( return } - // Generate quiz in background thread to avoid blocking UI + // Generate quiz and coding tasks in background thread to avoid blocking UI Thread { try { + // Generate both quiz and coding tasks in parallel for better performance val quizSuccess = QuizGenerator.generateQuizFromGuide(project) + val codingTaskSuccess = llm_pipeline.CodingTaskGenerator.generateCodingTasksFromGuide(project) SwingUtilities.invokeLater { - if (quizSuccess) { - // Quiz generated successfully, refresh quiz page and proceed + if (quizSuccess || codingTaskSuccess) { + // At least one generation was successful, refresh quiz page and proceed quizPage?.refreshQuizContent() onGoQuiz() + + // Log results for debugging + if (!quizSuccess) { + println("Quiz generation failed, but coding tasks generated successfully") + } + if (!codingTaskSuccess) { + println("Coding task generation failed, but quiz generated successfully") + } } else { - // Handle quiz generation failure - println("Failed to generate quiz from guide") + // Handle both generation failures + println("Failed to generate both quiz and coding tasks from guide") } } } catch (e: Exception) { SwingUtilities.invokeLater { - println("Error generating quiz: ${e.message}") + println("Error generating quiz and coding tasks: ${e.message}") } } }.start() diff --git a/src/main/kotlin/com/quiz/pages/QuizPage.kt b/src/main/kotlin/com/quiz/pages/QuizPage.kt index 348b795..9da2392 100644 --- a/src/main/kotlin/com/quiz/pages/QuizPage.kt +++ b/src/main/kotlin/com/quiz/pages/QuizPage.kt @@ -74,6 +74,11 @@ class QuizPage( } content.add(header) + // Load coding tasks + loadCodingTasks() + } + + private fun loadCodingTasks() { // Get coding tasks from service val codingTasks = codingTaskService.getTasks() @@ -178,23 +183,25 @@ class QuizPage( } fun refreshQuizContent() { - // Remove existing quiz cards but keep back button and fixed content + // Remove existing quiz cards and coding task cards but keep back button and fixed content val componentsToKeep = mutableListOf() for (i in 0 until content.componentCount) { val component = content.getComponent(i) - // Keep back button and headers, but remove quiz cards and no-quiz message - if (component !is QuizCard && !isNoQuizMessage(component)) { + // Keep back button and headers, but remove quiz cards, task cards, and status messages + if (component !is QuizCard && component !is TaskCard && + !isNoQuizMessage(component) && !isNoCodingTaskMessage(component) && + !isGeneratingMessage(component) && !isFallbackTaskHeader(component)) { componentsToKeep.add(component) } } content.removeAll() - // Re-add kept components (back button, headers, tasks) + // Re-add kept components (back button and main headers) componentsToKeep.forEach { content.add(it) } - // Clear theme children of removed quiz cards - themeChildren.removeAll { it is QuizCard } + // Clear theme children of removed cards + themeChildren.removeAll { it is QuizCard || it is TaskCard } // Check and reload quiz content val questions = quizService.getQuestions() @@ -210,11 +217,20 @@ class QuizPage( afterQuizComponents.forEach { content.remove(it) } // Add quiz questions loadQuizQuestions(questions) - // Re-add the practical tasks section + // Re-add the practical tasks section header afterQuizComponents.forEach { content.add(it) } + // Load coding tasks after the header + loadCodingTasks() } else { // Fallback: just add questions at the end loadQuizQuestions(questions) + // Add practical tasks header and load coding tasks + val header = JLabel("Practical tasks").apply { + font = JBFont.label().asBold() + border = JBUI.Borders.emptyTop(16) + } + content.add(header) + loadCodingTasks() } } else { // Show no quiz message @@ -231,13 +247,22 @@ class QuizPage( component is JLabel && component.text.contains("Practical tasks") } if (headerIndex >= 0) { - content.remove(componentsToKeep[headerIndex]) + // Remove components from header onwards to re-add them after no-quiz message + val afterQuizComponents = componentsToKeep.drop(headerIndex) + afterQuizComponents.forEach { content.remove(it) } content.add(noQuizLabel) - content.add(componentsToKeep[headerIndex]) - // Re-add remaining components - componentsToKeep.drop(headerIndex + 1).forEach { content.add(it) } + afterQuizComponents.forEach { content.add(it) } + // Load coding tasks after the header + loadCodingTasks() } else { content.add(noQuizLabel) + // Add practical tasks header and load coding tasks + val header = JLabel("Practical tasks").apply { + font = JBFont.label().asBold() + border = JBUI.Borders.emptyTop(16) + } + content.add(header) + loadCodingTasks() } } @@ -248,4 +273,17 @@ class QuizPage( private fun isNoQuizMessage(component: java.awt.Component): Boolean { return component is JLabel && component.text.contains("No Quiz Available") } + + private fun isNoCodingTaskMessage(component: java.awt.Component): Boolean { + return component is JLabel && (component.text.contains("No Coding Tasks Available") || + component.text.contains("Coding tasks will be generated")) + } + + private fun isGeneratingMessage(component: java.awt.Component): Boolean { + return component is JLabel && component.text.contains("Generating Coding Tasks") + } + + private fun isFallbackTaskHeader(component: java.awt.Component): Boolean { + return component is JLabel && component.text.contains("Example Tasks (Static)") + } } diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index d9dc788..124aa7e 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -85,14 +85,6 @@ description="Generate a concept-focused quiz based on code changes"> - - - - From a84fbf6a4c33593dba119524460f37824c355805 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 18 Sep 2025 01:45:09 +0000 Subject: [PATCH 5/6] Improve coding task generation to include complete function templates with clear input/output Co-authored-by: Anton15K <54205422+Anton15K@users.noreply.github.com> --- .../llm_pipeline/CodingTaskGenerator.kt | 106 ++++++++++++++++-- 1 file changed, 96 insertions(+), 10 deletions(-) diff --git a/src/main/kotlin/llm_pipeline/CodingTaskGenerator.kt b/src/main/kotlin/llm_pipeline/CodingTaskGenerator.kt index 1b0f32c..86c7ee3 100644 --- a/src/main/kotlin/llm_pipeline/CodingTaskGenerator.kt +++ b/src/main/kotlin/llm_pipeline/CodingTaskGenerator.kt @@ -86,7 +86,14 @@ object CodingTaskGenerator { line.startsWith("### Task ") -> { // Save previous task if exists currentTask?.let { task -> - tasks.add(task.copy(initialCode = currentCode.toString().trim())) + val finalCode = currentCode.toString().trim() + // Ensure we have some code content, provide fallback if empty + val codeToUse = if (finalCode.isBlank()) { + generateFallbackCode(task.title, task.languageId) + } else { + finalCode + } + tasks.add(task.copy(initialCode = codeToUse)) } // Start new task @@ -123,12 +130,64 @@ object CodingTaskGenerator { // Add the last task currentTask?.let { task -> - tasks.add(task.copy(initialCode = currentCode.toString().trim())) + val finalCode = currentCode.toString().trim() + // Ensure we have some code content, provide fallback if empty + val codeToUse = if (finalCode.isBlank()) { + generateFallbackCode(task.title, task.languageId) + } else { + finalCode + } + tasks.add(task.copy(initialCode = codeToUse)) } return tasks } + /** + * Generate a fallback code template if LLM doesn't provide proper code + */ + private fun generateFallbackCode(title: String, language: String): String { + return when (language.lowercase()) { + "kotlin" -> """ + // TODO: Implement $title + // Input: [describe your input parameters here] + // Output: [describe expected return value here] + + fun solve(): Unit { + // TODO: Add your function parameters + // TODO: Implement the solution for: $title + // TODO: Return appropriate value + } + """.trimIndent() + + "java" -> """ + // TODO: Implement $title + // Input: [describe your input parameters here] + // Output: [describe expected return value here] + + public class Solution { + public void solve() { + // TODO: Add your function parameters + // TODO: Implement the solution for: $title + // TODO: Return appropriate value + } + } + """.trimIndent() + + else -> """ + # TODO: Implement $title + # Input: [describe your input parameters here] + # Output: [describe expected return value here] + + def solve(): + # TODO: Add your function parameters + # TODO: Implement the solution for: $title + # TODO: Return appropriate value + pass + """.trimIndent() + } + } + /** * Build a prompt that generates practical coding tasks based on the guide content */ @@ -138,8 +197,11 @@ object CodingTaskGenerator { appendLine() appendLine("CODING TASK REQUIREMENTS:") appendLine("- Create hands-on programming exercises that relate to the code changes shown in the guide") - appendLine("- Tasks should help developers practice the concepts, patterns, and techniques discussed") - appendLine("- Include TODO comments and placeholders for students to fill in") + appendLine("- Each task MUST include a complete function template with clear input/output parameters") + appendLine("- Function signatures should be complete and compilable") + appendLine("- Include meaningful parameter names that indicate expected input") + appendLine("- Add return type that clearly shows expected output") + appendLine("- Include TODO comments explaining what to implement") appendLine("- Focus on algorithmic thinking, design patterns, and practical implementation") appendLine("- Use languages relevant to the codebase (prefer Kotlin, but Java/Python acceptable)") appendLine("- Make tasks progressively challenging") @@ -147,17 +209,41 @@ object CodingTaskGenerator { appendLine("REQUIRED FORMAT:") appendLine("### Task 1: [Clear, descriptive title]") appendLine("**Language:** [programming language]") - appendLine("**Description:** [Brief description of what to implement]") + appendLine("**Description:** [Brief description of what to implement, including input/output explanation]") appendLine("```[language]") - appendLine("// TODO: [Specific instruction]") - appendLine("// Implementation code with TODO placeholders") + appendLine("// TODO: [Specific instruction about what the function should do]") + appendLine("// Input: [describe input parameters]") + appendLine("// Output: [describe expected return value]") + appendLine("") + appendLine("fun functionName(param1: Type, param2: Type): ReturnType {") + appendLine(" // TODO: [specific implementation steps]") + appendLine(" // TODO: [more specific steps]") + appendLine(" return defaultValue // TODO: replace with actual implementation") + appendLine("}") appendLine("```") appendLine() appendLine("### Task 2: [Clear, descriptive title]") - appendLine("[... same format ...]") + appendLine("[... same format with complete function template ...]") appendLine() appendLine("### Task 3: [Clear, descriptive title]") - appendLine("[... same format ...]") + appendLine("[... same format with complete function template ...]") + appendLine() + appendLine("EXAMPLE GOOD TASK FORMAT:") + appendLine("```kotlin") + appendLine("// TODO: Implement BFS to find shortest path in a grid") + appendLine("// Input: grid (2D char array), start (Point), goal (Point)") + appendLine("// Output: shortest distance as Int, or -1 if unreachable") + appendLine("") + appendLine("data class Point(val row: Int, val col: Int)") + appendLine("") + appendLine("fun findShortestPath(grid: Array, start: Point, goal: Point): Int {") + appendLine(" // TODO: Create a queue for BFS and visited set") + appendLine(" // TODO: Add start point to queue with distance 0") + appendLine(" // TODO: While queue is not empty, process neighbors") + appendLine(" // TODO: Return distance when goal is found") + appendLine(" return -1 // TODO: replace with actual implementation") + appendLine("}") + appendLine("```") appendLine() appendLine("FOCUS AREAS:") appendLine("- Data structures and algorithms from the codebase") @@ -171,7 +257,7 @@ object CodingTaskGenerator { appendLine(guideContent) appendLine("=".repeat(50)) appendLine() - appendLine("Generate the coding tasks now using the EXACT format specified above:") + appendLine("Generate the coding tasks now using the EXACT format specified above with COMPLETE function templates:") } } From bc6b104cf985f577e844aa0b18998db469ba964e Mon Sep 17 00:00:00 2001 From: Ak-15 <54205422+Anton15K@users.noreply.github.com> Date: Thu, 18 Sep 2025 10:13:35 +0300 Subject: [PATCH 6/6] Refactor QuizPage and ChatPage to pass `Project` as a parameter for `loadCodingTasks` and `refreshQuizContent`. Update default model configuration in `GlobalConfig`. Fix `setCancelButtonText` value in `CodingTaskAction`. --- src/main/kotlin/com/config/GlobalConfig.kt | 2 +- src/main/kotlin/com/quiz/pages/ChatPage.kt | 2 +- src/main/kotlin/com/quiz/pages/QuizPage.kt | 14 +++++++------- src/main/kotlin/llm_pipeline/CodingTaskAction.kt | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/kotlin/com/config/GlobalConfig.kt b/src/main/kotlin/com/config/GlobalConfig.kt index 8faa701..7918e9a 100644 --- a/src/main/kotlin/com/config/GlobalConfig.kt +++ b/src/main/kotlin/com/config/GlobalConfig.kt @@ -9,5 +9,5 @@ object GlobalConfig { // Default chat model (OpenRouter identifier) // Using a model with larger context window for better guide generation @JvmStatic - var model_name: String = "mistralai/mistral-small-3.2-24b-instruct:free" + var model_name: String = "openai/gpt-oss-20b:free" } diff --git a/src/main/kotlin/com/quiz/pages/ChatPage.kt b/src/main/kotlin/com/quiz/pages/ChatPage.kt index ceecf6f..f85f32b 100644 --- a/src/main/kotlin/com/quiz/pages/ChatPage.kt +++ b/src/main/kotlin/com/quiz/pages/ChatPage.kt @@ -207,7 +207,7 @@ class ChatPage( SwingUtilities.invokeLater { if (quizSuccess || codingTaskSuccess) { // At least one generation was successful, refresh quiz page and proceed - quizPage?.refreshQuizContent() + quizPage?.refreshQuizContent(project) onGoQuiz() // Log results for debugging diff --git a/src/main/kotlin/com/quiz/pages/QuizPage.kt b/src/main/kotlin/com/quiz/pages/QuizPage.kt index 9da2392..3532d4b 100644 --- a/src/main/kotlin/com/quiz/pages/QuizPage.kt +++ b/src/main/kotlin/com/quiz/pages/QuizPage.kt @@ -75,10 +75,10 @@ class QuizPage( content.add(header) // Load coding tasks - loadCodingTasks() + loadCodingTasks(project) } - private fun loadCodingTasks() { + private fun loadCodingTasks(project: Project) { // Get coding tasks from service val codingTasks = codingTaskService.getTasks() @@ -182,7 +182,7 @@ class QuizPage( backBtn.requestFocusInWindow() } - fun refreshQuizContent() { + fun refreshQuizContent(project: Project) { // Remove existing quiz cards and coding task cards but keep back button and fixed content val componentsToKeep = mutableListOf() for (i in 0 until content.componentCount) { @@ -220,7 +220,7 @@ class QuizPage( // Re-add the practical tasks section header afterQuizComponents.forEach { content.add(it) } // Load coding tasks after the header - loadCodingTasks() + loadCodingTasks(project ) } else { // Fallback: just add questions at the end loadQuizQuestions(questions) @@ -230,7 +230,7 @@ class QuizPage( border = JBUI.Borders.emptyTop(16) } content.add(header) - loadCodingTasks() + loadCodingTasks(project) } } else { // Show no quiz message @@ -253,7 +253,7 @@ class QuizPage( content.add(noQuizLabel) afterQuizComponents.forEach { content.add(it) } // Load coding tasks after the header - loadCodingTasks() + loadCodingTasks(project) } else { content.add(noQuizLabel) // Add practical tasks header and load coding tasks @@ -262,7 +262,7 @@ class QuizPage( border = JBUI.Borders.emptyTop(16) } content.add(header) - loadCodingTasks() + loadCodingTasks(project) } } diff --git a/src/main/kotlin/llm_pipeline/CodingTaskAction.kt b/src/main/kotlin/llm_pipeline/CodingTaskAction.kt index c2d460a..302b232 100644 --- a/src/main/kotlin/llm_pipeline/CodingTaskAction.kt +++ b/src/main/kotlin/llm_pipeline/CodingTaskAction.kt @@ -128,7 +128,7 @@ private class CodingTaskDialog(private val content: String) : DialogWrapper(true init { title = "Generated Coding Tasks" setOKButtonText("Close") - setCancelButtonText(null) + setCancelButtonText("null") init() }