feat(web): add skills menu
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
/*
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-05 10:45:08
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-05 10:45:08
|
||||
*/
|
||||
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
|
||||
import { Space, List, Flex, Tooltip } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import clsx from 'clsx'
|
||||
|
||||
import type { SkillModalRef } from './types'
|
||||
import type { Skill } from '@/views/Skills/types'
|
||||
import RbModal from '@/components/RbModal'
|
||||
import { getSkillList } from '@/api/skill'
|
||||
import SearchInput from '@/components/SearchInput'
|
||||
import Empty from '@/components/Empty'
|
||||
|
||||
/**
|
||||
* Props for SkillListModal Component
|
||||
*/
|
||||
interface SkillModalProps {
|
||||
/** Callback function to refresh parent component with selected skills */
|
||||
refresh: (rows: Skill[], type: 'skill') => void;
|
||||
/** Array of already selected skills to exclude from selection */
|
||||
selectedList: Skill[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Skill List Modal Component
|
||||
*
|
||||
* A modal dialog for selecting skills from a searchable list.
|
||||
* Features:
|
||||
* - Search functionality to filter skills by keywords
|
||||
* - Grid layout displaying skill cards with icons and descriptions
|
||||
* - Multi-select capability with visual feedback
|
||||
* - Excludes already selected skills from the list
|
||||
* - Displays skill name initial as avatar when no icon available
|
||||
*
|
||||
* @param refresh - Callback to update parent with selected skills
|
||||
* @param selectedList - Currently selected skills to filter out
|
||||
* @param ref - Forwarded ref exposing handleOpen and handleClose methods
|
||||
*/
|
||||
const SkillListModal = forwardRef<SkillModalRef, SkillModalProps>(({
|
||||
refresh,
|
||||
selectedList
|
||||
}, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [list, setList] = useState<Skill[]>([])
|
||||
const [filterList, setFilterList] = useState<Skill[]>([])
|
||||
const [query, setQuery] = useState<{keywords?: string}>({})
|
||||
const [selectedIds, setSelectedIds] = useState<string[]>([])
|
||||
const [selectedRows, setSelectedRows] = useState<Skill[]>([])
|
||||
|
||||
/**
|
||||
* Closes the modal and resets all state
|
||||
* Clears search query, selected IDs, and selected rows
|
||||
*/
|
||||
const handleClose = () => {
|
||||
setVisible(false);
|
||||
setQuery({})
|
||||
setSelectedIds([])
|
||||
setSelectedRows([])
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens the modal and resets selection state
|
||||
* Clears any previous selections when reopening
|
||||
*/
|
||||
const handleOpen = () => {
|
||||
setVisible(true);
|
||||
setQuery({})
|
||||
setSelectedIds([])
|
||||
setSelectedRows([])
|
||||
};
|
||||
|
||||
/**
|
||||
* Effect: Fetch skill list when modal is visible or search query changes
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
getList()
|
||||
}
|
||||
}, [query.keywords, visible])
|
||||
|
||||
/**
|
||||
* Fetches the skill list from API with current search parameters
|
||||
* Sorts by creation date in descending order
|
||||
*/
|
||||
const getList = () => {
|
||||
getSkillList({
|
||||
...query,
|
||||
pagesize: 100,
|
||||
})
|
||||
.then(res => {
|
||||
const response = res as { items: Skill[] }
|
||||
setList(response.items || [])
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves selected skills and closes modal
|
||||
* Passes selected skills to parent component via refresh callback
|
||||
*/
|
||||
const handleSave = () => {
|
||||
refresh(selectedRows, 'skill')
|
||||
setVisible(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes methods to parent component via ref
|
||||
* Allows parent to programmatically open/close the modal
|
||||
*/
|
||||
useImperativeHandle(ref, () => ({
|
||||
handleOpen,
|
||||
handleClose
|
||||
}));
|
||||
|
||||
/**
|
||||
* Handles search input changes and resets selection
|
||||
* Clears current selections when search query changes
|
||||
* @param value - Search keyword
|
||||
*/
|
||||
const handleSearch = (value?: string) => {
|
||||
setQuery({keywords: value})
|
||||
setSelectedIds([])
|
||||
setSelectedRows([])
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles skill selection state
|
||||
* Adds skill to selection if not selected, removes if already selected
|
||||
* @param item - Skill to select/deselect
|
||||
*/
|
||||
const handleSelect = (item: Skill) => {
|
||||
const index = selectedIds.indexOf(item.id)
|
||||
if (index === -1) {
|
||||
// Add to selection
|
||||
setSelectedIds([...selectedIds, item.id])
|
||||
setSelectedRows([...selectedRows, item])
|
||||
} else {
|
||||
// Remove from selection
|
||||
setSelectedIds(selectedIds.filter(id => id !== item.id))
|
||||
setSelectedRows(selectedRows.filter(row => row.id !== item.id))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Effect: Filter out already selected skills from the display list
|
||||
* Updates filterList whenever list or selectedList changes
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (list.length && selectedList.length) {
|
||||
const unSelectedList = list.filter(item => selectedList.findIndex(vo => vo.id === item.id) < 0)
|
||||
setFilterList([...unSelectedList])
|
||||
} else if (list.length) {
|
||||
setFilterList([...list])
|
||||
}
|
||||
}, [list, selectedList])
|
||||
|
||||
return (
|
||||
<>
|
||||
<RbModal
|
||||
title={t('application.chooseSkill')}
|
||||
open={visible}
|
||||
onCancel={handleClose}
|
||||
okText={t('common.save')}
|
||||
onOk={handleSave}
|
||||
width={1000}
|
||||
>
|
||||
<Space size={24} direction="vertical" className="rb:w-full">
|
||||
{/* Search input for filtering skills */}
|
||||
<SearchInput
|
||||
placeholder={t('skills.searchPlaceholder')}
|
||||
onSearch={handleSearch}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
{/* Display empty state or skill grid */}
|
||||
{filterList.length === 0
|
||||
? <Empty />
|
||||
: <List
|
||||
grid={{ gutter: 16, column: 2 }}
|
||||
dataSource={filterList}
|
||||
renderItem={(item: Skill) => (
|
||||
<List.Item>
|
||||
{/* Skill card with selection state styling */}
|
||||
<div key={item.id} className={clsx("rb:border rb:rounded-lg rb:p-[17px_16px] rb:cursor-pointer rb:hover:bg-[#F0F3F8]", {
|
||||
"rb:bg-[rgba(21,94,239,0.06)] rb:border-[#155EEF] rb:text-[#155EEF]": selectedIds.includes(item.id),
|
||||
"rb:border-[#DFE4ED] rb:text-[#212332]": !selectedIds.includes(item.id),
|
||||
})} onClick={() => handleSelect(item)}>
|
||||
<Flex>
|
||||
{/* Skill avatar showing first letter of name */}
|
||||
<div className="rb:w-12 rb:h-12 rb:rounded-lg rb:mr-3.25 rb:bg-[#155eef] rb:flex rb:items-center rb:justify-center rb:text-[28px] rb:text-[#ffffff]">
|
||||
{item.name[0]}
|
||||
</div>
|
||||
{/* Skill name and description */}
|
||||
<div className="rb:flex-1 rb:max-w-[calc(100%-60px)]">
|
||||
<div className="rb:font-medium rb:wrap-break-word rb:line-clamp-1">{item.name}</div>
|
||||
<Tooltip title={item.description}>
|
||||
<div className="rb:text-[#5B6167] rb:text-[12px] rb:leading-4.25 rb:font-regular rb:-mt-1 rb:wrap-break-word rb:line-clamp-1">{item.description}</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Flex>
|
||||
</div>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
}
|
||||
</Space>
|
||||
</RbModal>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export default SkillListModal;
|
||||
161
web/src/views/ApplicationConfig/components/Skill/SkillsItem.tsx
Normal file
161
web/src/views/ApplicationConfig/components/Skill/SkillsItem.tsx
Normal file
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-05 10:43:03
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-05 10:43:03
|
||||
*/
|
||||
import { type FC, useEffect, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Space, Button, Form, Flex, Tooltip, Checkbox } from 'antd'
|
||||
import { CloseOutlined, CheckCircleFilled } from '@ant-design/icons'
|
||||
|
||||
import type {
|
||||
SkillConfigForm,
|
||||
SkillModalRef,
|
||||
} from './types'
|
||||
import Empty from '@/components/Empty'
|
||||
import SkillListModal from './SkillListModal'
|
||||
import Card from '../Card'
|
||||
import RbAlert from '@/components/RbAlert'
|
||||
import type { Skill } from '@/views/Skills/types'
|
||||
|
||||
/**
|
||||
* Props for SkillsItem Component
|
||||
*/
|
||||
interface SkillsItemProps {
|
||||
/** Title displayed in the card header */
|
||||
title: string;
|
||||
/** Form field path for nested form structure */
|
||||
parentName: string[];
|
||||
/** Whether to show "Allow all skills" checkbox option */
|
||||
supportAll?: boolean;
|
||||
/** Message displayed when no skills are configured */
|
||||
emptyTitle: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skills Item Component
|
||||
*
|
||||
* Displays and manages a list of configured skills with the following features:
|
||||
* - Add new skills via modal selection
|
||||
* - Optional "Allow all skills" toggle
|
||||
* - Display skill cards with icons and descriptions
|
||||
* - Remove individual skills
|
||||
* - Empty state when no skills configured
|
||||
* - Alert message when all skills are enabled
|
||||
*
|
||||
* @param title - Card header title
|
||||
* @param parentName - Form field path array for nested structure
|
||||
* @param supportAll - Enable "Allow all skills" checkbox
|
||||
* @param emptyTitle - Empty state message
|
||||
*/
|
||||
const SkillsItem: FC<SkillsItemProps> = ({
|
||||
title,
|
||||
parentName,
|
||||
supportAll = false,
|
||||
emptyTitle
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const skillModalRef = useRef<SkillModalRef>(null)
|
||||
const form = Form.useFormInstance()
|
||||
const allSkills = Form.useWatch([...parentName, 'all_skills'], form)
|
||||
|
||||
/**
|
||||
* Opens the skill selection modal
|
||||
*/
|
||||
const handleAddSkill = () => {
|
||||
skillModalRef.current?.handleOpen()
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates form with newly selected skills
|
||||
* Merges new selections with existing skills, avoiding duplicates
|
||||
* @param values - Array of newly selected skills
|
||||
*/
|
||||
const refresh = (values: SkillConfigForm['skill_ids']) => {
|
||||
const currentSkills = form.getFieldValue([...parentName, 'skill_ids']) || []
|
||||
const newSkills = values?.filter(v => !currentSkills?.find((s: Skill) => s.id === (v as Skill).id)) || []
|
||||
form.setFieldValue([...parentName, 'skill_ids'], [...currentSkills, ...newSkills])
|
||||
}
|
||||
|
||||
/**
|
||||
* Effect: Clear skill list when "all skills" is enabled
|
||||
*/
|
||||
useEffect(() => {
|
||||
form.setFieldValue([...parentName, 'skill_ids'], [])
|
||||
}, [allSkills])
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={title}
|
||||
extra={
|
||||
<Space>
|
||||
{/* "Allow all skills" checkbox - only shown if supportAll is true */}
|
||||
{supportAll && <Form.Item name={[...parentName, 'all_skills']} valuePropName="checked" noStyle>
|
||||
<Checkbox>{t('application.allSkill')}</Checkbox>
|
||||
</Form.Item>}
|
||||
{/* Add skill button - disabled when all skills are enabled */}
|
||||
<Button disabled={allSkills} style={{ padding: '0 8px', height: '24px' }} onClick={handleAddSkill}>+ {t('application.addSkill')}</Button>
|
||||
</Space>
|
||||
}
|
||||
variant="borderL"
|
||||
>
|
||||
{/* Show alert when all skills enabled, otherwise show skill list */}
|
||||
{allSkills
|
||||
? <RbAlert color="green" icon={<CheckCircleFilled />}>{t('application.allSkillIntro')}</RbAlert>
|
||||
: <>
|
||||
<Form.List name={[...parentName, 'skill_ids']}>
|
||||
{(fields, { remove }) => (
|
||||
fields.length === 0 ? (
|
||||
/* Empty state when no skills configured */
|
||||
<Empty size={88} subTitle={emptyTitle} />
|
||||
) : (
|
||||
/* Render list of configured skills */
|
||||
<Space direction="vertical" size={12} className="rb:w-full">
|
||||
{fields.map((field) => {
|
||||
const skill = form.getFieldValue([...parentName, 'skill_ids', field.name])
|
||||
return (
|
||||
/* Individual skill card */
|
||||
<div key={field.key} className="rb:flex rb:items-center rb:justify-between rb:p-[12px_16px] rb:bg-[#FBFDFF] rb:border rb:border-[#DFE4ED] rb:rounded-lg">
|
||||
<Flex className="rb:flex-1 rb:max-w-[calc(100%-186px)]!">
|
||||
{/* Skill icon or fallback initial */}
|
||||
{skill.icon
|
||||
? <img src={skill.icon} className="rb:mr-3.25 rb:size-12 rb:rounded-lg" />
|
||||
: <div className="rb:w-12 rb:h-12 rb:rounded-lg rb:mr-3.25 rb:bg-[#155eef] rb:flex rb:items-center rb:justify-center rb:text-[28px] rb:text-[#ffffff]">
|
||||
{skill.name?.[0]}
|
||||
</div>
|
||||
}
|
||||
{/* Skill name and description */}
|
||||
<div className="rb:flex-1 rb:max-w-[calc(100%-60px)]">
|
||||
<div className="rb:font-medium rb:wrap-break-word rb:line-clamp-1">{skill.name}</div>
|
||||
<Tooltip title={skill.desciption}>
|
||||
<div className="rb:text-[#5B6167] rb:text-[12px] rb:leading-4.25 rb:font-regular rb:-mt-1 rb:wrap-break-word rb:line-clamp-1">{skill.desciption}</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Flex>
|
||||
<Space size={16} align="center">
|
||||
{/* Remove skill button */}
|
||||
<CloseOutlined
|
||||
className="rb:cursor-pointer rb:text-[#5B6167] hover:rb:text-[#155EEF]"
|
||||
onClick={() => remove(field.name)}
|
||||
/>
|
||||
</Space>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</Space>
|
||||
)
|
||||
)}
|
||||
</Form.List>
|
||||
</>
|
||||
}
|
||||
{/* Skill selection modal */}
|
||||
<SkillListModal
|
||||
ref={skillModalRef}
|
||||
selectedList={form.getFieldValue([...parentName, 'skill_ids']) || []}
|
||||
refresh={refresh}
|
||||
/>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
export default SkillsItem
|
||||
151
web/src/views/ApplicationConfig/components/Skill/index.tsx
Normal file
151
web/src/views/ApplicationConfig/components/Skill/index.tsx
Normal file
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-05 10:42:56
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-05 10:42:56
|
||||
*/
|
||||
import { useEffect, type FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Space, Button, Switch, Form, Flex } from 'antd'
|
||||
|
||||
import type {
|
||||
SkillConfigForm,
|
||||
} from './types'
|
||||
import Card from '../Card'
|
||||
import SkillsItem from './SkillsItem'
|
||||
import { getSkillList } from '@/api/skill'
|
||||
import type { Skill } from '@/views/Skills/types'
|
||||
|
||||
/**
|
||||
* Process flow steps for skill execution
|
||||
* Defines the sequential steps in the skill execution workflow
|
||||
*/
|
||||
const processObj = [
|
||||
'receiveTask', // Step 1: Receive task
|
||||
'analyTask', // Step 2: Analyze task intent
|
||||
'dynamicMatchSkill', // Step 3: Dynamically match appropriate skill
|
||||
'executeTask' // Step 4: Execute the task
|
||||
]
|
||||
|
||||
/**
|
||||
* Skill Configuration Component
|
||||
*
|
||||
* Main component for managing agent skill configuration including:
|
||||
* - Enabling/disabling skill functionality
|
||||
* - Configuring dynamic skill binding
|
||||
* - Displaying skill execution process flow
|
||||
* - Managing skill selection and assignment
|
||||
*
|
||||
* @param value - Current skill configuration values
|
||||
* @param onChange - Callback function when configuration changes
|
||||
*/
|
||||
const Skill: FC<{value?: SkillConfigForm; onChange?: (config: SkillConfigForm) => void}> = () => {
|
||||
const { t } = useTranslation()
|
||||
const form = Form.useFormInstance()
|
||||
const skillConfig = Form.useWatch(['skills'], form)
|
||||
|
||||
/**
|
||||
* Effect: Fetch and populate skill details for skills without names
|
||||
* Ensures all selected skills have complete information by fetching from API
|
||||
*/
|
||||
useEffect(() => {
|
||||
const { skill_ids = [] } = skillConfig || {}
|
||||
|
||||
// Filter skills that don't have name property
|
||||
const skillsWithoutName = skill_ids.filter((vo: Skill) => !vo.name)
|
||||
|
||||
if (skillsWithoutName.length > 0) {
|
||||
getSkillList({ page: 1, pagesize: 100 })
|
||||
.then(res => {
|
||||
const response = res as { items: Skill[] }
|
||||
// Create a map of skill ID to skill object for quick lookup
|
||||
const map = response.items.reduce((prev: any, curr: any) => {
|
||||
prev[curr.id] = curr
|
||||
return prev
|
||||
}, {})
|
||||
|
||||
// Merge fetched skill details with existing skill IDs
|
||||
const newSkillIds = skill_ids.map((vo: any) => {
|
||||
return {
|
||||
...vo,
|
||||
...map[vo.id]
|
||||
}
|
||||
})
|
||||
|
||||
form.setFieldValue(['skills', 'skill_ids'], newSkillIds)
|
||||
})
|
||||
}
|
||||
|
||||
}, [skillConfig?.skill_ids])
|
||||
|
||||
/**
|
||||
* Effect: Reset skill configuration when skill functionality is disabled
|
||||
* Clears all_skills flag and skill_ids array when enabled is set to false
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (!skillConfig?.enabled) {
|
||||
form.setFieldValue('skills', {
|
||||
...skillConfig,
|
||||
all_skills: false,
|
||||
skill_ids: []
|
||||
})
|
||||
}
|
||||
}, [skillConfig?.enabled])
|
||||
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={<>
|
||||
{t('application.skill')}
|
||||
<span className="rb:font-regular rb:text-[12px] rb:text-[#5B6167]"> ({t('application.skillTitle')})</span>
|
||||
</>}
|
||||
extra={
|
||||
<Space>
|
||||
{/* Help button for skill configuration guidance */}
|
||||
<Button style={{ padding: '0 8px', height: '24px' }}>{t('application.skillHelp')}</Button>
|
||||
{/* Toggle switch to enable/disable skill functionality */}
|
||||
<Form.Item
|
||||
valuePropName="checked"
|
||||
name={['skills', 'enabled']}
|
||||
noStyle
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
{/* Render skill configuration UI only when enabled */}
|
||||
{skillConfig?.enabled && <Flex vertical gap={12}>
|
||||
{/* Dynamic skill binding configuration section */}
|
||||
<Form.Item noStyle>
|
||||
<SkillsItem
|
||||
title={t('application.dynamicBindingSkill')}
|
||||
parentName={['skills']}
|
||||
supportAll={true}
|
||||
emptyTitle={t('application.dynamicBindingSkill_empty')}
|
||||
/>
|
||||
</Form.Item>
|
||||
{/* Execution process preview card showing workflow steps */}
|
||||
<Card
|
||||
title={t('application.executeProcessPreview')}
|
||||
variant="borderL"
|
||||
>
|
||||
<Flex align="center" gap={8} className="rb:text-[12px]">
|
||||
{/* Render each step in the process flow with numbered badges */}
|
||||
{processObj.map((key, index) => (<>
|
||||
<Flex align="center" gap={8} className="rb:border rb:border-[#DFE4ED] rb:rounded-lg rb:bg-white rb:p-2!">
|
||||
{/* Step number badge */}
|
||||
<div className="rb:size-4 rb:rounded-full rb:bg-[#155EEF] rb:text-white rb:flex rb:items-center rb:justify-center rb:font-medium">{index + 1}</div>
|
||||
{/* Step label */}
|
||||
<span>{t(`application.${key}`)}</span>
|
||||
</Flex>
|
||||
{/* Arrow separator between steps (except after last step) */}
|
||||
{index !== processObj.length - 1 && <div className="rb:text-[#8C9196]">→</div>}
|
||||
</>))}
|
||||
</Flex>
|
||||
</Card>
|
||||
</Flex>}
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
export default Skill
|
||||
53
web/src/views/ApplicationConfig/components/Skill/types.ts
Normal file
53
web/src/views/ApplicationConfig/components/Skill/types.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-05 10:43:09
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-05 10:43:09
|
||||
*/
|
||||
import type { Skill } from '@/views/Skills/types'
|
||||
|
||||
/**
|
||||
* Skill Configuration Form Data Structure
|
||||
* Used to manage skill configuration settings in the application
|
||||
*/
|
||||
export interface SkillConfigForm {
|
||||
/** Whether skill configuration is enabled */
|
||||
enabled?: boolean;
|
||||
/** Whether all skills are accessible */
|
||||
all_skills?: boolean;
|
||||
/** Array of selected skill IDs or full skill objects */
|
||||
skill_ids?: Skill[] | string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Skill Binding Mode Types
|
||||
* Defines different strategies for skill assignment
|
||||
*/
|
||||
export type SkillMode = 'staticBinding' | 'dynamicBinding' | 'mixedMode'
|
||||
|
||||
/**
|
||||
* Skill Configuration Modal Reference Interface
|
||||
* Provides methods to control the skill configuration modal
|
||||
*/
|
||||
export interface SkillConfigModalRef {
|
||||
/** Opens the modal with optional initial data */
|
||||
handleOpen: (data: SkillConfigForm) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skill Global Configuration Modal Reference Interface
|
||||
* Provides methods to control the global skill configuration modal
|
||||
*/
|
||||
export interface SkillGlobalConfigModalRef {
|
||||
/** Opens the global configuration modal */
|
||||
handleOpen: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skill Selection Modal Reference Interface
|
||||
* Provides methods to control the skill selection modal
|
||||
*/
|
||||
export interface SkillModalRef {
|
||||
/** Opens the modal with optional skill configuration array */
|
||||
handleOpen: (config?: SkillConfigForm[]) => void;
|
||||
}
|
||||
Reference in New Issue
Block a user