From 602c61ad4c400747cfc5f9db874026b1857b89bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E5=91=98=E9=98=BF=E6=B1=9F=28Relakkes?= =?UTF-8?q?=29?= Date: Tue, 7 Apr 2026 21:56:23 +0800 Subject: [PATCH] feat: redesign scheduled task dialog to match Claude Code original Refactor PermissionModeSelector and ModelSelector to support controlled (prop-driven) mode alongside the existing store-driven mode, enabling reuse in the new task creation dialog. Rewrite NewTaskModal with embedded prompt editor that combines textarea + permission/model/folder controls, matching Claude's original design. Add desktop-online warning banner on the scheduled tasks list page and default folder to current session workDir. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/components/controls/ModelSelector.tsx | 33 +++- .../controls/PermissionModeSelector.tsx | 39 ++-- desktop/src/components/tasks/NewTaskModal.tsx | 169 +++++++++--------- desktop/src/components/tasks/PromptEditor.tsx | 72 ++++++++ desktop/src/pages/ScheduledTasks.tsx | 22 ++- 5 files changed, 223 insertions(+), 112 deletions(-) create mode 100644 desktop/src/components/tasks/PromptEditor.tsx diff --git a/desktop/src/components/controls/ModelSelector.tsx b/desktop/src/components/controls/ModelSelector.tsx index 126fd8b9..755782fd 100644 --- a/desktop/src/components/controls/ModelSelector.tsx +++ b/desktop/src/components/controls/ModelSelector.tsx @@ -15,11 +15,21 @@ const EFFORT_OPTIONS: { value: EffortLevel; label: string }[] = [ { value: 'max', label: 'Max' }, ] -export function ModelSelector() { - const { currentModel, availableModels, effortLevel, setModel, setEffort } = useSettingsStore() +type Props = { + /** Controlled mode: model ID override */ + value?: string + /** Controlled mode: called on change instead of updating global store */ + onChange?: (modelId: string) => void +} + +export function ModelSelector({ value, onChange }: Props = {}) { + const { currentModel: storeModel, availableModels, effortLevel, setModel, setEffort } = useSettingsStore() const [open, setOpen] = useState(false) const ref = useRef(null) + const isControlled = value !== undefined + const selectedModel = isControlled ? availableModels.find((m) => m.id === value) || null : storeModel + useEffect(() => { if (!open) return const handleClick = (e: MouseEvent) => { @@ -51,7 +61,7 @@ export function ModelSelector() { className="flex items-center gap-1.5 px-2.5 py-1.5 bg-[var(--color-surface-container-low)] hover:bg-[var(--color-surface-hover)] rounded-full text-xs font-medium text-[var(--color-text-secondary)] transition-colors" > auto_awesome - {currentModel?.name ?? 'Select model'} + {selectedModel?.name ?? 'Select model'} expand_more @@ -64,11 +74,18 @@ export function ModelSelector() {
{availableModels.map((model) => { - const isSelected = model.id === currentModel?.id + const isSelected = model.id === selectedModel?.id return (
- {/* Effort */} -
+ {/* Effort — hidden in controlled mode (not relevant for task creation) */} + {!isControlled &&
Effort
@@ -129,7 +146,7 @@ export function ModelSelector() { ) })}
-
+ } )} diff --git a/desktop/src/components/controls/PermissionModeSelector.tsx b/desktop/src/components/controls/PermissionModeSelector.tsx index f2fe2145..9f128c5c 100644 --- a/desktop/src/components/controls/PermissionModeSelector.tsx +++ b/desktop/src/components/controls/PermissionModeSelector.tsx @@ -56,8 +56,16 @@ const MODE_LABELS: Record = { dontAsk: 'Don\'t ask', } -export function PermissionModeSelector({ workDir: workDirProp }: { workDir?: string } = {}) { - const { permissionMode, setPermissionMode } = useSettingsStore() +type Props = { + workDir?: string + /** Controlled mode: override current value */ + value?: PermissionMode + /** Controlled mode: called on change instead of updating global store */ + onChange?: (mode: PermissionMode) => void +} + +export function PermissionModeSelector({ workDir: workDirProp, value, onChange }: Props = {}) { + const { permissionMode: storeMode, setPermissionMode } = useSettingsStore() const setSessionPermissionMode = useChatStore((s) => s.setSessionPermissionMode) const sessions = useSessionStore((s) => s.sessions) const activeSessionId = useSessionStore((s) => s.activeSessionId) @@ -65,6 +73,9 @@ export function PermissionModeSelector({ workDir: workDirProp }: { workDir?: str const [confirmDialog, setConfirmDialog] = useState(false) const ref = useRef(null) + const isControlled = value !== undefined + const currentMode = isControlled ? value : storeMode + const activeSession = sessions.find((s) => s.id === activeSessionId) const workDir = workDirProp || activeSession?.workDir || '~' @@ -90,8 +101,8 @@ export function PermissionModeSelector({ workDir: workDirProp }: { workDir?: str onClick={() => setOpen(!open)} className="flex items-center gap-1.5 px-2.5 py-1.5 bg-[var(--color-surface-container-low)] hover:bg-[var(--color-surface-hover)] rounded-full text-xs font-medium text-[var(--color-text-secondary)] transition-colors" > - {MODE_ICONS[permissionMode]} - {MODE_LABELS[permissionMode]} + {MODE_ICONS[currentMode]} + {MODE_LABELS[currentMode]} expand_more @@ -109,14 +120,18 @@ export function PermissionModeSelector({ workDir: workDirProp }: { workDir?: str setConfirmDialog(true) return } - void setPermissionMode(item.value) - setSessionPermissionMode(item.value) + if (isControlled) { + onChange?.(item.value) + } else { + void setPermissionMode(item.value) + setSessionPermissionMode(item.value) + } setOpen(false) }} className={` w-full flex items-start gap-3 px-4 py-3 text-left transition-colors hover:bg-[var(--color-surface-hover)] - ${item.value === permissionMode ? 'bg-[var(--color-surface-selected)]' : ''} + ${item.value === currentMode ? 'bg-[var(--color-surface-selected)]' : ''} `} > @@ -126,7 +141,7 @@ export function PermissionModeSelector({ workDir: workDirProp }: { workDir?: str
{item.label}
{item.description}
- {item.value === permissionMode && ( + {item.value === currentMode && ( check_circle @@ -189,8 +204,12 @@ export function PermissionModeSelector({ workDir: workDirProp }: { workDir?: str