mirror of
https://github.com/carlrobertoh/ProxyAI.git
synced 2026-05-19 16:28:46 +00:00
feat: add task summaries and refine rollback panel ui
This commit is contained in:
parent
5e65d81751
commit
5c16465c26
8 changed files with 130 additions and 99 deletions
|
|
@ -158,7 +158,7 @@ class AgentEventHandler(
|
|||
currentResponseBody = responseBody
|
||||
}
|
||||
|
||||
override fun onAgentCompleted(ctx: AgentCompletedContext) {
|
||||
override fun onAgentCompleted(agentId: String) {
|
||||
runCatching {
|
||||
project.service<AgentToolWindowContentManager>().getTabbedPane()
|
||||
.onAgentCompleted(sessionId)
|
||||
|
|
@ -171,7 +171,7 @@ class AgentEventHandler(
|
|||
.orElse(null)
|
||||
}.getOrNull()
|
||||
val resolvedAgentId = project.service<AgentService>().getAgentForSession(sessionId)?.id
|
||||
?: ctx.agentId
|
||||
?: agentId
|
||||
project.service<AgentSessionState>().updateSession(
|
||||
sessionId,
|
||||
lastAgentId = resolvedAgentId,
|
||||
|
|
|
|||
|
|
@ -93,12 +93,17 @@ class AgentRunDslPanel(
|
|||
|
||||
rowsPanel.removeAll()
|
||||
finalList.forEach { value ->
|
||||
val summary = when (value) {
|
||||
is RunEntry.TaskEntry -> formatTaskSummary(value.summary)
|
||||
else -> null
|
||||
}
|
||||
val descriptor = ToolCallDescriptorFactory.create(
|
||||
project = project,
|
||||
toolName = value.toolName,
|
||||
args = value.args ?: "",
|
||||
result = value.result,
|
||||
overrideKind = value.kind
|
||||
overrideKind = value.kind,
|
||||
summary = summary
|
||||
)
|
||||
val view = ToolCallView(descriptor)
|
||||
viewByEntryId[value.id] = view
|
||||
|
|
@ -132,4 +137,16 @@ class AgentRunDslPanel(
|
|||
fun complete(entryId: String, success: Boolean, result: Any?) {
|
||||
viewByEntryId[entryId]?.complete(success, result)
|
||||
}
|
||||
|
||||
private fun formatTaskSummary(summary: TaskSummary?): String? {
|
||||
if (summary == null) return null
|
||||
val parts = mutableListOf<String>()
|
||||
if (summary.toolCalls > 0) {
|
||||
parts.add("${summary.toolCalls} calls")
|
||||
}
|
||||
if (summary.tokens > 0) {
|
||||
parts.add("${summary.tokens} tokens")
|
||||
}
|
||||
return if (parts.isNotEmpty()) parts.joinToString(" · ") else null
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ import ee.carlrobert.codegpt.toolwindow.agent.ui.renderer.ChangeColors
|
|||
import ee.carlrobert.codegpt.toolwindow.agent.ui.renderer.lineDiffStats
|
||||
import ee.carlrobert.codegpt.ui.IconActionButton
|
||||
import kotlinx.coroutines.*
|
||||
import java.awt.Dimension
|
||||
import java.awt.FlowLayout
|
||||
import java.awt.GridLayout
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
|
@ -38,13 +38,12 @@ class RollbackPanel(
|
|||
private val sessionId: String,
|
||||
private val onRollbackComplete: () -> Unit
|
||||
) : BorderLayoutPanel() {
|
||||
|
||||
private val rollbackService = RollbackService.getInstance(project)
|
||||
private val titleLabel = JBLabel()
|
||||
private val timeLabel = JBLabel()
|
||||
private val summaryPanel = JPanel(FlowLayout(FlowLayout.RIGHT, 6, 2))
|
||||
private val changesPanel = JPanel()
|
||||
private val footerPanel = JPanel(GridLayout(1, 2, 8, 0))
|
||||
private val scrollPane = JScrollPane(changesPanel)
|
||||
private val rollbackAllLink = createRollbackAllLink()
|
||||
private val diffStatsCache = ConcurrentHashMap<String, Triple<Int, Int, Int>>()
|
||||
private val diffDataCache = ConcurrentHashMap<String, RollbackDiffData>()
|
||||
private val backgroundScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||
|
|
@ -54,10 +53,8 @@ class RollbackPanel(
|
|||
}
|
||||
|
||||
private fun setupUI() {
|
||||
val headerPanel = JPanel(FlowLayout(FlowLayout.LEFT, 6, 2)).apply {
|
||||
val headerPanel = JPanel(FlowLayout(FlowLayout.LEFT, 4, 2)).apply {
|
||||
isOpaque = false
|
||||
add(JBLabel(AllIcons.Actions.Diff))
|
||||
add(JBLabel(AllIcons.General.Add))
|
||||
add(titleLabel.apply {
|
||||
font = font.deriveFont(java.awt.Font.BOLD)
|
||||
})
|
||||
|
|
@ -66,14 +63,10 @@ class RollbackPanel(
|
|||
})
|
||||
}
|
||||
|
||||
summaryPanel.apply {
|
||||
isOpaque = false
|
||||
}
|
||||
|
||||
val topPanel = BorderLayoutPanel().apply {
|
||||
addToLeft(headerPanel)
|
||||
addToRight(summaryPanel)
|
||||
border = JBUI.Borders.empty(8)
|
||||
addToRight(rollbackAllLink)
|
||||
border = JBUI.Borders.empty(6, 0)
|
||||
}
|
||||
|
||||
changesPanel.apply {
|
||||
|
|
@ -81,18 +74,17 @@ class RollbackPanel(
|
|||
isOpaque = false
|
||||
}
|
||||
|
||||
footerPanel.apply {
|
||||
isOpaque = true
|
||||
border = JBUI.Borders.customLine(JBColor.border(), 1, 0, 0, 0)
|
||||
add(createRollbackAllButton())
|
||||
add(createKeepButton())
|
||||
scrollPane.apply {
|
||||
horizontalScrollBarPolicy = JScrollPane.HORIZONTAL_SCROLLBAR_NEVER
|
||||
verticalScrollBarPolicy = JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED
|
||||
border = null
|
||||
preferredSize = Dimension(0, 240)
|
||||
}
|
||||
|
||||
addToTop(topPanel)
|
||||
addToCenter(
|
||||
BorderLayoutPanel().apply {
|
||||
addToCenter(changesPanel)
|
||||
addToBottom(footerPanel)
|
||||
addToCenter(scrollPane)
|
||||
}
|
||||
)
|
||||
border = JBUI.Borders.compound(
|
||||
|
|
@ -110,21 +102,19 @@ class RollbackPanel(
|
|||
.sortedBy { it.path }
|
||||
isVisible = changes.isNotEmpty()
|
||||
if (changes.isEmpty()) {
|
||||
titleLabel.text = "Agent changes"
|
||||
titleLabel.text = "Changes"
|
||||
timeLabel.text = ""
|
||||
changesPanel.removeAll()
|
||||
summaryPanel.removeAll()
|
||||
footerPanel.isVisible = false
|
||||
rollbackAllLink.isVisible = false
|
||||
revalidate()
|
||||
repaint()
|
||||
return
|
||||
}
|
||||
|
||||
val timeText = snapshot?.completedAt?.let { formatTime(it) } ?: ""
|
||||
titleLabel.text = "Agent changes (${changes.size})"
|
||||
titleLabel.text = "Changes (${changes.size})"
|
||||
timeLabel.text = if (timeText.isNotBlank()) "• $timeText" else ""
|
||||
|
||||
updateSummary(changes)
|
||||
preloadDiffStats(changes)
|
||||
|
||||
changesPanel.removeAll()
|
||||
|
|
@ -132,7 +122,8 @@ class RollbackPanel(
|
|||
if (index > 0) changesPanel.add(Box.createVerticalStrut(4))
|
||||
changesPanel.add(createChangeRow(change))
|
||||
}
|
||||
footerPanel.isVisible = true
|
||||
updateScrollPaneSizing()
|
||||
rollbackAllLink.isVisible = true
|
||||
|
||||
revalidate()
|
||||
repaint()
|
||||
|
|
@ -220,6 +211,7 @@ class RollbackPanel(
|
|||
return JBLabel(display).apply {
|
||||
foreground = JBUI.CurrentTheme.Label.disabledForeground()
|
||||
font = JBUI.Fonts.smallFont()
|
||||
toolTipText = display
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -287,48 +279,29 @@ class RollbackPanel(
|
|||
return "Last run at ${formatter.format(instant)}"
|
||||
}
|
||||
|
||||
private fun updateSummary(changes: List<FileChange>) {
|
||||
val added = changes.count { it.kind == ChangeKind.ADDED }
|
||||
val deleted = changes.count { it.kind == ChangeKind.DELETED }
|
||||
val modified =
|
||||
changes.count { it.kind == ChangeKind.MODIFIED || it.kind == ChangeKind.MOVED }
|
||||
summaryPanel.removeAll()
|
||||
if (added > 0) summaryPanel.add(colorChip("+$added", ChangeColors.inserted))
|
||||
if (deleted > 0) summaryPanel.add(colorChip("-$deleted", ChangeColors.deleted))
|
||||
if (modified > 0) summaryPanel.add(colorChip("~$modified", ChangeColors.modified))
|
||||
summaryPanel.revalidate()
|
||||
summaryPanel.repaint()
|
||||
}
|
||||
|
||||
private fun colorChip(text: String, color: JBColor): JBLabel =
|
||||
JBLabel(text).apply {
|
||||
foreground = color
|
||||
font = JBUI.Fonts.smallFont()
|
||||
}
|
||||
|
||||
private fun createRollbackAllButton(): JComponent {
|
||||
return JButton("Rollback all").apply {
|
||||
isFocusable = false
|
||||
isContentAreaFilled = true
|
||||
isOpaque = true
|
||||
putClientProperty("JButton.buttonType", "roundRect")
|
||||
margin = JBUI.insets(6, 12)
|
||||
addActionListener { handleRollback() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun createKeepButton(): JComponent {
|
||||
return JButton("Keep changes").apply {
|
||||
isFocusable = false
|
||||
isContentAreaFilled = true
|
||||
isOpaque = true
|
||||
putClientProperty("JButton.buttonType", "roundRect")
|
||||
margin = JBUI.insets(6, 12)
|
||||
addActionListener {
|
||||
rollbackService.clearSnapshot(sessionId)
|
||||
refreshOperations()
|
||||
private fun updateScrollPaneSizing() {
|
||||
val maxVisibleItems = 5
|
||||
val componentList = changesPanel.components.toList()
|
||||
var visibleRows = 0
|
||||
var height = 0
|
||||
componentList.forEach { component ->
|
||||
if (visibleRows >= maxVisibleItems) return@forEach
|
||||
height += component.preferredSize.height
|
||||
if (component is BorderLayoutPanel) {
|
||||
visibleRows += 1
|
||||
}
|
||||
}
|
||||
if (height == 0) {
|
||||
scrollPane.preferredSize = Dimension(0, 0)
|
||||
return
|
||||
}
|
||||
scrollPane.preferredSize = Dimension(0, height)
|
||||
scrollPane.maximumSize = Dimension(Int.MAX_VALUE, height)
|
||||
maximumSize = Dimension(Int.MAX_VALUE, preferredSize.height)
|
||||
}
|
||||
|
||||
private fun createRollbackAllLink(): JComponent {
|
||||
return ActionLink("Rollback all changes") { handleRollback() }
|
||||
}
|
||||
|
||||
private fun rollbackFile(path: String) {
|
||||
|
|
|
|||
|
|
@ -41,5 +41,6 @@ data class ToolCallDescriptor(
|
|||
val args: Any,
|
||||
val result: Any? = null,
|
||||
val projectId: String? = null,
|
||||
val prefixColor: JBColor? = null
|
||||
val prefixColor: JBColor? = null,
|
||||
val summary: String? = null
|
||||
)
|
||||
|
|
|
|||
|
|
@ -29,7 +29,8 @@ object ToolCallDescriptorFactory {
|
|||
toolName: String,
|
||||
args: Any,
|
||||
result: Any? = null,
|
||||
overrideKind: ToolKind? = null
|
||||
overrideKind: ToolKind? = null,
|
||||
summary: String? = null
|
||||
): ToolCallDescriptor {
|
||||
val kind = overrideKind ?: detectToolKind(toolName, args)
|
||||
val projectId = project.locationHash
|
||||
|
|
@ -41,7 +42,7 @@ object ToolCallDescriptorFactory {
|
|||
ToolKind.EDIT -> createEditDescriptor(project, args, result, projectId)
|
||||
ToolKind.BASH -> createBashDescriptor(args, result, projectId)
|
||||
ToolKind.WEB -> createWebDescriptor(args, result, projectId)
|
||||
ToolKind.TASK -> createTaskDescriptor(args, result, projectId)
|
||||
ToolKind.TASK -> createTaskDescriptor(args, result, projectId, summary)
|
||||
ToolKind.LIBRARY_RESOLVE -> createLibraryResolveDescriptor(args, result, projectId)
|
||||
ToolKind.LIBRARY_DOCS -> createLibraryDocsDescriptor(args, result, projectId)
|
||||
ToolKind.ASK_QUESTION -> createAskDescriptor(args, result, projectId)
|
||||
|
|
@ -374,28 +375,12 @@ object ToolCallDescriptorFactory {
|
|||
val pattern = searchArgs?.pattern ?: ""
|
||||
val scopeOrPath = searchArgs?.path?.substringAfterLast('/') ?: (searchArgs?.scope ?: "")
|
||||
val titleMain = buildSearchDisplay(truncatePattern(pattern), scopeOrPath)
|
||||
|
||||
val badges = mutableListOf<Badge>()
|
||||
val actions = mutableListOf<ToolAction>()
|
||||
|
||||
when (result) {
|
||||
is IntelliJSearchTool.Result -> {
|
||||
badges.add(Badge("${result.totalMatches} matches", JBColor.BLUE))
|
||||
if (result.output.isNotBlank()) {
|
||||
actions.add(
|
||||
ToolAction("View Results", AllIcons.Actions.Find) { _ ->
|
||||
showTextDialog(result.output, "Search Results")
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is String -> {
|
||||
actions.add(
|
||||
ToolAction("View Results", AllIcons.Actions.Find) { _ ->
|
||||
showTextDialog(result, "Search Results")
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -442,7 +427,8 @@ object ToolCallDescriptorFactory {
|
|||
private fun createTaskDescriptor(
|
||||
args: Any,
|
||||
result: Any?,
|
||||
projectId: String?
|
||||
projectId: String?,
|
||||
summary: String? = null
|
||||
): ToolCallDescriptor {
|
||||
val description = when (args) {
|
||||
is TaskTool.Args -> args.description
|
||||
|
|
@ -463,6 +449,12 @@ object ToolCallDescriptorFactory {
|
|||
prefixColor = null
|
||||
}
|
||||
|
||||
val taskSummary = when {
|
||||
summary != null -> summary
|
||||
result is TaskTool.Result -> formatTaskSummary(result)
|
||||
else -> null
|
||||
}
|
||||
|
||||
return ToolCallDescriptor(
|
||||
kind = ToolKind.TASK,
|
||||
icon = AllIcons.Actions.RunAnything,
|
||||
|
|
@ -474,10 +466,30 @@ object ToolCallDescriptorFactory {
|
|||
args = args,
|
||||
result = result,
|
||||
projectId = projectId,
|
||||
prefixColor = prefixColor
|
||||
prefixColor = prefixColor,
|
||||
summary = taskSummary
|
||||
)
|
||||
}
|
||||
|
||||
private fun formatTaskSummary(result: TaskTool.Result): String? {
|
||||
val parts = mutableListOf<String>()
|
||||
if (result.executionTime > 0) {
|
||||
parts.add(formatDuration(result.executionTime))
|
||||
}
|
||||
if (result.totalTokens > 0) {
|
||||
parts.add("${result.totalTokens} tokens")
|
||||
}
|
||||
return if (parts.isNotEmpty()) parts.joinToString(" · ") else null
|
||||
}
|
||||
|
||||
private fun formatDuration(ms: Long): String {
|
||||
return when {
|
||||
ms < 1000 -> "${ms}ms"
|
||||
ms < 60000 -> "${ms / 1000}s"
|
||||
else -> "${ms / 60000}m ${((ms % 60000) / 1000)}s"
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSubagentColor(subagentType: String): JBColor {
|
||||
val hue = subagentType.hashCode().absoluteValue % 360
|
||||
val hueNormalized = hue.toFloat() / 360f
|
||||
|
|
|
|||
|
|
@ -120,6 +120,12 @@ private class ToolCallHeaderPanel(
|
|||
|
||||
this.fileLink = link
|
||||
leftRow.add(link)
|
||||
|
||||
descriptor.summary?.let { summary ->
|
||||
leftRow.add(JBLabel(" · $summary").withFont(JBFont.small()).apply {
|
||||
foreground = JBUI.CurrentTheme.Label.disabledForeground()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private fun addRegularContent() {
|
||||
|
|
@ -129,7 +135,12 @@ private class ToolCallHeaderPanel(
|
|||
}
|
||||
leftRow.add(content)
|
||||
|
||||
// Show IntelliJSearch parameter chips for compact rows for parity with main cards
|
||||
descriptor.summary?.let { summary ->
|
||||
leftRow.add(JBLabel(" · $summary").withFont(JBFont.small()).apply {
|
||||
foreground = JBUI.CurrentTheme.Label.disabledForeground()
|
||||
})
|
||||
}
|
||||
|
||||
addSearchParametersIfAny()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -41,12 +41,30 @@ private fun longestCommonSubsequenceLength(a: List<String>, b: List<String>): In
|
|||
val n = a.size
|
||||
val m = b.size
|
||||
if (n == 0 || m == 0) return 0
|
||||
val dp = Array(n + 1) { IntArray(m + 1) }
|
||||
for (i in 1..n) {
|
||||
val ai = a[i - 1]
|
||||
for (j in 1..m) {
|
||||
dp[i][j] = if (ai == b[j - 1]) dp[i - 1][j - 1] + 1 else maxOf(dp[i - 1][j], dp[i][j - 1])
|
||||
|
||||
val smaller = if (n < m) a else b
|
||||
val larger = if (n < m) b else a
|
||||
|
||||
val smallSize = smaller.size
|
||||
val largeSize = larger.size
|
||||
|
||||
var prev = IntArray(smallSize + 1)
|
||||
var curr = IntArray(smallSize + 1)
|
||||
|
||||
for (i in 1..largeSize) {
|
||||
val largerLine = larger[i - 1]
|
||||
val temp = prev
|
||||
prev = curr
|
||||
curr = temp
|
||||
|
||||
for (j in 1..smallSize) {
|
||||
curr[j] = if (largerLine == smaller[j - 1]) {
|
||||
prev[j - 1] + 1
|
||||
} else {
|
||||
maxOf(prev[j], curr[j - 1])
|
||||
}
|
||||
}
|
||||
}
|
||||
return dp[n][m]
|
||||
|
||||
return curr[smallSize]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,7 +87,6 @@ The user will primarily request you perform software engineering tasks. This inc
|
|||
- When doing file search, prefer to use the Task tool in order to reduce context usage.
|
||||
- You should proactively use the Task tool with specialized agents when the task at hand matches the agent's description.
|
||||
|
||||
- When WebFetch returns a message about a redirect to a different host, you should immediately make a new WebFetch request with the redirect URL provided in the response.
|
||||
- You can call multiple tools in a single response. If you intend to call multiple tools and there are no dependencies between them, make all independent tool calls in parallel. Maximize use of parallel tool calls where possible to increase efficiency. However, if some tool calls depend on previous calls to inform dependent values, do NOT call these tools in parallel and instead call them sequentially. For instance, if one operation must complete before another starts, run these operations sequentially instead. Never use placeholders or guess missing parameters in tool calls.
|
||||
- Use specialized tools instead of bash commands when possible, as this provides a better user experience. For file operations, use dedicated tools: Read for reading files instead of cat/head/tail, Edit for editing instead of sed/awk, and Write for creating files instead of cat with heredoc or echo redirection. Reserve bash tools exclusively for actual system commands and terminal operations that require shell execution. NEVER use bash echo or other command-line tools to communicate thoughts, explanations, or instructions to the user. Output all communication directly in your response text instead.
|
||||
- VERY IMPORTANT: When exploring the codebase to gather context or to answer a question that is not a needle query for a specific file/class/function, it is CRITICAL that you use the Task tool with subagent_type=Explore instead of running search commands directly.
|
||||
|
|
@ -100,7 +99,7 @@ user: What is the codebase structure?
|
|||
assistant: [Uses the Task tool with subagent_type=Explore]
|
||||
</example>
|
||||
|
||||
You can use the following tools without requiring user approval: Bash(curl:*), Bash(chmod:*), Bash(docker-compose restart:*), Bash(docker-compose down:*), Bash(docker-compose up:*), Bash(docker-compose:*), Bash(find:*), WebFetch(domain:medium.com), Bash(cp:*), Bash(mv:*), Bash(mkdir:*), Bash(touch:*), Bash(ls:*), Bash(./gradlew build:*), Bash(./gradlew:*), Bash(true), Bash(cat:*), Bash(diff:*), Bash(rm:*), WebSearch, BashOutput, KillShell
|
||||
You can use the following tools without requiring user approval: Bash(curl:*), Bash(chmod:*), Bash(docker-compose restart:*), Bash(docker-compose down:*), Bash(docker-compose up:*), Bash(docker-compose:*), Bash(find:*), Bash(cp:*), Bash(mv:*), Bash(mkdir:*), Bash(touch:*), Bash(ls:*), Bash(./gradlew build:*), Bash(./gradlew:*), Bash(true), Bash(cat:*), Bash(diff:*), Bash(rm:*), WebSearch, BashOutput, KillShell
|
||||
|
||||
Here is useful information about the environment you are running in:
|
||||
<env>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue