From e0c031f0df500f4f44081a1975bc392203e82c7f Mon Sep 17 00:00:00 2001 From: Satya Patel Date: Fri, 6 Mar 2026 17:31:54 -0800 Subject: [PATCH 1/9] WIP --- .../NewWorkspaceModal.tsx | 469 +++++++++++++++++ .../ExistingWorktreesList.tsx | 0 .../components/BranchesSection.tsx | 0 .../components/PrUrlSection.tsx | 0 .../components/WorktreesSection.tsx | 0 .../ExistingWorktreesList/components/index.ts | 0 .../components/ExistingWorktreesList/index.ts | 0 .../components/ImportFlow/ImportFlow.tsx | 0 .../components/ImportFlow/index.ts | 0 .../NewWorkspaceAdvancedOptions.tsx | 0 .../NewWorkspaceAdvancedOptions/index.ts | 0 .../NewWorkspaceCreateFlow.tsx | 0 .../NewWorkspaceCreateFlow/index.ts | 0 .../NewWorkspaceHeader/NewWorkspaceHeader.tsx | 0 .../components/NewWorkspaceHeader/index.ts | 0 .../ProjectSelector/ProjectSelector.tsx | 74 +++ .../components/ProjectSelector/index.ts | 1 + .../components/NewWorkspaceModal.old/index.ts | 1 + .../NewWorkspaceModal/NewWorkspaceModal.tsx | 480 +++--------------- .../BranchesGroup/BranchesGroup.tsx | 95 ++++ .../components/BranchesGroup/index.ts | 1 + .../components/IssuesGroup/IssuesGroup.tsx | 69 +++ .../components/IssuesGroup/index.ts | 1 + .../ProjectSelector/ProjectSelector.tsx | 49 +- .../components/PromptGroup/PromptGroup.tsx | 108 ++++ .../components/PromptGroup/index.ts | 1 + .../PullRequestsGroup/PullRequestsGroup.tsx | 70 +++ .../components/PullRequestsGroup/index.ts | 1 + apps/desktop/src/renderer/lib/slug-width.ts | 15 + .../hooks/useTasksTable/useTasksTable.tsx | 20 +- .../NewWorkspaceButton.tsx | 2 - 31 files changed, 1004 insertions(+), 453 deletions(-) create mode 100644 apps/desktop/src/renderer/components/NewWorkspaceModal.old/NewWorkspaceModal.tsx rename apps/desktop/src/renderer/components/{NewWorkspaceModal => NewWorkspaceModal.old}/components/ExistingWorktreesList/ExistingWorktreesList.tsx (100%) rename apps/desktop/src/renderer/components/{NewWorkspaceModal => NewWorkspaceModal.old}/components/ExistingWorktreesList/components/BranchesSection.tsx (100%) rename apps/desktop/src/renderer/components/{NewWorkspaceModal => NewWorkspaceModal.old}/components/ExistingWorktreesList/components/PrUrlSection.tsx (100%) rename apps/desktop/src/renderer/components/{NewWorkspaceModal => NewWorkspaceModal.old}/components/ExistingWorktreesList/components/WorktreesSection.tsx (100%) rename apps/desktop/src/renderer/components/{NewWorkspaceModal => NewWorkspaceModal.old}/components/ExistingWorktreesList/components/index.ts (100%) rename apps/desktop/src/renderer/components/{NewWorkspaceModal => NewWorkspaceModal.old}/components/ExistingWorktreesList/index.ts (100%) rename apps/desktop/src/renderer/components/{NewWorkspaceModal => NewWorkspaceModal.old}/components/ImportFlow/ImportFlow.tsx (100%) rename apps/desktop/src/renderer/components/{NewWorkspaceModal => NewWorkspaceModal.old}/components/ImportFlow/index.ts (100%) rename apps/desktop/src/renderer/components/{NewWorkspaceModal => NewWorkspaceModal.old}/components/NewWorkspaceAdvancedOptions/NewWorkspaceAdvancedOptions.tsx (100%) rename apps/desktop/src/renderer/components/{NewWorkspaceModal => NewWorkspaceModal.old}/components/NewWorkspaceAdvancedOptions/index.ts (100%) rename apps/desktop/src/renderer/components/{NewWorkspaceModal => NewWorkspaceModal.old}/components/NewWorkspaceCreateFlow/NewWorkspaceCreateFlow.tsx (100%) rename apps/desktop/src/renderer/components/{NewWorkspaceModal => NewWorkspaceModal.old}/components/NewWorkspaceCreateFlow/index.ts (100%) rename apps/desktop/src/renderer/components/{NewWorkspaceModal => NewWorkspaceModal.old}/components/NewWorkspaceHeader/NewWorkspaceHeader.tsx (100%) rename apps/desktop/src/renderer/components/{NewWorkspaceModal => NewWorkspaceModal.old}/components/NewWorkspaceHeader/index.ts (100%) create mode 100644 apps/desktop/src/renderer/components/NewWorkspaceModal.old/components/ProjectSelector/ProjectSelector.tsx create mode 100644 apps/desktop/src/renderer/components/NewWorkspaceModal.old/components/ProjectSelector/index.ts create mode 100644 apps/desktop/src/renderer/components/NewWorkspaceModal.old/index.ts create mode 100644 apps/desktop/src/renderer/components/NewWorkspaceModal/components/BranchesGroup/BranchesGroup.tsx create mode 100644 apps/desktop/src/renderer/components/NewWorkspaceModal/components/BranchesGroup/index.ts create mode 100644 apps/desktop/src/renderer/components/NewWorkspaceModal/components/IssuesGroup/IssuesGroup.tsx create mode 100644 apps/desktop/src/renderer/components/NewWorkspaceModal/components/IssuesGroup/index.ts create mode 100644 apps/desktop/src/renderer/components/NewWorkspaceModal/components/PromptGroup/PromptGroup.tsx create mode 100644 apps/desktop/src/renderer/components/NewWorkspaceModal/components/PromptGroup/index.ts create mode 100644 apps/desktop/src/renderer/components/NewWorkspaceModal/components/PullRequestsGroup/PullRequestsGroup.tsx create mode 100644 apps/desktop/src/renderer/components/NewWorkspaceModal/components/PullRequestsGroup/index.ts create mode 100644 apps/desktop/src/renderer/lib/slug-width.ts diff --git a/apps/desktop/src/renderer/components/NewWorkspaceModal.old/NewWorkspaceModal.tsx b/apps/desktop/src/renderer/components/NewWorkspaceModal.old/NewWorkspaceModal.tsx new file mode 100644 index 00000000000..e6831844e83 --- /dev/null +++ b/apps/desktop/src/renderer/components/NewWorkspaceModal.old/NewWorkspaceModal.tsx @@ -0,0 +1,469 @@ +import { + AGENT_PRESET_COMMANDS, + buildAgentPromptCommand, +} from "@superset/shared/agent-command"; +import { + type AgentLaunchRequest, + STARTABLE_AGENT_TYPES, + type StartableAgentType, +} from "@superset/shared/agent-launch"; +import { Dialog, DialogContent } from "@superset/ui/dialog"; +import { toast } from "@superset/ui/sonner"; +import { useNavigate } from "@tanstack/react-router"; +import { useEffect, useMemo, useRef, useState } from "react"; +import { launchAgentSession } from "renderer/lib/agent-session-orchestrator"; +import { electronTrpc } from "renderer/lib/electron-trpc"; +import { resolveEffectiveWorkspaceBaseBranch } from "renderer/lib/workspaceBaseBranch"; +import { useOpenProject } from "renderer/react-query/projects"; +import { useCreateWorkspace } from "renderer/react-query/workspaces"; +import { + useCloseNewWorkspaceModal, + useNewWorkspaceModalOpen, + usePreSelectedProjectId, +} from "renderer/stores/new-workspace-modal"; +import { + resolveBranchPrefix, + sanitizeBranchNameWithMaxLength, +} from "shared/utils/branch"; +import type { ImportSourceTab } from "./components/ExistingWorktreesList"; +import { ImportFlow } from "./components/ImportFlow"; +import { NewWorkspaceAdvancedOptions } from "./components/NewWorkspaceAdvancedOptions"; +import { + NewWorkspaceCreateFlow, + type WorkspaceCreateAgent, +} from "./components/NewWorkspaceCreateFlow"; +import { NewWorkspaceHeader } from "./components/NewWorkspaceHeader"; +import { ProjectSelector } from "./components/ProjectSelector"; + +type Mode = "existing" | "new"; +const WORKSPACE_AGENT_STORAGE_KEY = "lastSelectedWorkspaceCreateAgent"; + +export function NewWorkspaceModal() { + const navigate = useNavigate(); + const isOpen = useNewWorkspaceModalOpen(); + const closeModal = useCloseNewWorkspaceModal(); + const preSelectedProjectId = usePreSelectedProjectId(); + const [selectedProjectId, setSelectedProjectId] = useState( + null, + ); + const [title, setTitle] = useState(""); + const [branchName, setBranchName] = useState(""); + const [branchNameEdited, setBranchNameEdited] = useState(false); + const [mode, setMode] = useState("new"); + const [baseBranch, setBaseBranch] = useState(null); + const [baseBranchOpen, setBaseBranchOpen] = useState(false); + const [branchSearch, setBranchSearch] = useState(""); + const [showAdvanced, setShowAdvanced] = useState(false); + const [runSetupScript, setRunSetupScript] = useState(true); + const [importTab, setImportTab] = useState("pull-request"); + const [selectedAgent, setSelectedAgent] = useState( + () => { + if (typeof window === "undefined") return "none"; + const stored = window.localStorage.getItem(WORKSPACE_AGENT_STORAGE_KEY); + if (stored === "none") return "none"; + return stored && + (STARTABLE_AGENT_TYPES as readonly string[]).includes(stored) + ? (stored as WorkspaceCreateAgent) + : "none"; + }, + ); + const runSetupScriptRef = useRef(true); + runSetupScriptRef.current = runSetupScript; + const titleInputRef = useRef(null); + + const { data: recentProjects = [] } = + electronTrpc.projects.getRecents.useQuery(); + const { data: project } = electronTrpc.projects.get.useQuery( + { id: selectedProjectId ?? "" }, + { enabled: !!selectedProjectId }, + ); + const { + data: branchData, + isLoading: isBranchesLoading, + isError: isBranchesError, + } = electronTrpc.projects.getBranches.useQuery( + { projectId: selectedProjectId ?? "" }, + { enabled: !!selectedProjectId }, + ); + const { data: gitAuthor } = electronTrpc.projects.getGitAuthor.useQuery( + { id: selectedProjectId ?? "" }, + { enabled: !!selectedProjectId }, + ); + const { data: globalBranchPrefix } = + electronTrpc.settings.getBranchPrefix.useQuery(); + const { data: gitInfo } = electronTrpc.settings.getGitInfo.useQuery(); + const terminalCreateOrAttach = + electronTrpc.terminal.createOrAttach.useMutation(); + const terminalWrite = electronTrpc.terminal.write.useMutation(); + const createWorkspace = useCreateWorkspace({ + resolveInitialCommands: (commands) => + runSetupScriptRef.current ? commands : null, + }); + const { openNew } = useOpenProject(); + const selectableAgents = + STARTABLE_AGENT_TYPES as readonly StartableAgentType[]; + + const resolvedPrefix = useMemo(() => { + const projectOverrides = project?.branchPrefixMode != null; + return resolveBranchPrefix({ + mode: projectOverrides + ? project?.branchPrefixMode + : (globalBranchPrefix?.mode ?? "none"), + customPrefix: projectOverrides + ? project?.branchPrefixCustom + : globalBranchPrefix?.customPrefix, + authorPrefix: gitAuthor?.prefix, + githubUsername: gitInfo?.githubUsername, + }); + }, [project, globalBranchPrefix, gitAuthor, gitInfo]); + + const filteredBranches = useMemo(() => { + if (!branchData?.branches) return []; + if (!branchSearch) return branchData.branches; + const searchLower = branchSearch.toLowerCase(); + return branchData.branches.filter((b) => + b.name.toLowerCase().includes(searchLower), + ); + }, [branchData?.branches, branchSearch]); + + // biome-ignore lint/correctness/useExhaustiveDependencies: reset form each time the modal opens + useEffect(() => { + if (!isOpen) return; + resetForm(); + if (preSelectedProjectId) { + setSelectedProjectId(preSelectedProjectId); + } + }, [isOpen]); + + useEffect(() => { + if (selectedAgent === "none") return; + if ((STARTABLE_AGENT_TYPES as readonly string[]).includes(selectedAgent)) { + return; + } + setSelectedAgent("none"); + window.localStorage.setItem(WORKSPACE_AGENT_STORAGE_KEY, "none"); + }, [selectedAgent]); + + const effectiveBaseBranch = resolveEffectiveWorkspaceBaseBranch({ + explicitBaseBranch: baseBranch, + workspaceBaseBranch: project?.workspaceBaseBranch, + defaultBranch: branchData?.defaultBranch, + branches: branchData?.branches, + }); + + // biome-ignore lint/correctness/useExhaustiveDependencies: intentionally reset when project changes + useEffect(() => { + setBaseBranch(null); + }, [selectedProjectId]); + + const branchSlug = branchNameEdited + ? sanitizeBranchNameWithMaxLength(branchName, undefined, { + preserveFirstSegmentCase: true, + }) + : ""; + + const applyPrefix = !branchNameEdited; + + const branchPreview = + branchSlug && applyPrefix && resolvedPrefix + ? sanitizeBranchNameWithMaxLength(`${resolvedPrefix}/${branchSlug}`) + : branchSlug; + + const resetForm = () => { + setSelectedProjectId(null); + setTitle(""); + setBranchName(""); + setBranchNameEdited(false); + setMode("new"); + setImportTab("pull-request"); + setBaseBranch(null); + setBranchSearch(""); + setShowAdvanced(false); + setRunSetupScript(true); + }; + + useEffect(() => { + if (isOpen && selectedProjectId && mode === "new") { + const timer = setTimeout(() => titleInputRef.current?.focus(), 50); + return () => clearTimeout(timer); + } + }, [isOpen, selectedProjectId, mode]); + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.defaultPrevented) return; + + const isTextareaTarget = e.target instanceof HTMLTextAreaElement; + const isSubmitShortcutInTextarea = + isTextareaTarget && (e.metaKey || e.ctrlKey); + + if (isTextareaTarget && !isSubmitShortcutInTextarea) { + return; + } + + if ( + e.key === "Enter" && + !e.shiftKey && + mode === "new" && + selectedProjectId && + !createWorkspace.isPending + ) { + e.preventDefault(); + handleCreateWorkspace(); + } + }; + + const handleClose = () => { + closeModal(); + resetForm(); + }; + + const handleBranchNameChange = (value: string) => { + setBranchName(value); + setBranchNameEdited(true); + }; + + const handleBranchNameBlur = () => { + if (!branchName.trim()) { + setBranchName(""); + setBranchNameEdited(false); + } + }; + + const handleImportRepo = async () => { + try { + const projects = await openNew(); + + if (projects.length > 1) { + toast.success(`${projects.length} projects imported`); + } + + if (projects.length > 0) { + setSelectedProjectId(projects[0].id); + } + } catch (error) { + toast.error("Failed to open project", { + description: + error instanceof Error ? error.message : "An unknown error occurred", + }); + } + }; + + const selectedProject = recentProjects.find( + (p) => p.id === selectedProjectId, + ); + const projectSelector = ( + Boolean(project.id))} + onSelectProject={setSelectedProjectId} + onImportRepo={handleImportRepo} + /> + ); + const isCreateDisabled = createWorkspace.isPending || isBranchesError; + const buildLaunchRequestForWorkspace = ( + workspaceId: string, + prompt: string, + ): AgentLaunchRequest | null => { + if (selectedAgent === "none") { + return null; + } + + if (selectedAgent === "superset-chat") { + return { + kind: "chat", + workspaceId, + agentType: "superset-chat", + source: "new-workspace", + chat: { + initialPrompt: prompt || undefined, + retryCount: 1, + }, + }; + } + + const command = prompt + ? buildAgentPromptCommand({ + prompt, + randomId: window.crypto.randomUUID(), + agent: selectedAgent, + }) + : (AGENT_PRESET_COMMANDS[selectedAgent][0] ?? null); + + if (!command) { + return null; + } + + return { + kind: "terminal", + workspaceId, + agentType: selectedAgent, + source: "new-workspace", + terminal: { + command, + name: "Agent", + }, + }; + }; + + const handleCreateWorkspace = async () => { + if (!selectedProjectId) return; + // Keep the agent prompt uncapped; only trim surrounding whitespace. + const prompt = title.trim(); + + const workspaceName = undefined; + const launchRequestTemplate = buildLaunchRequestForWorkspace( + "pending-workspace", + prompt, + ); + + closeModal(); + + try { + const result = await createWorkspace.mutateAsyncWithPendingSetup( + { + projectId: selectedProjectId, + name: workspaceName, + prompt: prompt || undefined, + branchName: branchSlug || undefined, + baseBranch: baseBranch || undefined, + applyPrefix, + }, + launchRequestTemplate + ? { agentLaunchRequest: launchRequestTemplate } + : undefined, + ); + + const launchRequest = launchRequestTemplate + ? { + ...launchRequestTemplate, + workspaceId: result.workspace.id, + } + : null; + + if (launchRequest && result.wasExisting) { + const launchResult = await launchAgentSession(launchRequest, { + source: "new-workspace", + createOrAttach: (input) => terminalCreateOrAttach.mutateAsync(input), + write: (input) => terminalWrite.mutateAsync(input), + }); + if (launchResult.status === "failed") { + toast.error("Failed to start agent", { + description: launchResult.error ?? "Failed to start agent session.", + }); + } + } + + if (result.wasExisting) { + toast.success("Opened existing workspace"); + } else if (result.isInitializing) { + toast.success("Workspace created", { + description: "Setting up in the background...", + }); + } else { + toast.success("Workspace created"); + } + } catch (err) { + toast.error( + err instanceof Error ? err.message : "Failed to create workspace", + ); + } + }; + + const handleAgentChange = (value: WorkspaceCreateAgent) => { + setSelectedAgent(value); + window.localStorage.setItem(WORKSPACE_AGENT_STORAGE_KEY, value); + }; + + const handleBaseBranchSelect = (branchName: string) => { + setBaseBranch(branchName); + setBaseBranchOpen(false); + setBranchSearch(""); + }; + + const advancedOptions = ( + { + handleClose(); + navigate({ to: "/settings/behavior" }); + }} + isBranchesError={isBranchesError} + isBranchesLoading={isBranchesLoading} + baseBranchOpen={baseBranchOpen} + onBaseBranchOpenChange={setBaseBranchOpen} + effectiveBaseBranch={effectiveBaseBranch} + defaultBranch={branchData?.defaultBranch} + branchSearch={branchSearch} + onBranchSearchChange={setBranchSearch} + filteredBranches={filteredBranches} + onSelectBaseBranch={handleBaseBranchSelect} + runSetupScript={runSetupScript} + onRunSetupScriptChange={setRunSetupScript} + /> + ); + + return ( + !open && handleClose()}> + + setMode("new")} + onOpenImport={() => setMode("existing")} + /> + + {!selectedProjectId && ( +
{projectSelector}
+ )} + + {selectedProjectId && ( +
+ {mode === "new" && ( + + )} + {mode === "existing" && ( + + )} +
+ )} + + {!selectedProjectId && ( +
+
+ Select a project to get started +
+
+ )} +
+
+ ); +} diff --git a/apps/desktop/src/renderer/components/NewWorkspaceModal/components/ExistingWorktreesList/ExistingWorktreesList.tsx b/apps/desktop/src/renderer/components/NewWorkspaceModal.old/components/ExistingWorktreesList/ExistingWorktreesList.tsx similarity index 100% rename from apps/desktop/src/renderer/components/NewWorkspaceModal/components/ExistingWorktreesList/ExistingWorktreesList.tsx rename to apps/desktop/src/renderer/components/NewWorkspaceModal.old/components/ExistingWorktreesList/ExistingWorktreesList.tsx diff --git a/apps/desktop/src/renderer/components/NewWorkspaceModal/components/ExistingWorktreesList/components/BranchesSection.tsx b/apps/desktop/src/renderer/components/NewWorkspaceModal.old/components/ExistingWorktreesList/components/BranchesSection.tsx similarity index 100% rename from apps/desktop/src/renderer/components/NewWorkspaceModal/components/ExistingWorktreesList/components/BranchesSection.tsx rename to apps/desktop/src/renderer/components/NewWorkspaceModal.old/components/ExistingWorktreesList/components/BranchesSection.tsx diff --git a/apps/desktop/src/renderer/components/NewWorkspaceModal/components/ExistingWorktreesList/components/PrUrlSection.tsx b/apps/desktop/src/renderer/components/NewWorkspaceModal.old/components/ExistingWorktreesList/components/PrUrlSection.tsx similarity index 100% rename from apps/desktop/src/renderer/components/NewWorkspaceModal/components/ExistingWorktreesList/components/PrUrlSection.tsx rename to apps/desktop/src/renderer/components/NewWorkspaceModal.old/components/ExistingWorktreesList/components/PrUrlSection.tsx diff --git a/apps/desktop/src/renderer/components/NewWorkspaceModal/components/ExistingWorktreesList/components/WorktreesSection.tsx b/apps/desktop/src/renderer/components/NewWorkspaceModal.old/components/ExistingWorktreesList/components/WorktreesSection.tsx similarity index 100% rename from apps/desktop/src/renderer/components/NewWorkspaceModal/components/ExistingWorktreesList/components/WorktreesSection.tsx rename to apps/desktop/src/renderer/components/NewWorkspaceModal.old/components/ExistingWorktreesList/components/WorktreesSection.tsx diff --git a/apps/desktop/src/renderer/components/NewWorkspaceModal/components/ExistingWorktreesList/components/index.ts b/apps/desktop/src/renderer/components/NewWorkspaceModal.old/components/ExistingWorktreesList/components/index.ts similarity index 100% rename from apps/desktop/src/renderer/components/NewWorkspaceModal/components/ExistingWorktreesList/components/index.ts rename to apps/desktop/src/renderer/components/NewWorkspaceModal.old/components/ExistingWorktreesList/components/index.ts diff --git a/apps/desktop/src/renderer/components/NewWorkspaceModal/components/ExistingWorktreesList/index.ts b/apps/desktop/src/renderer/components/NewWorkspaceModal.old/components/ExistingWorktreesList/index.ts similarity index 100% rename from apps/desktop/src/renderer/components/NewWorkspaceModal/components/ExistingWorktreesList/index.ts rename to apps/desktop/src/renderer/components/NewWorkspaceModal.old/components/ExistingWorktreesList/index.ts diff --git a/apps/desktop/src/renderer/components/NewWorkspaceModal/components/ImportFlow/ImportFlow.tsx b/apps/desktop/src/renderer/components/NewWorkspaceModal.old/components/ImportFlow/ImportFlow.tsx similarity index 100% rename from apps/desktop/src/renderer/components/NewWorkspaceModal/components/ImportFlow/ImportFlow.tsx rename to apps/desktop/src/renderer/components/NewWorkspaceModal.old/components/ImportFlow/ImportFlow.tsx diff --git a/apps/desktop/src/renderer/components/NewWorkspaceModal/components/ImportFlow/index.ts b/apps/desktop/src/renderer/components/NewWorkspaceModal.old/components/ImportFlow/index.ts similarity index 100% rename from apps/desktop/src/renderer/components/NewWorkspaceModal/components/ImportFlow/index.ts rename to apps/desktop/src/renderer/components/NewWorkspaceModal.old/components/ImportFlow/index.ts diff --git a/apps/desktop/src/renderer/components/NewWorkspaceModal/components/NewWorkspaceAdvancedOptions/NewWorkspaceAdvancedOptions.tsx b/apps/desktop/src/renderer/components/NewWorkspaceModal.old/components/NewWorkspaceAdvancedOptions/NewWorkspaceAdvancedOptions.tsx similarity index 100% rename from apps/desktop/src/renderer/components/NewWorkspaceModal/components/NewWorkspaceAdvancedOptions/NewWorkspaceAdvancedOptions.tsx rename to apps/desktop/src/renderer/components/NewWorkspaceModal.old/components/NewWorkspaceAdvancedOptions/NewWorkspaceAdvancedOptions.tsx diff --git a/apps/desktop/src/renderer/components/NewWorkspaceModal/components/NewWorkspaceAdvancedOptions/index.ts b/apps/desktop/src/renderer/components/NewWorkspaceModal.old/components/NewWorkspaceAdvancedOptions/index.ts similarity index 100% rename from apps/desktop/src/renderer/components/NewWorkspaceModal/components/NewWorkspaceAdvancedOptions/index.ts rename to apps/desktop/src/renderer/components/NewWorkspaceModal.old/components/NewWorkspaceAdvancedOptions/index.ts diff --git a/apps/desktop/src/renderer/components/NewWorkspaceModal/components/NewWorkspaceCreateFlow/NewWorkspaceCreateFlow.tsx b/apps/desktop/src/renderer/components/NewWorkspaceModal.old/components/NewWorkspaceCreateFlow/NewWorkspaceCreateFlow.tsx similarity index 100% rename from apps/desktop/src/renderer/components/NewWorkspaceModal/components/NewWorkspaceCreateFlow/NewWorkspaceCreateFlow.tsx rename to apps/desktop/src/renderer/components/NewWorkspaceModal.old/components/NewWorkspaceCreateFlow/NewWorkspaceCreateFlow.tsx diff --git a/apps/desktop/src/renderer/components/NewWorkspaceModal/components/NewWorkspaceCreateFlow/index.ts b/apps/desktop/src/renderer/components/NewWorkspaceModal.old/components/NewWorkspaceCreateFlow/index.ts similarity index 100% rename from apps/desktop/src/renderer/components/NewWorkspaceModal/components/NewWorkspaceCreateFlow/index.ts rename to apps/desktop/src/renderer/components/NewWorkspaceModal.old/components/NewWorkspaceCreateFlow/index.ts diff --git a/apps/desktop/src/renderer/components/NewWorkspaceModal/components/NewWorkspaceHeader/NewWorkspaceHeader.tsx b/apps/desktop/src/renderer/components/NewWorkspaceModal.old/components/NewWorkspaceHeader/NewWorkspaceHeader.tsx similarity index 100% rename from apps/desktop/src/renderer/components/NewWorkspaceModal/components/NewWorkspaceHeader/NewWorkspaceHeader.tsx rename to apps/desktop/src/renderer/components/NewWorkspaceModal.old/components/NewWorkspaceHeader/NewWorkspaceHeader.tsx diff --git a/apps/desktop/src/renderer/components/NewWorkspaceModal/components/NewWorkspaceHeader/index.ts b/apps/desktop/src/renderer/components/NewWorkspaceModal.old/components/NewWorkspaceHeader/index.ts similarity index 100% rename from apps/desktop/src/renderer/components/NewWorkspaceModal/components/NewWorkspaceHeader/index.ts rename to apps/desktop/src/renderer/components/NewWorkspaceModal.old/components/NewWorkspaceHeader/index.ts diff --git a/apps/desktop/src/renderer/components/NewWorkspaceModal.old/components/ProjectSelector/ProjectSelector.tsx b/apps/desktop/src/renderer/components/NewWorkspaceModal.old/components/ProjectSelector/ProjectSelector.tsx new file mode 100644 index 00000000000..f4629c821bd --- /dev/null +++ b/apps/desktop/src/renderer/components/NewWorkspaceModal.old/components/ProjectSelector/ProjectSelector.tsx @@ -0,0 +1,74 @@ +import { Button } from "@superset/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@superset/ui/dropdown-menu"; +import { HiCheck, HiChevronDown } from "react-icons/hi2"; +import { LuFolderOpen } from "react-icons/lu"; + +interface ProjectOption { + id: string; + name: string; +} + +interface ProjectSelectorProps { + selectedProjectId: string | null; + selectedProjectName: string | null; + recentProjects: ProjectOption[]; + onSelectProject: (projectId: string) => void; + onImportRepo: () => void; + className?: string; +} + +export function ProjectSelector({ + selectedProjectId, + selectedProjectName, + recentProjects, + onSelectProject, + onImportRepo, + className, +}: ProjectSelectorProps) { + return ( + + + + + + {recentProjects.map((project) => ( + onSelectProject(project.id)} + > + {project.name} + {project.id === selectedProjectId && ( + + )} + + ))} + + + + Import repo + + + + ); +} diff --git a/apps/desktop/src/renderer/components/NewWorkspaceModal.old/components/ProjectSelector/index.ts b/apps/desktop/src/renderer/components/NewWorkspaceModal.old/components/ProjectSelector/index.ts new file mode 100644 index 00000000000..a524b03c166 --- /dev/null +++ b/apps/desktop/src/renderer/components/NewWorkspaceModal.old/components/ProjectSelector/index.ts @@ -0,0 +1 @@ +export { ProjectSelector } from "./ProjectSelector"; diff --git a/apps/desktop/src/renderer/components/NewWorkspaceModal.old/index.ts b/apps/desktop/src/renderer/components/NewWorkspaceModal.old/index.ts new file mode 100644 index 00000000000..6267ce7fe48 --- /dev/null +++ b/apps/desktop/src/renderer/components/NewWorkspaceModal.old/index.ts @@ -0,0 +1 @@ +export { NewWorkspaceModal } from "./NewWorkspaceModal"; diff --git a/apps/desktop/src/renderer/components/NewWorkspaceModal/NewWorkspaceModal.tsx b/apps/desktop/src/renderer/components/NewWorkspaceModal/NewWorkspaceModal.tsx index e6831844e83..e2b9af81919 100644 --- a/apps/desktop/src/renderer/components/NewWorkspaceModal/NewWorkspaceModal.tsx +++ b/apps/desktop/src/renderer/components/NewWorkspaceModal/NewWorkspaceModal.tsx @@ -1,242 +1,58 @@ import { - AGENT_PRESET_COMMANDS, - buildAgentPromptCommand, -} from "@superset/shared/agent-command"; -import { - type AgentLaunchRequest, - STARTABLE_AGENT_TYPES, - type StartableAgentType, -} from "@superset/shared/agent-launch"; -import { Dialog, DialogContent } from "@superset/ui/dialog"; + CommandDialog, + CommandInput, + CommandList, +} from "@superset/ui/command"; +import { Tabs, TabsList, TabsTrigger } from "@superset/ui/tabs"; import { toast } from "@superset/ui/sonner"; -import { useNavigate } from "@tanstack/react-router"; -import { useEffect, useMemo, useRef, useState } from "react"; -import { launchAgentSession } from "renderer/lib/agent-session-orchestrator"; +import { useEffect, useState } from "react"; import { electronTrpc } from "renderer/lib/electron-trpc"; -import { resolveEffectiveWorkspaceBaseBranch } from "renderer/lib/workspaceBaseBranch"; import { useOpenProject } from "renderer/react-query/projects"; -import { useCreateWorkspace } from "renderer/react-query/workspaces"; import { useCloseNewWorkspaceModal, useNewWorkspaceModalOpen, usePreSelectedProjectId, } from "renderer/stores/new-workspace-modal"; -import { - resolveBranchPrefix, - sanitizeBranchNameWithMaxLength, -} from "shared/utils/branch"; -import type { ImportSourceTab } from "./components/ExistingWorktreesList"; -import { ImportFlow } from "./components/ImportFlow"; -import { NewWorkspaceAdvancedOptions } from "./components/NewWorkspaceAdvancedOptions"; -import { - NewWorkspaceCreateFlow, - type WorkspaceCreateAgent, -} from "./components/NewWorkspaceCreateFlow"; -import { NewWorkspaceHeader } from "./components/NewWorkspaceHeader"; +import { BranchesGroup } from "./components/BranchesGroup"; +import { IssuesGroup } from "./components/IssuesGroup"; import { ProjectSelector } from "./components/ProjectSelector"; +import { PromptGroup } from "./components/PromptGroup"; +import { PullRequestsGroup } from "./components/PullRequestsGroup"; -type Mode = "existing" | "new"; -const WORKSPACE_AGENT_STORAGE_KEY = "lastSelectedWorkspaceCreateAgent"; +type Tab = "pull-requests" | "branches" | "issues" | "prompt"; export function NewWorkspaceModal() { - const navigate = useNavigate(); const isOpen = useNewWorkspaceModalOpen(); const closeModal = useCloseNewWorkspaceModal(); const preSelectedProjectId = usePreSelectedProjectId(); + const [activeTab, setActiveTab] = useState("pull-requests"); const [selectedProjectId, setSelectedProjectId] = useState( null, ); - const [title, setTitle] = useState(""); - const [branchName, setBranchName] = useState(""); - const [branchNameEdited, setBranchNameEdited] = useState(false); - const [mode, setMode] = useState("new"); - const [baseBranch, setBaseBranch] = useState(null); - const [baseBranchOpen, setBaseBranchOpen] = useState(false); - const [branchSearch, setBranchSearch] = useState(""); - const [showAdvanced, setShowAdvanced] = useState(false); - const [runSetupScript, setRunSetupScript] = useState(true); - const [importTab, setImportTab] = useState("pull-request"); - const [selectedAgent, setSelectedAgent] = useState( - () => { - if (typeof window === "undefined") return "none"; - const stored = window.localStorage.getItem(WORKSPACE_AGENT_STORAGE_KEY); - if (stored === "none") return "none"; - return stored && - (STARTABLE_AGENT_TYPES as readonly string[]).includes(stored) - ? (stored as WorkspaceCreateAgent) - : "none"; - }, - ); - const runSetupScriptRef = useRef(true); - runSetupScriptRef.current = runSetupScript; - const titleInputRef = useRef(null); + const { openNew } = useOpenProject(); const { data: recentProjects = [] } = electronTrpc.projects.getRecents.useQuery(); - const { data: project } = electronTrpc.projects.get.useQuery( - { id: selectedProjectId ?? "" }, - { enabled: !!selectedProjectId }, - ); - const { - data: branchData, - isLoading: isBranchesLoading, - isError: isBranchesError, - } = electronTrpc.projects.getBranches.useQuery( - { projectId: selectedProjectId ?? "" }, - { enabled: !!selectedProjectId }, - ); - const { data: gitAuthor } = electronTrpc.projects.getGitAuthor.useQuery( - { id: selectedProjectId ?? "" }, - { enabled: !!selectedProjectId }, - ); - const { data: globalBranchPrefix } = - electronTrpc.settings.getBranchPrefix.useQuery(); - const { data: gitInfo } = electronTrpc.settings.getGitInfo.useQuery(); - const terminalCreateOrAttach = - electronTrpc.terminal.createOrAttach.useMutation(); - const terminalWrite = electronTrpc.terminal.write.useMutation(); - const createWorkspace = useCreateWorkspace({ - resolveInitialCommands: (commands) => - runSetupScriptRef.current ? commands : null, - }); - const { openNew } = useOpenProject(); - const selectableAgents = - STARTABLE_AGENT_TYPES as readonly StartableAgentType[]; - - const resolvedPrefix = useMemo(() => { - const projectOverrides = project?.branchPrefixMode != null; - return resolveBranchPrefix({ - mode: projectOverrides - ? project?.branchPrefixMode - : (globalBranchPrefix?.mode ?? "none"), - customPrefix: projectOverrides - ? project?.branchPrefixCustom - : globalBranchPrefix?.customPrefix, - authorPrefix: gitAuthor?.prefix, - githubUsername: gitInfo?.githubUsername, - }); - }, [project, globalBranchPrefix, gitAuthor, gitInfo]); - const filteredBranches = useMemo(() => { - if (!branchData?.branches) return []; - if (!branchSearch) return branchData.branches; - const searchLower = branchSearch.toLowerCase(); - return branchData.branches.filter((b) => - b.name.toLowerCase().includes(searchLower), - ); - }, [branchData?.branches, branchSearch]); - - // biome-ignore lint/correctness/useExhaustiveDependencies: reset form each time the modal opens + // Sync pre-selected project when modal opens + // biome-ignore lint/correctness/useExhaustiveDependencies: reset on modal open useEffect(() => { if (!isOpen) return; - resetForm(); if (preSelectedProjectId) { setSelectedProjectId(preSelectedProjectId); + } else if (recentProjects.length > 0 && !selectedProjectId) { + setSelectedProjectId(recentProjects[0].id); } }, [isOpen]); - useEffect(() => { - if (selectedAgent === "none") return; - if ((STARTABLE_AGENT_TYPES as readonly string[]).includes(selectedAgent)) { - return; - } - setSelectedAgent("none"); - window.localStorage.setItem(WORKSPACE_AGENT_STORAGE_KEY, "none"); - }, [selectedAgent]); - - const effectiveBaseBranch = resolveEffectiveWorkspaceBaseBranch({ - explicitBaseBranch: baseBranch, - workspaceBaseBranch: project?.workspaceBaseBranch, - defaultBranch: branchData?.defaultBranch, - branches: branchData?.branches, - }); - - // biome-ignore lint/correctness/useExhaustiveDependencies: intentionally reset when project changes - useEffect(() => { - setBaseBranch(null); - }, [selectedProjectId]); - - const branchSlug = branchNameEdited - ? sanitizeBranchNameWithMaxLength(branchName, undefined, { - preserveFirstSegmentCase: true, - }) - : ""; - - const applyPrefix = !branchNameEdited; - - const branchPreview = - branchSlug && applyPrefix && resolvedPrefix - ? sanitizeBranchNameWithMaxLength(`${resolvedPrefix}/${branchSlug}`) - : branchSlug; - - const resetForm = () => { - setSelectedProjectId(null); - setTitle(""); - setBranchName(""); - setBranchNameEdited(false); - setMode("new"); - setImportTab("pull-request"); - setBaseBranch(null); - setBranchSearch(""); - setShowAdvanced(false); - setRunSetupScript(true); - }; - - useEffect(() => { - if (isOpen && selectedProjectId && mode === "new") { - const timer = setTimeout(() => titleInputRef.current?.focus(), 50); - return () => clearTimeout(timer); - } - }, [isOpen, selectedProjectId, mode]); - - const handleKeyDown = (e: React.KeyboardEvent) => { - if (e.defaultPrevented) return; - - const isTextareaTarget = e.target instanceof HTMLTextAreaElement; - const isSubmitShortcutInTextarea = - isTextareaTarget && (e.metaKey || e.ctrlKey); - - if (isTextareaTarget && !isSubmitShortcutInTextarea) { - return; - } - - if ( - e.key === "Enter" && - !e.shiftKey && - mode === "new" && - selectedProjectId && - !createWorkspace.isPending - ) { - e.preventDefault(); - handleCreateWorkspace(); - } - }; - - const handleClose = () => { - closeModal(); - resetForm(); - }; - - const handleBranchNameChange = (value: string) => { - setBranchName(value); - setBranchNameEdited(true); - }; - - const handleBranchNameBlur = () => { - if (!branchName.trim()) { - setBranchName(""); - setBranchNameEdited(false); - } - }; + const selectedProject = recentProjects.find( + (p) => p.id === selectedProjectId, + ); + const isListTab = activeTab !== "prompt"; const handleImportRepo = async () => { try { const projects = await openNew(); - - if (projects.length > 1) { - toast.success(`${projects.length} projects imported`); - } - if (projects.length > 0) { setSelectedProjectId(projects[0].id); } @@ -248,222 +64,46 @@ export function NewWorkspaceModal() { } }; - const selectedProject = recentProjects.find( - (p) => p.id === selectedProjectId, - ); - const projectSelector = ( - Boolean(project.id))} - onSelectProject={setSelectedProjectId} - onImportRepo={handleImportRepo} - /> - ); - const isCreateDisabled = createWorkspace.isPending || isBranchesError; - const buildLaunchRequestForWorkspace = ( - workspaceId: string, - prompt: string, - ): AgentLaunchRequest | null => { - if (selectedAgent === "none") { - return null; - } - - if (selectedAgent === "superset-chat") { - return { - kind: "chat", - workspaceId, - agentType: "superset-chat", - source: "new-workspace", - chat: { - initialPrompt: prompt || undefined, - retryCount: 1, - }, - }; - } - - const command = prompt - ? buildAgentPromptCommand({ - prompt, - randomId: window.crypto.randomUUID(), - agent: selectedAgent, - }) - : (AGENT_PRESET_COMMANDS[selectedAgent][0] ?? null); - - if (!command) { - return null; - } - - return { - kind: "terminal", - workspaceId, - agentType: selectedAgent, - source: "new-workspace", - terminal: { - command, - name: "Agent", - }, - }; - }; - - const handleCreateWorkspace = async () => { - if (!selectedProjectId) return; - // Keep the agent prompt uncapped; only trim surrounding whitespace. - const prompt = title.trim(); - - const workspaceName = undefined; - const launchRequestTemplate = buildLaunchRequestForWorkspace( - "pending-workspace", - prompt, - ); - - closeModal(); - - try { - const result = await createWorkspace.mutateAsyncWithPendingSetup( - { - projectId: selectedProjectId, - name: workspaceName, - prompt: prompt || undefined, - branchName: branchSlug || undefined, - baseBranch: baseBranch || undefined, - applyPrefix, - }, - launchRequestTemplate - ? { agentLaunchRequest: launchRequestTemplate } - : undefined, - ); - - const launchRequest = launchRequestTemplate - ? { - ...launchRequestTemplate, - workspaceId: result.workspace.id, - } - : null; - - if (launchRequest && result.wasExisting) { - const launchResult = await launchAgentSession(launchRequest, { - source: "new-workspace", - createOrAttach: (input) => terminalCreateOrAttach.mutateAsync(input), - write: (input) => terminalWrite.mutateAsync(input), - }); - if (launchResult.status === "failed") { - toast.error("Failed to start agent", { - description: launchResult.error ?? "Failed to start agent session.", - }); - } - } - - if (result.wasExisting) { - toast.success("Opened existing workspace"); - } else if (result.isInitializing) { - toast.success("Workspace created", { - description: "Setting up in the background...", - }); - } else { - toast.success("Workspace created"); - } - } catch (err) { - toast.error( - err instanceof Error ? err.message : "Failed to create workspace", - ); - } - }; - - const handleAgentChange = (value: WorkspaceCreateAgent) => { - setSelectedAgent(value); - window.localStorage.setItem(WORKSPACE_AGENT_STORAGE_KEY, value); - }; - - const handleBaseBranchSelect = (branchName: string) => { - setBaseBranch(branchName); - setBaseBranchOpen(false); - setBranchSearch(""); - }; - - const advancedOptions = ( - { - handleClose(); - navigate({ to: "/settings/behavior" }); - }} - isBranchesError={isBranchesError} - isBranchesLoading={isBranchesLoading} - baseBranchOpen={baseBranchOpen} - onBaseBranchOpenChange={setBaseBranchOpen} - effectiveBaseBranch={effectiveBaseBranch} - defaultBranch={branchData?.defaultBranch} - branchSearch={branchSearch} - onBranchSearchChange={setBranchSearch} - filteredBranches={filteredBranches} - onSelectBaseBranch={handleBaseBranchSelect} - runSetupScript={runSetupScript} - onRunSetupScriptChange={setRunSetupScript} - /> - ); - return ( - !open && handleClose()}> - - setMode("new")} - onOpenImport={() => setMode("existing")} + !open && closeModal()} + title="New Workspace" + description="Create a new workspace from a PR, branch, issue, or prompt." + showCloseButton={false} + className="sm:max-w-[540px] max-h-[min(70vh,600px)] !top-[calc(50%-min(35vh,300px))] !-translate-y-0" + > +
+ setActiveTab(value as Tab)} + > + + Pull requests + Branches + Issues + Prompt + + + Boolean(p.id))} + onSelectProject={setSelectedProjectId} + onImportRepo={handleImportRepo} /> - - {!selectedProjectId && ( -
{projectSelector}
- )} - - {selectedProjectId && ( -
- {mode === "new" && ( - - )} - {mode === "existing" && ( - - )} -
- )} - - {!selectedProjectId && ( -
-
- Select a project to get started -
-
- )} - -
+ + + {isListTab && ( + + )} + + + {activeTab === "pull-requests" && } + {activeTab === "branches" && } + {activeTab === "issues" && } + {activeTab === "prompt" && } + + ); } diff --git a/apps/desktop/src/renderer/components/NewWorkspaceModal/components/BranchesGroup/BranchesGroup.tsx b/apps/desktop/src/renderer/components/NewWorkspaceModal/components/BranchesGroup/BranchesGroup.tsx new file mode 100644 index 00000000000..de6b81ef221 --- /dev/null +++ b/apps/desktop/src/renderer/components/NewWorkspaceModal/components/BranchesGroup/BranchesGroup.tsx @@ -0,0 +1,95 @@ +import { CommandEmpty, CommandGroup, CommandItem } from "@superset/ui/command"; +import { GoGitBranch } from "react-icons/go"; + +const MOCK_BRANCHES = [ + { + name: "main", + lastCommitDate: Date.now() - 1000 * 60 * 30, + isLocal: true, + isRemote: true, + isDefault: true, + }, + { + name: "develop", + lastCommitDate: Date.now() - 1000 * 60 * 60 * 2, + isLocal: true, + isRemote: true, + isDefault: false, + }, + { + name: "feat/new-dashboard", + lastCommitDate: Date.now() - 1000 * 60 * 60 * 5, + isLocal: true, + isRemote: true, + isDefault: false, + }, + { + name: "fix/login-redirect", + lastCommitDate: Date.now() - 1000 * 60 * 60 * 24, + isLocal: false, + isRemote: true, + isDefault: false, + }, + { + name: "chore/update-ci", + lastCommitDate: Date.now() - 1000 * 60 * 60 * 24 * 3, + isLocal: true, + isRemote: false, + isDefault: false, + }, + { + name: "refactor/auth-module", + lastCommitDate: Date.now() - 1000 * 60 * 60 * 24 * 7, + isLocal: false, + isRemote: true, + isDefault: false, + }, +]; + +function formatRelative(timestamp: number): string { + const seconds = Math.floor((Date.now() - timestamp) / 1000); + if (seconds < 60) return "just now"; + const minutes = Math.floor(seconds / 60); + if (minutes < 60) return `${minutes}m ago`; + const hours = Math.floor(minutes / 60); + if (hours < 24) return `${hours}h ago`; + const days = Math.floor(hours / 24); + return `${days}d ago`; +} + +export function BranchesGroup() { + return ( + + No branches found. + {MOCK_BRANCHES.map((branch) => ( + { + console.log("[mock] Create workspace from branch", branch.name); + }} + className="group" + > + + {branch.name} + {branch.isDefault && ( + + default + + )} + {!branch.isLocal && branch.isRemote && ( + + remote + + )} + + {formatRelative(branch.lastCommitDate)} + + + Open → + + + ))} + + ); +} diff --git a/apps/desktop/src/renderer/components/NewWorkspaceModal/components/BranchesGroup/index.ts b/apps/desktop/src/renderer/components/NewWorkspaceModal/components/BranchesGroup/index.ts new file mode 100644 index 00000000000..75953e3d249 --- /dev/null +++ b/apps/desktop/src/renderer/components/NewWorkspaceModal/components/BranchesGroup/index.ts @@ -0,0 +1 @@ +export { BranchesGroup } from "./BranchesGroup"; diff --git a/apps/desktop/src/renderer/components/NewWorkspaceModal/components/IssuesGroup/IssuesGroup.tsx b/apps/desktop/src/renderer/components/NewWorkspaceModal/components/IssuesGroup/IssuesGroup.tsx new file mode 100644 index 00000000000..a18520a6c75 --- /dev/null +++ b/apps/desktop/src/renderer/components/NewWorkspaceModal/components/IssuesGroup/IssuesGroup.tsx @@ -0,0 +1,69 @@ +import { CommandEmpty, CommandGroup, CommandItem } from "@superset/ui/command"; +import { eq, isNull } from "@tanstack/db"; +import { useLiveQuery } from "@tanstack/react-db"; +import { useMemo } from "react"; +import { getSlugColumnWidth } from "renderer/lib/slug-width"; +import { useCollections } from "renderer/routes/_authenticated/providers/CollectionsProvider"; +import { + StatusIcon, + type StatusType, +} from "renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/components/shared/StatusIcon"; + +export function IssuesGroup() { + const collections = useCollections(); + + const { data } = useLiveQuery( + (q) => + q + .from({ tasks: collections.tasks }) + .innerJoin( + { status: collections.taskStatuses }, + ({ tasks, status }) => eq(tasks.statusId, status.id), + ) + .select(({ tasks, status }) => ({ + ...tasks, + status, + })) + .where(({ tasks }) => isNull(tasks.deletedAt)), + [collections], + ); + + const tasks = useMemo(() => data ?? [], [data]); + + const slugWidth = useMemo( + () => getSlugColumnWidth(tasks.map((t) => t.slug)), + [tasks], + ); + + return ( + + No issues found. + {tasks.map((task) => ( + { + console.log("[mock] Create workspace from issue", task.slug); + }} + className="group" + > + + + {task.slug} + + {task.title} + + Open → + + + ))} + + ); +} diff --git a/apps/desktop/src/renderer/components/NewWorkspaceModal/components/IssuesGroup/index.ts b/apps/desktop/src/renderer/components/NewWorkspaceModal/components/IssuesGroup/index.ts new file mode 100644 index 00000000000..c0762c8495d --- /dev/null +++ b/apps/desktop/src/renderer/components/NewWorkspaceModal/components/IssuesGroup/index.ts @@ -0,0 +1 @@ +export { IssuesGroup } from "./IssuesGroup"; diff --git a/apps/desktop/src/renderer/components/NewWorkspaceModal/components/ProjectSelector/ProjectSelector.tsx b/apps/desktop/src/renderer/components/NewWorkspaceModal/components/ProjectSelector/ProjectSelector.tsx index f4629c821bd..be774c855cf 100644 --- a/apps/desktop/src/renderer/components/NewWorkspaceModal/components/ProjectSelector/ProjectSelector.tsx +++ b/apps/desktop/src/renderer/components/NewWorkspaceModal/components/ProjectSelector/ProjectSelector.tsx @@ -6,12 +6,17 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from "@superset/ui/dropdown-menu"; -import { HiCheck, HiChevronDown } from "react-icons/hi2"; +import { HiCheck, HiChevronUpDown } from "react-icons/hi2"; import { LuFolderOpen } from "react-icons/lu"; +import { ProjectThumbnail } from "renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectThumbnail"; interface ProjectOption { id: string; name: string; + color: string; + githubOwner: string | null; + iconUrl: string | null; + hideImage: boolean | null; } interface ProjectSelectorProps { @@ -20,7 +25,6 @@ interface ProjectSelectorProps { recentProjects: ProjectOption[]; onSelectProject: (projectId: string) => void; onImportRepo: () => void; - className?: string; } export function ProjectSelector({ @@ -29,34 +33,45 @@ export function ProjectSelector({ recentProjects, onSelectProject, onImportRepo, - className, }: ProjectSelectorProps) { + const selectedProject = recentProjects.find( + (p) => p.id === selectedProjectId, + ); + return ( - - + {recentProjects.map((project) => ( onSelectProject(project.id)} > + {project.name} {project.id === selectedProjectId && ( diff --git a/apps/desktop/src/renderer/components/NewWorkspaceModal/components/PromptGroup/PromptGroup.tsx b/apps/desktop/src/renderer/components/NewWorkspaceModal/components/PromptGroup/PromptGroup.tsx new file mode 100644 index 00000000000..46434adab00 --- /dev/null +++ b/apps/desktop/src/renderer/components/NewWorkspaceModal/components/PromptGroup/PromptGroup.tsx @@ -0,0 +1,108 @@ +import { + STARTABLE_AGENT_LABELS, + STARTABLE_AGENT_TYPES, + type StartableAgentType, +} from "@superset/shared/agent-launch"; +import { Button } from "@superset/ui/button"; +import { Kbd, KbdGroup } from "@superset/ui/kbd"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@superset/ui/select"; +import { Textarea } from "@superset/ui/textarea"; +import { useRef, useState } from "react"; +import { + getPresetIcon, + useIsDarkTheme, +} from "renderer/assets/app-icons/preset-icons"; + +type WorkspaceCreateAgent = StartableAgentType | "none"; + +const AGENT_STORAGE_KEY = "lastSelectedWorkspaceCreateAgent"; + +export function PromptGroup() { + const isDark = useIsDarkTheme(); + const textareaRef = useRef(null); + const [prompt, setPrompt] = useState(""); + const [selectedAgent, setSelectedAgent] = useState( + () => { + if (typeof window === "undefined") return "none"; + const stored = window.localStorage.getItem(AGENT_STORAGE_KEY); + if (stored === "none") return "none"; + return stored && + (STARTABLE_AGENT_TYPES as readonly string[]).includes(stored) + ? (stored as WorkspaceCreateAgent) + : "none"; + }, + ); + + const handleAgentChange = (value: WorkspaceCreateAgent) => { + setSelectedAgent(value); + window.localStorage.setItem(AGENT_STORAGE_KEY, value); + }; + + return ( +
+ + +