@@ -123,7 +123,7 @@ export function AgentCreationDialog({
setInput ( ( current ) = > ( { . . . current , avatarDataUrl : prepared.previewUrl } ) ) ;
setAvatarPickerOpen ( false ) ;
} catch ( error ) {
toast . error ( error instanceof Error ? error . message : '伙伴 头像图片处理失败' ) ;
toast . error ( error instanceof Error ? error . message : '智能体 头像图片处理失败' ) ;
} finally {
setAvatarProcessing ( false ) ;
}
@@ -141,11 +141,11 @@ export function AgentCreationDialog({
const name = input . name . trim ( ) ;
const responsibility = input . responsibility . trim ( ) ;
if ( ! name || ! responsibility || ! input . model ) {
toast . error ( '请填写伙伴 名称、 职责,并配置伙伴 模型。' ) ;
toast . error ( '请填写智能体 名称和 职责说明 ,并配置默认 模型。' ) ;
return ;
}
if ( existingAgentNames . some ( ( agentName ) = > agentName . trim ( ) === name ) ) {
toast . error ( '伙伴 名称需要在当前项目内唯一。' ) ;
toast . error ( '智能体 名称需要在当前项目内唯一。' ) ;
return ;
}
const submit = editing ? onUpdate : onCreate ;
@@ -165,35 +165,35 @@ export function AgentCreationDialog({
< Dialog open = { open } onOpenChange = { onOpenChange } >
< DialogContent className = "max-h-[min(820px,calc(100vh-2rem))] overflow-y-auto sm:max-w-lg" >
< DialogHeader >
< DialogTitle > { title ? ? ( editing ? '维护项目伙伴 ' : '创建项目伙伴 ' ) } < / DialogTitle >
< DialogDescription > 设 置 伙 伴 的 基 础 信 息 , 也 可 以 补 充 提 示 词 和 技 能 。 < / DialogDescription >
< DialogTitle > { title ? ? ( editing ? '编辑项目智能体 ' : '创建项目智能体 ' ) } < / DialogTitle >
< DialogDescription > 配 置 智 能 体 的 身 份 、 默 认 模 型 、 职 责 、 系 统 指 令 和 可 用 技能 。 < / DialogDescription >
< / DialogHeader >
< div className = "space-y-4 py-1" >
< div >
< label htmlFor = "agent-create-name" className = "text-sm font-semibold" > 伙 伴 名 称 < span className = "text-destructive" > * < / span > < / label >
< label htmlFor = "agent-create-name" className = "text-sm font-semibold" > 智 能 体 名 称 < span className = "text-destructive" > * < / span > < / label >
< div className = "mt-2 flex items-center gap-3" >
< button type = "button" aria-label = "更换头像" title = "更换头像" aria-haspopup = "dialog" onClick = { ( ) = > setAvatarPickerOpen ( true ) } className = "motion-press h-10 w-10 shrink-0 overflow-hidden rounded-xl border border-brand bg-brand-soft p-0.5 shadow-soft" >
< img src = { selectedAvatarSrc } alt = { input . avatarDataUrl ? '当前上传头像' : selectedAvatar ? . label ? ? '当前头像' } className = "h-full w-full rounded-lg object-cover [image-rendering:pixelated]" / >
< / button >
< Input id = "agent-create-name" value = { input . name } maxLength = { 30 } placeholder = "例如:小明 " onChange = { ( event ) = > setInput ( ( current ) = > ( { . . . current , name : event.target.value } ) ) } className = "mt-0 flex-1" / >
< Input id = "agent-create-name" value = { input . name } maxLength = { 30 } placeholder = "例如:前端开发智能体 " onChange = { ( event ) = > setInput ( ( current ) = > ( { . . . current , name : event.target.value } ) ) } className = "mt-0 flex-1" / >
< / div >
< / div >
< div >
< label htmlFor = "agent-create-model" className = "text-sm font-semibold" > 伙 伴 模 型 < span className = "text-destructive" > * < / span > < / label >
< p className = "mt-1 text-xs font-medium text-muted-foreground" > 当 前 伙 伴 的 消 息 固 定 使 用 此 模 型 ; 如 需 切 换 , 请 在 这 里 修 改 。 < / p >
< label htmlFor = "agent-create-model" className = "text-sm font-semibold" > 默 认 模 型 < span className = "text-destructive" > * < / span > < / label >
< p className = "mt-1 text-xs font-medium text-muted-foreground" > 新 对 话 默 认 使 用 此 模 型 ; 在 对 话 中 临 时 切 换 不 会 修 改 这 里 的 配 置 。 < / p >
< select id = "agent-create-model" value = { input . model } onChange = { ( event ) = > setInput ( ( current ) = > ( { . . . current , model : event.target.value } ) ) } className = "mt-2 h-10 w-full rounded-xl border border-border bg-surface-input px-3 text-sm" >
< option value = "" disabled > 请 选 择 已 配 置 模 型 < / option >
{ modelOptions . map ( ( option ) = > < option key = { option . key } value = { option . key } > { option . label } < / option > ) }
< / select >
< / div >
< div >
< label htmlFor = "agent-create-responsibility" className = "text-sm font-semibold" > 职 责 简 述 < span className = "text-destructive" > * < / span > < / label >
< label htmlFor = "agent-create-responsibility" className = "text-sm font-semibold" > 职 责 说 明 < span className = "text-destructive" > * < / span > < / label >
< Input id = "agent-create-responsibility" value = { input . responsibility } maxLength = { 200 } placeholder = "例如:负责把想法拆成清晰的产品计划。" onChange = { ( event ) = > setInput ( ( current ) = > ( { . . . current , responsibility : event.target.value } ) ) } className = "mt-2" / >
< / div >
< section className = "space-y-4 rounded-lg border border-border/80 bg-surface-subtle p-4" >
< h3 className = "font-semibold" > 高 级 设 置 ( 提 示 词 与 技 能 ) < / h3 >
< Textarea aria-label = "A gent 提示词 " value = { input . prompt } onChange = { ( event ) = > setInput ( ( current ) = > ( { . . . current , prompt : event.target.value } ) ) } className = "mt-3 min-h-28 bg-background font-mono text-xs leading-5" placeholder = "可选:补充这个伙伴的工作方式与边界。" / >
< 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 >
< h3 className = "font-semibold" > 高 级 设 置 < / h3 >
< div > < label htmlFor = "agent-create-system-instructions" className = "text-sm font-semibold" > 系 统 指 令 < / label > < Textarea id = "a gent-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 >
{ 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 >
@@ -201,10 +201,10 @@ export function AgentCreationDialog({
< / section >
< / div >
< DialogFooter className = { cn ( agent && onArchive && 'sm:justify-between' ) } >
{ agent && onArchive ? < Button type = "button" variant = "outline" aria-label = "归档伙伴 " className = "border-border/80 bg-accent-soft" onClick = { onArchive } disabled = { submitting } > < Trash2 className = "mr-2 h-4 w-4" / > 归 档 伙 伴 < / Button > : null }
{ agent && onArchive ? < Button type = "button" variant = "outline" aria-label = "归档智能体 " className = "border-border/80 bg-accent-soft" onClick = { onArchive } disabled = { submitting } > < Trash2 className = "mr-2 h-4 w-4" / > 归 档 智 能 体 < / Button > : null }
< div className = "flex flex-col-reverse gap-2 sm:ml-auto sm:flex-row" >
< Button type = "button" variant = "outline" onClick = { ( ) = > onOpenChange ( false ) } disabled = { submitting } > 取 消 < / Button >
< Button type = "button" onClick = { ( ) = > void handleSubmit ( ) } disabled = { submitting || modelOptions . length === 0 } > { submitting ? ( editing ? '保存中…' : '创建中…' ) : ( submitLabel ? ? ( editing ? '保存伙伴 ' : '创建并开始对话' ) ) } < / Button >
< Button type = "button" onClick = { ( ) = > void handleSubmit ( ) } disabled = { submitting || modelOptions . length === 0 } > { submitting ? ( editing ? '保存中…' : '创建中…' ) : ( submitLabel ? ? ( editing ? '保存智能体 ' : '创建并开始对话' ) ) } < / Button >
< / div >
< / DialogFooter >
< / DialogContent >
@@ -215,7 +215,7 @@ export function AgentCreationDialog({
ref = { avatarUploadInputRef }
type = "file"
accept = "image/png,image/jpeg,image/webp"
aria-label = "上传伙伴 头像"
aria-label = "上传智能体 头像"
data-testid = "agent-avatar-upload-input"
className = "sr-only"
onChange = { ( event ) = > { void handleAvatarFileChange ( event ) ; } }
@@ -223,7 +223,7 @@ export function AgentCreationDialog({
< div className = "flex flex-col gap-2 rounded-lg border border-dashed border-brand/35 bg-brand-soft/40 p-3 sm:flex-row sm:items-center sm:justify-between" >
< div >
< p className = "text-sm font-semibold" > 上 传 本 地 图 片 < / p >
< p className = "mt-1 text-xs font-medium text-muted-foreground" > 自 动 裁 剪 为 256 × 256 , 并 优 先 压 缩 为 WebP , 节 省 项 目 空 间 。 < / p >
< p className = "mt-1 text-xs font-medium text-muted-foreground" > 自 动 裁 剪 为 256 × 256 , 并 优 先 压 缩 为 WebP , 减 少 项 目 配 置 体 积 。 < / p >
< / div >
< Button type = "button" variant = "outline" className = "shrink-0 border-brand/30 bg-background" onClick = { ( ) = > avatarUploadInputRef . current ? . click ( ) } disabled = { avatarProcessing } >
< Upload className = "mr-2 h-4 w-4" / > { avatarProcessing ? '处理中…' : '从本地上传' }