- 新增审核卡片和确认卡片模型,支持Agent推送审核任务和用户确认 - 实现审核卡片API服务,支持创建、更新、批准、驳回等操作 - 扩展审核维度配置,新增角色一致性、剧情连贯性等维度 - 优化前端审核配置页面,修复API路径错误和状态枚举问题 - 改进剧集创作平台布局,新增左侧边栏用于剧集管理和上下文查看 - 增强Skill管理,支持从审核系统跳转创建/编辑Skill - 修复episodes.json数据问题,清理聊天历史记录 - 更新Agent提示词,明确Skill引用加载流程 - 统一前端主题配置,优化整体UI体验
1580 lines
51 KiB
TypeScript
1580 lines
51 KiB
TypeScript
/**
|
||
* Skills 管理中心 - 列表页面
|
||
*/
|
||
import { useEffect, useState } from 'react'
|
||
import {
|
||
Card,
|
||
Table,
|
||
Tag,
|
||
Button,
|
||
Space,
|
||
Input,
|
||
message,
|
||
Tabs,
|
||
Drawer,
|
||
Modal,
|
||
Form,
|
||
Select,
|
||
Popconfirm,
|
||
Badge,
|
||
Divider,
|
||
Tooltip,
|
||
Switch,
|
||
Empty,
|
||
Spin,
|
||
Alert,
|
||
Progress,
|
||
List,
|
||
Typography
|
||
} from 'antd'
|
||
import { SkillCreate } from '@/components/SkillCreate'
|
||
import {
|
||
PlusOutlined,
|
||
EyeOutlined,
|
||
EditOutlined,
|
||
PlayCircleOutlined,
|
||
SearchOutlined,
|
||
CopyOutlined,
|
||
DeleteOutlined,
|
||
AppstoreOutlined,
|
||
SettingOutlined,
|
||
CheckCircleOutlined,
|
||
LockOutlined,
|
||
RobotOutlined,
|
||
EditFilled,
|
||
ReloadOutlined,
|
||
ClockCircleOutlined,
|
||
CloseCircleOutlined,
|
||
CheckOutlined
|
||
} from '@ant-design/icons'
|
||
import { useNavigate, useSearchParams, useLocation } from 'react-router-dom'
|
||
import { useSkillStore, Skill } from '@/stores/skillStore'
|
||
import { skillService, SkillDraft } from '@/services/skillService'
|
||
import { taskService } from '@/services/taskService'
|
||
import type { ColumnsType } from 'antd/es/table'
|
||
import { TablePaginationConfig } from 'antd/es/table'
|
||
import dayjs from 'dayjs'
|
||
|
||
const { Search, TextArea } = Input
|
||
const { TabPane } = Tabs
|
||
const { Option } = Select
|
||
|
||
// Keep Input component available
|
||
const InputComponent = Input
|
||
|
||
interface SkillFormData {
|
||
id: string
|
||
name: string
|
||
category: string
|
||
behavior_guide: string
|
||
tags: string[]
|
||
config?: Record<string, any>
|
||
}
|
||
|
||
// AI 创建模式
|
||
type CreateMode = 'ai' | 'manual' | 'template'
|
||
|
||
export const SkillManagement = () => {
|
||
const navigate = useNavigate()
|
||
const [searchParams] = useSearchParams()
|
||
const location = useLocation()
|
||
const { skills, builtinSkills, userSkills, loading, fetchSkills, testSkill } = useSkillStore()
|
||
|
||
// List view states
|
||
const [searchText, setSearchText] = useState('')
|
||
const [activeTab, setActiveTab] = useState('all')
|
||
const [viewMode, setViewMode] = useState<'list' | 'grid'>('list')
|
||
const [selectedCategories, setSelectedCategories] = useState<string[]>([])
|
||
|
||
// Detail view states
|
||
const [detailDrawerOpen, setDetailDrawerOpen] = useState(false)
|
||
const [selectedSkill, setSelectedSkill] = useState<Skill | null>(null)
|
||
|
||
// Test states
|
||
const [testModalOpen, setTestModalOpen] = useState(false)
|
||
const [testInput, setTestInput] = useState('')
|
||
const [testResult, setTestResult] = useState('')
|
||
const [testing, setTesting] = useState(false)
|
||
|
||
// CRUD states
|
||
const [editModalOpen, setEditModalOpen] = useState(false)
|
||
const [createModalOpen, setCreateModalOpen] = useState(false)
|
||
const [editingSkill, setEditingSkill] = useState<Skill | null>(null)
|
||
const [form] = Form.useForm()
|
||
const [submitting, setSubmitting] = useState(false)
|
||
|
||
// AI 创建状态
|
||
const [createMode, setCreateMode] = useState<CreateMode>('ai')
|
||
const [aiIdea, setAiIdea] = useState('')
|
||
const [aiCategory, setAiCategory] = useState<string | undefined>()
|
||
const [generating, setGenerating] = useState(false)
|
||
const [generatedDraft, setGeneratedDraft] = useState<SkillDraft | null>(null)
|
||
|
||
// 技能生成任务状态
|
||
const [skillGenerationTasks, setSkillGenerationTasks] = useState<any[]>([])
|
||
const [loadingTasks, setLoadingTasks] = useState(false)
|
||
const [optimizing, setOptimizing] = useState(false)
|
||
const [optimizeRequirements, setOptimizeRequirements] = useState('')
|
||
|
||
// 模板初始化状态
|
||
const [templateSkillName, setTemplateSkillName] = useState('')
|
||
const [templateOutputPath, setTemplateOutputPath] = useState('')
|
||
const [initializing, setInitializing] = useState(false)
|
||
const [initResult, setInitResult] = useState<string | null>(null)
|
||
|
||
// 向导状态
|
||
const [wizardVisible, setWizardVisible] = useState(false)
|
||
// 跳转回退信息(从外部跳转过来时使用)
|
||
const [redirectState, setRedirectState] = useState<{ path: string; activeTab?: string; reviewSubTab?: string } | null>(null)
|
||
|
||
useEffect(() => {
|
||
fetchSkills()
|
||
|
||
// 轮询技能生成任务 - 使用递归 setTimeout 而不是 setInterval
|
||
// 这样可以确保前一个请求完成后才开始下一个请求
|
||
let isMounted = true
|
||
|
||
const pollTasks = async () => {
|
||
if (!isMounted) return
|
||
|
||
try {
|
||
await fetchSkillGenerationTasks()
|
||
} catch (error) {
|
||
console.error('Error polling tasks:', error)
|
||
}
|
||
|
||
// 等待 3 秒后再次轮询
|
||
if (isMounted) {
|
||
setTimeout(pollTasks, 3000)
|
||
}
|
||
}
|
||
|
||
// 启动轮询
|
||
pollTasks()
|
||
|
||
return () => {
|
||
isMounted = false
|
||
}
|
||
}, [])
|
||
|
||
// 处理URL参数和location.state - 支持通过URL参数打开编辑或创建
|
||
useEffect(() => {
|
||
// 从 location.state 读取 redirect 信息
|
||
const state = location.state as any
|
||
if (state?.redirect) {
|
||
setRedirectState({
|
||
path: state.redirect,
|
||
activeTab: state.activeTab,
|
||
reviewSubTab: state.reviewSubTab
|
||
})
|
||
// 清除 state 避免重复处理
|
||
location.state = null
|
||
}
|
||
|
||
// 处理编辑参数 - 使用新的 SkillCreate 向导组件
|
||
const editSkillId = searchParams.get('edit')
|
||
if (editSkillId) {
|
||
const skillToEdit = skills.find(s => s.id === editSkillId)
|
||
if (skillToEdit) {
|
||
setEditingSkill(skillToEdit)
|
||
setWizardVisible(true)
|
||
// 清除URL参数
|
||
searchParams.delete('edit')
|
||
navigate(`/skills?${searchParams.toString()}`, { replace: true })
|
||
return
|
||
} else {
|
||
// Skill 不在本地列表中,尝试从服务器直接获取
|
||
// 这允许编辑在 review 配置中引用但不在主列表中的技能
|
||
const fetchSkillForEdit = async () => {
|
||
try {
|
||
const result = await skillService.getSkillWithReferences(editSkillId, false)
|
||
if (result?.skill) {
|
||
setEditingSkill(result.skill)
|
||
setWizardVisible(true)
|
||
} else {
|
||
message.error(`Skill ID "${editSkillId}" 不存在`)
|
||
}
|
||
} catch (error) {
|
||
console.error('Failed to fetch skill for edit:', error)
|
||
message.error(`无法加载 Skill "${editSkillId}": ${(error as Error).message}`)
|
||
} finally {
|
||
// 清除URL参数
|
||
searchParams.delete('edit')
|
||
navigate(`/skills?${searchParams.toString()}`, { replace: true })
|
||
}
|
||
}
|
||
fetchSkillForEdit()
|
||
return
|
||
}
|
||
}
|
||
|
||
// 处理创建参数 - 使用新的 SkillCreate 向导组件
|
||
const shouldCreate = searchParams.get('create')
|
||
if (shouldCreate === 'true') {
|
||
// 确保重置 editingSkill,避免误用之前的编辑状态
|
||
setEditingSkill(null)
|
||
setWizardVisible(true)
|
||
// 清除URL参数
|
||
searchParams.delete('create')
|
||
navigate(`/skills?${searchParams.toString()}`, { replace: true })
|
||
}
|
||
}, [skills, searchParams, navigate, location])
|
||
|
||
// 获取技能生成任务
|
||
const fetchSkillGenerationTasks = async () => {
|
||
try {
|
||
const tasks = await taskService.listTasks({
|
||
type: 'generate_skill',
|
||
status: 'running'
|
||
})
|
||
setSkillGenerationTasks(tasks)
|
||
|
||
// 如果有任务完成,刷新技能列表
|
||
const completedTasks = tasks.filter(task => task.status === 'completed')
|
||
if (completedTasks.length > 0) {
|
||
fetchSkills()
|
||
}
|
||
} catch (error) {
|
||
console.error('Failed to fetch skill generation tasks:', error)
|
||
}
|
||
}
|
||
|
||
// Get unique categories
|
||
const categories = Array.from(new Set(skills.map(s => s.category)))
|
||
|
||
// Filter skills
|
||
const filteredSkills = skills.filter(skill => {
|
||
const matchSearch =
|
||
skill.name.toLowerCase().includes(searchText.toLowerCase()) ||
|
||
skill.category.toLowerCase().includes(searchText.toLowerCase()) ||
|
||
skill.id.toLowerCase().includes(searchText.toLowerCase())
|
||
|
||
const matchTab =
|
||
activeTab === 'all' ||
|
||
(activeTab === 'builtin' && skill.type === 'builtin') ||
|
||
(activeTab === 'user' && skill.type === 'user')
|
||
|
||
const matchCategory = selectedCategories.length === 0 || selectedCategories.includes(skill.category)
|
||
|
||
return matchSearch && matchTab && matchCategory
|
||
})
|
||
|
||
// Handlers
|
||
const handleView = (skill: Skill) => {
|
||
setSelectedSkill(skill)
|
||
setDetailDrawerOpen(true)
|
||
}
|
||
|
||
const handleTest = (skill: Skill) => {
|
||
setSelectedSkill(skill)
|
||
setTestInput('')
|
||
setTestResult('')
|
||
setTestModalOpen(true)
|
||
}
|
||
|
||
const handleRunTest = async () => {
|
||
if (!selectedSkill) return
|
||
|
||
setTesting(true)
|
||
setTestResult('')
|
||
|
||
try {
|
||
const result = await testSkill(selectedSkill.id, testInput || '请创作一段示例内容')
|
||
setTestResult(result)
|
||
message.success('测试成功!')
|
||
} catch (error) {
|
||
message.error(`测试失败: ${(error as Error).message}`)
|
||
} finally {
|
||
setTesting(false)
|
||
}
|
||
}
|
||
|
||
const handleEdit = (skill: Skill) => {
|
||
if (skill.type === 'builtin') {
|
||
message.warning('内置 Skill 不允许编辑')
|
||
return
|
||
}
|
||
// 使用新的 SkillCreate 组件进行编辑
|
||
setEditingSkill(skill)
|
||
setWizardVisible(true)
|
||
}
|
||
|
||
const handleCopy = (skill: Skill) => {
|
||
form.setFieldsValue({
|
||
id: `${skill.id}-copy-${Date.now()}`,
|
||
name: `${skill.name} (副本)`,
|
||
category: skill.category,
|
||
behavior_guide: skill.behavior_guide,
|
||
tags: skill.tags || []
|
||
})
|
||
setEditingSkill(null)
|
||
setCreateModalOpen(true)
|
||
message.info('已复制 Skill 内容,请修改后保存')
|
||
}
|
||
|
||
const handleDelete = async (skill: Skill) => {
|
||
if (skill.type === 'builtin') {
|
||
message.warning('内置 Skill 不允许删除')
|
||
return
|
||
}
|
||
|
||
try {
|
||
await skillService.deleteSkill(skill.id)
|
||
message.success('删除成功')
|
||
fetchSkills()
|
||
} catch (error) {
|
||
message.error(`删除失败: ${(error as Error).message}`)
|
||
}
|
||
}
|
||
|
||
const handleCreate = () => {
|
||
// 打开新的向导
|
||
setWizardVisible(true)
|
||
}
|
||
|
||
// 从模板初始化 Skill(使用 skill-creator 的 init_skill.py 脚本)
|
||
const handleInitFromTemplate = async () => {
|
||
if (!templateSkillName.trim()) {
|
||
message.warning('请输入 Skill 名称')
|
||
return
|
||
}
|
||
|
||
// 验证格式:hyphen-case
|
||
if (!/^[a-z0-9-]+$/.test(templateSkillName)) {
|
||
message.warning('Skill 名称只能包含小写字母、数字和连字符 (hyphen-case)')
|
||
return
|
||
}
|
||
|
||
setInitializing(true)
|
||
setInitResult(null)
|
||
try {
|
||
const result = await skillService.initSkillTemplate(
|
||
templateSkillName,
|
||
templateOutputPath || undefined
|
||
)
|
||
|
||
if (result.success) {
|
||
setInitResult(result.output || 'Skill 模板初始化成功!')
|
||
message.success('Skill 模板初始化成功!')
|
||
|
||
// 自动填充表单
|
||
form.setFieldsValue({
|
||
id: templateSkillName,
|
||
name: templateSkillName.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' '),
|
||
category: 'general',
|
||
behavior_guide: `# ${form.getFieldValue('name') || templateSkillName}\n\n## Overview\n\n[TODO: Describe what this skill does]\n\n## Behavior Guide\n\n[TODO: Add behavior guide content]`,
|
||
tags: []
|
||
})
|
||
}
|
||
} catch (error) {
|
||
message.error(`初始化失败: ${(error as Error).message}`)
|
||
} finally {
|
||
setInitializing(false)
|
||
}
|
||
}
|
||
|
||
// AI 生成 Skill
|
||
const handleGenerate = async () => {
|
||
if (!aiIdea.trim()) {
|
||
message.warning('请输入您的想法或需求')
|
||
return
|
||
}
|
||
|
||
setGenerating(true)
|
||
try {
|
||
const draft = await skillService.generateSkill(aiIdea, aiCategory)
|
||
setGeneratedDraft(draft)
|
||
|
||
// 将生成的数据填充到表单
|
||
form.setFieldsValue({
|
||
id: draft.suggested_id,
|
||
name: draft.suggested_name,
|
||
category: draft.suggested_category,
|
||
behavior_guide: draft.behavior_guide,
|
||
tags: draft.suggested_tags
|
||
})
|
||
|
||
message.success('AI 生成成功!您可以继续编辑后保存')
|
||
} catch (error) {
|
||
message.error(`AI 生成失败: ${(error as Error).message}`)
|
||
} finally {
|
||
setGenerating(false)
|
||
}
|
||
}
|
||
|
||
// AI 优化 Skill
|
||
const handleOptimize = async () => {
|
||
if (!generatedDraft && !editingSkill) {
|
||
message.warning('请先生成或加载 Skill 内容')
|
||
return
|
||
}
|
||
|
||
if (!optimizeRequirements.trim()) {
|
||
message.warning('请输入优化需求')
|
||
return
|
||
}
|
||
|
||
const currentContent = form.getFieldValue('behavior_guide')
|
||
if (!currentContent) {
|
||
message.warning('没有可优化的内容')
|
||
return
|
||
}
|
||
|
||
setOptimizing(true)
|
||
try {
|
||
const skillId = form.getFieldValue('id') || 'temp'
|
||
const result = await skillService.optimizeSkill(skillId, currentContent, optimizeRequirements)
|
||
|
||
// 更新行为指导
|
||
form.setFieldsValue({
|
||
behavior_guide: result.behavior_guide
|
||
})
|
||
|
||
// 更新 generatedDraft
|
||
if (generatedDraft) {
|
||
setGeneratedDraft({
|
||
...generatedDraft,
|
||
behavior_guide: result.behavior_guide
|
||
})
|
||
}
|
||
|
||
message.success('优化成功!')
|
||
|
||
// 显示改进说明
|
||
if (result.improvements && result.improvements.length > 0) {
|
||
Modal.info({
|
||
title: '优化改进',
|
||
content: (
|
||
<ul>
|
||
{result.improvements.map((item, index) => (
|
||
<li key={index}>{item}</li>
|
||
))}
|
||
</ul>
|
||
),
|
||
width: 600
|
||
})
|
||
}
|
||
} catch (error) {
|
||
message.error(`优化失败: ${(error as Error).message}`)
|
||
} finally {
|
||
setOptimizing(false)
|
||
}
|
||
}
|
||
|
||
// 重新生成
|
||
const handleRegenerate = () => {
|
||
setGeneratedDraft(null)
|
||
handleGenerate()
|
||
}
|
||
|
||
const handleCreateSubmit = async () => {
|
||
try {
|
||
const values = await form.validateFields()
|
||
setSubmitting(true)
|
||
|
||
await skillService.createSkill({
|
||
id: values.id,
|
||
name: values.name,
|
||
content: values.behavior_guide,
|
||
category: values.category
|
||
})
|
||
|
||
message.success('创建成功')
|
||
setCreateModalOpen(false)
|
||
form.resetFields()
|
||
fetchSkills()
|
||
} catch (error) {
|
||
message.error(`创建失败: ${(error as Error).message}`)
|
||
} finally {
|
||
setSubmitting(false)
|
||
}
|
||
}
|
||
|
||
const handleEditSubmit = async () => {
|
||
if (!editingSkill) return
|
||
|
||
try {
|
||
const values = await form.validateFields()
|
||
setSubmitting(true)
|
||
|
||
await skillService.updateSkill(editingSkill.id, {
|
||
name: values.name,
|
||
content: values.behavior_guide
|
||
})
|
||
|
||
message.success('更新成功')
|
||
setEditModalOpen(false)
|
||
setEditingSkill(null)
|
||
form.resetFields()
|
||
fetchSkills()
|
||
} catch (error) {
|
||
message.error(`更新失败: ${(error as Error).message}`)
|
||
} finally {
|
||
setSubmitting(false)
|
||
}
|
||
}
|
||
|
||
const columns: ColumnsType<Skill> = [
|
||
{
|
||
title: 'Skill ID',
|
||
dataIndex: 'id',
|
||
key: 'id',
|
||
width: 200,
|
||
render: (id: string, record: Skill) => (
|
||
<Space>
|
||
<span style={{ fontFamily: 'monospace' }}>{id}</span>
|
||
{record.type === 'builtin' && (
|
||
<Tooltip title="内置 Skill">
|
||
<LockOutlined style={{ color: '#1890ff' }} />
|
||
</Tooltip>
|
||
)}
|
||
</Space>
|
||
)
|
||
},
|
||
{
|
||
title: '名称',
|
||
dataIndex: 'name',
|
||
key: 'name',
|
||
render: (name: string, record: Skill) => (
|
||
<Space>
|
||
<span>{name}</span>
|
||
{record.type === 'builtin' && <Badge count="系统" style={{ backgroundColor: '#1890ff' }} />}
|
||
</Space>
|
||
)
|
||
},
|
||
{
|
||
title: '类型',
|
||
dataIndex: 'type',
|
||
key: 'type',
|
||
width: 100,
|
||
filters: [
|
||
{ text: '内置', value: 'builtin' },
|
||
{ text: '用户', value: 'user' }
|
||
],
|
||
onFilter: (value, record) => record.type === value,
|
||
render: (type: string) => (
|
||
<Tag color={type === 'builtin' ? 'blue' : 'green'} icon={type === 'builtin' ? <LockOutlined /> : undefined}>
|
||
{type === 'builtin' ? '内置' : '用户'}
|
||
</Tag>
|
||
)
|
||
},
|
||
{
|
||
title: '分类',
|
||
dataIndex: 'category',
|
||
key: 'category',
|
||
width: 120,
|
||
filters: categories.map(cat => ({ text: cat, value: cat })),
|
||
onFilter: (value, record) => record.category === value,
|
||
render: (category: string) => <Tag>{category}</Tag>
|
||
},
|
||
{
|
||
title: '版本',
|
||
dataIndex: 'version',
|
||
key: 'version',
|
||
width: 100,
|
||
},
|
||
{
|
||
title: '标签',
|
||
dataIndex: 'tags',
|
||
key: 'tags',
|
||
width: 150,
|
||
render: (tags: string[]) => (
|
||
<Space size={[4, 4]} wrap>
|
||
{tags?.slice(0, 3).map(tag => (
|
||
<Tag key={tag} size="small">{tag}</Tag>
|
||
))}
|
||
{tags?.length > 3 && <Tag size="small">+{tags.length - 3}</Tag>}
|
||
</Space>
|
||
)
|
||
},
|
||
{
|
||
title: '操作',
|
||
key: 'actions',
|
||
width: 280,
|
||
fixed: 'right',
|
||
render: (_, record) => (
|
||
<Space size="small">
|
||
<Tooltip title="查看详情">
|
||
<Button type="link" size="small" icon={<EyeOutlined />} onClick={() => handleView(record)}>
|
||
查看
|
||
</Button>
|
||
</Tooltip>
|
||
<Tooltip title="测试 Skill">
|
||
<Button type="link" size="small" icon={<PlayCircleOutlined />} onClick={() => handleTest(record)}>
|
||
测试
|
||
</Button>
|
||
</Tooltip>
|
||
{record.type === 'user' && (
|
||
<>
|
||
<Tooltip title="编辑">
|
||
<Button type="link" size="small" icon={<EditOutlined />} onClick={() => handleEdit(record)}>
|
||
编辑
|
||
</Button>
|
||
</Tooltip>
|
||
<Tooltip title="复制">
|
||
<Button type="link" size="small" icon={<CopyOutlined />} onClick={() => handleCopy(record)}>
|
||
复制
|
||
</Button>
|
||
</Tooltip>
|
||
<Popconfirm
|
||
title="确认删除"
|
||
description="确定要删除这个 Skill 吗?"
|
||
onConfirm={() => handleDelete(record)}
|
||
okText="确认"
|
||
cancelText="取消"
|
||
>
|
||
<Tooltip title="删除">
|
||
<Button type="link" size="small" danger icon={<DeleteOutlined />}>
|
||
删除
|
||
</Button>
|
||
</Tooltip>
|
||
</Popconfirm>
|
||
</>
|
||
)}
|
||
{record.type === 'builtin' && (
|
||
<>
|
||
<Tooltip title="复制内置 Skill">
|
||
<Button type="link" size="small" icon={<CopyOutlined />} onClick={() => handleCopy(record)}>
|
||
复制
|
||
</Button>
|
||
</Tooltip>
|
||
<Tooltip title="内置 Skill 不可编辑">
|
||
<Button type="link" size="small" icon={<LockOutlined />} disabled>
|
||
编辑
|
||
</Button>
|
||
</Tooltip>
|
||
</>
|
||
)}
|
||
</Space>
|
||
)
|
||
}
|
||
]
|
||
|
||
return (
|
||
<div style={{ padding: '24px' }}>
|
||
<Card
|
||
title={
|
||
<Space>
|
||
<AppstoreOutlined />
|
||
<span>Skills 管理中心</span>
|
||
</Space>
|
||
}
|
||
extra={
|
||
<Space>
|
||
<Button
|
||
type="primary"
|
||
icon={<PlusOutlined />}
|
||
onClick={handleCreate}
|
||
>
|
||
创建 Skill
|
||
</Button>
|
||
</Space>
|
||
}
|
||
>
|
||
{/* 技能生成任务状态 */}
|
||
{skillGenerationTasks.length > 0 && (
|
||
<Alert
|
||
type="info"
|
||
showIcon
|
||
style={{ marginBottom: 16 }}
|
||
action={
|
||
<Button size="small" onClick={fetchSkillGenerationTasks}>
|
||
刷新
|
||
</Button>
|
||
}
|
||
>
|
||
<Space>
|
||
<ClockCircleOutlined />
|
||
<span>当前有 {skillGenerationTasks.length} 个 Skill 正在生成中</span>
|
||
</Space>
|
||
</Alert>
|
||
)}
|
||
|
||
{/* 搜索和筛选 */}
|
||
<div style={{ marginBottom: '16px' }}>
|
||
<Space size="large" wrap>
|
||
<Search
|
||
placeholder="搜索 Skills (名称/ID/分类)..."
|
||
allowClear
|
||
style={{ width: 350 }}
|
||
onChange={(e) => setSearchText(e.target.value)}
|
||
prefix={<SearchOutlined />}
|
||
/>
|
||
<Space>
|
||
<span>分类:</span>
|
||
<Select
|
||
mode="multiple"
|
||
placeholder="选择分类"
|
||
style={{ width: 250 }}
|
||
value={selectedCategories}
|
||
onChange={setSelectedCategories}
|
||
allowClear
|
||
>
|
||
{categories.map(cat => (
|
||
<Option key={cat} value={cat}>{cat}</Option>
|
||
))}
|
||
</Select>
|
||
</Space>
|
||
<Space>
|
||
<span>视图:</span>
|
||
<Switch
|
||
checked={viewMode === 'grid'}
|
||
onChange={(checked) => setViewMode(checked ? 'grid' : 'list')}
|
||
checkedChildren="网格"
|
||
unCheckedChildren="列表"
|
||
/>
|
||
</Space>
|
||
<Divider type="vertical" />
|
||
<Tag color="blue">内置: {builtinSkills.length}</Tag>
|
||
<Tag color="green">用户: {userSkills.length}</Tag>
|
||
<Tag>总计: {skills.length}</Tag>
|
||
</Space>
|
||
</div>
|
||
|
||
{/* Skills 列表 */}
|
||
<Table
|
||
columns={columns}
|
||
dataSource={filteredSkills}
|
||
rowKey="id"
|
||
loading={loading}
|
||
pagination={{
|
||
pageSize: 10,
|
||
showSizeChanger: true,
|
||
showTotal: (total) => `共 ${total} 个 Skills`
|
||
}}
|
||
scroll={{ x: 1200 }}
|
||
/>
|
||
</Card>
|
||
|
||
{/* Skill 详情抽屉 - 使用 Drawer 替代 Modal 避免重叠 */}
|
||
<Drawer
|
||
title={
|
||
<Space>
|
||
<EyeOutlined />
|
||
<span>{selectedSkill?.name}</span>
|
||
{selectedSkill?.type === 'builtin' && <Badge count="系统" style={{ backgroundColor: '#1890ff' }} />}
|
||
</Space>
|
||
}
|
||
placement="right"
|
||
width={720}
|
||
open={detailDrawerOpen}
|
||
onClose={() => setDetailDrawerOpen(false)}
|
||
extra={
|
||
<Space>
|
||
<Button
|
||
icon={<PlayCircleOutlined />}
|
||
onClick={() => {
|
||
setDetailDrawerOpen(false)
|
||
handleTest(selectedSkill!)
|
||
}}
|
||
>
|
||
测试
|
||
</Button>
|
||
{selectedSkill?.type === 'user' && (
|
||
<Button
|
||
type="primary"
|
||
icon={<EditOutlined />}
|
||
onClick={() => {
|
||
setDetailDrawerOpen(false)
|
||
handleEdit(selectedSkill!)
|
||
}}
|
||
>
|
||
编辑
|
||
</Button>
|
||
)}
|
||
</Space>
|
||
}
|
||
>
|
||
{selectedSkill && (
|
||
<Tabs defaultActiveKey="info">
|
||
<TabPane tab="基础信息" key="info">
|
||
<Space direction="vertical" style={{ width: '100%' }} size="middle">
|
||
<Card size="small" title="基本信息">
|
||
<Space direction="vertical" style={{ width: '100%' }} size="small">
|
||
<div>
|
||
<strong>Skill ID:</strong>
|
||
<p style={{ fontFamily: 'monospace', color: '#666', marginTop: '4px' }}>{selectedSkill.id}</p>
|
||
</div>
|
||
<Divider style={{ margin: '8px 0' }} />
|
||
<div>
|
||
<strong>名称:</strong>
|
||
<p style={{ marginTop: '4px' }}>{selectedSkill.name}</p>
|
||
</div>
|
||
<div>
|
||
<strong>版本:</strong>
|
||
<Tag color="blue" style={{ marginLeft: '8px' }}>{selectedSkill.version}</Tag>
|
||
</div>
|
||
<div>
|
||
<strong>类型:</strong>
|
||
<Tag
|
||
color={selectedSkill.type === 'builtin' ? 'blue' : 'green'}
|
||
icon={selectedSkill.type === 'builtin' ? <LockOutlined /> : undefined}
|
||
style={{ marginLeft: '8px' }}
|
||
>
|
||
{selectedSkill.type === 'builtin' ? '内置 (只读)' : '用户 (可编辑)'}
|
||
</Tag>
|
||
</div>
|
||
<div>
|
||
<strong>分类:</strong>
|
||
<Tag style={{ marginLeft: '8px' }}>{selectedSkill.category}</Tag>
|
||
</div>
|
||
{selectedSkill.created_at && (
|
||
<div>
|
||
<strong>创建时间:</strong>
|
||
<p style={{ color: '#666', marginTop: '4px' }}>
|
||
{dayjs(selectedSkill.created_at).format('YYYY-MM-DD HH:mm:ss')}
|
||
</p>
|
||
</div>
|
||
)}
|
||
</Space>
|
||
</Card>
|
||
|
||
<Card size="small" title="标签">
|
||
<Space size={[8, 8]} wrap>
|
||
{selectedSkill.tags && selectedSkill.tags.length > 0 ? (
|
||
selectedSkill.tags.map(tag => <Tag key={tag}>{tag}</Tag>)
|
||
) : (
|
||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="暂无标签" />
|
||
)}
|
||
</Space>
|
||
</Card>
|
||
</Space>
|
||
</TabPane>
|
||
|
||
<TabPane tab="行为指导 (核心)" key="behavior">
|
||
<Card
|
||
size="small"
|
||
title="核心行为指导"
|
||
extra={
|
||
<Button
|
||
size="small"
|
||
icon={<CopyOutlined />}
|
||
onClick={() => {
|
||
navigator.clipboard.writeText(selectedSkill.behavior_guide)
|
||
message.success('已复制到剪贴板')
|
||
}}
|
||
>
|
||
复制
|
||
</Button>
|
||
}
|
||
>
|
||
<pre style={{
|
||
background: '#f5f5f5',
|
||
padding: '16px',
|
||
borderRadius: '4px',
|
||
maxHeight: '70vh',
|
||
overflowY: 'auto',
|
||
whiteSpace: 'pre-wrap',
|
||
wordBreak: 'break-word',
|
||
fontFamily: 'monospace',
|
||
fontSize: '13px',
|
||
lineHeight: '1.6'
|
||
}}>
|
||
{selectedSkill.behavior_guide}
|
||
</pre>
|
||
</Card>
|
||
</TabPane>
|
||
|
||
<TabPane tab="配置" key="config">
|
||
<Card size="small" title="配置参数">
|
||
<pre style={{
|
||
background: '#f5f5f5',
|
||
padding: '16px',
|
||
borderRadius: '4px',
|
||
maxHeight: '70vh',
|
||
overflowY: 'auto'
|
||
}}>
|
||
{JSON.stringify(selectedSkill, null, 2)}
|
||
</pre>
|
||
</Card>
|
||
</TabPane>
|
||
</Tabs>
|
||
)}
|
||
</Drawer>
|
||
|
||
{/* Skill 测试弹窗 */}
|
||
<Modal
|
||
title={
|
||
<Space>
|
||
<PlayCircleOutlined />
|
||
<span>测试 Skill: {selectedSkill?.name}</span>
|
||
</Space>
|
||
}
|
||
open={testModalOpen}
|
||
onCancel={() => {
|
||
setTestModalOpen(false)
|
||
setTestInput('')
|
||
setTestResult('')
|
||
}}
|
||
width={800}
|
||
footer={null}
|
||
destroyOnHidden
|
||
>
|
||
{selectedSkill && (
|
||
<Space direction="vertical" style={{ width: '100%' }} size="large">
|
||
<div>
|
||
<div style={{ marginBottom: '8px' }}>
|
||
<strong>测试输入:</strong>
|
||
<span style={{ color: '#999', marginLeft: '8px', fontSize: '12px' }}>
|
||
输入要发送给 Skill 的内容
|
||
</span>
|
||
</div>
|
||
<TextArea
|
||
rows={4}
|
||
placeholder="输入测试内容... 例如:请写一段关于春天的诗歌"
|
||
value={testInput}
|
||
onChange={(e) => setTestInput(e.target.value)}
|
||
/>
|
||
</div>
|
||
|
||
<Button
|
||
type="primary"
|
||
size="large"
|
||
loading={testing}
|
||
onClick={handleRunTest}
|
||
disabled={!testInput.trim()}
|
||
block
|
||
icon={<PlayCircleOutlined />}
|
||
>
|
||
{testing ? '测试中...' : '开始测试'}
|
||
</Button>
|
||
|
||
{testResult && (
|
||
<Card
|
||
title={
|
||
<Space>
|
||
<CheckCircleOutlined style={{ color: '#52c41a' }} />
|
||
<span>测试结果</span>
|
||
</Space>
|
||
}
|
||
size="small"
|
||
extra={
|
||
<Button
|
||
size="small"
|
||
icon={<CopyOutlined />}
|
||
onClick={() => {
|
||
navigator.clipboard.writeText(testResult)
|
||
message.success('已复制到剪贴板')
|
||
}}
|
||
>
|
||
复制结果
|
||
</Button>
|
||
}
|
||
>
|
||
<pre style={{
|
||
whiteSpace: 'pre-wrap',
|
||
background: '#f5f5f5',
|
||
padding: '12px',
|
||
borderRadius: '4px',
|
||
maxHeight: '60vh',
|
||
overflowY: 'auto',
|
||
fontSize: '13px',
|
||
lineHeight: '1.6'
|
||
}}>
|
||
{testResult}
|
||
</pre>
|
||
</Card>
|
||
)}
|
||
|
||
{!testing && !testResult && (
|
||
<Card size="small" style={{ background: '#fafafa' }}>
|
||
<Space direction="vertical" size="small">
|
||
<p style={{ margin: 0, color: '#666' }}>
|
||
<strong>提示:</strong> 输入测试内容后点击"开始测试"按钮
|
||
</p>
|
||
<p style={{ margin: 0, color: '#999', fontSize: '12px' }}>
|
||
测试将使用 Skill 的行为指导来生成响应
|
||
</p>
|
||
</Space>
|
||
</Card>
|
||
)}
|
||
</Space>
|
||
)}
|
||
</Modal>
|
||
|
||
{/* 创建 Skill 弹窗 - AI 辅助版本 */}
|
||
<Modal
|
||
title={
|
||
<Space>
|
||
<PlusOutlined />
|
||
<span>创建新 Skill</span>
|
||
</Space>
|
||
}
|
||
open={createModalOpen}
|
||
onCancel={() => {
|
||
setCreateModalOpen(false)
|
||
form.resetFields()
|
||
setGeneratedDraft(null)
|
||
setAiIdea('')
|
||
setAiCategory(undefined)
|
||
}}
|
||
width={900}
|
||
footer={null}
|
||
destroyOnHidden
|
||
>
|
||
<Tabs
|
||
activeKey={createMode}
|
||
onChange={(key) => setCreateMode(key as CreateMode)}
|
||
type="card"
|
||
>
|
||
{/* AI 辅助创建 */}
|
||
<TabPane
|
||
tab={
|
||
<Space>
|
||
<RobotOutlined />
|
||
<span>AI 辅助创建</span>
|
||
</Space>
|
||
}
|
||
key="ai"
|
||
>
|
||
<Space direction="vertical" style={{ width: '100%' }} size="large">
|
||
{/* AI 输入区域 */}
|
||
<Card size="small" title="1. 描述您的想法">
|
||
<Space direction="vertical" style={{ width: '100%' }} size="middle">
|
||
<div>
|
||
<div style={{ marginBottom: '8px' }}>
|
||
<strong>您的想法:</strong>
|
||
<span style={{ color: '#999', marginLeft: '8px', fontSize: '12px' }}>
|
||
描述您想要的 Skill 功能和用途
|
||
</span>
|
||
</div>
|
||
<TextArea
|
||
rows={3}
|
||
placeholder="例如: 我想要一个能写现代都市对话的 Skill,要求对话自然、口语化,适合年轻人的交流方式..."
|
||
value={aiIdea}
|
||
onChange={(e) => setAiIdea(e.target.value)}
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<strong>可选分类:</strong>
|
||
<Select
|
||
placeholder="选择分类(可选)"
|
||
style={{ width: '100%', marginTop: '8px' }}
|
||
value={aiCategory}
|
||
onChange={setAiCategory}
|
||
allowClear
|
||
>
|
||
<Option value="writing">写作</Option>
|
||
<Option value="dialogue">对话</Option>
|
||
<Option value="analysis">分析</Option>
|
||
<Option value="translation">翻译</Option>
|
||
<Option value="general">通用</Option>
|
||
</Select>
|
||
</div>
|
||
|
||
<Space>
|
||
<Button
|
||
type="primary"
|
||
size="large"
|
||
icon={<RobotOutlined />}
|
||
loading={generating}
|
||
onClick={handleGenerate}
|
||
disabled={!aiIdea.trim()}
|
||
>
|
||
{generating ? 'AI 生成中...' : 'AI 生成 Skill'}
|
||
</Button>
|
||
{generatedDraft && (
|
||
<Button
|
||
icon={<ReloadOutlined />}
|
||
onClick={handleRegenerate}
|
||
disabled={generating}
|
||
>
|
||
重新生成
|
||
</Button>
|
||
)}
|
||
</Space>
|
||
</Space>
|
||
</Card>
|
||
|
||
{/* 生成的结果 */}
|
||
{generatedDraft && (
|
||
<>
|
||
<Alert
|
||
message="AI 生成成功"
|
||
description="您可以继续编辑下面的内容,或使用优化功能进一步改进"
|
||
type="success"
|
||
showIcon
|
||
closable
|
||
/>
|
||
|
||
<Card
|
||
size="small"
|
||
title="2. 编辑和优化"
|
||
extra={
|
||
<Space>
|
||
<Button
|
||
size="small"
|
||
icon={<EditFilled />}
|
||
onClick={() => {
|
||
Modal.info({
|
||
title: '优化 Skill',
|
||
content: (
|
||
<Space direction="vertical" style={{ width: '100%' }}>
|
||
<div>
|
||
<strong>优化需求:</strong>
|
||
<TextArea
|
||
rows={3}
|
||
placeholder="描述您希望如何改进这个 Skill..."
|
||
value={optimizeRequirements}
|
||
onChange={(e) => setOptimizeRequirements(e.target.value)}
|
||
/>
|
||
</div>
|
||
<Button
|
||
type="primary"
|
||
loading={optimizing}
|
||
onClick={() => {
|
||
handleOptimize()
|
||
}}
|
||
block
|
||
>
|
||
开始优化
|
||
</Button>
|
||
</Space>
|
||
),
|
||
width: 600,
|
||
okText: '取消'
|
||
})
|
||
}}
|
||
>
|
||
优化
|
||
</Button>
|
||
</Space>
|
||
}
|
||
>
|
||
<Form
|
||
form={form}
|
||
layout="vertical"
|
||
initialValues={{
|
||
tags: [],
|
||
category: 'general'
|
||
}}
|
||
>
|
||
<Form.Item
|
||
label="Skill ID"
|
||
name="id"
|
||
rules={[
|
||
{ required: true, message: '请输入 Skill ID' },
|
||
{ pattern: /^[a-z0-9-]+$/, message: '只能包含小写字母、数字和连字符' }
|
||
]}
|
||
>
|
||
<InputComponent placeholder="例如: my-custom-skill" prefix={<AppstoreOutlined />} />
|
||
</Form.Item>
|
||
|
||
<Form.Item
|
||
label="Skill 名称"
|
||
name="name"
|
||
rules={[{ required: true, message: '请输入 Skill 名称' }]}
|
||
>
|
||
<InputComponent placeholder="例如: 自定义创作助手" />
|
||
</Form.Item>
|
||
|
||
<Form.Item
|
||
label="分类"
|
||
name="category"
|
||
rules={[{ required: true, message: '请选择分类' }]}
|
||
>
|
||
<Select placeholder="选择分类">
|
||
<Option value="writing">写作</Option>
|
||
<Option value="dialogue">对话</Option>
|
||
<Option value="analysis">分析</Option>
|
||
<Option value="translation">翻译</Option>
|
||
<Option value="general">通用</Option>
|
||
</Select>
|
||
</Form.Item>
|
||
|
||
<Form.Item
|
||
label="行为指导 (核心)"
|
||
name="behavior_guide"
|
||
rules={[{ required: true, message: '请输入行为指导' }]}
|
||
extra="这是 AI 执行任务时遵循的核心指导原则"
|
||
>
|
||
<TextArea
|
||
rows={10}
|
||
placeholder="AI 生成的行为指导将显示在这里..."
|
||
style={{ fontFamily: 'monospace', fontSize: '13px' }}
|
||
/>
|
||
</Form.Item>
|
||
|
||
<Form.Item
|
||
label="标签"
|
||
name="tags"
|
||
>
|
||
<Select mode="tags" placeholder="输入标签后按回车">
|
||
</Select>
|
||
</Form.Item>
|
||
</Form>
|
||
</Card>
|
||
</>
|
||
)}
|
||
|
||
{/* 保存按钮 */}
|
||
{generatedDraft && (
|
||
<Card size="small">
|
||
<Space style={{ width: '100%', justifyContent: 'flex-end' }}>
|
||
<Button onClick={() => setCreateModalOpen(false)}>
|
||
取消
|
||
</Button>
|
||
<Button
|
||
type="primary"
|
||
loading={submitting}
|
||
onClick={handleCreateSubmit}
|
||
size="large"
|
||
>
|
||
保存 Skill
|
||
</Button>
|
||
</Space>
|
||
</Card>
|
||
)}
|
||
</Space>
|
||
</TabPane>
|
||
|
||
{/* 手动创建 */}
|
||
<TabPane
|
||
tab={
|
||
<Space>
|
||
<EditOutlined />
|
||
<span>手动创建</span>
|
||
</Space>
|
||
}
|
||
key="manual"
|
||
>
|
||
<Form
|
||
form={form}
|
||
layout="vertical"
|
||
initialValues={{
|
||
tags: [],
|
||
category: 'general'
|
||
}}
|
||
>
|
||
<Form.Item
|
||
label="Skill ID"
|
||
name="id"
|
||
rules={[
|
||
{ required: true, message: '请输入 Skill ID' },
|
||
{ pattern: /^[a-z0-9-]+$/, message: '只能包含小写字母、数字和连字符' }
|
||
]}
|
||
>
|
||
<InputComponent placeholder="例如: my-custom-skill" prefix={<AppstoreOutlined />} />
|
||
</Form.Item>
|
||
|
||
<Form.Item
|
||
label="Skill 名称"
|
||
name="name"
|
||
rules={[{ required: true, message: '请输入 Skill 名称' }]}
|
||
>
|
||
<InputComponent placeholder="例如: 自定义创作助手" />
|
||
</Form.Item>
|
||
|
||
<Form.Item
|
||
label="分类"
|
||
name="category"
|
||
rules={[{ required: true, message: '请选择分类' }]}
|
||
>
|
||
<Select placeholder="选择分类">
|
||
<Option value="writing">写作</Option>
|
||
<Option value="dialogue">对话</Option>
|
||
<Option value="analysis">分析</Option>
|
||
<Option value="translation">翻译</Option>
|
||
<Option value="general">通用</Option>
|
||
</Select>
|
||
</Form.Item>
|
||
|
||
<Form.Item
|
||
label="行为指导 (核心)"
|
||
name="behavior_guide"
|
||
rules={[{ required: true, message: '请输入行为指导' }]}
|
||
extra="这是 AI 执行任务时遵循的核心指导原则"
|
||
>
|
||
<TextArea
|
||
rows={8}
|
||
placeholder="输入详细的行为指导... 例如:你是一个专业的编剧助手,擅长创作古风对话..."
|
||
/>
|
||
</Form.Item>
|
||
|
||
<Form.Item
|
||
label="标签"
|
||
name="tags"
|
||
>
|
||
<Select mode="tags" placeholder="输入标签后按回车">
|
||
</Select>
|
||
</Form.Item>
|
||
</Form>
|
||
|
||
<Card size="small" style={{ marginTop: '16px' }}>
|
||
<Space style={{ width: '100%', justifyContent: 'flex-end' }}>
|
||
<Button onClick={() => setCreateModalOpen(false)}>
|
||
取消
|
||
</Button>
|
||
<Button
|
||
type="primary"
|
||
loading={submitting}
|
||
onClick={handleCreateSubmit}
|
||
size="large"
|
||
>
|
||
创建 Skill
|
||
</Button>
|
||
</Space>
|
||
</Card>
|
||
</TabPane>
|
||
|
||
{/* 从模板创建 - 快速创建 Skill */}
|
||
<TabPane
|
||
tab={
|
||
<Space>
|
||
<AppstoreOutlined />
|
||
<span>快速创建</span>
|
||
</Space>
|
||
}
|
||
key="template"
|
||
>
|
||
<Space direction="vertical" style={{ width: '100%' }} size="large">
|
||
<Card
|
||
size="small"
|
||
title="创建新 Skill"
|
||
>
|
||
<Space direction="vertical" style={{ width: '100%' }} size="middle">
|
||
<div>
|
||
<div style={{ marginBottom: '8px' }}>
|
||
<strong>Skill ID:</strong>
|
||
<span style={{ color: '#999', marginLeft: '8px', fontSize: '12px' }}>
|
||
小写字母和连字符,如: my-awesome-skill
|
||
</span>
|
||
</div>
|
||
<InputComponent
|
||
placeholder="例如: my-awesome-skill"
|
||
value={templateSkillName}
|
||
onChange={(e) => setTemplateSkillName(e.target.value)}
|
||
prefix={<AppstoreOutlined />}
|
||
/>
|
||
</div>
|
||
|
||
<Button
|
||
type="primary"
|
||
size="large"
|
||
icon={<AppstoreOutlined />}
|
||
loading={initializing}
|
||
onClick={handleInitFromTemplate}
|
||
disabled={!templateSkillName.trim()}
|
||
block
|
||
>
|
||
{initializing ? '创建中...' : '创建 Skill'}
|
||
</Button>
|
||
|
||
{initResult && (
|
||
<Alert
|
||
message="Skill 创建成功!"
|
||
description="请在下方完善 Skill 的内容"
|
||
type="success"
|
||
showIcon
|
||
closable
|
||
/>
|
||
)}
|
||
</Space>
|
||
</Card>
|
||
|
||
{initResult && (
|
||
<>
|
||
<Alert
|
||
message="请完善 Skill 内容"
|
||
description="模板已创建,现在请完善下面的 Skill 信息"
|
||
type="info"
|
||
showIcon
|
||
/>
|
||
|
||
<Card size="small" title="完善 Skill 信息">
|
||
<Form
|
||
form={form}
|
||
layout="vertical"
|
||
initialValues={{
|
||
tags: [],
|
||
category: 'general'
|
||
}}
|
||
>
|
||
<Form.Item
|
||
label="Skill ID"
|
||
name="id"
|
||
rules={[
|
||
{ required: true, message: '请输入 Skill ID' },
|
||
{ pattern: /^[a-z0-9-]+$/, message: '只能包含小写字母、数字和连字符' }
|
||
]}
|
||
>
|
||
<InputComponent disabled prefix={<AppstoreOutlined />} />
|
||
</Form.Item>
|
||
|
||
<Form.Item
|
||
label="Skill 名称"
|
||
name="name"
|
||
rules={[{ required: true, message: '请输入 Skill 名称' }]}
|
||
>
|
||
<InputComponent placeholder="例如: 自定义创作助手" />
|
||
</Form.Item>
|
||
|
||
<Form.Item
|
||
label="分类"
|
||
name="category"
|
||
rules={[{ required: true, message: '请选择分类' }]}
|
||
>
|
||
<Select placeholder="选择分类">
|
||
<Option value="writing">写作</Option>
|
||
<Option value="dialogue">对话</Option>
|
||
<Option value="analysis">分析</Option>
|
||
<Option value="translation">翻译</Option>
|
||
<Option value="general">通用</Option>
|
||
</Select>
|
||
</Form.Item>
|
||
|
||
<Form.Item
|
||
label="行为指导 (核心)"
|
||
name="behavior_guide"
|
||
rules={[{ required: true, message: '请输入行为指导' }]}
|
||
extra="这是 AI 执行任务时遵循的核心指导原则"
|
||
>
|
||
<TextArea
|
||
rows={10}
|
||
placeholder="输入详细的行为指导..."
|
||
style={{ fontFamily: 'monospace', fontSize: '13px' }}
|
||
/>
|
||
</Form.Item>
|
||
|
||
<Form.Item
|
||
label="标签"
|
||
name="tags"
|
||
>
|
||
<Select mode="tags" placeholder="输入标签后按回车">
|
||
</Select>
|
||
</Form.Item>
|
||
</Form>
|
||
</Card>
|
||
|
||
<Card size="small">
|
||
<Space style={{ width: '100%', justifyContent: 'flex-end' }}>
|
||
<Button onClick={() => setCreateModalOpen(false)}>
|
||
取消
|
||
</Button>
|
||
<Button
|
||
type="primary"
|
||
loading={submitting}
|
||
onClick={handleCreateSubmit}
|
||
size="large"
|
||
>
|
||
保存 Skill
|
||
</Button>
|
||
</Space>
|
||
</Card>
|
||
</>
|
||
)}
|
||
</Space>
|
||
</TabPane>
|
||
</Tabs>
|
||
</Modal>
|
||
|
||
{/* 编辑 Skill 弹窗 */}
|
||
<Modal
|
||
title={
|
||
<Space>
|
||
<EditOutlined />
|
||
<span>编辑 Skill: {editingSkill?.name}</span>
|
||
</Space>
|
||
}
|
||
open={editModalOpen}
|
||
onCancel={() => {
|
||
setEditModalOpen(false)
|
||
setEditingSkill(null)
|
||
form.resetFields()
|
||
}}
|
||
width={700}
|
||
footer={[
|
||
<Button key="cancel" onClick={() => setEditModalOpen(false)}>
|
||
取消
|
||
</Button>,
|
||
<Button
|
||
key="submit"
|
||
type="primary"
|
||
loading={submitting}
|
||
onClick={handleEditSubmit}
|
||
>
|
||
保存
|
||
</Button>
|
||
]}
|
||
destroyOnHidden
|
||
>
|
||
<Form
|
||
form={form}
|
||
layout="vertical"
|
||
>
|
||
<Form.Item
|
||
label="Skill ID"
|
||
name="id"
|
||
>
|
||
<InputComponent disabled prefix={<AppstoreOutlined />} />
|
||
</Form.Item>
|
||
|
||
<Form.Item
|
||
label="Skill 名称"
|
||
name="name"
|
||
rules={[{ required: true, message: '请输入 Skill 名称' }]}
|
||
>
|
||
<InputComponent placeholder="例如: 自定义创作助手" />
|
||
</Form.Item>
|
||
|
||
<Form.Item
|
||
label="分类"
|
||
name="category"
|
||
>
|
||
<Select disabled>
|
||
<Option value="writing">写作</Option>
|
||
<Option value="dialogue">对话</Option>
|
||
<Option value="analysis">分析</Option>
|
||
<Option value="translation">翻译</Option>
|
||
<Option value="general">通用</Option>
|
||
</Select>
|
||
</Form.Item>
|
||
|
||
<Form.Item
|
||
label="行为指导 (核心)"
|
||
name="behavior_guide"
|
||
rules={[{ required: true, message: '请输入行为指导' }]}
|
||
>
|
||
<TextArea
|
||
rows={8}
|
||
placeholder="输入详细的行为指导..."
|
||
/>
|
||
</Form.Item>
|
||
|
||
<Form.Item
|
||
label="标签"
|
||
name="tags"
|
||
>
|
||
<Select mode="tags" placeholder="输入标签后按回车">
|
||
</Select>
|
||
</Form.Item>
|
||
</Form>
|
||
</Modal>
|
||
|
||
{/* Skill 创建 - AI 自动生成 */}
|
||
<SkillCreate
|
||
visible={wizardVisible}
|
||
onClose={() => {
|
||
setWizardVisible(false)
|
||
setEditingSkill(null)
|
||
// 如果有跳转回退信息,关闭时跳转回去
|
||
if (redirectState) {
|
||
navigate(redirectState.path, {
|
||
state: { activeTab: redirectState.activeTab, reviewSubTab: redirectState.reviewSubTab }
|
||
})
|
||
setRedirectState(null)
|
||
}
|
||
}}
|
||
onSuccess={() => {
|
||
fetchSkills()
|
||
// 不自动关闭,让用户查看创建结果后主动关闭
|
||
// setWizardVisible(false)
|
||
// setEditingSkill(null)
|
||
}}
|
||
editingSkillId={editingSkill?.id}
|
||
/>
|
||
</div>
|
||
)
|
||
}
|