Skip to content

Commit

Permalink
Merge pull request #127 from animo/feature/G-119-agent-deletion
Browse files Browse the repository at this point in the history
feat: Agent deletion
  • Loading branch information
Tommylans authored May 11, 2023
2 parents f81c2d1 + de3da7c commit a4f0211
Show file tree
Hide file tree
Showing 13 changed files with 148 additions and 36 deletions.
2 changes: 1 addition & 1 deletion packages/siera-desktop/src/ui/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export const App = () => {
>
<UpdateInformationProvider checkUpdate={checkForUpdates}>
<ConfigProvider configRepository={configRepository}>
<AgentManagerProvider>
<AgentManagerProvider agentDependenciesProvider={agentDependenciesProvider}>
<AgentContextWrapper>
<SieraUiApp router={router} />
</AgentContextWrapper>
Expand Down
4 changes: 3 additions & 1 deletion packages/siera-ui/src/SieraUiApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import { sieraUiTheme } from './SieraUiTheme'
import { GlobalErrorHandler } from './components/GlobalErrorHandler'
import { UpdateNotifierModal } from './components/UpdateNotifierModal'
import { useConfigUnsafe } from './contexts/ConfigProvider'
import { CreateAgentModal, PresentInviteModal, ConfirmActionModal } from './modals'
import { ConfirmActionModal } from './modals/ConfirmActionModal'
import { CreateAgentModal } from './modals/CreateAgentModal'
import { PresentInviteModal } from './modals/PresentInviteModal'

interface SieraUiAppProps {
router: RouterProviderProps['router']
Expand Down
5 changes: 5 additions & 0 deletions packages/siera-ui/src/SieraUiTheme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ export const sieraUiTheme = (colorScheme: ColorScheme): MantineThemeOverride =>
dropdown: {
backgroundColor: theme.colorScheme === 'dark' ? theme.colors.backgroundOne[7] : '#ffffff',
},
item: {
padding: '0.4rem 0.6rem',
minWidth: '8rem',
},
}),
},

Expand All @@ -39,6 +43,7 @@ export const sieraUiTheme = (colorScheme: ColorScheme): MantineThemeOverride =>
fontWeight: 700,
fontSize: '1.7rem',
padding: `0 ${theme.spacing.xl}px`,
color: theme.colors.textOne[7],
},
modal: {
border: `1px solid ${theme.colors.backgroundOne[6]}`,
Expand Down
11 changes: 8 additions & 3 deletions packages/siera-ui/src/components/RecordActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ActionIcon, createStyles, Group, Loader } from '@mantine/core'
import { IconTrash } from '@tabler/icons'
import React from 'react'

import { openConfirmActionModal } from '../modals'
import { openConfirmActionModal } from '../modals/ConfirmActionModal'

import { PrimaryButton, SecondaryButton } from './generic'

Expand All @@ -28,9 +28,14 @@ export const RecordActions = ({ onAccept, onDecline, onDelete, isLoading, propag
returnFn()
}

const dangerAction = (returnFn: () => void) => (event: React.MouseEvent<HTMLButtonElement>) => {
const dangerAction = (confirmMethod: () => void) => (event: React.MouseEvent<HTMLButtonElement>) => {
!propagateEvent && event.stopPropagation()
openConfirmActionModal('Delete record', 'Are you sure you want to delete this record?', returnFn)
openConfirmActionModal({
title: 'Delete record',
description: 'Are you sure you want to delete this record?',
confirmLabel: 'Delete',
onConfirm: confirmMethod,
})
}

const actions = [
Expand Down
13 changes: 12 additions & 1 deletion packages/siera-ui/src/components/generic/buttons/Buttons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { ButtonProps } from '@mantine/core'
import type { ButtonHTMLAttributes } from 'react'

import { Button, createStyles } from '@mantine/core'
import { useColorScheme } from '@mantine/hooks'
import { IconArrowLeft, IconPlus } from '@tabler/icons'
import React from 'react'

Expand Down Expand Up @@ -43,12 +44,22 @@ type PrimaryButtonProps = StandardButton & {
}

export const PrimaryButton = (props: PrimaryButtonProps) => {
const colorScheme = useColorScheme()
const { classes, cx } = useStyles()
const { withPlusIcon } = props

const plusButton = withPlusIcon && <IconPlus size={16} stroke={3} />

return <Button leftIcon={plusButton} {...props} className={cx(classes.primary, props.className)} />
return (
<Button
leftIcon={plusButton}
loaderProps={{
stroke: colorScheme === 'dark' ? 'black' : 'white',
}}
{...props}
className={cx(classes.primary, props.className)}
/>
)
}

export const SecondaryButton = (props: StandardButton) => {
Expand Down
41 changes: 38 additions & 3 deletions packages/siera-ui/src/contexts/AgentManagerContext.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import type { AgentConfigRecord } from '@animo/siera-core'
import type { AgentConfigRecord, AgentDependenciesProvider } from '@animo/siera-core'
import type { ReactNode } from 'react'

import { agentInitializer } from '@animo/siera-core/src/agent/AgentInitializer'
import { showNotification } from '@mantine/notifications'
import React, { createContext, useContext, useMemo, useState } from 'react'

import { openConfirmActionModal } from '../modals/ConfirmActionModal'

import { useConfigUnsafe } from './ConfigProvider'

export interface IAgentContext {
agents: AgentConfigRecord[]
currentAgentId?: string
setCurrentAgentId: (id: string | undefined) => void
addAgent: (agent: AgentConfigRecord) => Promise<void>
removeAgent: (agentId: string) => Promise<void>
loading: boolean
logout: () => void
}
Expand All @@ -30,23 +35,53 @@ export const useCurrentAgentRecord = (): AgentConfigRecord | undefined => {

interface AgentManagerProviderProps {
children?: ReactNode
agentDependenciesProvider: AgentDependenciesProvider
}

export const AgentManagerProvider = ({ children }: AgentManagerProviderProps) => {
const { config, addAgent, loading } = useConfigUnsafe()
export const AgentManagerProvider = ({ children, agentDependenciesProvider }: AgentManagerProviderProps) => {
const { config, addAgent, removeAgent: removeAgentFromConfig, loading } = useConfigUnsafe()
const [currentAgentId, setCurrentAgentId] = useState<string>()

const logout = () => {
setCurrentAgentId(undefined)
}

const removeAgent = async (agentId: string) => {
const agentRecord = config?.agents.find((agent) => agent.id === agentId)
if (!agentRecord) {
showNotification({
title: 'Error',
message: `Agent with id ${agentId} not found`,
})
return
}

try {
const loadedAgent = await agentInitializer(agentRecord.agentConfig, agentDependenciesProvider)
await loadedAgent.wallet.delete()
await loadedAgent.shutdown()
} catch (error) {
openConfirmActionModal({
title: 'Error while removing agent',
description: 'Do you want to continue? The agent wallet will not be deleted.',
onConfirm: async () => {
await removeAgentFromConfig(agentId)
},
})
return
}

await removeAgentFromConfig(agentId)
}

return (
<AgentManagerContext.Provider
value={{
setCurrentAgentId,
currentAgentId,
agents: config?.agents || [],
addAgent,
removeAgent,
loading,
logout,
}}
Expand Down
10 changes: 10 additions & 0 deletions packages/siera-ui/src/contexts/ConfigProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ type ConfigContext = {
config?: SieraUiConfig
loading: boolean
addAgent: (agent: AgentConfigRecord) => Promise<void>
removeAgent: (agentId: string) => Promise<void>
setColorScheme: (colorScheme: 'dark' | 'light') => Promise<void>
}

Expand Down Expand Up @@ -62,6 +63,14 @@ export const ConfigProvider = ({ children, configRepository }: ConfigProviderPro
setConfig(updatedConfig)
}

const removeAgent = async (agentId: string) => {
if (!config) return

const updatedConfig = { ...config, agents: config.agents.filter((a) => a.id !== agentId) }
await saveConfig(updatedConfig)
setConfig(updatedConfig)
}

const setColorScheme = async (colorScheme: 'dark' | 'light') => {
if (!config) return

Expand All @@ -76,6 +85,7 @@ export const ConfigProvider = ({ children, configRepository }: ConfigProviderPro
config,
loading,
addAgent,
removeAgent,
setColorScheme,
}}
>
Expand Down
6 changes: 3 additions & 3 deletions packages/siera-ui/src/layout/LayoutNavBar.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Agent } from '@aries-framework/core'

import { ActionIcon, createStyles, Flex, Menu, Navbar, useMantineColorScheme } from '@mantine/core'
import { IconChevronDown } from '@tabler/icons'
import { IconDotsVertical } from '@tabler/icons'
import React, { useState } from 'react'

import { useAgentManager } from '../contexts/AgentManagerContext'
Expand Down Expand Up @@ -71,10 +71,10 @@ export const LayoutNavBar = ({ navigationItems, agent }: LayoutNavigationProps)
<Navbar.Section mx="md" className={classes.layoutAvatar}>
<Flex gap="xs" justify="space-between">
<LayoutAvatar agent={agent} />
<Menu shadow="md" width={200} position="bottom-end">
<Menu shadow="md" position="bottom-end" withArrow>
<Menu.Target>
<ActionIcon mt="xs">
<IconChevronDown />
<IconDotsVertical size={20} />
</ActionIcon>
</Menu.Target>

Expand Down
46 changes: 32 additions & 14 deletions packages/siera-ui/src/modals/ConfirmActionModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,62 @@ import type { ContextModalProps } from '@mantine/modals'

import { Divider, Group, Stack, Text } from '@mantine/core'
import { openContextModal } from '@mantine/modals'
import React from 'react'
import React, { useState } from 'react'

import { PrimaryButton, SecondaryButton } from '../components/generic'

type InnerProps = {
message: string
onConfirm: () => void
description: string
onConfirm: () => Promise<void> | void
confirmLabel?: string
}

export const ConfirmActionModal = ({
id,
context,
innerProps: { message, onConfirm },
innerProps: { description, onConfirm, confirmLabel },
}: ContextModalProps<InnerProps>) => {
const [isSubmitting, setIsSubmitting] = useState(false)
const onConfirmClick = async () => {
setIsSubmitting(true)
try {
await onConfirm()
setIsSubmitting(false)
context.closeModal(id)
} catch (error) {
setIsSubmitting(false)
throw error
}
}

return (
<Stack mt="md">
<Text px="xl">{message}</Text>
<Text px="xl">{description}</Text>
<Divider mt="md" mb="xs" />
<Group position="right" px="xl">
<SecondaryButton onClick={() => context.closeModal(id)}>Cancel</SecondaryButton>
<PrimaryButton
onClick={() => {
onConfirm()
context.closeModal(id)
}}
>
Confirm
<PrimaryButton onClick={onConfirmClick} loading={isSubmitting}>
{confirmLabel}
</PrimaryButton>
</Group>
</Stack>
)
}

export const openConfirmActionModal = (title: string, message: string, onConfirm: () => void) => {
interface OpenConfirmActionModalProps extends InnerProps {
title: string
}

export const openConfirmActionModal = ({
title,
description,
onConfirm,
confirmLabel = 'Confirm',
}: OpenConfirmActionModalProps) => {
openContextModal({
modal: ConfirmActionModal.name,
title,
innerProps: { message, onConfirm },
innerProps: { description: description, onConfirm, confirmLabel },
centered: true,
withCloseButton: false,
})
Expand Down
3 changes: 0 additions & 3 deletions packages/siera-ui/src/modals/index.ts

This file was deleted.

39 changes: 34 additions & 5 deletions packages/siera-ui/src/pages/AgentSelectionScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
import { Box, Card, Container, createStyles, Flex, Text, Title, UnstyledButton } from '@mantine/core'
import type { AgentConfigRecord } from '@animo/siera-core'

import { ActionIcon, Box, Card, Container, createStyles, Flex, Menu, Text, Title, UnstyledButton } from '@mantine/core'
import { IconDotsVertical } from '@tabler/icons'
import React from 'react'

import { Loading } from '../components/Loading'
import { SmartAvatar } from '../components/SmartAvatar'
import { PrimaryButton } from '../components/generic'
import { useAgentManager } from '../contexts/AgentManagerContext'
import { useNavigation } from '../hooks/useNavigation'
import { openCreateAgentModal } from '../modals'
import { openConfirmActionModal } from '../modals/ConfirmActionModal'
import { openCreateAgentModal } from '../modals/CreateAgentModal'

import { WelcomeScreen } from './agent/WelcomeScreen'

const useStyles = createStyles((theme) => ({
card: {
backgroundColor: theme.colorScheme === 'dark' ? theme.colors.backgroundOne[7] : '#ffffff',
border: `2px solid ${theme.colors.backgroundOne[6]}`,
overflow: 'unset',

'&:hover': {
border: `2px solid ${theme.colors.backgroundOne[5]}`,
Expand All @@ -33,13 +38,24 @@ const useStyles = createStyles((theme) => ({
export const AgentSelectionScreen = () => {
const { classes } = useStyles()
const navigation = useNavigation()
const { agents, setCurrentAgentId, loading } = useAgentManager()
const { agents, setCurrentAgentId, removeAgent, loading } = useAgentManager()

const switchToAgent = (agentId: string) => {
setCurrentAgentId(agentId)
navigation.navigate('/agent/connections')
}

const stopPropagation = (event: React.MouseEvent<HTMLButtonElement | HTMLDivElement>) => event.stopPropagation()

const onDeleteAgent = (agentConfigRecord: AgentConfigRecord) => {
openConfirmActionModal({
title: 'Delete agent',
description: `Are you sure you want to delete '${agentConfigRecord.name}' ?`,
confirmLabel: 'Delete',
onConfirm: async () => await removeAgent(agentConfigRecord.id),
})
}

if (loading) {
return <Loading description="Loading configuration" />
}
Expand All @@ -64,8 +80,8 @@ export const AgentSelectionScreen = () => {
<Flex gap="md" wrap="wrap" mt="xl">
{agents.map((agent) => (
<UnstyledButton key={agent.id} onClick={() => switchToAgent(agent.id)}>
<Card h={80} w={220} className={classes.card} radius="md">
<Flex gap="sm" maw="100%" wrap="nowrap">
<Card h={80} w={220} className={classes.card} radius="md" p={0}>
<Flex gap="sm" maw="100%" align="center" wrap="nowrap" pl="sm" mih="100%">
<SmartAvatar src={agent.agentConfig.connectionImageUrl} size={38} radius="xl">
{agent.agentConfig.label}
</SmartAvatar>
Expand All @@ -77,6 +93,19 @@ export const AgentSelectionScreen = () => {
Native (AFJ)
</Text>
</Box>
<Flex style={{ flex: 'auto', alignSelf: 'start' }} justify="end">
<Menu shadow="md" position="bottom-end" withArrow>
<Menu.Target>
<ActionIcon mt={2} onClick={stopPropagation} variant="transparent">
<IconDotsVertical size={16} />
</ActionIcon>
</Menu.Target>

<Menu.Dropdown onClick={stopPropagation}>
<Menu.Item onClick={() => onDeleteAgent(agent)}>Delete</Menu.Item>
</Menu.Dropdown>
</Menu>
</Flex>
</Flex>
</Card>
</UnstyledButton>
Expand Down
Loading

0 comments on commit a4f0211

Please sign in to comment.