mirror of
https://github.com/davidkaya/aryx.git
synced 2026-08-28 21:57:13 +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);
|
void onSend(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleImplementPlan() {
|
|
||||||
if (!pendingPlanReview) return;
|
|
||||||
onDismissPlanReview?.();
|
|
||||||
void onSend('Implement the plan.');
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleDismissPlan() {
|
function handleDismissPlan() {
|
||||||
onDismissPlanReview?.();
|
onDismissPlanReview?.();
|
||||||
}
|
}
|
||||||
@@ -399,7 +393,6 @@ export function ChatPane({
|
|||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<PlanReviewBanner
|
<PlanReviewBanner
|
||||||
onDismiss={handleDismissPlan}
|
onDismiss={handleDismissPlan}
|
||||||
onImplement={handleImplementPlan}
|
|
||||||
planReview={pendingPlanReview}
|
planReview={pendingPlanReview}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -426,22 +419,6 @@ export function ChatPane({
|
|||||||
onUpdate={onUpdateSessionApprovalSettings}
|
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 && (
|
{primaryAgent && (
|
||||||
<div className="ml-auto flex items-center gap-2">
|
<div className="ml-auto flex items-center gap-2">
|
||||||
<InlineModelPill
|
<InlineModelPill
|
||||||
@@ -521,32 +498,63 @@ export function ChatPane({
|
|||||||
: 'Message...'
|
: 'Message...'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<button
|
<div className="absolute bottom-2 right-2 flex items-center gap-1">
|
||||||
className={`absolute bottom-2 right-2 flex size-8 items-center justify-center rounded-lg transition ${
|
{/* Plan mode toggle */}
|
||||||
isSessionBusy
|
{onSetInteractionMode && !isSessionBusy && (
|
||||||
? 'bg-red-600/80 text-white hover:bg-red-500'
|
<button
|
||||||
: canSubmitInput
|
aria-label={isPlanMode ? 'Switch to interactive mode' : 'Switch to plan mode'}
|
||||||
? 'bg-indigo-600 text-white hover:bg-indigo-500'
|
aria-pressed={isPlanMode}
|
||||||
: 'bg-zinc-800 text-zinc-600'
|
className={`flex size-8 items-center justify-center rounded-lg transition ${
|
||||||
}`}
|
isPlanMode
|
||||||
disabled={!canSubmitInput && !isSessionBusy}
|
? 'bg-emerald-600/20 text-emerald-400 hover:bg-emerald-600/30'
|
||||||
onClick={() => {
|
: 'text-zinc-500 hover:bg-zinc-800 hover:text-zinc-300'
|
||||||
if (isSessionBusy) {
|
}`}
|
||||||
onCancelTurn?.();
|
disabled={isComposerDisabled}
|
||||||
} else {
|
onClick={() => onSetInteractionMode(isPlanMode ? 'interactive' : 'plan')}
|
||||||
composerRef.current?.submit();
|
type="button"
|
||||||
}
|
>
|
||||||
}}
|
<ClipboardList className="size-3.5" />
|
||||||
type="button"
|
</button>
|
||||||
aria-label={isSessionBusy ? 'Stop generating' : 'Send message'}
|
|
||||||
>
|
|
||||||
{isSessionBusy ? (
|
|
||||||
<Square className="size-3.5" fill="currentColor" />
|
|
||||||
) : (
|
|
||||||
<ArrowUp className="size-4" />
|
|
||||||
)}
|
)}
|
||||||
</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>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,22 +1,16 @@
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { ClipboardList, Play, X } from 'lucide-react';
|
import { ClipboardList, X } from 'lucide-react';
|
||||||
|
|
||||||
import { MarkdownContent } from '@renderer/components/MarkdownContent';
|
import { MarkdownContent } from '@renderer/components/MarkdownContent';
|
||||||
import type { PendingPlanReviewRecord } from '@shared/domain/planReview';
|
import type { PendingPlanReviewRecord } from '@shared/domain/planReview';
|
||||||
|
|
||||||
export function PlanReviewBanner({
|
export function PlanReviewBanner({
|
||||||
planReview,
|
planReview,
|
||||||
onImplement,
|
|
||||||
onDismiss,
|
onDismiss,
|
||||||
}: {
|
}: {
|
||||||
planReview: PendingPlanReviewRecord;
|
planReview: PendingPlanReviewRecord;
|
||||||
onImplement: (planReview: PendingPlanReviewRecord) => void;
|
|
||||||
onDismiss: (planReview: PendingPlanReviewRecord) => void;
|
onDismiss: (planReview: PendingPlanReviewRecord) => void;
|
||||||
}) {
|
}) {
|
||||||
const handleImplement = useCallback(() => {
|
|
||||||
onImplement(planReview);
|
|
||||||
}, [planReview, onImplement]);
|
|
||||||
|
|
||||||
const handleDismiss = useCallback(() => {
|
const handleDismiss = useCallback(() => {
|
||||||
onDismiss(planReview);
|
onDismiss(planReview);
|
||||||
}, [planReview, onDismiss]);
|
}, [planReview, onDismiss]);
|
||||||
@@ -63,26 +57,14 @@ export function PlanReviewBanner({
|
|||||||
<MarkdownContent content={planReview.planContent} />
|
<MarkdownContent content={planReview.planContent} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Actions */}
|
{/* Guidance */}
|
||||||
<div className="mt-3 flex items-center gap-2">
|
<p className="mt-3 text-[12px] leading-relaxed text-zinc-400">
|
||||||
<button
|
Send a follow-up message to proceed — e.g.{' '}
|
||||||
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"
|
<span className="text-zinc-300">"implement the plan"</span>,{' '}
|
||||||
onClick={handleImplement}
|
<span className="text-zinc-300">"adjust step 3"</span>, or ask for a different approach.
|
||||||
type="button"
|
</p>
|
||||||
>
|
</div>
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user