-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
feat(ui): show live action status on Kanban task cards #792
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from 4 commits
ccd0f3b
b8a5b50
ff9a327
2366b52
31afb6f
65ce7a9
36f664d
f0621a6
038e459
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -162,17 +162,39 @@ async def run_qa_fixer_session( | |
| # Safely extract tool input (handles None, non-dict, etc.) | ||
| inp = get_safe_tool_input(block) | ||
|
|
||
| # DEBUG: Print what keys are in inp | ||
|
|
||
| if inp: | ||
| if "file_path" in inp: | ||
| fp = inp["file_path"] | ||
| if len(fp) > 50: | ||
| fp = "..." + fp[-47:] | ||
| if len(fp) > 80: | ||
| fp = "..." + fp[-77:] | ||
| tool_input_display = fp | ||
| elif "command" in inp: | ||
| cmd = inp["command"] | ||
| if len(cmd) > 50: | ||
| cmd = cmd[:47] + "..." | ||
| if len(cmd) > 80: | ||
| cmd = cmd[:77] + "..." | ||
| tool_input_display = cmd | ||
| elif "pattern" in inp: | ||
| pat = inp["pattern"] | ||
| if len(pat) > 60: | ||
| pat = pat[:57] + "..." | ||
| tool_input_display = f"/{pat}/" | ||
| elif "path" in inp: | ||
| p = inp["path"] | ||
| if len(p) > 80: | ||
| p = "..." + p[-77:] | ||
| tool_input_display = p | ||
| elif "url" in inp: | ||
| url = inp["url"] | ||
| if len(url) > 80: | ||
| url = url[:77] + "..." | ||
| tool_input_display = url | ||
| elif "query" in inp: | ||
| q = inp["query"] | ||
| if len(q) > 60: | ||
| q = q[:57] + "..." | ||
| tool_input_display = f'"{q}"' | ||
|
Comment on lines
166
to
+195
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Coerce tool input values to strings before truncation.
π οΈ Proposed fix- if "file_path" in inp:
- fp = inp["file_path"]
+ if "file_path" in inp:
+ fp = str(inp["file_path"])
if len(fp) > 80:
fp = "..." + fp[-77:]
tool_input_display = fp
elif "command" in inp:
- cmd = inp["command"]
+ cmd = str(inp["command"])
if len(cmd) > 80:
cmd = cmd[:77] + "..."
tool_input_display = cmd
elif "pattern" in inp:
- pat = inp["pattern"]
+ pat = str(inp["pattern"])
if len(pat) > 60:
pat = pat[:57] + "..."
tool_input_display = f"/{pat}/"
elif "path" in inp:
- p = inp["path"]
+ p = str(inp["path"])
if len(p) > 80:
p = "..." + p[-77:]
tool_input_display = p
elif "url" in inp:
- url = inp["url"]
+ url = str(inp["url"])
if len(url) > 80:
url = url[:77] + "..."
tool_input_display = url
elif "query" in inp:
- q = inp["query"]
+ q = str(inp["query"])
if len(q) > 60:
q = q[:57] + "..."
tool_input_display = f'"{q}"'π€ Prompt for AI Agents |
||
|
|
||
| debug( | ||
| "qa_fixer", | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| import { useState, useEffect, useRef, useCallback, memo, useMemo } from 'react'; | ||
| import { useTranslation } from 'react-i18next'; | ||
| import { Play, Square, Clock, Zap, Target, Shield, Gauge, Palette, FileCode, Bug, Wrench, Loader2, AlertTriangle, RotateCcw, Archive, GitPullRequest, MoreVertical } from 'lucide-react'; | ||
| import { Play, Square, Clock, Zap, Target, Shield, Gauge, Palette, FileCode, Bug, Wrench, Loader2, AlertTriangle, RotateCcw, Archive, GitPullRequest, MoreVertical, FileText, Search, FolderSearch, Terminal, Pencil } from 'lucide-react'; | ||
| import { Card, CardContent } from './ui/card'; | ||
| import { Badge } from './ui/badge'; | ||
| import { Button } from './ui/button'; | ||
|
|
@@ -44,6 +44,29 @@ const CategoryIcon: Record<TaskCategory, typeof Zap> = { | |
| testing: FileCode | ||
| }; | ||
|
|
||
| // Data-driven tool styles for live action status | ||
| const TOOL_STYLES: Array<{ | ||
| patterns: string[]; | ||
| icon: typeof FileText; | ||
| color: string; | ||
| }> = [ | ||
| { patterns: ['reading'], icon: FileText, color: 'text-blue-500 bg-blue-500/10' }, | ||
| { patterns: ['searching files', 'globbing'], icon: FolderSearch, color: 'text-amber-500 bg-amber-500/10' }, | ||
| { patterns: ['searching code', 'grep'], icon: Search, color: 'text-green-500 bg-green-500/10' }, | ||
| { patterns: ['editing'], icon: Pencil, color: 'text-purple-500 bg-purple-500/10' }, | ||
| { patterns: ['writing'], icon: FileCode, color: 'text-cyan-500 bg-cyan-500/10' }, | ||
| { patterns: ['running', 'executing'], icon: Terminal, color: 'text-orange-500 bg-orange-500/10' }, | ||
|
||
| ]; | ||
|
|
||
| // Helper to detect tool type from execution message and return styling | ||
| function getToolStyleFromMessage(message: string): { icon: typeof FileText; color: string } | null { | ||
| const lowerMessage = message.toLowerCase(); | ||
| const match = TOOL_STYLES.find(style => | ||
| style.patterns.some(pattern => lowerMessage.startsWith(pattern)) | ||
| ); | ||
| return match ? { icon: match.icon, color: match.color } : null; | ||
| } | ||
|
|
||
| interface TaskCardProps { | ||
| task: Task; | ||
| onClick: () => void; | ||
|
|
@@ -61,6 +84,14 @@ function taskCardPropsAreEqual(prevProps: TaskCardProps, nextProps: TaskCardProp | |
| } | ||
|
|
||
| // Compare only the fields that affect rendering | ||
| // DEBUG: Log message changes | ||
| if (prevTask.executionProgress?.message !== nextTask.executionProgress?.message) { | ||
| console.log('[TaskCard memo] Message changed:', { | ||
| taskId: prevTask.id, | ||
| prev: prevTask.executionProgress?.message, | ||
| next: nextTask.executionProgress?.message | ||
| }); | ||
| } | ||
coderabbitai[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| const isEqual = ( | ||
| prevTask.id === nextTask.id && | ||
| prevTask.status === nextTask.status && | ||
|
|
@@ -70,6 +101,7 @@ function taskCardPropsAreEqual(prevProps: TaskCardProps, nextProps: TaskCardProp | |
| prevTask.reviewReason === nextTask.reviewReason && | ||
| prevTask.executionProgress?.phase === nextTask.executionProgress?.phase && | ||
| prevTask.executionProgress?.phaseProgress === nextTask.executionProgress?.phaseProgress && | ||
| prevTask.executionProgress?.message === nextTask.executionProgress?.message && | ||
| prevTask.subtasks.length === nextTask.subtasks.length && | ||
| prevTask.metadata?.category === nextTask.metadata?.category && | ||
| prevTask.metadata?.complexity === nextTask.metadata?.complexity && | ||
|
|
@@ -104,7 +136,7 @@ export const TaskCard = memo(function TaskCard({ task, onClick, onStatusChange } | |
| interval: null | ||
| }); | ||
|
|
||
| const isRunning = task.status === 'in_progress'; | ||
| const isRunning = task.status === 'in_progress' || task.status === 'ai_review'; | ||
| const executionPhase = task.executionProgress?.phase; | ||
| const hasActiveExecution = executionPhase && executionPhase !== 'idle' && executionPhase !== 'complete' && executionPhase !== 'failed'; | ||
|
|
||
|
|
@@ -137,6 +169,20 @@ export const TaskCard = memo(function TaskCard({ task, onClick, onStatusChange } | |
| )); | ||
| }, [task.status, onStatusChange, t]); | ||
|
|
||
| // Memoize live action status to avoid recreating on every render | ||
| const liveActionStatus = useMemo(() => { | ||
| const message = task.executionProgress?.message; | ||
| const phase = task.executionProgress?.phase; | ||
| // Don't show live status for terminal phases (complete/failed) | ||
| if (!message || phase === 'complete' || phase === 'failed') return null; | ||
| const toolStyle = getToolStyleFromMessage(message); | ||
| return { | ||
| icon: toolStyle?.icon ?? Loader2, | ||
| colorClass: toolStyle?.color ?? 'text-muted-foreground bg-muted/50', | ||
| message, | ||
| }; | ||
| }, [task.executionProgress?.message, task.executionProgress?.phase]); | ||
|
|
||
| // Memoized stuck check function to avoid recreating on every render | ||
| const performStuckCheck = useCallback(() => { | ||
| // IMPORTANT: If the execution phase is 'complete' or 'failed', the task is NOT stuck. | ||
|
|
@@ -480,6 +526,16 @@ export const TaskCard = memo(function TaskCard({ task, onClick, onStatusChange } | |
| </div> | ||
| )} | ||
|
|
||
| {/* Live action status - shows current tool activity */} | ||
| {isRunning && !isStuck && liveActionStatus && ( | ||
| <div className={cn("mt-2 flex items-start gap-1.5 rounded-md px-2 py-1.5 text-xs", liveActionStatus.colorClass)}> | ||
| <liveActionStatus.icon className="h-3.5 w-3.5 shrink-0 animate-pulse mt-0.5" /> | ||
| <span className="break-all leading-tight"> | ||
| {liveActionStatus.message} | ||
| </span> | ||
| </div> | ||
| )} | ||
|
Comment on lines
609
to
620
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor: Implementation differs from PR objective regarding truncation/tooltip. The PR objective specifies "A truncated action message with a tooltip for full text", but this implementation uses If truncation with tooltip is preferred per the original requirements: π‘ Optional: Restore truncation with tooltip- <div className={cn("mt-2 flex items-start gap-1.5 rounded-md px-2 py-1.5 text-xs", liveActionStatus.colorClass)}>
- <liveActionStatus.icon className="h-3.5 w-3.5 shrink-0 animate-pulse mt-0.5" />
- <span className="break-all leading-tight">
+ <div className={cn("mt-2 flex items-center gap-1.5 rounded-md px-2 py-1 text-[10px]", liveActionStatus.colorClass)}>
+ <liveActionStatus.icon className="h-3 w-3 shrink-0 animate-pulse" />
+ <span className="truncate" title={liveActionStatus.message}>
{liveActionStatus.message}
</span>
</div>π€ Prompt for AI Agents |
||
|
|
||
| {/* Footer */} | ||
| <div className="mt-4 flex items-center justify-between"> | ||
| <div className="flex items-center gap-1.5 text-xs text-muted-foreground"> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π§Ή Nitpick | π΅ Trivial
π§© Analysis chain
π Script executed:
Repository: AndyMik90/Auto-Claude
Length of output: 3025
π Script executed:
Repository: AndyMik90/Auto-Claude
Length of output: 1195
Align file_path truncation behavior across agent modules.
fixer.pytruncatesfile_pathto 80 characters when displaying tool inputs, whilereviewer.pyandsession.pyshowfile_pathverbatim. This inconsistency means the same tool input displays differently depending on which agent is running, creating a confusing user experience. Standardize the truncation approach across all three modules.π€ Prompt for AI Agents