feat(plugins): merge Skill management into plugins
This commit is contained in:
@@ -11,7 +11,6 @@ import { agentAvatarOptions } from '@/lib/agent-avatars';
|
||||
import { codingModelKey, type CodingModelOption } from '@/lib/coding-model-options';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { CodingProjectAgent } from '@/types/coding-project';
|
||||
import { DEFAULT_PROJECT_AGENT_SKILL_IDS } from '../../../shared/coding-skills';
|
||||
|
||||
export type AgentCreationSkillOption = {
|
||||
id: string;
|
||||
@@ -84,7 +83,6 @@ export function AgentCreationDialog({
|
||||
}: AgentCreationDialogProps) {
|
||||
const editing = Boolean(agent);
|
||||
const [input, setInput] = useState<AgentCreationInput>(() => createInitialInput(modelOptions, agent));
|
||||
const defaultSkillsAppliedRef = useRef(false);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [skillQuery, setSkillQuery] = useState('');
|
||||
const [avatarPickerOpen, setAvatarPickerOpen] = useState(false);
|
||||
@@ -93,25 +91,13 @@ export function AgentCreationDialog({
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
defaultSkillsAppliedRef.current = false;
|
||||
return;
|
||||
}
|
||||
defaultSkillsAppliedRef.current = editing;
|
||||
setInput(createInitialInput(modelOptions, agent));
|
||||
setSkillQuery('');
|
||||
setAvatarPickerOpen(false);
|
||||
}, [agent, editing, modelOptions, open]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open || editing || defaultSkillsAppliedRef.current) return;
|
||||
const defaultSkillIds = DEFAULT_PROJECT_AGENT_SKILL_IDS.filter((skillId) => skills.some((skill) => skill.id === skillId));
|
||||
if (defaultSkillIds.length === 0) return;
|
||||
setInput((current) => current.skillIds.length > 0
|
||||
? current
|
||||
: { ...current, skillIds: [...defaultSkillIds] });
|
||||
defaultSkillsAppliedRef.current = true;
|
||||
}, [editing, open, skills]);
|
||||
|
||||
const handleAvatarFileChange = async (event: ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.target.files?.[0];
|
||||
event.target.value = '';
|
||||
@@ -166,7 +152,7 @@ export function AgentCreationDialog({
|
||||
<DialogContent className="max-h-[min(820px,calc(100vh-2rem))] overflow-y-auto sm:max-w-lg">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{title ?? (editing ? '编辑项目智能体' : '创建项目智能体')}</DialogTitle>
|
||||
<DialogDescription>配置智能体的身份、默认模型、职责、系统指令和可用技能。</DialogDescription>
|
||||
<DialogDescription>配置智能体的身份、默认模型、职责、系统指令和插件能力。</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4 py-1">
|
||||
<div>
|
||||
@@ -193,11 +179,11 @@ export function AgentCreationDialog({
|
||||
<section className="space-y-4 rounded-lg border border-border/80 bg-surface-subtle p-4">
|
||||
<h3 className="font-semibold">高级设置</h3>
|
||||
<div><label htmlFor="agent-create-system-instructions" className="text-sm font-semibold">系统指令</label><Textarea id="agent-create-system-instructions" value={input.prompt} onChange={(event) => setInput((current) => ({ ...current, prompt: event.target.value }))} className="mt-2 min-h-28 bg-background font-mono text-xs leading-5" placeholder="可选:定义智能体的工作方式、约束和输出要求。" /></div>
|
||||
<div className="flex items-center justify-between gap-3"><h4 className="text-sm font-semibold">启用技能</h4><Badge className="border border-border/80 bg-background text-foreground">已选 {input.skillIds.length}</Badge></div>
|
||||
<div className="flex items-center justify-between gap-3"><h4 className="text-sm font-semibold">插件能力</h4><Badge className="border border-border/80 bg-background text-foreground">已选 {input.skillIds.length}</Badge></div>
|
||||
{skills.length > 0 ? <>
|
||||
<div className="relative"><Search className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" /><Input aria-label="搜索技能" value={skillQuery} onChange={(event) => setSkillQuery(event.target.value)} placeholder="搜索技能" className="bg-background pl-9" /></div>
|
||||
<div className="space-y-2">{filteredSkills.map((skill) => { const checked = input.skillIds.includes(skill.id); const unavailable = skill.available === false; return <label key={skill.id} className={cn('flex items-start gap-2 rounded-md border border-border/80 p-2.5', unavailable ? 'cursor-not-allowed opacity-70' : 'cursor-pointer', checked ? 'bg-background' : 'bg-surface-subtle')}><input type="checkbox" aria-label={`绑定技能:${skill.name}`} checked={checked} disabled={unavailable} onChange={() => setInput((current) => ({ ...current, skillIds: checked ? current.skillIds.filter((id) => id !== skill.id) : [...current.skillIds, skill.id] }))} /><span><span className="text-sm font-semibold">{skill.name}</span><span className="mt-0.5 block text-xs text-muted-foreground">{skill.description}</span>{unavailable ? <span className="mt-1 block text-xs font-medium text-amber-700">插件未启用;现有分配会保留,但当前不可用。</span> : null}</span></label>; })}{filteredSkills.length === 0 ? <p className="rounded-md border border-dashed border-border/80 p-3 text-center text-xs font-medium text-muted-foreground">没有匹配的技能</p> : null}</div>
|
||||
</> : <p className="mt-3 rounded-md border border-dashed border-border/80 p-3 text-xs font-medium text-muted-foreground">当前暂无可绑定技能。</p>}
|
||||
<div className="relative"><Search className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" /><Input aria-label="搜索插件能力" value={skillQuery} onChange={(event) => setSkillQuery(event.target.value)} placeholder="搜索插件能力" className="bg-background pl-9" /></div>
|
||||
<div className="space-y-2">{filteredSkills.map((skill) => { const checked = input.skillIds.includes(skill.id); const unavailable = skill.available === false; return <label key={skill.id} className={cn('flex items-start gap-2 rounded-md border border-border/80 p-2.5', unavailable ? 'cursor-not-allowed opacity-70' : 'cursor-pointer', checked ? 'bg-background' : 'bg-surface-subtle')}><input type="checkbox" aria-label={`绑定插件能力:${skill.name}`} checked={checked} disabled={unavailable} onChange={() => setInput((current) => ({ ...current, skillIds: checked ? current.skillIds.filter((id) => id !== skill.id) : [...current.skillIds, skill.id] }))} /><span><span className="text-sm font-semibold">{skill.name}</span><span className="mt-0.5 block text-xs text-muted-foreground">{skill.description}</span>{unavailable ? <span className="mt-1 block text-xs font-medium text-amber-700">插件未启用;现有分配会保留,但当前不可用。</span> : null}</span></label>; })}{filteredSkills.length === 0 ? <p className="rounded-md border border-dashed border-border/80 p-3 text-center text-xs font-medium text-muted-foreground">没有匹配的插件能力</p> : null}</div>
|
||||
</> : <p className="mt-3 rounded-md border border-dashed border-border/80 p-3 text-xs font-medium text-muted-foreground">当前暂无可分配能力,请先在插件模块完成添加与启用。</p>}
|
||||
</section>
|
||||
</div>
|
||||
<DialogFooter className={cn(agent && onArchive && 'sm:justify-between')}>
|
||||
|
||||
Reference in New Issue
Block a user