feat: slug change + streaming fix + specs

This commit is contained in:
Markov 2026-03-13 22:55:53 +01:00
parent 79541d133a
commit 13a1748162
3 changed files with 31 additions and 7 deletions

View File

@ -11,6 +11,8 @@ interface Props {
export default function AgentModal({ agent, onClose, onUpdated }: Props) { export default function AgentModal({ agent, onClose, onUpdated }: Props) {
const [name, setName] = useState(agent.name); const [name, setName] = useState(agent.name);
const [slug, setSlug] = useState(agent.slug);
const [slugError, setSlugError] = useState("");
const [capabilities, setCapabilities] = useState( const [capabilities, setCapabilities] = useState(
agent.agent_config?.capabilities?.join(", ") || "" agent.agent_config?.capabilities?.join(", ") || ""
); );
@ -34,8 +36,14 @@ export default function AgentModal({ agent, onClose, onUpdated }: Props) {
setSaving(true); setSaving(true);
try { try {
const caps = capabilities.split(",").map((c) => c.trim()).filter(Boolean); const caps = capabilities.split(",").map((c) => c.trim()).filter(Boolean);
const trimmedSlug = slug.trim().toLowerCase().replace(/[^a-z0-9_-]/g, '');
if (!trimmedSlug) {
setSlugError("Slug не может быть пустым");
return;
}
const updated = await updateMember(agent.id, { const updated = await updateMember(agent.id, {
name: name.trim(), name: name.trim(),
slug: trimmedSlug !== agent.slug ? trimmedSlug : undefined,
agent_config: { agent_config: {
capabilities: caps, capabilities: caps,
labels: agentLabels, labels: agentLabels,
@ -47,8 +55,12 @@ export default function AgentModal({ agent, onClose, onUpdated }: Props) {
}); });
onUpdated(updated); onUpdated(updated);
onClose(); onClose();
} catch (e) { } catch (e: any) {
console.error(e); if (e?.response?.status === 409) {
setSlugError("Этот slug уже занят");
} else {
console.error(e);
}
} finally { } finally {
setSaving(false); setSaving(false);
} }
@ -114,6 +126,17 @@ export default function AgentModal({ agent, onClose, onUpdated }: Props) {
/> />
</div> </div>
<div>
<label className="block text-sm text-[var(--muted)] mb-1">Slug</label>
<input
value={slug}
onChange={(e) => { setSlug(e.target.value); setSlugError(""); }}
className={`w-full px-3 py-2 bg-[var(--bg)] border rounded-lg outline-none text-sm ${slugError ? 'border-red-500' : 'border-[var(--border)] focus:border-[var(--accent)]'}`}
placeholder="agent-slug"
/>
{slugError && <div className="text-xs text-red-400 mt-1">{slugError}</div>}
</div>
<div> <div>
<label className="block text-sm text-[var(--muted)] mb-1">Capabilities</label> <label className="block text-sm text-[var(--muted)] mb-1">Capabilities</label>
<input <input

View File

@ -354,12 +354,12 @@ export default function ChatPanel({ chatId, projectId }: Props) {
<span className="animate-pulse"></span> <span className="animate-pulse"></span>
</div> </div>
{streaming.thinking && ( {streaming.thinking && (
<details className="ml-5 mb-1" open> <details className="ml-5 mb-1">
<summary className="text-xs text-[var(--muted)] cursor-pointer hover:text-[var(--fg)]"> <summary className="text-xs text-[var(--muted)] cursor-pointer hover:text-[var(--fg)]">
💭 Размышления 💭 Размышления ({streaming.thinking.length} символов)
</summary> </summary>
<div className="mt-1 text-xs text-[var(--muted)] whitespace-pre-wrap bg-white/5 rounded p-2 max-h-[200px] overflow-y-auto"> <div className="mt-1 text-xs text-[var(--muted)] whitespace-pre-wrap bg-white/5 rounded p-2 max-h-[200px] overflow-y-auto">
{streaming.thinking} {streaming.thinking.length > 500 ? '…' + streaming.thinking.slice(-500) : streaming.thinking}
</div> </div>
</details> </details>
)} )}
@ -372,8 +372,8 @@ export default function ChatPanel({ chatId, projectId }: Props) {
))} ))}
</div> </div>
)} )}
{streaming.text && ( {streaming.text && !streaming.tools.some(t => t.status === "running") && (
<div className="ml-5 whitespace-pre-wrap">{streaming.text}</div> <div className="ml-5 whitespace-pre-wrap">{streaming.text.length > 500 ? '…' + streaming.text.slice(-500) : streaming.text}</div>
)} )}
{!streaming.text && streaming.tools.length === 0 && !streaming.thinking && ( {!streaming.text && streaming.tools.length === 0 && !streaming.thinking && (
<div className="ml-5 text-[var(--muted)] italic">Думает...</div> <div className="ml-5 text-[var(--muted)] italic">Думает...</div>

View File

@ -316,6 +316,7 @@ export async function createMember(data: {
export async function updateMember(memberId: string, data: { export async function updateMember(memberId: string, data: {
name?: string; name?: string;
slug?: string;
role?: string; role?: string;
status?: string; status?: string;
agent_config?: Partial<AgentConfig>; agent_config?: Partial<AgentConfig>;