mirror of
https://github.com/davidkaya/aryx.git
synced 2026-08-08 04:38:45 +02:00
feat: add turn cancellation UI and main process wiring
Adds cancel-turn IPC channel, SidecarClient.cancelTurn(), TurnCancelledError, EryxAppService.cancelSessionTurn(), finalizeCancelledTurn(), stop button in ChatPane, and cancelled run status throughout the run timeline. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { AlertCircle, ArrowUp, Bot, Circle, GitBranch, Loader2, ShieldAlert, User } from 'lucide-react';
|
||||
import { AlertCircle, ArrowUp, Bot, Circle, GitBranch, Loader2, ShieldAlert, Square, User } from 'lucide-react';
|
||||
|
||||
import { MarkdownContent } from '@renderer/components/MarkdownContent';
|
||||
import { MarkdownComposer, type MarkdownComposerHandle } from '@renderer/components/MarkdownComposer';
|
||||
@@ -34,6 +34,7 @@ interface ChatPaneProps {
|
||||
toolingSettings: WorkspaceToolingSettings;
|
||||
runtimeTools?: ReadonlyArray<RuntimeToolDefinition>;
|
||||
onSend: (content: string) => Promise<void>;
|
||||
onCancelTurn?: () => void;
|
||||
onResolveApproval?: (approvalId: string, decision: ApprovalDecision) => Promise<unknown>;
|
||||
onUpdateScratchpadConfig?: (config: {
|
||||
model: string;
|
||||
@@ -51,6 +52,7 @@ export function ChatPane({
|
||||
toolingSettings,
|
||||
runtimeTools,
|
||||
onSend,
|
||||
onCancelTurn,
|
||||
onResolveApproval,
|
||||
onUpdateScratchpadConfig,
|
||||
onUpdateSessionTooling,
|
||||
@@ -432,16 +434,25 @@ export function ChatPane({
|
||||
>
|
||||
<button
|
||||
className={`absolute bottom-2 right-2 flex size-8 items-center justify-center rounded-lg transition ${
|
||||
canSubmitInput
|
||||
? 'bg-indigo-600 text-white hover:bg-indigo-500'
|
||||
: 'bg-zinc-800 text-zinc-600'
|
||||
isSessionBusy
|
||||
? 'bg-red-600/80 text-white hover:bg-red-500'
|
||||
: canSubmitInput
|
||||
? 'bg-indigo-600 text-white hover:bg-indigo-500'
|
||||
: 'bg-zinc-800 text-zinc-600'
|
||||
}`}
|
||||
disabled={!canSubmitInput}
|
||||
onClick={() => composerRef.current?.submit()}
|
||||
disabled={!canSubmitInput && !isSessionBusy}
|
||||
onClick={() => {
|
||||
if (isSessionBusy) {
|
||||
onCancelTurn?.();
|
||||
} else {
|
||||
composerRef.current?.submit();
|
||||
}
|
||||
}}
|
||||
type="button"
|
||||
aria-label={isSessionBusy ? 'Stop generating' : 'Send message'}
|
||||
>
|
||||
{isSessionBusy ? (
|
||||
<Loader2 className="size-4 animate-spin" />
|
||||
<Square className="size-3.5" fill="currentColor" />
|
||||
) : (
|
||||
<ArrowUp className="size-4" />
|
||||
)}
|
||||
|
||||
@@ -43,6 +43,7 @@ const modeAccent: Record<OrchestrationMode, { dot: string; ring: string; text: s
|
||||
const runStatusStyles: Record<SessionRunRecord['status'], { icon: ReactNode; className: string }> = {
|
||||
running: { icon: <CircleDot className="size-3" />, className: 'text-blue-400' },
|
||||
completed: { icon: <CheckCircle2 className="size-3" />, className: 'text-emerald-400' },
|
||||
cancelled: { icon: <XCircle className="size-3" />, className: 'text-zinc-400' },
|
||||
error: { icon: <XCircle className="size-3" />, className: 'text-red-400' },
|
||||
};
|
||||
|
||||
@@ -65,6 +66,8 @@ function EventIcon({ kind, status }: { kind: RunTimelineEventRecord['kind']; sta
|
||||
return <MessageSquare className={`${base} ${status === 'running' ? 'text-blue-400 animate-pulse' : status === 'error' ? 'text-red-400' : 'text-emerald-400'}`} />;
|
||||
case 'run-completed':
|
||||
return <CheckCircle2 className={`${base} text-emerald-400`} />;
|
||||
case 'run-cancelled':
|
||||
return <XCircle className={`${base} text-zinc-400`} />;
|
||||
case 'run-failed':
|
||||
return <AlertTriangle className={`${base} text-red-400`} />;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user