feat: add dialog for "Start new session from this answer" (#1501)
* feat: add dialog for "Start new session from this answer" Replace the one-click fork action on assistant messages with a dialog (ForkSessionDialog) that lets the user pick model, thinking level, and agent, plus edit the instructions sent to the new session. The instructions field is prefilled with the previous fixed fork prompt and is mandatory. The composed message is now fully visible (no synthetic preface): the user's instructions sit above a short fixed connective that opens the assistant content. createSessionFromAssistantMessage takes the chosen execution params instead of reading from config. Also fix TodoSendDialog visuals: narrower vertical layout, model trigger no longer stretches with centered text, and the agent/thinking dropdowns portal to body so opening them no longer nudges the dialog height. Extract the shared ThinkingPill into its own component. * fix: address review feedback on fork session dialog - Fix "bellow" -> "below" typo in the fork content preface (now user-visible since the message is no longer synthetic) - Reset ForkSessionDialog state only on open transition, reading the config store snapshot via getState() so background store refreshes can't discard in-progress instruction edits
This commit is contained in:
committed by
GitHub
parent
aefbdbc52f
commit
6a88cd09cc
@@ -6,19 +6,12 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { ModelSelector } from '@/components/sections/agents/ModelSelector';
|
||||
import { AgentSelector } from '@/components/sections/commands/AgentSelector';
|
||||
import { ThinkingPill } from '@/components/session/ThinkingPill';
|
||||
import { useConfigStore } from '@/stores/useConfigStore';
|
||||
import { useAgentsStore } from '@/stores/useAgentsStore';
|
||||
import { isPrimaryMode } from '@/components/chat/mobileControlsUtils';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
|
||||
type TodoSendTarget = 'session' | 'worktree';
|
||||
@@ -51,56 +44,6 @@ const getInitialExecution = (params: {
|
||||
agent: params.agent,
|
||||
});
|
||||
|
||||
type ThinkingPillProps = {
|
||||
value: string;
|
||||
options: string[];
|
||||
disabled?: boolean;
|
||||
onChange: (value: string) => void;
|
||||
};
|
||||
|
||||
const ThinkingPill = ({ value, options, disabled, onChange }: ThinkingPillProps) => {
|
||||
const { t } = useI18n();
|
||||
const label = value || t('rightSidebar.contextNotesTodo.sendDialog.variant.default');
|
||||
|
||||
const trigger = (
|
||||
<div
|
||||
className={cn(
|
||||
'flex h-6 w-fit items-center gap-1.5 rounded-lg border border-border/20 bg-interactive-selection/20 px-2',
|
||||
disabled ? 'cursor-not-allowed opacity-60' : 'cursor-pointer hover:bg-interactive-hover/30',
|
||||
)}
|
||||
>
|
||||
<span className="typography-micro whitespace-nowrap font-medium capitalize">{label}</span>
|
||||
<Icon name="arrow-down-s" className="h-3 w-3 flex-shrink-0 text-muted-foreground" />
|
||||
</div>
|
||||
);
|
||||
|
||||
if (disabled) return trigger;
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>{trigger}</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" className="max-w-[220px]">
|
||||
<DropdownMenuItem className="typography-meta" onSelect={() => onChange('')}>
|
||||
<span className={cn('font-medium', !value && 'text-primary')}>
|
||||
{t('rightSidebar.contextNotesTodo.sendDialog.variant.default')}
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
{options.map((option) => (
|
||||
<DropdownMenuItem
|
||||
key={option}
|
||||
className="typography-meta"
|
||||
onSelect={() => onChange(option)}
|
||||
>
|
||||
<span className={cn('font-medium capitalize', value === option && 'text-primary')}>
|
||||
{option}
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
};
|
||||
|
||||
export function TodoSendDialog(props: TodoSendDialogProps) {
|
||||
const { t } = useI18n();
|
||||
const { open, onOpenChange, target, projectDirectory, submitting = false, onConfirm } = props;
|
||||
@@ -196,18 +139,18 @@ export function TodoSendDialog(props: TodoSendDialogProps) {
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={(nextOpen) => { if (!submitting) onOpenChange(nextOpen); }}>
|
||||
<DialogContent className="max-w-2xl overflow-visible">
|
||||
<DialogContent className="max-w-md overflow-visible">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="grid gap-4 sm:grid-cols-[minmax(0,1fr)_auto_auto]">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex min-w-0 flex-col gap-1.5">
|
||||
<span className="typography-meta font-medium text-muted-foreground">{t('chat.modelControls.model')}</span>
|
||||
<ModelSelector
|
||||
providerId={execution.providerID}
|
||||
modelId={execution.modelID}
|
||||
className="w-full justify-between"
|
||||
className="max-w-[320px] justify-between"
|
||||
dropdownPortalToBody
|
||||
onChange={(providerID, modelID) => {
|
||||
setExecution((prev) => ({ ...prev, providerID, modelID, variant: '' }));
|
||||
@@ -228,6 +171,7 @@ export function TodoSendDialog(props: TodoSendDialogProps) {
|
||||
<AgentSelector
|
||||
agentName={execution.agent}
|
||||
filter={agentFilter}
|
||||
dropdownPortalToBody
|
||||
onChange={(agent) => setExecution((prev) => ({ ...prev, agent }))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user