mirror of
https://github.com/davidkaya/aryx.git
synced 2026-08-03 10:28:43 +02:00
refactor: improve plan mode UX with composer-integrated toggle and guidance text
- Move plan mode toggle from pills row to composer send button area - Plan toggle sits next to the send button as a mode switch icon - Send button turns emerald when plan mode is active - Add mode indicator strip below composer when plan mode is on - Replace 'Implement this plan' auto-send button with instructional guidance text that tells the user to send a follow-up message - Keep Dismiss button to clear the plan review banner Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -130,12 +130,6 @@ export function ChatPane({
|
||||
void onSend(content);
|
||||
}
|
||||
|
||||
function handleImplementPlan() {
|
||||
if (!pendingPlanReview) return;
|
||||
onDismissPlanReview?.();
|
||||
void onSend('Implement the plan.');
|
||||
}
|
||||
|
||||
function handleDismissPlan() {
|
||||
onDismissPlanReview?.();
|
||||
}
|
||||
@@ -399,7 +393,6 @@ export function ChatPane({
|
||||
<div className="mb-3">
|
||||
<PlanReviewBanner
|
||||
onDismiss={handleDismissPlan}
|
||||
onImplement={handleImplementPlan}
|
||||
planReview={pendingPlanReview}
|
||||
/>
|
||||
</div>
|
||||
@@ -426,22 +419,6 @@ export function ChatPane({
|
||||
onUpdate={onUpdateSessionApprovalSettings}
|
||||
/>
|
||||
)}
|
||||
{onSetInteractionMode && (
|
||||
<button
|
||||
aria-pressed={isPlanMode}
|
||||
className={`inline-flex items-center gap-1 rounded-lg border px-2.5 py-1 text-[11px] font-medium transition ${
|
||||
isPlanMode
|
||||
? 'border-emerald-500/40 bg-emerald-500/15 text-emerald-300 hover:bg-emerald-500/25'
|
||||
: 'border-zinc-700 text-zinc-400 hover:border-zinc-600 hover:text-zinc-300'
|
||||
}`}
|
||||
disabled={isComposerDisabled}
|
||||
onClick={() => onSetInteractionMode(isPlanMode ? 'interactive' : 'plan')}
|
||||
type="button"
|
||||
>
|
||||
<ClipboardList className="size-3" />
|
||||
Plan
|
||||
</button>
|
||||
)}
|
||||
{primaryAgent && (
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
<InlineModelPill
|
||||
@@ -521,32 +498,63 @@ export function ChatPane({
|
||||
: 'Message...'
|
||||
}
|
||||
>
|
||||
<button
|
||||
className={`absolute bottom-2 right-2 flex size-8 items-center justify-center rounded-lg transition ${
|
||||
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 && !isSessionBusy}
|
||||
onClick={() => {
|
||||
if (isSessionBusy) {
|
||||
onCancelTurn?.();
|
||||
} else {
|
||||
composerRef.current?.submit();
|
||||
}
|
||||
}}
|
||||
type="button"
|
||||
aria-label={isSessionBusy ? 'Stop generating' : 'Send message'}
|
||||
>
|
||||
{isSessionBusy ? (
|
||||
<Square className="size-3.5" fill="currentColor" />
|
||||
) : (
|
||||
<ArrowUp className="size-4" />
|
||||
<div className="absolute bottom-2 right-2 flex items-center gap-1">
|
||||
{/* Plan mode toggle */}
|
||||
{onSetInteractionMode && !isSessionBusy && (
|
||||
<button
|
||||
aria-label={isPlanMode ? 'Switch to interactive mode' : 'Switch to plan mode'}
|
||||
aria-pressed={isPlanMode}
|
||||
className={`flex size-8 items-center justify-center rounded-lg transition ${
|
||||
isPlanMode
|
||||
? 'bg-emerald-600/20 text-emerald-400 hover:bg-emerald-600/30'
|
||||
: 'text-zinc-500 hover:bg-zinc-800 hover:text-zinc-300'
|
||||
}`}
|
||||
disabled={isComposerDisabled}
|
||||
onClick={() => onSetInteractionMode(isPlanMode ? 'interactive' : 'plan')}
|
||||
type="button"
|
||||
>
|
||||
<ClipboardList className="size-3.5" />
|
||||
</button>
|
||||
)}
|
||||
</button>
|
||||
|
||||
{/* Send / Stop button */}
|
||||
<button
|
||||
className={`flex size-8 items-center justify-center rounded-lg transition ${
|
||||
isSessionBusy
|
||||
? 'bg-red-600/80 text-white hover:bg-red-500'
|
||||
: canSubmitInput
|
||||
? isPlanMode
|
||||
? 'bg-emerald-600 text-white hover:bg-emerald-500'
|
||||
: 'bg-indigo-600 text-white hover:bg-indigo-500'
|
||||
: 'bg-zinc-800 text-zinc-600'
|
||||
}`}
|
||||
disabled={!canSubmitInput && !isSessionBusy}
|
||||
onClick={() => {
|
||||
if (isSessionBusy) {
|
||||
onCancelTurn?.();
|
||||
} else {
|
||||
composerRef.current?.submit();
|
||||
}
|
||||
}}
|
||||
type="button"
|
||||
aria-label={isSessionBusy ? 'Stop generating' : isPlanMode ? 'Send as plan request' : 'Send message'}
|
||||
>
|
||||
{isSessionBusy ? (
|
||||
<Square className="size-3.5" fill="currentColor" />
|
||||
) : (
|
||||
<ArrowUp className="size-4" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</MarkdownComposer>
|
||||
{isPlanMode && !isSessionBusy && (
|
||||
<div className="flex items-center gap-1.5 px-3 pb-1.5 pt-0.5">
|
||||
<div className="size-1.5 rounded-full bg-emerald-500" />
|
||||
<span className="text-[10px] font-medium text-emerald-400/80">
|
||||
Plan mode — the agent will propose a plan instead of implementing
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,22 +1,16 @@
|
||||
import { useCallback } from 'react';
|
||||
import { ClipboardList, Play, X } from 'lucide-react';
|
||||
import { ClipboardList, X } from 'lucide-react';
|
||||
|
||||
import { MarkdownContent } from '@renderer/components/MarkdownContent';
|
||||
import type { PendingPlanReviewRecord } from '@shared/domain/planReview';
|
||||
|
||||
export function PlanReviewBanner({
|
||||
planReview,
|
||||
onImplement,
|
||||
onDismiss,
|
||||
}: {
|
||||
planReview: PendingPlanReviewRecord;
|
||||
onImplement: (planReview: PendingPlanReviewRecord) => void;
|
||||
onDismiss: (planReview: PendingPlanReviewRecord) => void;
|
||||
}) {
|
||||
const handleImplement = useCallback(() => {
|
||||
onImplement(planReview);
|
||||
}, [planReview, onImplement]);
|
||||
|
||||
const handleDismiss = useCallback(() => {
|
||||
onDismiss(planReview);
|
||||
}, [planReview, onDismiss]);
|
||||
@@ -63,26 +57,14 @@ export function PlanReviewBanner({
|
||||
<MarkdownContent content={planReview.planContent} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="mt-3 flex items-center gap-2">
|
||||
<button
|
||||
className="inline-flex items-center gap-1.5 rounded-lg bg-emerald-600 px-3.5 py-1.5 text-[12px] font-medium text-white transition hover:bg-emerald-500"
|
||||
onClick={handleImplement}
|
||||
type="button"
|
||||
>
|
||||
<Play className="size-3" />
|
||||
Implement this plan
|
||||
</button>
|
||||
<button
|
||||
className="rounded-lg border border-zinc-600 px-3.5 py-1.5 text-[12px] font-medium text-zinc-300 transition hover:border-zinc-500 hover:bg-zinc-800 hover:text-white"
|
||||
onClick={handleDismiss}
|
||||
type="button"
|
||||
>
|
||||
Dismiss
|
||||
</button>
|
||||
{/* Guidance */}
|
||||
<p className="mt-3 text-[12px] leading-relaxed text-zinc-400">
|
||||
Send a follow-up message to proceed — e.g.{' '}
|
||||
<span className="text-zinc-300">"implement the plan"</span>,{' '}
|
||||
<span className="text-zinc-300">"adjust step 3"</span>, or ask for a different approach.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user