diff --git a/apps/desktop/src/lib/trpc/routers/settings/index.ts b/apps/desktop/src/lib/trpc/routers/settings/index.ts index 0007907dbe6..b84446865d2 100644 --- a/apps/desktop/src/lib/trpc/routers/settings/index.ts +++ b/apps/desktop/src/lib/trpc/routers/settings/index.ts @@ -1,4 +1,5 @@ import { db } from "main/lib/db"; +import type { TerminalPreset } from "main/lib/db/schemas"; import { nanoid } from "nanoid"; import { DEFAULT_RINGTONE_ID, RINGTONES } from "shared/ringtones"; import { z } from "zod"; @@ -7,20 +8,67 @@ import { publicProcedure, router } from "../.."; /** Valid ringtone IDs for validation */ const VALID_RINGTONE_IDS = RINGTONES.map((r) => r.id); +/** Default presets to load when no presets exist */ +const DEFAULT_PRESETS: Omit[] = [ + { + name: "codex", + description: "Danger mode: All permissions auto-approved", + cwd: "", + commands: [ + 'codex -c model_reasoning_effort="high" --ask-for-approval never --sandbox danger-full-access -c model_reasoning_summary="detailed" -c model_supports_reasoning_summaries=true', + ], + }, + { + name: "claude", + description: "Danger mode: All permissions auto-approved", + cwd: "", + commands: ["claude --dangerously-skip-permissions"], + }, +]; + export const createSettingsRouter = () => { return router({ getLastUsedApp: publicProcedure.query(() => { return db.data.settings.lastUsedApp ?? "cursor"; }), - getTerminalPresets: publicProcedure.query(() => { - return db.data.settings.terminalPresets ?? []; + getTerminalPresets: publicProcedure.query(async () => { + const { terminalPresets, terminalPresetsInitialized } = db.data.settings; + + // Handle first-time initialization + if (!terminalPresetsInitialized) { + // If user already has presets (from before the flag existed), preserve them + if (terminalPresets && terminalPresets.length > 0) { + await db.update((data) => { + data.settings.terminalPresetsInitialized = true; + }); + return terminalPresets; + } + + // No existing presets - seed with defaults + const defaultPresetsWithIds: TerminalPreset[] = DEFAULT_PRESETS.map( + (preset) => ({ + id: nanoid(), + ...preset, + }), + ); + + await db.update((data) => { + data.settings.terminalPresets = defaultPresetsWithIds; + data.settings.terminalPresetsInitialized = true; + }); + + return defaultPresetsWithIds; + } + + return terminalPresets ?? []; }), createTerminalPreset: publicProcedure .input( z.object({ name: z.string(), + description: z.string().optional(), cwd: z.string(), commands: z.array(z.string()), }), @@ -47,6 +95,7 @@ export const createSettingsRouter = () => { id: z.string(), patch: z.object({ name: z.string().optional(), + description: z.string().optional(), cwd: z.string().optional(), commands: z.array(z.string()).optional(), }), @@ -62,6 +111,8 @@ export const createSettingsRouter = () => { } if (input.patch.name !== undefined) preset.name = input.patch.name; + if (input.patch.description !== undefined) + preset.description = input.patch.description; if (input.patch.cwd !== undefined) preset.cwd = input.patch.cwd; if (input.patch.commands !== undefined) preset.commands = input.patch.commands; diff --git a/apps/desktop/src/main/lib/db/schemas.ts b/apps/desktop/src/main/lib/db/schemas.ts index 81bf2053c75..f4f9f6d412c 100644 --- a/apps/desktop/src/main/lib/db/schemas.ts +++ b/apps/desktop/src/main/lib/db/schemas.ts @@ -99,6 +99,7 @@ export type ExternalApp = (typeof EXTERNAL_APPS)[number]; export interface TerminalPreset { id: string; name: string; + description?: string; cwd: string; commands: string[]; } @@ -107,6 +108,7 @@ export interface Settings { lastActiveWorkspaceId?: string; lastUsedApp?: ExternalApp; terminalPresets?: TerminalPreset[]; + terminalPresetsInitialized?: boolean; selectedRingtoneId?: string; } diff --git a/apps/desktop/src/renderer/assets/app-icons/preset-icons/claude.svg b/apps/desktop/src/renderer/assets/app-icons/preset-icons/claude.svg new file mode 100644 index 00000000000..62dc0db12da --- /dev/null +++ b/apps/desktop/src/renderer/assets/app-icons/preset-icons/claude.svg @@ -0,0 +1 @@ +Claude \ No newline at end of file diff --git a/apps/desktop/src/renderer/assets/app-icons/preset-icons/codex-white.svg b/apps/desktop/src/renderer/assets/app-icons/preset-icons/codex-white.svg new file mode 100644 index 00000000000..ba36fc2aa74 --- /dev/null +++ b/apps/desktop/src/renderer/assets/app-icons/preset-icons/codex-white.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/apps/desktop/src/renderer/assets/app-icons/preset-icons/codex.svg b/apps/desktop/src/renderer/assets/app-icons/preset-icons/codex.svg new file mode 100644 index 00000000000..832fa6a5f9b --- /dev/null +++ b/apps/desktop/src/renderer/assets/app-icons/preset-icons/codex.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/apps/desktop/src/renderer/assets/app-icons/preset-icons/cursor.svg b/apps/desktop/src/renderer/assets/app-icons/preset-icons/cursor.svg new file mode 100644 index 00000000000..1f8a7c338a5 --- /dev/null +++ b/apps/desktop/src/renderer/assets/app-icons/preset-icons/cursor.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/desktop/src/renderer/assets/app-icons/preset-icons/gemini.svg b/apps/desktop/src/renderer/assets/app-icons/preset-icons/gemini.svg new file mode 100644 index 00000000000..f1cf357573d --- /dev/null +++ b/apps/desktop/src/renderer/assets/app-icons/preset-icons/gemini.svg @@ -0,0 +1 @@ +Gemini \ No newline at end of file diff --git a/apps/desktop/src/renderer/assets/app-icons/preset-icons/index.ts b/apps/desktop/src/renderer/assets/app-icons/preset-icons/index.ts new file mode 100644 index 00000000000..04eb9cd6f71 --- /dev/null +++ b/apps/desktop/src/renderer/assets/app-icons/preset-icons/index.ts @@ -0,0 +1,39 @@ +import { useThemeStore } from "renderer/stores/theme/store"; +import claudeIcon from "./claude.svg"; +import codexIcon from "./codex.svg"; +import codexWhiteIcon from "./codex-white.svg"; +import cursorIcon from "./cursor.svg"; +import geminiIcon from "./gemini.svg"; + +interface PresetIconSet { + light: string; + dark: string; +} + +const PRESET_ICONS: Record = { + claude: { light: claudeIcon, dark: claudeIcon }, + codex: { light: codexIcon, dark: codexWhiteIcon }, + gemini: { light: geminiIcon, dark: geminiIcon }, + "cursor-agent": { light: cursorIcon, dark: cursorIcon }, +}; + +export function getPresetIcon( + presetName: string, + isDark: boolean, +): string | undefined { + const normalizedName = presetName.toLowerCase().trim(); + const iconSet = PRESET_ICONS[normalizedName]; + if (!iconSet) return undefined; + return isDark ? iconSet.dark : iconSet.light; +} + +export function usePresetIcon(presetName: string): string | undefined { + const activeTheme = useThemeStore((state) => state.activeTheme); + const isDark = activeTheme?.type === "dark"; + return getPresetIcon(presetName, isDark); +} + +export function useIsDarkTheme(): boolean { + const activeTheme = useThemeStore((state) => state.activeTheme); + return activeTheme?.type === "dark"; +} diff --git a/apps/desktop/src/renderer/screens/main/components/SettingsView/PresetsSettings/PresetRow/PresetRow.tsx b/apps/desktop/src/renderer/screens/main/components/SettingsView/PresetsSettings/PresetRow/PresetRow.tsx index e482da99212..76c9f9aeae0 100644 --- a/apps/desktop/src/renderer/screens/main/components/SettingsView/PresetsSettings/PresetRow/PresetRow.tsx +++ b/apps/desktop/src/renderer/screens/main/components/SettingsView/PresetsSettings/PresetRow/PresetRow.tsx @@ -44,7 +44,7 @@ function PresetCell({ return ( onChange(rowIndex, column.key, e.target.value)} onBlur={() => onBlur(rowIndex, column.key)} className={`h-8 px-2 text-sm w-full min-w-0 truncate ${column.mono ? "font-mono" : ""}`} diff --git a/apps/desktop/src/renderer/screens/main/components/SettingsView/PresetsSettings/PresetsSettings.tsx b/apps/desktop/src/renderer/screens/main/components/SettingsView/PresetsSettings/PresetsSettings.tsx index 071b37fb552..ab0982ea917 100644 --- a/apps/desktop/src/renderer/screens/main/components/SettingsView/PresetsSettings/PresetsSettings.tsx +++ b/apps/desktop/src/renderer/screens/main/components/SettingsView/PresetsSettings/PresetsSettings.tsx @@ -1,7 +1,10 @@ import { Button } from "@superset/ui/button"; import { useEffect, useMemo, useState } from "react"; import { HiOutlineCheck, HiOutlinePlus } from "react-icons/hi2"; -import { LuSparkles } from "react-icons/lu"; +import { + getPresetIcon, + useIsDarkTheme, +} from "renderer/assets/app-icons/preset-icons"; import { usePresets } from "renderer/react-query/presets"; import { PresetRow } from "./PresetRow"; import { @@ -12,9 +15,9 @@ import { interface PresetTemplate { name: string; - description: string; preset: { name: string; + description: string; cwd: string; commands: string[]; }; @@ -22,39 +25,39 @@ interface PresetTemplate { const PRESET_TEMPLATES: PresetTemplate[] = [ { - name: "Claude (Danger Mode)", - description: "Claude Code with permissions auto-approved", + name: "codex", preset: { - name: "Claude Danger", + name: "codex", + description: "Danger mode: All permissions auto-approved", cwd: "", - commands: ["claude --dangerously-skip-permissions"], + commands: [ + 'codex -c model_reasoning_effort="high" --ask-for-approval never --sandbox danger-full-access -c model_reasoning_summary="detailed" -c model_supports_reasoning_summaries=true', + ], }, }, { - name: "Codex (Danger Mode)", - description: "OpenAI Codex with full sandbox access and high reasoning", + name: "claude", preset: { - name: "Codex Danger", + name: "claude", + description: "Danger mode: All permissions auto-approved", cwd: "", - commands: [ - 'codex -c model_reasoning_effort="high" --ask-for-approval never --sandbox danger-full-access -c model_reasoning_summary="detailed" -c model_supports_reasoning_summaries=true', - ], + commands: ["claude --dangerously-skip-permissions"], }, }, { - name: "Gemini CLI (YOLO)", - description: "Google Gemini CLI with auto-approve all actions", + name: "gemini", preset: { - name: "Gemini YOLO", + name: "gemini", + description: "Danger mode: All permissions auto-approved", cwd: "", commands: ["gemini --yolo"], }, }, { - name: "Cursor Agent", - description: "Cursor AI agent for terminal-based coding assistance", + name: "cursor-agent", preset: { - name: "Cursor Agent", + name: "cursor-agent", + description: "Cursor AI agent for terminal-based coding assistance", cwd: "", commands: ["cursor-agent"], }, @@ -71,6 +74,7 @@ export function PresetsSettings() { } = usePresets(); const [localPresets, setLocalPresets] = useState(serverPresets); + const isDark = useIsDarkTheme(); useEffect(() => { setLocalPresets(serverPresets); @@ -185,6 +189,7 @@ export function PresetsSettings() { {PRESET_TEMPLATES.map((template) => { const alreadyAdded = isTemplateAdded(template); + const presetIcon = getPresetIcon(template.name, isDark); return ( ); diff --git a/apps/desktop/src/renderer/screens/main/components/SettingsView/PresetsSettings/types.ts b/apps/desktop/src/renderer/screens/main/components/SettingsView/PresetsSettings/types.ts index de733981492..cad62567d79 100644 --- a/apps/desktop/src/renderer/screens/main/components/SettingsView/PresetsSettings/types.ts +++ b/apps/desktop/src/renderer/screens/main/components/SettingsView/PresetsSettings/types.ts @@ -13,6 +13,11 @@ export interface PresetColumnConfig { export const PRESET_COLUMNS: PresetColumnConfig[] = [ { key: "name", label: "Name", placeholder: "e.g. Dev Server" }, + { + key: "description", + label: "Description", + placeholder: "e.g. Starts the dev server (optional)", + }, { key: "cwd", label: "CWD", diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabsCommandDialog/index.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabsCommandDialog/index.tsx index 009bb55d3eb..b6a3ea2ab36 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabsCommandDialog/index.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabsCommandDialog/index.tsx @@ -12,6 +12,10 @@ import { HiMiniPlus, HiOutlineCog6Tooth, } from "react-icons/hi2"; +import { + getPresetIcon, + useIsDarkTheme, +} from "renderer/assets/app-icons/preset-icons"; interface TabsCommandDialogProps { open: boolean; @@ -30,6 +34,8 @@ export function TabsCommandDialog({ presets, onSelectPreset, }: TabsCommandDialogProps) { + const isDark = useIsDarkTheme(); + return ( @@ -43,22 +49,39 @@ export function TabsCommandDialog({ {presets.length > 0 && ( - {presets.map((preset) => ( - onSelectPreset(preset)} - > - - - {preset.name || "Unnamed Preset"} - - {preset.cwd && ( - - {preset.cwd} + {presets.map((preset) => { + const presetIcon = getPresetIcon(preset.name, isDark); + return ( + onSelectPreset(preset)} + > + {presetIcon ? ( + + ) : ( + + )} + + {preset.name || "default"} - )} - - ))} + {preset.description ? ( + + {preset.description} + + ) : ( + preset.cwd && ( + + {preset.cwd} + + ) + )} + + ); + })} )} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx index ecac48391e8..f27941f1913 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx @@ -1,5 +1,6 @@ import { Button } from "@superset/ui/button"; import { ButtonGroup } from "@superset/ui/button-group"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; import { LayoutGroup, motion } from "framer-motion"; import type { TerminalPreset } from "main/lib/db/schemas"; import { useMemo, useRef, useState } from "react"; @@ -9,6 +10,10 @@ import { HiMiniEllipsisHorizontal, HiMiniPlus, } from "react-icons/hi2"; +import { + getPresetIcon, + useIsDarkTheme, +} from "renderer/assets/app-icons/preset-icons"; import { trpc } from "renderer/lib/trpc"; import { usePresets } from "renderer/react-query/presets"; import { useOpenSettings, useSidebarStore } from "renderer/stores"; @@ -39,6 +44,7 @@ export function TabsView() { const containerRef = useRef(null); const { presets } = usePresets(); + const isDark = useIsDarkTheme(); const tabs = useMemo( () => @@ -154,19 +160,42 @@ export function TabsView() { {presets.length > 0 && (
- {presets.map((preset) => ( - - ))} + {presets.map((preset) => { + const tooltipText = preset.description || preset.cwd; + const presetIcon = getPresetIcon(preset.name, isDark); + const button = ( + + ); + + if (tooltipText) { + return ( + + {button} + + {tooltipText} + + + ); + } + + return
{button}
; + })}
)}