Skip to content

Commit 56bde59

Browse files
committed
feat(agent): refactor plan structure to use PlanList and PlanTask #331
- Rename `PlanItem` to `PlanList` and `Task` to `PlanTask` for clarity. - Move plan-related data classes to a new file `PlanList.kt`. - Update `MarkdownPlanParser` and related UI components to use the new structure. - Add `updatePlan` method to `AgentStateService` for managing plan updates.
1 parent 581e358 commit 56bde59

File tree

7 files changed

+138
-121
lines changed

7 files changed

+138
-121
lines changed

core/src/main/kotlin/cc/unitmesh/devti/observer/agent/AgentState.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ data class AgentState(
2222
/**
2323
* Logging environment variables, maybe related to [cc.unitmesh.devti.provider.context.ChatContextProvider]
2424
*/
25-
var environment: Map<String, String> = emptyMap()
25+
var environment: Map<String, String> = emptyMap(),
26+
27+
var planLists: MutableList<PlanList> = mutableListOf()
2628
)
2729

core/src/main/kotlin/cc/unitmesh/devti/observer/agent/AgentStateService.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ class AgentStateService {
5252
return state.messages
5353
}
5454

55+
fun updatePlan(items: MutableList<PlanList>) {
56+
this.state.planLists = items
57+
}
58+
5559
fun resolveIssue() {
5660
// todo resolve issue
5761
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package cc.unitmesh.devti.observer.agent
2+
3+
data class PlanList(
4+
val title: String,
5+
val planTasks: List<PlanTask>,
6+
val completed: Boolean = false
7+
)
8+
9+
/**
10+
* 表示计划项中的单个任务
11+
*
12+
* @property description 任务描述文本
13+
* @property completed 任务是否已完成
14+
*/
15+
data class PlanTask(
16+
val description: String,
17+
var completed: Boolean = false
18+
) {
19+
companion object {
20+
/**
21+
* 从任务文本创建Task对象
22+
*
23+
* @param taskText 任务文本,可能包含完成标记
24+
* @return 封装了任务描述和状态的Task对象
25+
*/
26+
fun fromText(taskText: String): PlanTask {
27+
val isCompleted = taskText.contains("") ||
28+
Regex("\\[\\s*([xX])\\s*\\]").containsMatchIn(taskText)
29+
30+
// 清理描述文本,移除完成标记
31+
val cleanedDescription = taskText
32+
.replace("", "")
33+
.replace(Regex("\\[\\s*[xX]\\s*\\]"), "[ ]")
34+
.replace(Regex("\\[\\s*\\]"), "")
35+
.trim()
36+
37+
return PlanTask(cleanedDescription, isCompleted)
38+
}
39+
}
40+
}

core/src/main/kotlin/cc/unitmesh/devti/sketch/ui/ThoughtPlanSketchProvider.kt

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
package cc.unitmesh.devti.sketch.ui
22

3+
import cc.unitmesh.devti.observer.agent.AgentStateService
34
import cc.unitmesh.devti.sketch.ui.code.CodeHighlightSketch
45
import cc.unitmesh.devti.sketch.ui.plan.MarkdownPlanParser
5-
import cc.unitmesh.devti.sketch.ui.plan.PlanItem
6+
import cc.unitmesh.devti.observer.agent.PlanList
67
import com.intellij.icons.AllIcons
78
import com.intellij.lang.Language
89
import com.intellij.openapi.actionSystem.ActionManager
@@ -49,29 +50,29 @@ class ThoughtPlanSketchProvider : LanguageSketchProvider {
4950
class PlanSketch(
5051
private val project: Project,
5152
private var content: String,
52-
private val planItems: MutableList<PlanItem>
53+
private val planLists: MutableList<PlanList>
5354
) : JBPanel<PlanSketch>(BorderLayout()), ExtensionLangSketch {
5455

5556
private val panel = JBPanel<PlanSketch>(BorderLayout())
5657
private val contentPanel = JBPanel<PlanSketch>().apply {
5758
layout = BoxLayout(this, BoxLayout.Y_AXIS)
5859
border = JBEmptyBorder(JBUI.insets(8))
5960
}
60-
61+
6162
private val actionGroup = DefaultActionGroup(createConsoleActions())
6263
private val toolbar = ActionManager.getInstance().createActionToolbar("PlanSketch", actionGroup, true).apply {
6364
targetComponent = panel
6465
}
65-
66+
6667
private val titleLabel = JLabel("Thought Plan").apply {
6768
border = JBUI.Borders.empty(0, 10)
6869
}
69-
70+
7071
private val toolbarPanel = JPanel(BorderLayout()).apply {
7172
add(titleLabel, BorderLayout.WEST)
7273
add(toolbar.component, BorderLayout.EAST)
7374
}
74-
75+
7576
private val toolbarWrapper = Wrapper(JBUI.Panels.simplePanel(toolbarPanel)).also {
7677
it.border = JBUI.Borders.customLine(UIUtil.getBoundsColor(), 1, 1, 1, 1)
7778
}
@@ -82,10 +83,10 @@ class PlanSketch(
8283
val scrollPane = JBScrollPane(contentPanel)
8384
panel.add(scrollPane, BorderLayout.CENTER)
8485
panel.add(toolbarWrapper, BorderLayout.NORTH)
85-
86+
8687
add(panel, BorderLayout.CENTER)
8788
}
88-
89+
8990
private fun createConsoleActions(): List<AnAction> {
9091
val popupAction = object : AnAction("Popup", "Show in popup window", AllIcons.Ide.External_link_arrow) {
9192
override fun displayTextInToolbar(): Boolean = true
@@ -97,7 +98,7 @@ class PlanSketch(
9798

9899
return listOf(popupAction)
99100
}
100-
101+
101102
private fun executePopup(): MouseAdapter = object : MouseAdapter() {
102103
override fun mouseClicked(e: MouseEvent?) {
103104
var popup: JBPopup? = null
@@ -131,7 +132,7 @@ class PlanSketch(
131132
}
132133

133134
private fun createPlanUI() {
134-
planItems.forEachIndexed { index, planItem ->
135+
planLists.forEachIndexed { index, planItem ->
135136
val titlePanel = JBPanel<JBPanel<*>>(FlowLayout(FlowLayout.LEFT)).apply {
136137
border = JBUI.Borders.empty()
137138
}
@@ -146,7 +147,7 @@ class PlanSketch(
146147
titlePanel.add(sectionLabel)
147148
contentPanel.add(titlePanel)
148149

149-
planItem.tasks.forEachIndexed { taskIndex, task ->
150+
planItem.planTasks.forEachIndexed { taskIndex, task ->
150151
val taskPanel = JBPanel<JBPanel<*>>(FlowLayout(FlowLayout.LEFT)).apply {
151152
border = JBUI.Borders.empty()
152153
}
@@ -162,7 +163,7 @@ class PlanSketch(
162163
contentPanel.add(taskPanel)
163164
}
164165

165-
if (index < planItems.size - 1) {
166+
if (index < planLists.size - 1) {
166167
contentPanel.add(Box.createVerticalStrut(8))
167168
}
168169
}
@@ -181,23 +182,23 @@ class PlanSketch(
181182
val newPlanItems = MarkdownPlanParser.parse(text)
182183
if (newPlanItems.isNotEmpty()) {
183184
val completionState = mutableMapOf<String, Boolean>()
184-
185+
185186
// 保存当前完成状态
186-
planItems.forEach { planItem ->
187-
planItem.tasks.forEach { task ->
187+
planLists.forEach { planItem ->
188+
planItem.planTasks.forEach { task ->
188189
completionState[task.description] = task.completed
189190
}
190191
}
191192

192193
contentPanel.removeAll()
193-
planItems.clear()
194+
planLists.clear()
194195

195196
// 应用新规划项,保留任务完成状态
196197
newPlanItems.forEach { newItem ->
197-
planItems.add(newItem)
198-
198+
planLists.add(newItem)
199+
199200
// 恢复任务完成状态
200-
newItem.tasks.forEach { task ->
201+
newItem.planTasks.forEach { task ->
201202
val savedCompletionState = completionState[task.description]
202203
if (savedCompletionState != null) {
203204
task.completed = savedCompletionState
@@ -216,6 +217,8 @@ class PlanSketch(
216217

217218
override fun onDoneStream(allText: String) {
218219
updateUi(this.content)
220+
221+
project.getService(AgentStateService::class.java).updatePlan(planLists)
219222
}
220223

221224
override fun updateLanguage(language: Language?, originLanguage: String?) {}

core/src/main/kotlin/cc/unitmesh/devti/sketch/ui/plan/MarkdownPlanParser.kt

Lines changed: 29 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package cc.unitmesh.devti.sketch.ui.plan
22

3+
import cc.unitmesh.devti.observer.agent.PlanList
4+
import cc.unitmesh.devti.observer.agent.PlanTask
35
import com.intellij.openapi.diagnostic.logger
46
import org.intellij.markdown.IElementType
57
import org.intellij.markdown.MarkdownElementTypes
@@ -35,7 +37,7 @@ object MarkdownPlanParser {
3537
* @param content markdown格式的计划文本
3638
* @return 解析得到的计划项列表,若解析失败则返回空列表
3739
*/
38-
fun parse(content: String): List<PlanItem> {
40+
fun parse(content: String): List<PlanList> {
3941
try {
4042
val flavour = GFMFlavourDescriptor()
4143
val parsedTree = MarkdownParser(flavour).parse(ROOT_ELEMENT_TYPE, content)
@@ -46,16 +48,16 @@ object MarkdownPlanParser {
4648
}
4749
}
4850

49-
private fun parsePlanItems(node: ASTNode, content: String): List<PlanItem> {
50-
val planItems = mutableListOf<PlanItem>()
51+
private fun parsePlanItems(node: ASTNode, content: String): List<PlanList> {
52+
val planLists = mutableListOf<PlanList>()
5153
val topLevelOrderedLists = findTopLevelOrderedLists(node)
5254
if (topLevelOrderedLists.isNotEmpty() && isFlatOrderedList(topLevelOrderedLists.first(), content)) {
53-
processFlatOrderedList(topLevelOrderedLists.first(), content, planItems)
55+
processFlatOrderedList(topLevelOrderedLists.first(), content, planLists)
5456
} else {
55-
processSectionedList(node, content, planItems)
57+
processSectionedList(node, content, planLists)
5658
}
5759

58-
return planItems
60+
return planLists
5961
}
6062

6163
private fun findTopLevelOrderedLists(node: ASTNode): List<ASTNode> {
@@ -70,7 +72,7 @@ object MarkdownPlanParser {
7072
return orderedLists
7173
}
7274

73-
private fun processFlatOrderedList(node: ASTNode, content: String, planItems: MutableList<PlanItem>) {
75+
private fun processFlatOrderedList(node: ASTNode, content: String, planLists: MutableList<PlanList>) {
7476
node.children.forEach { listItemNode ->
7577
if (listItemNode.type == MarkdownElementTypes.LIST_ITEM) {
7678
val listItemText = listItemNode.getTextInNode(content).toString().trim()
@@ -79,15 +81,15 @@ object MarkdownPlanParser {
7981
if (titleMatch != null) {
8082
val title = titleMatch.groupValues[2].trim()
8183
val completed = listItemText.contains(CHECKMARK)
82-
planItems.add(PlanItem(title, emptyList(), completed))
84+
planLists.add(PlanList(title, emptyList(), completed))
8385
}
8486
}
8587
}
8688
}
8789

88-
private fun processSectionedList(node: ASTNode, content: String, planItems: MutableList<PlanItem>) {
90+
private fun processSectionedList(node: ASTNode, content: String, planLists: MutableList<PlanList>) {
8991
var currentSectionTitle = ""
90-
var currentSectionItems = mutableListOf<Task>()
92+
var currentSectionItems = mutableListOf<PlanTask>()
9193
var currentSectionCompleted = false
9294

9395
node.accept(object : RecursiveVisitor() {
@@ -111,11 +113,13 @@ object MarkdownPlanParser {
111113
if (titleMatch != null) {
112114
// Save previous section if exists
113115
if (currentSectionTitle.isNotEmpty()) {
114-
planItems.add(PlanItem(
115-
currentSectionTitle,
116-
currentSectionItems.toList(),
117-
currentSectionCompleted
118-
))
116+
planLists.add(
117+
PlanList(
118+
currentSectionTitle,
119+
currentSectionItems.toList(),
120+
currentSectionCompleted
121+
)
122+
)
119123
currentSectionItems = mutableListOf()
120124
}
121125

@@ -151,11 +155,13 @@ object MarkdownPlanParser {
151155

152156
// 添加最后一个章节(如果有)
153157
if (currentSectionTitle.isNotEmpty()) {
154-
planItems.add(PlanItem(
155-
currentSectionTitle,
156-
currentSectionItems.toList(),
157-
currentSectionCompleted
158-
))
158+
planLists.add(
159+
PlanList(
160+
currentSectionTitle,
161+
currentSectionItems.toList(),
162+
currentSectionCompleted
163+
)
164+
)
159165
}
160166
}
161167

@@ -175,7 +181,7 @@ object MarkdownPlanParser {
175181
return !hasNestedLists
176182
}
177183

178-
private fun processTaskItems(listNode: ASTNode, content: String, itemsList: MutableList<Task>) {
184+
private fun processTaskItems(listNode: ASTNode, content: String, itemsList: MutableList<PlanTask>) {
179185
listNode.children.forEach { taskItemNode ->
180186
if (taskItemNode.type == MarkdownElementTypes.LIST_ITEM) {
181187
val taskText = taskItemNode.getTextInNode(content).toString().trim()
@@ -202,12 +208,12 @@ object MarkdownPlanParser {
202208
"[ ] $todoText"
203209
}
204210

205-
itemsList.add(Task.fromText(formattedTask))
211+
itemsList.add(PlanTask.fromText(formattedTask))
206212
} else {
207213
// Process task text and retain the checkmark in the text (original behavior)
208214
val cleanTaskText = taskFirstLine.replace(Regex("^[\\-\\*]\\s+"), "").trim()
209215
if (cleanTaskText.isNotEmpty()) {
210-
itemsList.add(Task.fromText(cleanTaskText))
216+
itemsList.add(PlanTask.fromText(cleanTaskText))
211217
}
212218
}
213219
}
@@ -223,41 +229,3 @@ object MarkdownPlanParser {
223229
}
224230
}
225231

226-
data class PlanItem(
227-
val title: String,
228-
val tasks: List<Task>,
229-
val completed: Boolean = false
230-
)
231-
232-
/**
233-
* 表示计划项中的单个任务
234-
*
235-
* @property description 任务描述文本
236-
* @property completed 任务是否已完成
237-
*/
238-
data class Task(
239-
val description: String,
240-
var completed: Boolean = false
241-
) {
242-
companion object {
243-
/**
244-
* 从任务文本创建Task对象
245-
*
246-
* @param taskText 任务文本,可能包含完成标记
247-
* @return 封装了任务描述和状态的Task对象
248-
*/
249-
fun fromText(taskText: String): Task {
250-
val isCompleted = taskText.contains("") ||
251-
Regex("\\[\\s*([xX])\\s*\\]").containsMatchIn(taskText)
252-
253-
// 清理描述文本,移除完成标记
254-
val cleanedDescription = taskText
255-
.replace("", "")
256-
.replace(Regex("\\[\\s*[xX]\\s*\\]"), "[ ]")
257-
.replace(Regex("\\[\\s*\\]"), "")
258-
.trim()
259-
260-
return Task(cleanedDescription, isCompleted)
261-
}
262-
}
263-
}

0 commit comments

Comments
 (0)