mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-15 04:18:44 +00:00
Merge pull request #1541 from maitanloi/fix/onboarding-agent-name-slug
fix(onboarding): keep the chosen agent identity when the prompt is edited
This commit is contained in:
3 files changed
+246
-110
No files matched your search
@@ -1,6 +1,7 @@
|
||||
import { useState, useMemo, useEffect } from 'react'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { getApiClient } from '../../lib/api'
|
||||
import { slugify, isValidSlug } from '../../lib/slug'
|
||||
import { agentService } from '../../services/agent-service'
|
||||
|
||||
import { SummoningModal } from './SummoningModal'
|
||||
@@ -28,6 +29,13 @@ export function AgentStep({ provider, model, onBack, onComplete }: AgentStepProp
|
||||
const { t } = useTranslation(['desktop', 'agents', 'common'])
|
||||
const [selectedPresetIdx, setSelectedPresetIdx] = useState<number>(0)
|
||||
const [description, setDescription] = useState('')
|
||||
// Identity is editable state, only *prefilled* from the selected preset. It must
|
||||
// survive description edits — deselecting a preset never rewrites the name/key.
|
||||
const [displayName, setDisplayName] = useState('')
|
||||
const [agentKey, setAgentKey] = useState('')
|
||||
const [displayNameEdited, setDisplayNameEdited] = useState(false)
|
||||
const [agentKeyEdited, setAgentKeyEdited] = useState(false)
|
||||
const [emoji, setEmoji] = useState('')
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState('')
|
||||
const [createdAgent, setCreatedAgent] = useState<{ id: string; name: string } | null>(null)
|
||||
@@ -39,62 +47,87 @@ export function AgentStep({ provider, model, onBack, onComplete }: AgentStepProp
|
||||
return t(`${preset.ns}:presets.${preset.key}.prompt`)
|
||||
}
|
||||
|
||||
// Init description from first preset
|
||||
// Label format: "🦊 Fox Spirit" — the display name drops the emoji prefix
|
||||
function getPresetName(idx: number): string {
|
||||
const preset = PRESET_KEYS[idx]
|
||||
if (!preset) return ''
|
||||
const label = t(`${preset.ns}:presets.${preset.key}.label`) as string
|
||||
return label.replace(/^\S+\s+/, '').trim() || label.trim()
|
||||
}
|
||||
|
||||
// Init key + emoji from the first preset; name and prompt are filled by the
|
||||
// locale-sync effects below.
|
||||
useEffect(() => {
|
||||
if (!description) setDescription(getPresetPrompt(0))
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
setAgentKey(PRESET_KEYS[0].agentKey)
|
||||
setEmoji(PRESET_KEYS[0].emoji)
|
||||
}, [])
|
||||
|
||||
const displayName = useMemo(() => {
|
||||
const preset = PRESET_KEYS[selectedPresetIdx]
|
||||
if (preset) {
|
||||
const label = t(`${preset.ns}:presets.${preset.key}.label`) as string
|
||||
// Label format: "🦊 Fox Spirit" — strip emoji prefix
|
||||
return label.replace(/^\S+\s*/, '')
|
||||
}
|
||||
return 'Fox Spirit'
|
||||
}, [selectedPresetIdx, t])
|
||||
const presetPrompt = selectedPresetIdx >= 0 ? getPresetPrompt(selectedPresetIdx) : undefined
|
||||
const presetName = selectedPresetIdx >= 0 ? getPresetName(selectedPresetIdx) : undefined
|
||||
|
||||
// Agent key: stable English slug, never translated
|
||||
const agentKey = useMemo(() => {
|
||||
const preset = PRESET_KEYS[selectedPresetIdx]
|
||||
return preset?.agentKey ?? 'little-fox'
|
||||
}, [selectedPresetIdx])
|
||||
const selectedEmoji = PRESET_KEYS[selectedPresetIdx]?.emoji ?? '🦊'
|
||||
|
||||
// Sync description when preset changes
|
||||
// Language-aware re-sync: while a preset is active and the field is untouched,
|
||||
// follow the translated preset text. Deps are plain strings, so this only fires
|
||||
// when the translation actually changes (locale switch), not on every render.
|
||||
useEffect(() => {
|
||||
if (selectedPresetIdx >= 0) {
|
||||
setDescription(getPresetPrompt(selectedPresetIdx))
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectedPresetIdx])
|
||||
if (presetPrompt === undefined) return
|
||||
setDescription(presetPrompt)
|
||||
}, [presetPrompt])
|
||||
|
||||
useEffect(() => {
|
||||
if (presetName === undefined || displayNameEdited) return
|
||||
setDisplayName(presetName)
|
||||
}, [presetName, displayNameEdited])
|
||||
|
||||
const trimmedName = displayName.trim()
|
||||
const keyValid = isValidSlug(agentKey)
|
||||
const canSubmit = !!trimmedName && keyValid && !!description.trim()
|
||||
|
||||
const handleSelectPreset = (idx: number) => {
|
||||
const preset = PRESET_KEYS[idx]
|
||||
if (!preset) return
|
||||
setSelectedPresetIdx(idx)
|
||||
setDescription(getPresetPrompt(idx))
|
||||
setDisplayName(getPresetName(idx))
|
||||
setDisplayNameEdited(false)
|
||||
setEmoji(preset.emoji)
|
||||
setAgentKey(preset.agentKey)
|
||||
setAgentKeyEdited(false)
|
||||
}
|
||||
|
||||
const handleDescriptionChange = (value: string) => {
|
||||
setDescription(value)
|
||||
if (selectedPresetIdx >= 0) {
|
||||
const presetPrompt = getPresetPrompt(selectedPresetIdx)
|
||||
if (value !== presetPrompt) setSelectedPresetIdx(-1)
|
||||
}
|
||||
// Editing the prompt only clears the preset highlight — identity stays as typed.
|
||||
if (selectedPresetIdx >= 0 && value !== presetPrompt) setSelectedPresetIdx(-1)
|
||||
}
|
||||
|
||||
const handleDisplayNameChange = (value: string) => {
|
||||
setDisplayNameEdited(true)
|
||||
setDisplayName(value)
|
||||
if (!agentKeyEdited) setAgentKey(slugify(value))
|
||||
}
|
||||
|
||||
const handleAgentKeyChange = (value: string) => {
|
||||
setAgentKeyEdited(true)
|
||||
setAgentKey(value)
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!description.trim()) return
|
||||
if (!canSubmit) return
|
||||
setLoading(true)
|
||||
setError('')
|
||||
try {
|
||||
const result = await getApiClient().post<{ id: string }>('/v1/agents', {
|
||||
agent_key: agentKey,
|
||||
display_name: displayName.trim() || undefined,
|
||||
display_name: trimmedName,
|
||||
provider: provider.name,
|
||||
model: model || '',
|
||||
agent_type: 'predefined',
|
||||
is_default: true,
|
||||
// Promoted fields at top level — agent_description triggers summoning on backend
|
||||
agent_description: description.trim() || null,
|
||||
emoji: selectedEmoji || null,
|
||||
emoji: emoji.trim() || null,
|
||||
})
|
||||
setCreatedAgent({ id: result.id, name: displayName.trim() || agentKey })
|
||||
setCreatedAgent({ id: result.id, name: trimmedName || agentKey })
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : t('common:failedToCreateAgent'))
|
||||
} finally {
|
||||
@@ -140,6 +173,50 @@ export function AgentStep({ provider, model, onBack, onComplete }: AgentStepProp
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Agent identity — editable name + key, prefilled from the preset */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div className="space-y-1.5">
|
||||
<label htmlFor="onboard-agent-name" className="block text-sm font-medium text-text-secondary">
|
||||
{t('agents:create.displayName')}
|
||||
</label>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
id="onboard-agent-emoji"
|
||||
value={emoji}
|
||||
onChange={(e) => setEmoji(e.target.value)}
|
||||
maxLength={2}
|
||||
placeholder="🤖"
|
||||
title={t('agents:create.emojiHint')}
|
||||
className="w-14 shrink-0 text-center w-full bg-surface-tertiary border border-border rounded-lg px-3 py-2.5 text-base md:text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-1 focus:ring-accent"
|
||||
/>
|
||||
<input
|
||||
id="onboard-agent-name"
|
||||
value={displayName}
|
||||
onChange={(e) => handleDisplayNameChange(e.target.value)}
|
||||
placeholder={t('agents:create.displayNamePlaceholder')}
|
||||
className="w-full bg-surface-tertiary border border-border rounded-lg px-3 py-2.5 text-base md:text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-1 focus:ring-accent"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<label htmlFor="onboard-agent-key" className="block text-sm font-medium text-text-secondary">
|
||||
{t('agents:create.agentKey')}
|
||||
</label>
|
||||
<input
|
||||
id="onboard-agent-key"
|
||||
value={agentKey}
|
||||
onChange={(e) => handleAgentKeyChange(e.target.value)}
|
||||
onBlur={(e) => setAgentKey(slugify(e.target.value))}
|
||||
placeholder={t('agents:create.agentKeyPlaceholder')}
|
||||
className="w-full bg-surface-tertiary border border-border rounded-lg px-3 py-2.5 text-base md:text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-1 focus:ring-accent"
|
||||
/>
|
||||
<p className={agentKey && !keyValid ? 'text-xs text-error' : 'text-xs text-text-muted'}>
|
||||
{t('agents:create.agentKeyHint')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Preset personality buttons */}
|
||||
<div className="space-y-2">
|
||||
<label className="block text-sm font-medium text-text-secondary">{t('agents:detail.personality')}</label>
|
||||
@@ -148,7 +225,7 @@ export function AgentStep({ provider, model, onBack, onComplete }: AgentStepProp
|
||||
<button
|
||||
key={preset.key}
|
||||
type="button"
|
||||
onClick={() => setSelectedPresetIdx(idx)}
|
||||
onClick={() => handleSelectPreset(idx)}
|
||||
className={[
|
||||
'cursor-pointer rounded-full border px-3 py-1 text-xs transition-colors',
|
||||
selectedPresetIdx === idx
|
||||
@@ -185,7 +262,7 @@ export function AgentStep({ provider, model, onBack, onComplete }: AgentStepProp
|
||||
</button>
|
||||
<button
|
||||
onClick={handleSubmit}
|
||||
disabled={loading || !description.trim()}
|
||||
disabled={loading || !canSubmit}
|
||||
className="px-6 py-2.5 bg-accent text-white rounded-lg font-medium hover:bg-accent-hover transition-colors disabled:opacity-40 disabled:cursor-not-allowed flex items-center gap-2"
|
||||
>
|
||||
{loading && <div className="w-3.5 h-3.5 border-2 border-white border-t-transparent rounded-full animate-spin" />}
|
||||
|
||||
@@ -1,48 +1,42 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export interface AgentPreset {
|
||||
/** Chip label as translated, may carry a leading emoji (e.g. "🦊 Fox Spirit"). */
|
||||
label: string;
|
||||
/** Translated display name with the leading emoji stripped. */
|
||||
name: string;
|
||||
prompt: string;
|
||||
emoji: string;
|
||||
/** Stable English slug used as agent_key — never translated. */
|
||||
agentKey: string;
|
||||
}
|
||||
|
||||
/** Labels are stored as "<emoji> <name>"; the emoji lives in its own field. */
|
||||
function stripLeadingEmoji(label: string): string {
|
||||
return label.replace(/^\S+\s+/, "").trim() || label.trim();
|
||||
}
|
||||
|
||||
export function useAgentPresets(): AgentPreset[] {
|
||||
const { t } = useTranslation("agents");
|
||||
|
||||
const build = (key: string, emoji: string, agentKey: string): AgentPreset => {
|
||||
const label = t(`presets.${key}.label`);
|
||||
return {
|
||||
label,
|
||||
name: stripLeadingEmoji(label),
|
||||
prompt: t(`presets.${key}.prompt`),
|
||||
emoji,
|
||||
agentKey,
|
||||
};
|
||||
};
|
||||
|
||||
return [
|
||||
{
|
||||
label: t("presets.foxSpirit.label"),
|
||||
prompt: t("presets.foxSpirit.prompt"),
|
||||
emoji: "🦊",
|
||||
},
|
||||
{
|
||||
label: t("presets.coder.label"),
|
||||
prompt: t("presets.coder.prompt"),
|
||||
emoji: "💻",
|
||||
},
|
||||
{
|
||||
label: t("presets.support.label"),
|
||||
prompt: t("presets.support.prompt"),
|
||||
emoji: "🎧",
|
||||
},
|
||||
{
|
||||
label: t("presets.writer.label"),
|
||||
prompt: t("presets.writer.prompt"),
|
||||
emoji: "✍️",
|
||||
},
|
||||
{
|
||||
label: t("presets.translator.label"),
|
||||
prompt: t("presets.translator.prompt"),
|
||||
emoji: "🌐",
|
||||
},
|
||||
{
|
||||
label: t("presets.artisan.label"),
|
||||
prompt: t("presets.artisan.prompt"),
|
||||
emoji: "🎨",
|
||||
},
|
||||
{
|
||||
label: t("presets.astrologer.label"),
|
||||
prompt: t("presets.astrologer.prompt"),
|
||||
emoji: "🔮",
|
||||
},
|
||||
build("foxSpirit", "🦊", "little-fox"),
|
||||
build("coder", "💻", "dev"),
|
||||
build("support", "🎧", "support"),
|
||||
build("writer", "✍️", "quill"),
|
||||
build("translator", "🌐", "translator"),
|
||||
build("artisan", "🎨", "artisan"),
|
||||
build("astrologer", "🔮", "mimi"),
|
||||
];
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState, useEffect, useMemo, useCallback } from "react";
|
||||
import { useState, useMemo, useEffect, useCallback } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
@@ -12,7 +13,7 @@ import { useAgents } from "@/pages/agents/hooks/use-agents";
|
||||
import { SummoningModal } from "@/pages/agents/summoning-modal";
|
||||
import { useAgentPresets } from "@/pages/agents/agent-presets";
|
||||
import { useWsEvent } from "@/hooks/use-ws-event";
|
||||
import { slugify } from "@/lib/slug";
|
||||
import { slugify, isValidSlug } from "@/lib/slug";
|
||||
import { toast } from "@/stores/use-toast-store";
|
||||
import type { ProviderData } from "@/types/provider";
|
||||
import type { AgentData } from "@/types/agent";
|
||||
@@ -27,6 +28,7 @@ interface StepAgentProps {
|
||||
|
||||
export function StepAgent({ provider, model, onComplete, onBack, existingAgent }: StepAgentProps) {
|
||||
const { t } = useTranslation("setup");
|
||||
const { t: tAgents } = useTranslation("agents");
|
||||
const { createAgent, updateAgent, deleteAgent, resummonAgent, cancelSummonAgent } = useAgents();
|
||||
const agentPresets = useAgentPresets();
|
||||
|
||||
@@ -40,6 +42,19 @@ export function StepAgent({ provider, model, onComplete, onBack, existingAgent }
|
||||
const [selectedPresetIdx, setSelectedPresetIdx] = useState<number | null>(
|
||||
existingAgent ? null : 0,
|
||||
);
|
||||
// Identity is editable state, only *prefilled* from the selected preset. It must
|
||||
// survive description edits — deselecting a preset never rewrites the name/key.
|
||||
const [displayName, setDisplayName] = useState(
|
||||
existingAgent?.display_name ?? defaultPreset?.name ?? "",
|
||||
);
|
||||
const [agentKey, setAgentKey] = useState(
|
||||
existingAgent?.agent_key ?? defaultPreset?.agentKey ?? "",
|
||||
);
|
||||
const [displayNameEdited, setDisplayNameEdited] = useState(false);
|
||||
const [agentKeyEdited, setAgentKeyEdited] = useState(false);
|
||||
const [emoji, setEmoji] = useState(
|
||||
existingAgent?.emoji ?? defaultPreset?.emoji ?? "",
|
||||
);
|
||||
const [selfEvolve, setSelfEvolve] = useState(
|
||||
Boolean(existingAgent?.self_evolve),
|
||||
);
|
||||
@@ -57,26 +72,25 @@ export function StepAgent({ provider, model, onComplete, onBack, existingAgent }
|
||||
const [createdAgent, setCreatedAgent] = useState<{ id: string; name: string } | null>(null);
|
||||
const [agentResult, setAgentResult] = useState<AgentData | null>(null);
|
||||
|
||||
// Derive agent key and display name from selected preset or description
|
||||
const displayName = useMemo(() => {
|
||||
if (existingAgent) return existingAgent.display_name ?? "";
|
||||
if (selectedPresetIdx !== null && agentPresets[selectedPresetIdx]) {
|
||||
return agentPresets[selectedPresetIdx].label;
|
||||
}
|
||||
return "Fox Spirit";
|
||||
}, [existingAgent, selectedPresetIdx, agentPresets]);
|
||||
const activePreset = selectedPresetIdx !== null ? agentPresets[selectedPresetIdx] : undefined;
|
||||
const presetPrompt = activePreset?.prompt;
|
||||
const presetName = activePreset?.name;
|
||||
|
||||
const agentKey = useMemo(() => {
|
||||
const slug = slugify(displayName);
|
||||
return slug || "fox-spirit";
|
||||
}, [displayName]);
|
||||
// Deps are plain strings, so this only fires when the translated preset text
|
||||
// actually changes (locale switch) — not on every render.
|
||||
useEffect(() => {
|
||||
if (presetPrompt === undefined) return;
|
||||
setDescription(presetPrompt);
|
||||
}, [presetPrompt]);
|
||||
|
||||
const selectedEmoji = useMemo(() => {
|
||||
if (selectedPresetIdx !== null && agentPresets[selectedPresetIdx]) {
|
||||
return agentPresets[selectedPresetIdx].emoji;
|
||||
}
|
||||
return "🦊";
|
||||
}, [selectedPresetIdx, agentPresets]);
|
||||
useEffect(() => {
|
||||
if (presetName === undefined || displayNameEdited || isEditing) return;
|
||||
setDisplayName(presetName);
|
||||
}, [presetName, displayNameEdited, isEditing]);
|
||||
|
||||
const trimmedName = displayName.trim();
|
||||
const keyValid = isValidSlug(agentKey);
|
||||
const canSubmit = !!trimmedName && (isEditing || keyValid) && !!description.trim();
|
||||
|
||||
const providerLabel = useMemo(() => {
|
||||
if (!provider) return "—";
|
||||
@@ -95,13 +109,6 @@ export function StepAgent({ provider, model, onComplete, onBack, existingAgent }
|
||||
);
|
||||
useWsEvent("agent.summoning", handleSummoningEvent);
|
||||
|
||||
// Sync description when preset changes on initial load
|
||||
useEffect(() => {
|
||||
if (selectedPresetIdx !== null && agentPresets[selectedPresetIdx]) {
|
||||
setDescription(agentPresets[selectedPresetIdx].prompt);
|
||||
}
|
||||
}, [selectedPresetIdx, agentPresets]);
|
||||
|
||||
const handleContinue = () => {
|
||||
if (agentResult) onComplete(agentResult);
|
||||
};
|
||||
@@ -111,19 +118,35 @@ export function StepAgent({ provider, model, onComplete, onBack, existingAgent }
|
||||
if (!preset) return;
|
||||
setSelectedPresetIdx(idx);
|
||||
setDescription(preset.prompt);
|
||||
setDisplayName(preset.name);
|
||||
setDisplayNameEdited(false);
|
||||
setEmoji(preset.emoji);
|
||||
if (!isEditing) {
|
||||
setAgentKey(preset.agentKey);
|
||||
setAgentKeyEdited(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDescriptionChange = (value: string) => {
|
||||
setDescription(value);
|
||||
// If user edits text, deselect preset (unless it still matches)
|
||||
if (selectedPresetIdx !== null) {
|
||||
const presetPrompt = agentPresets[selectedPresetIdx]?.prompt;
|
||||
if (value !== presetPrompt) setSelectedPresetIdx(null);
|
||||
}
|
||||
// Editing the prompt only clears the preset highlight — identity stays as typed.
|
||||
if (selectedPresetIdx !== null && value !== presetPrompt) setSelectedPresetIdx(null);
|
||||
};
|
||||
|
||||
const handleDisplayNameChange = (value: string) => {
|
||||
setDisplayNameEdited(true);
|
||||
setDisplayName(value);
|
||||
if (!agentKeyEdited && !isEditing) setAgentKey(slugify(value));
|
||||
};
|
||||
|
||||
const handleAgentKeyChange = (value: string) => {
|
||||
setAgentKeyEdited(true);
|
||||
setAgentKey(value);
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!provider) { setError(t("agent.errors.noProvider")); return; }
|
||||
if (!canSubmit) return;
|
||||
|
||||
setLoading(true);
|
||||
setError("");
|
||||
@@ -132,20 +155,20 @@ export function StepAgent({ provider, model, onComplete, onBack, existingAgent }
|
||||
try {
|
||||
if (isEditing) {
|
||||
const patch: Partial<AgentData> = {
|
||||
display_name: displayName.trim() || undefined,
|
||||
display_name: trimmedName,
|
||||
provider: provider.name,
|
||||
model: model || "",
|
||||
// Promoted fields at top level
|
||||
agent_description: description.trim() || null,
|
||||
self_evolve: selfEvolve,
|
||||
emoji: selectedEmoji || null,
|
||||
emoji: emoji.trim() || null,
|
||||
};
|
||||
await updateAgent(existingAgent!.id, patch);
|
||||
onComplete({ ...existingAgent!, ...patch } as AgentData);
|
||||
} else {
|
||||
const data: Partial<AgentData> = {
|
||||
agent_key: agentKey,
|
||||
display_name: displayName.trim() || undefined,
|
||||
display_name: trimmedName,
|
||||
provider: provider.name,
|
||||
model: model || "",
|
||||
agent_type: "predefined",
|
||||
@@ -153,7 +176,7 @@ export function StepAgent({ provider, model, onComplete, onBack, existingAgent }
|
||||
// Promoted fields at top level
|
||||
agent_description: description.trim() || null,
|
||||
self_evolve: selfEvolve,
|
||||
emoji: selectedEmoji || null,
|
||||
emoji: emoji.trim() || null,
|
||||
grant_gateway_operator_access: gatewayOperatorAccess || undefined,
|
||||
};
|
||||
|
||||
@@ -169,7 +192,7 @@ export function StepAgent({ provider, model, onComplete, onBack, existingAgent }
|
||||
}
|
||||
setAgentResult(result);
|
||||
setSummoningOutcome("pending");
|
||||
setCreatedAgent({ id: result.id, name: displayName.trim() || agentKey });
|
||||
setCreatedAgent({ id: result.id, name: trimmedName || agentKey });
|
||||
setSummoningOpen(true);
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -178,7 +201,6 @@ export function StepAgent({ provider, model, onComplete, onBack, existingAgent }
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSummoningComplete = () => {};
|
||||
|
||||
const handleModalClose = async () => {
|
||||
@@ -221,6 +243,49 @@ export function StepAgent({ provider, model, onComplete, onBack, existingAgent }
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Agent identity — editable name + key, prefilled from the preset */}
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="setup-display-name">{tAgents("create.displayName")}</Label>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
id="setup-emoji"
|
||||
value={emoji}
|
||||
onChange={(e) => setEmoji(e.target.value)}
|
||||
placeholder="🤖"
|
||||
className="w-14 shrink-0 text-center text-base md:text-lg"
|
||||
maxLength={2}
|
||||
title={tAgents("create.emojiHint")}
|
||||
/>
|
||||
<Input
|
||||
id="setup-display-name"
|
||||
value={displayName}
|
||||
onChange={(e) => handleDisplayNameChange(e.target.value)}
|
||||
placeholder={tAgents("create.displayNamePlaceholder")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="setup-agent-key">{tAgents("create.agentKey")}</Label>
|
||||
<Input
|
||||
id="setup-agent-key"
|
||||
value={agentKey}
|
||||
onChange={(e) => handleAgentKeyChange(e.target.value)}
|
||||
onBlur={(e) => setAgentKey(slugify(e.target.value))}
|
||||
placeholder={tAgents("create.agentKeyPlaceholder")}
|
||||
disabled={isEditing}
|
||||
/>
|
||||
<p
|
||||
className={`text-xs ${
|
||||
!isEditing && agentKey && !keyValid ? "text-destructive" : "text-muted-foreground"
|
||||
}`}
|
||||
>
|
||||
{tAgents("create.agentKeyHint")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Prompt / description with preset selection */}
|
||||
<div className="space-y-3">
|
||||
<Label className="inline-flex items-center gap-1.5">
|
||||
@@ -297,7 +362,7 @@ export function StepAgent({ provider, model, onComplete, onBack, existingAgent }
|
||||
)}
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
disabled={loading || !description.trim()}
|
||||
disabled={loading || !canSubmit}
|
||||
>
|
||||
{loading
|
||||
? isEditing ? t("agent.updating", "Updating...") : t("agent.creating")
|
||||
|
||||
Reference in new issue
Block a user