feat(web): skill toolList add is_active

This commit is contained in:
zhaoying
2026-04-01 13:33:16 +08:00
parent d3cd66fc6e
commit e77a1a92fd
4 changed files with 130 additions and 116 deletions

View File

@@ -19,6 +19,7 @@
import { Outlet } from 'react-router-dom';
import { useEffect, type FC } from 'react';
import { Layout } from 'antd';
import { useUser } from '@/store/user';
@@ -35,10 +36,10 @@ const BasicAuthLayout: FC = () => {
}, [getUserInfo]);
return (
<div className="rb:relative rb:min-h-screen rb:w-screen">
<Layout className="rb:min-h-screen!">
{/* Render child routes without additional UI */}
<Outlet />
</div>
</Layout>
)
};

View File

@@ -12,7 +12,7 @@
import { type FC, useRef, useState, useEffect } from 'react'
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 type {
@@ -22,6 +22,7 @@ import type {
import Empty from '@/components/Empty'
import ToolModal from './ToolModal'
import { getToolMethods, getToolDetail } from '@/api/tools'
import Tag from '@/components/Tag'
/**
* 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)
return {
...item,
is_active: (toolDetail as any).is_active,
label: mcpFilterItem?.description,
method_id: mcpFilterItem?.method_id,
value: mcpFilterItem?.name,
@@ -74,6 +76,7 @@ const ToolList: FC<ToolListProps> = ({value, onChange}) => {
const builtinFilterItem = (methods as any[]).find(vo => vo.name === item.operation)
return {
...item,
is_active: (toolDetail as any).is_active,
label: builtinFilterItem?.description,
method_id: builtinFilterItem?.method_id,
value: builtinFilterItem?.name,
@@ -84,6 +87,7 @@ const ToolList: FC<ToolListProps> = ({value, onChange}) => {
// Single method: Use first method
return {
...item,
is_active: (toolDetail as any).is_active,
label: (methods as any[])[0]?.description,
method_id: (methods as any[])[0]?.method_id,
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)
return {
...item,
is_active: (toolDetail as any).is_active,
label: customFilterItem?.name,
method_id: customFilterItem?.method_id,
value: customFilterItem?.name,
@@ -129,7 +134,10 @@ const ToolList: FC<ToolListProps> = ({value, onChange}) => {
* @param tool - Tool to add
*/
const updateTools = (tool: ToolOption) => {
const list = [...toolList, tool]
const list = [...toolList, {
...tool,
is_active: true,
}]
setToolList(list)
onChange && onChange(list)
}
@@ -146,42 +154,35 @@ const ToolList: FC<ToolListProps> = ({value, onChange}) => {
}
return (
<Card
<Card
title={t('application.toolConfiguration')}
extra={
<Button style={{ padding: '0 8px', height: '24px' }} onClick={handleAddTool}>
+ {t('application.addTool')}
</Button>
<Button className="rb:h-6! rb:py-0! rb:px-2! rb:rounded-md! rb:text-[#21233" onClick={handleAddTool}>+ {t('application.addTool')}</Button>
}
>
{/* Show empty state or tool list */}
{toolList.length === 0
? <Empty size={88} />
:
<List
grid={{ gutter: 12, column: 1 }}
dataSource={toolList}
renderItem={(item, index) => (
<List.Item>
{/* 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 className="rb-border rb:rounded-xl rb:pt-4 rb:pb-6"><Empty size={88} /></div>
: <Flex vertical gap={12}>
{toolList.map((item, index) => (
<Flex key={index} align="center" justify="space-between" className="rb:py-2.5! rb:pl-4! rb:pr-3! rb-border rb:rounded-lg">
<div>
<div className="rb:font-medium rb:leading-4">
{item.label}
</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
ref={toolModalRef}
refresh={updateTools}

View File

@@ -32,6 +32,7 @@ export interface ToolOption {
tool_id?: string;
/** Whether tool is enabled */
enabled?: boolean;
is_active?: boolean;
}
/**

View File

@@ -7,17 +7,16 @@
import { type FC, useEffect, useRef, useState } from "react";
import { useTranslation } from 'react-i18next';
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 aiPrompt from '@/assets/images/application/aiPrompt.png'
import AiPromptModal from '@/views/ApplicationConfig/components/AiPromptModal'
import ToolList from '../components/ToolList/ToolList'
import type { AiPromptModalRef } from '@/views/ApplicationConfig/types'
import exitIcon from '@/assets/images/knowledgeBase/exit.png';
import type { SkillFormData } from '../types'
import { getSkillDetail, createSkill, updateSkill } from '@/api/skill'
import { stringRegExp } from '@/utils/validator';
import PageHeader from '@/components/Layout/PageHeader'
/**
* Skill Configuration Page Component
@@ -43,6 +42,7 @@ const SkillConfig: FC = () => {
const { message } = App.useApp()
const [loading, setLoading] = useState(false)
const [form] = Form.useForm<SkillFormData>();
const [data, setData] = useState<SkillFormData | null>(null)
/**
* Effect: Load skill data if editing existing skill
@@ -70,6 +70,7 @@ const SkillConfig: FC = () => {
getSkillDetail(id)
.then(res => {
form.setFieldsValue(res as SkillFormData)
setData(res as SkillFormData)
})
.finally(() => {
setLoading(false)
@@ -131,93 +132,103 @@ const SkillConfig: FC = () => {
}
return (
<div className="rb:w-250 rb:mt-5 rb:pb-5 rb:mx-auto">
{/* Back button */}
<div className='rb:flex rb:items-center rb:gap-2 rb:mb-4 rb:cursor-pointer' onClick={handleBack}>
<img src={exitIcon} alt='exit' className='rb:w-4 rb:h-4' />
<span className='rb:text-gray-500 rb:text-sm'>{t('common.exit')}</span>
</div>
<Form form={form} layout="vertical">
<Space size={16} direction="vertical" className="rb:w-full">
{/* Manifest Section: Basic skill information */}
<Card title={t('skills.mainfest')}>
<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') },
]}
<Flex vertical className="rb:h-screen!">
<PageHeader
title={data?.name}
extra={
<Flex gap={12} align="center">
{/* Save button */}
<Button type="primary" className="rb:px-2! rb:gap-0.5!" disabled={loading} onClick={handleSave}>{t('skills.save')}</Button>
<Button
className="rb:px-2! rb:gap-0.5!"
icon={<div className="rb:bg-[url('@/assets/images/workflow/return.svg')] rb:size-4 rb:bg-cover"></div>}
onClick={handleBack}
>
<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>
{t('common.return')}
</Button>
</Flex>
}
/>
<div className="rb:w-250 rb:my-3 rb:mx-auto rb:flex-1 rb:overflow-y-auto">
<Form form={form} layout="vertical">
<Space size={16} direction="vertical" className="rb:w-full">
{/* Manifest Section: Basic skill information */}
<Card title={t('skills.mainfest')}>
<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')} />
</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 */}
<Card title={t('skills.promptConfiguration')}
extra={
<Button style={{ padding: '0 8px', height: '24px' }} onClick={handlePrompt}>
<img src={aiPrompt} className="rb:size-5" />
{t('skills.aiPrompt')}
</Button>
}
>
{/* Prompt Configuration Section: AI instructions */}
<Card title={t('skills.promptConfiguration')}
extra={
<Button style={{ padding: '0 8px', height: '24px' }} onClick={handlePrompt}>
<div className="rb:size-5 rb:bg-cover rb:bg-[url('@/assets/images/application/aiPrompt.png')] rb:mr-1!" />
{t('skills.aiPrompt')}
</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
name="prompt"
name="tools"
rules={[{ required: true, message: t('common.selectPlaceholder', { title: t('skills.tools') }) }]}
className="rb:mb-0!"
>
<Input.TextArea
placeholder={t('skills.promptPlaceholder')}
styles={{
textarea: {
minHeight: '200px',
borderRadius: '8px'
},
}}
/>
<ToolList />
</Form.Item>
</Card>
{/* Tool Configuration Section */}
<Form.Item
name="tools"
rules={[{ required: true, message: t('common.selectPlaceholder', { title: t('skills.tools') }) }]}
className="rb:mb-0!"
>
<ToolList />
</Form.Item>
{/* Save button */}
<Button type="primary" block disabled={loading} onClick={handleSave}>{t('skills.save')}</Button>
</Space>
</Form>
{/* AI Prompt Generation Modal */}
<AiPromptModal
ref={aiPromptModalRef}
refresh={updatePrompt}
source="skills"
/>
</div>
</Space>
</Form>
{/* AI Prompt Generation Modal */}
<AiPromptModal
ref={aiPromptModalRef}
refresh={updatePrompt}
source="skills"
/>
</div>
</Flex>
)
}