Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/main/kotlin/com/config/GlobalConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
45 changes: 45 additions & 0 deletions src/main/kotlin/com/gitdiff/CodingTaskService.kt
Original file line number Diff line number Diff line change
@@ -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<CodingTask> = 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()
}
}
14 changes: 13 additions & 1 deletion src/main/kotlin/com/gitdiff/Models.kt
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ data class Guide(
val commitDiff: CommitDiff,
var content: String,
val chat: MutableList<ChatMessage>,
val quiz : Quiz
val quiz : Quiz,
val codingTasks: CodingTaskList = CodingTaskList(emptyList())
)

data class Quiz(
Expand All @@ -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<CodingTask>
)
8 changes: 2 additions & 6 deletions src/main/kotlin/com/quiz/cards/TaskCard.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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
Expand Down
26 changes: 18 additions & 8 deletions src/main/kotlin/com/quiz/pages/ChatPage.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
quizPage?.refreshQuizContent()
if (quizSuccess || codingTaskSuccess) {
// At least one generation was successful, refresh quiz page and proceed
quizPage?.refreshQuizContent(project)
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()
Expand Down
119 changes: 105 additions & 14 deletions src/main/kotlin/com/quiz/pages/QuizPage.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -34,6 +35,7 @@ class QuizPage(
private val backBtn: JButton
private val themeChildren = mutableListOf<ThemeAware>()
private val quizService = QuizService.getInstance(project)
private val codingTaskService = CodingTaskService.getInstance(project)

init {
border = null
Expand Down Expand Up @@ -72,8 +74,64 @@ class QuizPage(
}
content.add(header)

// задачи
QuizBank.codingTasks.forEach { content.add(TaskCard(project, it)) }
// Load coding tasks
loadCodingTasks(project)
}

private fun loadCodingTasks(project: Project) {
// Get coding tasks from service
val codingTasks = codingTaskService.getTasks()

if (codingTasks.isEmpty()) {
// 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("<html><div style='text-align: center; padding: 20px;'>" +
"<h3>Generating Coding Tasks...</h3>" +
"<p>Creating practical programming exercises based on your guide content.</p>" +
"</div></html>").apply {
font = JBFont.regular()
foreground = JBColor.gray
border = JBUI.Borders.empty(20)
}
content.add(generatingLabel)
} else {
// No guide content available
val noCodingTasksLabel = JLabel("<html><div style='text-align: center; padding: 20px;'>" +
"<h3>No Coding Tasks Available</h3>" +
"<p>Coding tasks will be generated from the guide content. Please generate a guide first using the Tools menu.</p>" +
"</div></html>").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("<html><b>Example Tasks (Static)</b></html>").apply {
font = JBFont.label().asBold()
border = JBUI.Borders.emptyTop(16)
foreground = JBColor.gray
}
content.add(fallbackHeader)

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<com.gitdiff.QuizQuestion>) {
Expand Down Expand Up @@ -124,24 +182,26 @@ class QuizPage(
backBtn.requestFocusInWindow()
}

fun refreshQuizContent() {
// Remove existing quiz cards but keep back button and fixed content
fun refreshQuizContent(project: Project) {
// Remove existing quiz cards and coding task cards but keep back button and fixed content
val componentsToKeep = mutableListOf<java.awt.Component>()
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()
Expand All @@ -157,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(project )
} else {
// Fallback: just add questions at the end
loadQuizQuestions(questions)
// Add practical tasks header and load coding tasks
val header = JLabel("<html><b>Practical tasks</b></html>").apply {
font = JBFont.label().asBold()
border = JBUI.Borders.emptyTop(16)
}
content.add(header)
loadCodingTasks(project)
}
} else {
// Show no quiz message
Expand All @@ -178,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(project)
} else {
content.add(noQuizLabel)
// Add practical tasks header and load coding tasks
val header = JLabel("<html><b>Practical tasks</b></html>").apply {
font = JBFont.label().asBold()
border = JBUI.Borders.emptyTop(16)
}
content.add(header)
loadCodingTasks(project)
}
}

Expand All @@ -195,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)")
}
}
Loading