chore(integration): snapshot local main worktree
This commit is contained in:
@@ -121,7 +121,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);
|
||||
}
|
||||
@@ -139,11 +139,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;
|
||||
@@ -163,46 +163,46 @@ 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="Agent 提示词" 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="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>
|
||||
{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); return <label key={skill.id} className={cn('flex cursor-pointer items-start gap-2 rounded-md border border-border/80 p-2.5', checked ? 'bg-background' : 'bg-surface-subtle')}><input type="checkbox" aria-label={`绑定技能:${skill.name}`} checked={checked} 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></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="space-y-2">{filteredSkills.map((skill) => { const checked = input.skillIds.includes(skill.id); return <label key={skill.id} className={cn('flex cursor-pointer items-start gap-2 rounded-md border border-border/80 p-2.5', checked ? 'bg-background' : 'bg-surface-subtle')}><input type="checkbox" aria-label={`启用技能:${skill.name}`} checked={checked} 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></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')}>
|
||||
{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>
|
||||
@@ -213,7 +213,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); }}
|
||||
@@ -221,7 +221,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 ? '处理中…' : '从本地上传'}
|
||||
|
||||
Reference in New Issue
Block a user