feat(web): skill toolList add is_active
This commit is contained in:
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
import { Outlet } from 'react-router-dom';
|
import { Outlet } from 'react-router-dom';
|
||||||
import { useEffect, type FC } from 'react';
|
import { useEffect, type FC } from 'react';
|
||||||
|
import { Layout } from 'antd';
|
||||||
|
|
||||||
import { useUser } from '@/store/user';
|
import { useUser } from '@/store/user';
|
||||||
|
|
||||||
@@ -35,10 +36,10 @@ const BasicAuthLayout: FC = () => {
|
|||||||
}, [getUserInfo]);
|
}, [getUserInfo]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rb:relative rb:min-h-screen rb:w-screen">
|
<Layout className="rb:min-h-screen!">
|
||||||
{/* Render child routes without additional UI */}
|
{/* Render child routes without additional UI */}
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</div>
|
</Layout>
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
import { type FC, useRef, useState, useEffect } from 'react'
|
import { type FC, useRef, useState, useEffect } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Space, Button, List } from 'antd'
|
import { Space, Button, Flex } from 'antd'
|
||||||
|
|
||||||
import Card from '@/views/ApplicationConfig/components/Card'
|
import Card from '@/views/ApplicationConfig/components/Card'
|
||||||
import type {
|
import type {
|
||||||
@@ -22,6 +22,7 @@ import type {
|
|||||||
import Empty from '@/components/Empty'
|
import Empty from '@/components/Empty'
|
||||||
import ToolModal from './ToolModal'
|
import ToolModal from './ToolModal'
|
||||||
import { getToolMethods, getToolDetail } from '@/api/tools'
|
import { getToolMethods, getToolDetail } from '@/api/tools'
|
||||||
|
import Tag from '@/components/Tag'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tool List Component Props
|
* Tool List Component Props
|
||||||
@@ -61,6 +62,7 @@ const ToolList: FC<ToolListProps> = ({value, onChange}) => {
|
|||||||
const mcpFilterItem = (methods as any[]).find(vo => vo.name === item.operation)
|
const mcpFilterItem = (methods as any[]).find(vo => vo.name === item.operation)
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
|
is_active: (toolDetail as any).is_active,
|
||||||
label: mcpFilterItem?.description,
|
label: mcpFilterItem?.description,
|
||||||
method_id: mcpFilterItem?.method_id,
|
method_id: mcpFilterItem?.method_id,
|
||||||
value: mcpFilterItem?.name,
|
value: mcpFilterItem?.name,
|
||||||
@@ -74,6 +76,7 @@ const ToolList: FC<ToolListProps> = ({value, onChange}) => {
|
|||||||
const builtinFilterItem = (methods as any[]).find(vo => vo.name === item.operation)
|
const builtinFilterItem = (methods as any[]).find(vo => vo.name === item.operation)
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
|
is_active: (toolDetail as any).is_active,
|
||||||
label: builtinFilterItem?.description,
|
label: builtinFilterItem?.description,
|
||||||
method_id: builtinFilterItem?.method_id,
|
method_id: builtinFilterItem?.method_id,
|
||||||
value: builtinFilterItem?.name,
|
value: builtinFilterItem?.name,
|
||||||
@@ -84,6 +87,7 @@ const ToolList: FC<ToolListProps> = ({value, onChange}) => {
|
|||||||
// Single method: Use first method
|
// Single method: Use first method
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
|
is_active: (toolDetail as any).is_active,
|
||||||
label: (methods as any[])[0]?.description,
|
label: (methods as any[])[0]?.description,
|
||||||
method_id: (methods as any[])[0]?.method_id,
|
method_id: (methods as any[])[0]?.method_id,
|
||||||
value: (methods as any[])[0]?.name,
|
value: (methods as any[])[0]?.name,
|
||||||
@@ -96,6 +100,7 @@ const ToolList: FC<ToolListProps> = ({value, onChange}) => {
|
|||||||
const customFilterItem = (methods as any[]).find(vo => vo.method_id === item.operation)
|
const customFilterItem = (methods as any[]).find(vo => vo.method_id === item.operation)
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
|
is_active: (toolDetail as any).is_active,
|
||||||
label: customFilterItem?.name,
|
label: customFilterItem?.name,
|
||||||
method_id: customFilterItem?.method_id,
|
method_id: customFilterItem?.method_id,
|
||||||
value: customFilterItem?.name,
|
value: customFilterItem?.name,
|
||||||
@@ -129,7 +134,10 @@ const ToolList: FC<ToolListProps> = ({value, onChange}) => {
|
|||||||
* @param tool - Tool to add
|
* @param tool - Tool to add
|
||||||
*/
|
*/
|
||||||
const updateTools = (tool: ToolOption) => {
|
const updateTools = (tool: ToolOption) => {
|
||||||
const list = [...toolList, tool]
|
const list = [...toolList, {
|
||||||
|
...tool,
|
||||||
|
is_active: true,
|
||||||
|
}]
|
||||||
setToolList(list)
|
setToolList(list)
|
||||||
onChange && onChange(list)
|
onChange && onChange(list)
|
||||||
}
|
}
|
||||||
@@ -146,42 +154,35 @@ const ToolList: FC<ToolListProps> = ({value, onChange}) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
title={t('application.toolConfiguration')}
|
title={t('application.toolConfiguration')}
|
||||||
extra={
|
extra={
|
||||||
<Button style={{ padding: '0 8px', height: '24px' }} onClick={handleAddTool}>
|
<Button className="rb:h-6! rb:py-0! rb:px-2! rb:rounded-md! rb:text-[#21233" onClick={handleAddTool}>+ {t('application.addTool')}</Button>
|
||||||
+ {t('application.addTool')}
|
|
||||||
</Button>
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{/* Show empty state or tool list */}
|
|
||||||
{toolList.length === 0
|
{toolList.length === 0
|
||||||
? <Empty size={88} />
|
? <div className="rb-border rb:rounded-xl rb:pt-4 rb:pb-6"><Empty size={88} /></div>
|
||||||
:
|
: <Flex vertical gap={12}>
|
||||||
<List
|
{toolList.map((item, index) => (
|
||||||
grid={{ gutter: 12, column: 1 }}
|
<Flex key={index} align="center" justify="space-between" className="rb:py-2.5! rb:pl-4! rb:pr-3! rb-border rb:rounded-lg">
|
||||||
dataSource={toolList}
|
<div>
|
||||||
renderItem={(item, index) => (
|
<div className="rb:font-medium rb:leading-4">
|
||||||
<List.Item>
|
{item.label}
|
||||||
{/* Tool card with delete button */}
|
|
||||||
<div key={index} className="rb:flex rb:items-center rb:justify-between rb:p-[12px_16px] rb:bg-[#FBFDFF] rb:border rb:border-[#DFE4ED] rb:rounded-lg">
|
|
||||||
{/* Tool label/description */}
|
|
||||||
<div className="rb:font-medium rb:leading-4">
|
|
||||||
{item.label}
|
|
||||||
</div>
|
|
||||||
<Space size={12}>
|
|
||||||
{/* Delete button with hover effect */}
|
|
||||||
<div
|
|
||||||
className="rb:w-6 rb:h-6 rb:cursor-pointer rb:bg-[url('@/assets/images/deleteBorder.svg')] rb:hover:bg-[url('@/assets/images/deleteBg.svg')]"
|
|
||||||
onClick={() => handleDeleteTool(index)}
|
|
||||||
></div>
|
|
||||||
</Space>
|
|
||||||
</div>
|
</div>
|
||||||
</List.Item>
|
<Tag color={item.is_active ? 'success' : 'error'} className="rb:mt-1">
|
||||||
)}
|
{item.is_active ? t('common.enable') : t('common.deleted')}
|
||||||
/>
|
</Tag>
|
||||||
|
</div>
|
||||||
|
<Space size={12}>
|
||||||
|
<div
|
||||||
|
className="rb:w-6 rb:h-6 rb:cursor-pointer rb:bg-[url('@/assets/images/deleteBorder.svg')] rb:hover:bg-[url('@/assets/images/deleteBg.svg')]"
|
||||||
|
onClick={() => handleDeleteTool(index)}
|
||||||
|
></div>
|
||||||
|
</Space>
|
||||||
|
</Flex>
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
}
|
}
|
||||||
{/* Tool selection modal */}
|
|
||||||
<ToolModal
|
<ToolModal
|
||||||
ref={toolModalRef}
|
ref={toolModalRef}
|
||||||
refresh={updateTools}
|
refresh={updateTools}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export interface ToolOption {
|
|||||||
tool_id?: string;
|
tool_id?: string;
|
||||||
/** Whether tool is enabled */
|
/** Whether tool is enabled */
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
|
is_active?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -7,17 +7,16 @@
|
|||||||
import { type FC, useEffect, useRef, useState } from "react";
|
import { type FC, useEffect, useRef, useState } from "react";
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
import { Form, Input, Button, Space, Select, App } from 'antd'
|
import { Form, Input, Button, Space, Select, App, Flex } from 'antd'
|
||||||
|
|
||||||
import Card from '@/views/ApplicationConfig/components/Card'
|
import Card from '@/views/ApplicationConfig/components/Card'
|
||||||
import aiPrompt from '@/assets/images/application/aiPrompt.png'
|
|
||||||
import AiPromptModal from '@/views/ApplicationConfig/components/AiPromptModal'
|
import AiPromptModal from '@/views/ApplicationConfig/components/AiPromptModal'
|
||||||
import ToolList from '../components/ToolList/ToolList'
|
import ToolList from '../components/ToolList/ToolList'
|
||||||
import type { AiPromptModalRef } from '@/views/ApplicationConfig/types'
|
import type { AiPromptModalRef } from '@/views/ApplicationConfig/types'
|
||||||
import exitIcon from '@/assets/images/knowledgeBase/exit.png';
|
|
||||||
import type { SkillFormData } from '../types'
|
import type { SkillFormData } from '../types'
|
||||||
import { getSkillDetail, createSkill, updateSkill } from '@/api/skill'
|
import { getSkillDetail, createSkill, updateSkill } from '@/api/skill'
|
||||||
import { stringRegExp } from '@/utils/validator';
|
import { stringRegExp } from '@/utils/validator';
|
||||||
|
import PageHeader from '@/components/Layout/PageHeader'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Skill Configuration Page Component
|
* Skill Configuration Page Component
|
||||||
@@ -43,6 +42,7 @@ const SkillConfig: FC = () => {
|
|||||||
const { message } = App.useApp()
|
const { message } = App.useApp()
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [form] = Form.useForm<SkillFormData>();
|
const [form] = Form.useForm<SkillFormData>();
|
||||||
|
const [data, setData] = useState<SkillFormData | null>(null)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Effect: Load skill data if editing existing skill
|
* Effect: Load skill data if editing existing skill
|
||||||
@@ -70,6 +70,7 @@ const SkillConfig: FC = () => {
|
|||||||
getSkillDetail(id)
|
getSkillDetail(id)
|
||||||
.then(res => {
|
.then(res => {
|
||||||
form.setFieldsValue(res as SkillFormData)
|
form.setFieldsValue(res as SkillFormData)
|
||||||
|
setData(res as SkillFormData)
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
@@ -131,93 +132,103 @@ const SkillConfig: FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rb:w-250 rb:mt-5 rb:pb-5 rb:mx-auto">
|
<Flex vertical className="rb:h-screen!">
|
||||||
{/* Back button */}
|
<PageHeader
|
||||||
<div className='rb:flex rb:items-center rb:gap-2 rb:mb-4 rb:cursor-pointer' onClick={handleBack}>
|
title={data?.name}
|
||||||
<img src={exitIcon} alt='exit' className='rb:w-4 rb:h-4' />
|
extra={
|
||||||
<span className='rb:text-gray-500 rb:text-sm'>{t('common.exit')}</span>
|
<Flex gap={12} align="center">
|
||||||
</div>
|
{/* Save button */}
|
||||||
|
<Button type="primary" className="rb:px-2! rb:gap-0.5!" disabled={loading} onClick={handleSave}>{t('skills.save')}</Button>
|
||||||
<Form form={form} layout="vertical">
|
<Button
|
||||||
<Space size={16} direction="vertical" className="rb:w-full">
|
className="rb:px-2! rb:gap-0.5!"
|
||||||
{/* Manifest Section: Basic skill information */}
|
icon={<div className="rb:bg-[url('@/assets/images/workflow/return.svg')] rb:size-4 rb:bg-cover"></div>}
|
||||||
<Card title={t('skills.mainfest')}>
|
onClick={handleBack}
|
||||||
<Form.Item
|
|
||||||
name="name"
|
|
||||||
label={t('skills.name')}
|
|
||||||
rules={[
|
|
||||||
{ required: true, message: t('common.inputPlaceholder', { title: t('skills.name') }) },
|
|
||||||
{ max: 50 },
|
|
||||||
{ pattern: stringRegExp, message: t('common.nameInvalid') },
|
|
||||||
]}
|
|
||||||
>
|
>
|
||||||
<Input placeholder={t('common.pleaseEnter')} />
|
{t('common.return')}
|
||||||
</Form.Item>
|
</Button>
|
||||||
<Form.Item
|
</Flex>
|
||||||
name="description"
|
}
|
||||||
label={t('skills.description')}
|
/>
|
||||||
rules={[{ max: 500 }]}
|
<div className="rb:w-250 rb:my-3 rb:mx-auto rb:flex-1 rb:overflow-y-auto">
|
||||||
>
|
<Form form={form} layout="vertical">
|
||||||
<Input.TextArea placeholder={t('skills.descriptionPlaceholder')} />
|
<Space size={16} direction="vertical" className="rb:w-full">
|
||||||
</Form.Item>
|
{/* Manifest Section: Basic skill information */}
|
||||||
<Form.Item
|
<Card title={t('skills.mainfest')}>
|
||||||
name={['config', 'keywords']}
|
<Form.Item
|
||||||
label={t('skills.keywords')}
|
name="name"
|
||||||
rules={[{ required: true, message: t('common.inputPlaceholder', { title: t('skills.keywords') }) }]}
|
label={t('skills.name')}
|
||||||
>
|
rules={[
|
||||||
<Select
|
{ required: true, message: t('common.inputPlaceholder', { title: t('skills.name') }) },
|
||||||
mode="tags"
|
{ max: 50 },
|
||||||
placeholder={t('common.pleaseEnter')}
|
{ pattern: stringRegExp, message: t('common.nameInvalid') },
|
||||||
/>
|
]}
|
||||||
</Form.Item>
|
>
|
||||||
</Card>
|
<Input placeholder={t('common.pleaseEnter')} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="description"
|
||||||
|
label={t('skills.description')}
|
||||||
|
rules={[{ max: 500 }]}
|
||||||
|
>
|
||||||
|
<Input.TextArea placeholder={t('skills.descriptionPlaceholder')} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name={['config', 'keywords']}
|
||||||
|
label={t('skills.keywords')}
|
||||||
|
rules={[{ required: true, message: t('common.inputPlaceholder', { title: t('skills.keywords') }) }]}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
mode="tags"
|
||||||
|
placeholder={t('common.pleaseEnter')}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Card>
|
||||||
|
|
||||||
{/* Prompt Configuration Section: AI instructions */}
|
{/* Prompt Configuration Section: AI instructions */}
|
||||||
<Card title={t('skills.promptConfiguration')}
|
<Card title={t('skills.promptConfiguration')}
|
||||||
extra={
|
extra={
|
||||||
<Button style={{ padding: '0 8px', height: '24px' }} onClick={handlePrompt}>
|
<Button style={{ padding: '0 8px', height: '24px' }} onClick={handlePrompt}>
|
||||||
<img src={aiPrompt} className="rb:size-5" />
|
<div className="rb:size-5 rb:bg-cover rb:bg-[url('@/assets/images/application/aiPrompt.png')] rb:mr-1!" />
|
||||||
{t('skills.aiPrompt')}
|
{t('skills.aiPrompt')}
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
<Form.Item
|
||||||
|
name="prompt"
|
||||||
|
className="rb:mb-0!"
|
||||||
|
>
|
||||||
|
<Input.TextArea
|
||||||
|
placeholder={t('skills.promptPlaceholder')}
|
||||||
|
styles={{
|
||||||
|
textarea: {
|
||||||
|
minHeight: '200px',
|
||||||
|
borderRadius: '8px'
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Tool Configuration Section */}
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="prompt"
|
name="tools"
|
||||||
|
rules={[{ required: true, message: t('common.selectPlaceholder', { title: t('skills.tools') }) }]}
|
||||||
className="rb:mb-0!"
|
className="rb:mb-0!"
|
||||||
>
|
>
|
||||||
<Input.TextArea
|
<ToolList />
|
||||||
placeholder={t('skills.promptPlaceholder')}
|
|
||||||
styles={{
|
|
||||||
textarea: {
|
|
||||||
minHeight: '200px',
|
|
||||||
borderRadius: '8px'
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Tool Configuration Section */}
|
</Space>
|
||||||
<Form.Item
|
</Form>
|
||||||
name="tools"
|
|
||||||
rules={[{ required: true, message: t('common.selectPlaceholder', { title: t('skills.tools') }) }]}
|
{/* AI Prompt Generation Modal */}
|
||||||
className="rb:mb-0!"
|
<AiPromptModal
|
||||||
>
|
ref={aiPromptModalRef}
|
||||||
<ToolList />
|
refresh={updatePrompt}
|
||||||
</Form.Item>
|
source="skills"
|
||||||
|
/>
|
||||||
{/* Save button */}
|
</div>
|
||||||
<Button type="primary" block disabled={loading} onClick={handleSave}>{t('skills.save')}</Button>
|
</Flex>
|
||||||
</Space>
|
|
||||||
</Form>
|
|
||||||
|
|
||||||
{/* AI Prompt Generation Modal */}
|
|
||||||
<AiPromptModal
|
|
||||||
ref={aiPromptModalRef}
|
|
||||||
refresh={updatePrompt}
|
|
||||||
source="skills"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user