mirror of
https://github.com/stablyai/orca.git
synced 2026-10-23 16:03:49 +00:00
refactor: extract skill install dialog footer and content
Extract footer and content sections from SkillInstallDialog and SkillInstallManagementDialog into separate components for improved maintainability and clarity of component responsibilities.
This commit is contained in:
@@ -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}
|
||||
</p>
|
||||
) : null}
|
||||
{!bundleVersion ? (
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="ghost" onClick={close} disabled={busy}>
|
||||
{translate('auto.components.skills.SkillInstallDialog.d198ec91e5', 'Close')}
|
||||
</Button>
|
||||
{!preview && !resolvingInitialLink ? (
|
||||
<Button type="button" disabled={busy || !link.trim()} onClick={() => void inspect()}>
|
||||
{busy ? (
|
||||
<Loader2 className="size-4 animate-spin" />
|
||||
) : (
|
||||
<ShieldCheck className="size-4" />
|
||||
)}
|
||||
{busy
|
||||
? translate(
|
||||
'auto.components.skills.SkillInstallReviewContent.69236de8d6',
|
||||
'Checking…'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.skills.SkillInstallReviewContent.157de228b4',
|
||||
'Inspect skill'
|
||||
)}
|
||||
</Button>
|
||||
) : null}
|
||||
{busy && installProgress.activeOperationId ? (
|
||||
<Button type="button" variant="secondary" onClick={() => void cancelInstall()}>
|
||||
{translate(
|
||||
'auto.components.skills.SkillInstallDialog.05588076a9',
|
||||
'Cancel installation'
|
||||
)}
|
||||
</Button>
|
||||
) : null}
|
||||
{preview &&
|
||||
(!result || ['conflict', 'partial', 'failed', 'cancelled'].includes(result.status)) ? (
|
||||
<Button
|
||||
type="button"
|
||||
disabled={busy || (scope === 'workspace' && !workspace)}
|
||||
onClick={() => void install()}
|
||||
className="w-32"
|
||||
>
|
||||
{busy ? (
|
||||
<Loader2 className="size-4 animate-spin" />
|
||||
) : (
|
||||
<Download className="size-4" />
|
||||
)}
|
||||
{busy
|
||||
? translate('auto.components.skills.SkillInstallDialog.241e72f9d6', 'Installing…')
|
||||
: result
|
||||
? translate(
|
||||
'auto.components.skills.SkillInstallDialog.59c3b76cdd',
|
||||
'Retry install'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.skills.SkillInstallDialog.39acb9e8f4',
|
||||
'Install skill'
|
||||
)}
|
||||
</Button>
|
||||
) : null}
|
||||
</DialogFooter>
|
||||
) : null}
|
||||
<SkillInstallDialogFooter
|
||||
activeOperationId={installProgress.activeOperationId}
|
||||
busy={busy}
|
||||
hasBundleVersion={Boolean(bundleVersion)}
|
||||
hasPreview={Boolean(preview)}
|
||||
link={link}
|
||||
resolvingInitialLink={resolvingInitialLink}
|
||||
result={result}
|
||||
scope={scope}
|
||||
workspace={workspace}
|
||||
onCancelInstall={() => void cancelInstall()}
|
||||
onClose={close}
|
||||
onInspect={() => void inspect()}
|
||||
onInstall={(discardLocal) => void install(discardLocal)}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
|
||||
@@ -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 (
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="ghost" onClick={onClose} disabled={busy}>
|
||||
{translate('auto.components.skills.SkillInstallDialog.d198ec91e5', 'Close')}
|
||||
</Button>
|
||||
{!hasPreview && !resolvingInitialLink ? (
|
||||
<Button type="button" disabled={busy || !link.trim()} onClick={onInspect}>
|
||||
{busy ? <Loader2 className="size-4 animate-spin" /> : <ShieldCheck className="size-4" />}
|
||||
{busy
|
||||
? translate('auto.components.skills.SkillInstallReviewContent.69236de8d6', 'Checking…')
|
||||
: translate(
|
||||
'auto.components.skills.SkillInstallReviewContent.157de228b4',
|
||||
'Inspect skill'
|
||||
)}
|
||||
</Button>
|
||||
) : null}
|
||||
{busy && activeOperationId ? (
|
||||
<Button type="button" variant="secondary" onClick={onCancelInstall}>
|
||||
{translate('auto.components.skills.SkillInstallDialog.05588076a9', 'Cancel installation')}
|
||||
</Button>
|
||||
) : null}
|
||||
{hasPreview &&
|
||||
(!result || ['conflict', 'partial', 'failed', 'cancelled'].includes(result.status)) ? (
|
||||
<Button
|
||||
type="button"
|
||||
disabled={busy || (scope === 'workspace' && !workspace)}
|
||||
onClick={() => onInstall()}
|
||||
className="w-32"
|
||||
>
|
||||
{busy ? <Loader2 className="size-4 animate-spin" /> : <Download className="size-4" />}
|
||||
{busy
|
||||
? translate('auto.components.skills.SkillInstallDialog.241e72f9d6', 'Installing…')
|
||||
: result
|
||||
? translate('auto.components.skills.SkillInstallDialog.59c3b76cdd', 'Retry install')
|
||||
: translate('auto.components.skills.SkillInstallDialog.39acb9e8f4', 'Install skill')}
|
||||
</Button>
|
||||
) : null}
|
||||
</DialogFooter>
|
||||
)
|
||||
}
|
||||
@@ -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 (
|
||||
<Dialog open={open} onOpenChange={(next) => !next && !busy && close()}>
|
||||
<DialogContent className="max-h-[calc(100vh-3rem)] overflow-x-hidden overflow-y-auto scrollbar-sleek sm:max-w-2xl [&>*]:min-w-0">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{copy.title}</DialogTitle>
|
||||
<DialogDescription>{copy.description}</DialogDescription>
|
||||
</DialogHeader>
|
||||
<SkillInstallMachineSelect
|
||||
value={environmentId}
|
||||
onChange={setEnvironmentId}
|
||||
localLabel={copy.localMachine}
|
||||
sshLabel={copy.ssh}
|
||||
disconnectedLabel={copy.disconnected}
|
||||
environments={runtimeEnvironments}
|
||||
sshTargets={[...sshTargetLabels.entries()].map(([id, label]) => ({
|
||||
id: `ssh:${id}`,
|
||||
label,
|
||||
connected: sshConnectionStates.get(id)?.status === 'connected'
|
||||
}))}
|
||||
/>
|
||||
|
||||
{busy && installs.length === 0 ? <Loader2 className="mx-auto size-5 animate-spin" /> : null}
|
||||
{!busy && installs.length === 0 ? (
|
||||
<p className="rounded-md border border-border p-4 text-sm text-muted-foreground">
|
||||
{copy.noInstalls}
|
||||
</p>
|
||||
) : null}
|
||||
{groups.length > 0 ? (
|
||||
<ul className="divide-y divide-border rounded-md border border-border">
|
||||
{groups.map((group) => (
|
||||
<SkillManagedInstallRow
|
||||
key={group.key}
|
||||
group={group}
|
||||
open={selectedKey === group.key}
|
||||
details={selectedKey === group.key ? details : null}
|
||||
versionId={selectedKey === group.key ? versionId : ''}
|
||||
busy={busy}
|
||||
confirmRemove={selectedKey === group.key && confirmRemove}
|
||||
installActive={Boolean(installProgress.activeOperationId)}
|
||||
editedWarning={selectedKey === group.key && destructiveConflict}
|
||||
bundleResult={selectedKey === group.key ? bundleResult : null}
|
||||
result={selectedKey === group.key ? result : null}
|
||||
onOpenChange={(next) => (next ? void selectInstall(group) : collapse())}
|
||||
onVersionChange={setVersionId}
|
||||
onInstall={(discardLocal) => void installVersion(discardLocal)}
|
||||
onCancelInstall={() => void cancelInstall()}
|
||||
onSendToMachine={(shareId) => {
|
||||
close()
|
||||
useAppStore.getState().openSkillShare(shareId)
|
||||
}}
|
||||
onRemove={(discardLocal) => void remove(discardLocal)}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
) : null}
|
||||
<SkillInstallManagementStatus
|
||||
resultLabel={result ? skillInstallResultLabel(result) : null}
|
||||
progressLabel={installProgress.phaseLabel}
|
||||
error={error}
|
||||
notice={notice}
|
||||
/>
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="ghost" onClick={close} disabled={busy}>
|
||||
{copy.close}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
<SkillInstallManagementDialogContent
|
||||
bundleResult={bundleResult}
|
||||
busy={busy}
|
||||
confirmRemove={confirmRemove}
|
||||
copy={copy}
|
||||
details={details}
|
||||
environmentId={environmentId}
|
||||
error={error}
|
||||
groups={groups}
|
||||
installs={installs}
|
||||
installActive={Boolean(installProgress.activeOperationId)}
|
||||
notice={notice}
|
||||
progressLabel={installProgress.phaseLabel}
|
||||
result={result}
|
||||
runtimeEnvironments={runtimeEnvironments}
|
||||
selectedKey={selectedKey}
|
||||
sshConnectionStates={sshConnectionStates}
|
||||
sshTargetLabels={sshTargetLabels}
|
||||
versionId={versionId}
|
||||
onCancelInstall={() => void cancelInstall()}
|
||||
onClose={close}
|
||||
onEnvironmentChange={setEnvironmentId}
|
||||
onInstall={(discardLocal) => void installVersion(discardLocal)}
|
||||
onOpenChange={(group, next) => (next ? void selectInstall(group) : collapse())}
|
||||
onRemove={(discardLocal) => void remove(discardLocal)}
|
||||
onSendToMachine={(shareId) => {
|
||||
close()
|
||||
useAppStore.getState().openSkillShare(shareId)
|
||||
}}
|
||||
onVersionChange={setVersionId}
|
||||
/>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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<typeof skillInstallManagementCopy>
|
||||
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<string, { status: string }>
|
||||
sshTargetLabels: ReadonlyMap<string, string>
|
||||
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 (
|
||||
<DialogContent className="max-h-[calc(100vh-3rem)] overflow-x-hidden overflow-y-auto scrollbar-sleek sm:max-w-2xl [&>*]:min-w-0">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{copy.title}</DialogTitle>
|
||||
<DialogDescription>{copy.description}</DialogDescription>
|
||||
</DialogHeader>
|
||||
<SkillInstallMachineSelect
|
||||
value={environmentId}
|
||||
onChange={onEnvironmentChange}
|
||||
localLabel={copy.localMachine}
|
||||
sshLabel={copy.ssh}
|
||||
disconnectedLabel={copy.disconnected}
|
||||
environments={runtimeEnvironments}
|
||||
sshTargets={[...sshTargetLabels.entries()].map(([id, label]) => ({
|
||||
id: `ssh:${id}`,
|
||||
label,
|
||||
connected: sshConnectionStates.get(id)?.status === 'connected'
|
||||
}))}
|
||||
/>
|
||||
{busy && installs.length === 0 ? <Loader2 className="mx-auto size-5 animate-spin" /> : null}
|
||||
{!busy && installs.length === 0 ? (
|
||||
<p className="rounded-md border border-border p-4 text-sm text-muted-foreground">
|
||||
{copy.noInstalls}
|
||||
</p>
|
||||
) : null}
|
||||
{groups.length > 0 ? (
|
||||
<ul className="divide-y divide-border rounded-md border border-border">
|
||||
{groups.map((group) => (
|
||||
<SkillManagedInstallRow
|
||||
key={group.key}
|
||||
group={group}
|
||||
open={selectedKey === group.key}
|
||||
details={selectedKey === group.key ? details : null}
|
||||
versionId={selectedKey === group.key ? versionId : ''}
|
||||
busy={busy}
|
||||
confirmRemove={selectedKey === group.key && confirmRemove}
|
||||
installActive={installActive}
|
||||
editedWarning={selectedKey === group.key && destructiveConflict}
|
||||
bundleResult={selectedKey === group.key ? bundleResult : null}
|
||||
result={selectedKey === group.key ? result : null}
|
||||
onOpenChange={(next) => onOpenChange(group, next)}
|
||||
onVersionChange={onVersionChange}
|
||||
onInstall={onInstall}
|
||||
onCancelInstall={onCancelInstall}
|
||||
onSendToMachine={onSendToMachine}
|
||||
onRemove={onRemove}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
) : null}
|
||||
<SkillInstallManagementStatus
|
||||
resultLabel={result ? skillInstallResultLabel(result) : null}
|
||||
progressLabel={progressLabel}
|
||||
error={error}
|
||||
notice={notice}
|
||||
/>
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="ghost" onClick={onClose} disabled={busy}>
|
||||
{copy.close}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user