diff --git a/src/renderer/src/components/skills/SkillInstallDialog.tsx b/src/renderer/src/components/skills/SkillInstallDialog.tsx
index cc0ee06b198..6ad6edcbedf 100644
--- a/src/renderer/src/components/skills/SkillInstallDialog.tsx
+++ b/src/renderer/src/components/skills/SkillInstallDialog.tsx
@@ -1,13 +1,6 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
-import { Download, Loader2, ShieldCheck } from 'lucide-react'
-import { Button } from '@/components/ui/button'
-import {
- Dialog,
- DialogContent,
- DialogFooter,
- DialogHeader,
- DialogTitle
-} from '@/components/ui/dialog'
+import { Loader2 } from 'lucide-react'
+import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
import { useAppStore } from '@/store'
import type {
SkillInstallDestination,
@@ -32,6 +25,7 @@ import { useSkillInstallProgress } from './skill-install-progress-state'
import { translate } from '@/i18n/i18n'
import { resolveSkillShareForInstall } from './skill-warning-preview-gate'
import { useSkillInstallRisk } from './use-skill-install-risk'
+import { SkillInstallDialogFooter } from './SkillInstallDialogFooter'
export function SkillInstallDialog({
open,
@@ -374,65 +368,21 @@ export function SkillInstallDialog({
{installProgress.phaseLabel}
) : null}
- {!bundleVersion ? (
-
-
- {!preview && !resolvingInitialLink ? (
-
- ) : null}
- {busy && installProgress.activeOperationId ? (
-
- ) : null}
- {preview &&
- (!result || ['conflict', 'partial', 'failed', 'cancelled'].includes(result.status)) ? (
-
- ) : null}
-
- ) : null}
+ void cancelInstall()}
+ onClose={close}
+ onInspect={() => void inspect()}
+ onInstall={(discardLocal) => void install(discardLocal)}
+ />
)
diff --git a/src/renderer/src/components/skills/SkillInstallDialogFooter.tsx b/src/renderer/src/components/skills/SkillInstallDialogFooter.tsx
new file mode 100644
index 00000000000..8d87da3046a
--- /dev/null
+++ b/src/renderer/src/components/skills/SkillInstallDialogFooter.tsx
@@ -0,0 +1,81 @@
+import { Download, Loader2, ShieldCheck } from 'lucide-react'
+import { Button } from '@/components/ui/button'
+import { DialogFooter } from '@/components/ui/dialog'
+import type { SkillInstallResult } from '../../../../shared/skill-install-contract'
+import { translate } from '@/i18n/i18n'
+
+type SkillInstallDialogFooterProps = {
+ activeOperationId: string | null
+ busy: boolean
+ hasBundleVersion: boolean
+ hasPreview: boolean
+ link: string
+ resolvingInitialLink: boolean
+ result: SkillInstallResult | null
+ scope: 'global' | 'workspace'
+ workspace: string
+ onCancelInstall: () => void
+ onClose: () => void
+ onInspect: () => void
+ onInstall: (discardLocal?: boolean) => void
+}
+
+export function SkillInstallDialogFooter({
+ activeOperationId,
+ busy,
+ hasBundleVersion,
+ hasPreview,
+ link,
+ resolvingInitialLink,
+ result,
+ scope,
+ workspace,
+ onCancelInstall,
+ onClose,
+ onInspect,
+ onInstall
+}: SkillInstallDialogFooterProps): React.JSX.Element | null {
+ if (hasBundleVersion) {
+ return null
+ }
+
+ return (
+
+
+ {!hasPreview && !resolvingInitialLink ? (
+
+ ) : null}
+ {busy && activeOperationId ? (
+
+ ) : null}
+ {hasPreview &&
+ (!result || ['conflict', 'partial', 'failed', 'cancelled'].includes(result.status)) ? (
+
+ ) : null}
+
+ )
+}
diff --git a/src/renderer/src/components/skills/SkillInstallManagementDialog.tsx b/src/renderer/src/components/skills/SkillInstallManagementDialog.tsx
index 0479aae20fd..d1a6016655f 100644
--- a/src/renderer/src/components/skills/SkillInstallManagementDialog.tsx
+++ b/src/renderer/src/components/skills/SkillInstallManagementDialog.tsx
@@ -1,14 +1,5 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
-import { Loader2 } from 'lucide-react'
-import { Button } from '@/components/ui/button'
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle
-} from '@/components/ui/dialog'
+import { Dialog } from '@/components/ui/dialog'
import { useAppStore } from '@/store'
import type {
ManagedSkillInstall,
@@ -17,18 +8,15 @@ import type {
import type { SkillBundleInstallResult } from '../../../../shared/skill-bundle-install-contract'
import type { SkillCloudPackageDetails } from '../../../../shared/skill-cloud-contract'
import { notifyInstalledAgentSkillsChanged } from '@/hooks/useInstalledAgentSkills'
-import { skillInstallResultLabel } from './skill-install-result-label'
import { skillInstallManagementCopy } from './skill-install-management-copy'
import { useSkillInstallProgress } from './skill-install-progress-state'
-import { SkillManagedInstallRow } from './SkillManagedInstallRow'
import { summarizeManagedSkillRemoval } from './skill-managed-removal-summary'
import {
groupManagedSkillInstalls,
type SkillManagedInstallGroup
} from './skill-managed-install-groups'
-import { SkillInstallMachineSelect } from './SkillInstallMachineSelect'
-import { SkillInstallManagementStatus } from './SkillInstallManagementStatus'
import { translate } from '@/i18n/i18n'
+import { SkillInstallManagementDialogContent } from './SkillInstallManagementDialogContent'
export function SkillInstallManagementDialog({
open,
@@ -351,77 +339,39 @@ export function SkillInstallManagementDialog({
onOpenChange(false)
}
- const destructiveConflict =
- result?.status === 'conflict' ||
- Boolean(selected?.installs.some((install) => install.state === 'modified'))
-
return (
)
}
diff --git a/src/renderer/src/components/skills/SkillInstallManagementDialogContent.tsx b/src/renderer/src/components/skills/SkillInstallManagementDialogContent.tsx
new file mode 100644
index 00000000000..6d2abcc2263
--- /dev/null
+++ b/src/renderer/src/components/skills/SkillInstallManagementDialogContent.tsx
@@ -0,0 +1,151 @@
+import { Loader2 } from 'lucide-react'
+import { Button } from '@/components/ui/button'
+import {
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle
+} from '@/components/ui/dialog'
+import type {
+ ManagedSkillInstall,
+ SkillInstallResult
+} from '../../../../shared/skill-install-contract'
+import type { SkillBundleInstallResult } from '../../../../shared/skill-bundle-install-contract'
+import type { SkillCloudPackageDetails } from '../../../../shared/skill-cloud-contract'
+import type { skillInstallManagementCopy } from './skill-install-management-copy'
+import { skillInstallResultLabel } from './skill-install-result-label'
+import { SkillInstallMachineSelect } from './SkillInstallMachineSelect'
+import { SkillInstallManagementStatus } from './SkillInstallManagementStatus'
+import { SkillManagedInstallRow } from './SkillManagedInstallRow'
+import type { SkillManagedInstallGroup } from './skill-managed-install-groups'
+
+type SkillInstallManagementDialogContentProps = {
+ bundleResult: SkillBundleInstallResult | null
+ busy: boolean
+ confirmRemove: boolean
+ copy: ReturnType
+ details: SkillCloudPackageDetails | null
+ environmentId: string
+ error: string | null
+ groups: SkillManagedInstallGroup[]
+ installs: ManagedSkillInstall[]
+ installActive: boolean
+ notice: string | null
+ result: SkillInstallResult | null
+ progressLabel: string | null
+ runtimeEnvironments: readonly { id: string; name: string }[]
+ selectedKey: string
+ sshConnectionStates: ReadonlyMap
+ sshTargetLabels: ReadonlyMap
+ versionId: string
+ onCancelInstall: () => void
+ onClose: () => void
+ onEnvironmentChange: (value: string) => void
+ onInstall: (discardLocal?: boolean) => void
+ onOpenChange: (group: SkillManagedInstallGroup, open: boolean) => void
+ onRemove: (discardLocal?: boolean) => void
+ onSendToMachine: (shareId: string) => void
+ onVersionChange: (versionId: string) => void
+}
+
+export function SkillInstallManagementDialogContent({
+ bundleResult,
+ busy,
+ confirmRemove,
+ copy,
+ details,
+ environmentId,
+ error,
+ groups,
+ installs,
+ installActive,
+ notice,
+ progressLabel,
+ result,
+ runtimeEnvironments,
+ selectedKey,
+ sshConnectionStates,
+ sshTargetLabels,
+ versionId,
+ onCancelInstall,
+ onClose,
+ onEnvironmentChange,
+ onInstall,
+ onOpenChange,
+ onRemove,
+ onSendToMachine,
+ onVersionChange
+}: SkillInstallManagementDialogContentProps): React.JSX.Element {
+ const destructiveConflict =
+ result?.status === 'conflict' ||
+ Boolean(
+ groups
+ .find((group) => group.key === selectedKey)
+ ?.installs.some((install) => install.state === 'modified')
+ )
+
+ return (
+
+
+ {copy.title}
+ {copy.description}
+
+ ({
+ id: `ssh:${id}`,
+ label,
+ connected: sshConnectionStates.get(id)?.status === 'connected'
+ }))}
+ />
+ {busy && installs.length === 0 ? : null}
+ {!busy && installs.length === 0 ? (
+
+ {copy.noInstalls}
+
+ ) : null}
+ {groups.length > 0 ? (
+
+ {groups.map((group) => (
+ onOpenChange(group, next)}
+ onVersionChange={onVersionChange}
+ onInstall={onInstall}
+ onCancelInstall={onCancelInstall}
+ onSendToMachine={onSendToMachine}
+ onRemove={onRemove}
+ />
+ ))}
+
+ ) : null}
+
+
+
+
+
+ )
+}