diff --git a/ui/web/src/pages/cron/cron-detail/cron-advanced-dialog.tsx b/ui/web/src/pages/cron/cron-detail/cron-advanced-dialog.tsx index 0275cab6..b144aeb0 100644 --- a/ui/web/src/pages/cron/cron-detail/cron-advanced-dialog.tsx +++ b/ui/web/src/pages/cron/cron-detail/cron-advanced-dialog.tsx @@ -1,5 +1,7 @@ import { useState, useEffect, useCallback } from "react"; import { useTranslation } from "react-i18next"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; import { Save, Settings, Loader2 } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; @@ -15,6 +17,7 @@ import { useChannels } from "@/pages/channels/hooks/use-channels"; import { useWs } from "@/hooks/use-ws"; import { Methods } from "@/api/protocol"; import type { CronJob, CronJobPatch } from "../hooks/use-cron"; +import { cronAdvancedSchema, type CronAdvancedFormData } from "@/schemas/cron-advanced.schema"; interface DeliveryTarget { channel: string; @@ -30,7 +33,7 @@ interface CronAdvancedDialogProps { onUpdate?: (id: string, params: CronJobPatch) => Promise; } -function deriveState(job: CronJob) { +function deriveDefaults(job: CronJob): CronAdvancedFormData { return { timezone: job.schedule.tz ?? "UTC", deliver: job.deliver ?? false, @@ -49,17 +52,25 @@ export function CronAdvancedDialog({ open, onOpenChange, job, onUpdate }: CronAd const { channels: availableChannels } = useChannels(); const channelNames = Object.keys(availableChannels); - const init = deriveState(job); - const [timezone, setTimezone] = useState(init.timezone); - const [deliver, setDeliver] = useState(init.deliver); - const [channel, setChannel] = useState(init.channel); - const [to, setTo] = useState(init.to); - const [wakeHeartbeat, setWakeHeartbeat] = useState(init.wakeHeartbeat); - const [deleteAfterRun, setDeleteAfterRun] = useState(init.deleteAfterRun); - const [stateless, setStateless] = useState(init.stateless); + // UI-only state const [saving, setSaving] = useState(false); const [targets, setTargets] = useState([]); + const form = useForm({ + resolver: zodResolver(cronAdvancedSchema), + mode: "onChange", + defaultValues: deriveDefaults(job), + }); + + const { watch, setValue, reset } = form; + const timezone = watch("timezone"); + const deliver = watch("deliver"); + const channel = watch("channel"); + const to = watch("to"); + const wakeHeartbeat = watch("wakeHeartbeat"); + const deleteAfterRun = watch("deleteAfterRun"); + const stateless = watch("stateless"); + const fetchTargets = useCallback(async () => { if (!job.agentId || !ws.isConnected) return; try { @@ -73,14 +84,7 @@ export function CronAdvancedDialog({ open, onOpenChange, job, onUpdate }: CronAd // Re-sync when dialog opens useEffect(() => { if (!open) return; - const s = deriveState(job); - setTimezone(s.timezone); - setDeliver(s.deliver); - setChannel(s.channel); - setTo(s.to); - setWakeHeartbeat(s.wakeHeartbeat); - setDeleteAfterRun(s.deleteAfterRun); - setStateless(s.stateless); + reset(deriveDefaults(job)); fetchTargets(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [open]); @@ -90,7 +94,8 @@ export function CronAdvancedDialog({ open, onOpenChange, job, onUpdate }: CronAd onOpenChange(false); return; } - if (timezone && timezone !== "UTC" && !isValidIanaTimezone(timezone)) { + const data = form.getValues(); + if (data.timezone && data.timezone !== "UTC" && !isValidIanaTimezone(data.timezone)) { toast.error(t("detail.invalidTimezone", "Invalid timezone")); return; } @@ -99,14 +104,14 @@ export function CronAdvancedDialog({ open, onOpenChange, job, onUpdate }: CronAd await onUpdate(job.id, { schedule: { ...job.schedule, - tz: timezone !== "UTC" ? timezone : "", + tz: data.timezone !== "UTC" ? data.timezone : "", }, - deliver, - deliverChannel: deliver ? channel.trim() || undefined : undefined, - deliverTo: deliver ? to.trim() || undefined : undefined, - wakeHeartbeat, - deleteAfterRun, - stateless, + deliver: data.deliver, + deliverChannel: data.deliver ? data.channel.trim() || undefined : undefined, + deliverTo: data.deliver ? data.to.trim() || undefined : undefined, + wakeHeartbeat: data.wakeHeartbeat, + deleteAfterRun: data.deleteAfterRun, + stateless: data.stateless, }); onOpenChange(false); } catch { // toast shown by hook @@ -137,7 +142,7 @@ export function CronAdvancedDialog({ open, onOpenChange, job, onUpdate }: CronAd setValue("timezone", v)} options={getAllIanaTimezones()} placeholder={t("detail.timezone")} className="text-base md:text-sm" @@ -153,7 +158,7 @@ export function CronAdvancedDialog({ open, onOpenChange, job, onUpdate }: CronAd

{t("detail.deliverToChannel")}

- + setValue("deliver", v)} />
{deliver && ( @@ -163,7 +168,10 @@ export function CronAdvancedDialog({ open, onOpenChange, job, onUpdate }: CronAd {channelNames.length > 0 ? ( setChannel(e.target.value)} + onChange={(e) => setValue("channel", e.target.value)} placeholder={t("detail.channelPlaceholder")} className="text-base md:text-sm" /> @@ -201,7 +209,7 @@ export function CronAdvancedDialog({ open, onOpenChange, job, onUpdate }: CronAd return ( setTo(e.target.value)} + onChange={(e) => setValue("to", e.target.value)} placeholder={t("detail.toPlaceholder")} className="text-base md:text-sm" /> @@ -235,7 +243,7 @@ export function CronAdvancedDialog({ open, onOpenChange, job, onUpdate }: CronAd

{t("detail.wakeHeartbeat")}

{t("detail.wakeHeartbeatDesc")}

- + setValue("wakeHeartbeat", v)} /> @@ -249,7 +257,7 @@ export function CronAdvancedDialog({ open, onOpenChange, job, onUpdate }: CronAd

{t("detail.deleteAfterRun")}

{t("detail.deleteAfterRunDesc")}

- + setValue("deleteAfterRun", v)} />
@@ -257,7 +265,7 @@ export function CronAdvancedDialog({ open, onOpenChange, job, onUpdate }: CronAd

{t("stateless")}

{t("statelessHelp")}

- + setValue("stateless", v)} /> diff --git a/ui/web/src/pages/mcp/mcp-form-dialog.tsx b/ui/web/src/pages/mcp/mcp-form-dialog.tsx index c9c67451..b67bdd81 100644 --- a/ui/web/src/pages/mcp/mcp-form-dialog.tsx +++ b/ui/web/src/pages/mcp/mcp-form-dialog.tsx @@ -1,5 +1,7 @@ import { useState, useEffect } from "react"; import { useTranslation } from "react-i18next"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; import { Loader2, CheckCircle2, XCircle } from "lucide-react"; import { Dialog, @@ -15,6 +17,7 @@ import { Switch } from "@/components/ui/switch"; import { KeyValueEditor } from "@/components/shared/key-value-editor"; import type { MCPServerData, MCPServerInput } from "./hooks/use-mcp"; import { slugify, isValidSlug } from "@/lib/slug"; +import { mcpFormSchema, type MCPFormData } from "@/schemas/mcp.schema"; /** Header keys whose values should be masked in the form. */ const SENSITIVE_HEADER_RE = /^(authorization|x-api-key|api-key|bearer|token|secret|password|credential)/i; @@ -48,55 +51,75 @@ const TRANSPORTS = [ { value: "stdio", label: "stdio" }, { value: "sse", label: "SSE" }, { value: "streamable-http", label: "Streamable HTTP" }, -]; +] as const; export function MCPFormDialog({ open, onOpenChange, server, onSubmit, onTest }: MCPFormDialogProps) { const { t } = useTranslation("mcp"); - const [name, setName] = useState(""); - const [displayName, setDisplayName] = useState(""); - const [transport, setTransport] = useState("stdio"); - const [command, setCommand] = useState(""); - const [args, setArgs] = useState(""); - const [url, setUrl] = useState(""); - const [headers, setHeaders] = useState>({}); - const [env, setEnv] = useState>({}); - const [toolPrefix, setToolPrefix] = useState(""); - const [timeout, setTimeout] = useState(60); - const [enabled, setEnabled] = useState(true); - const [requireUserCreds, setRequireUserCreds] = useState(false); const [loading, setLoading] = useState(false); const [testing, setTesting] = useState(false); const [testResult, setTestResult] = useState<{ success: boolean; tool_count?: number; error?: string } | null>(null); const [error, setError] = useState(""); + const form = useForm({ + resolver: zodResolver(mcpFormSchema), + mode: "onChange", + defaultValues: { + name: "", + displayName: "", + transport: "stdio", + command: "", + args: "", + url: "", + headers: {}, + env: {}, + toolPrefix: "", + timeout: 60, + enabled: true, + requireUserCreds: false, + }, + }); + + const { register, watch, setValue, reset, handleSubmit: rhfHandleSubmit } = form; + const transport = watch("transport"); + const name = watch("name"); + const command = watch("command"); + const args = watch("args"); + const url = watch("url"); + const headers = watch("headers") as Record; + const env = watch("env") as Record; + const enabled = watch("enabled"); + const requireUserCreds = watch("requireUserCreds"); + const timeout = watch("timeout"); + const toolPrefix = watch("toolPrefix"); + + const isStdio = transport === "stdio"; + useEffect(() => { if (open) { - setName(server?.name ?? ""); - setDisplayName(server?.display_name ?? ""); - setTransport(server?.transport ?? "stdio"); - setCommand(server?.command ?? ""); - setArgs(Array.isArray(server?.args) ? server.args.join(", ") : ""); - setUrl(server?.url ?? ""); - setHeaders(server?.headers ?? {}); - setEnv(server?.env ?? {}); - setToolPrefix((server?.tool_prefix ?? "").replace(/^mcp_/, "")); - setTimeout(server?.timeout_sec ?? 60); - setEnabled(server?.enabled ?? true); - setRequireUserCreds(server?.settings?.require_user_credentials ?? false); + reset({ + name: server?.name ?? "", + displayName: server?.display_name ?? "", + transport: (server?.transport as MCPFormData["transport"]) ?? "stdio", + command: server?.command ?? "", + args: Array.isArray(server?.args) ? server.args.join(", ") : "", + url: server?.url ?? "", + headers: server?.headers ?? {}, + env: server?.env ?? {}, + toolPrefix: (server?.tool_prefix ?? "").replace(/^mcp_/, ""), + timeout: server?.timeout_sec ?? 60, + enabled: server?.enabled ?? true, + requireUserCreds: server?.settings?.require_user_credentials ?? false, + }); setError(""); setTestResult(null); } - }, [open, server]); - - const isStdio = transport === "stdio"; + }, [open, server, reset]); const buildConnectionData = () => { let parsedArgs: string[] | undefined = undefined; let resolvedCommand = command.trim(); if (isStdio) { - // If user pasted full command into Command field (e.g. "npx -y @foo/bar"), - // split it: first token is the command, rest are prepended to args. const cmdTokens = splitShellTokens(resolvedCommand); if (cmdTokens.length > 1) { resolvedCommand = cmdTokens[0]!; @@ -108,8 +131,12 @@ export function MCPFormDialog({ open, onOpenChange, server, onSubmit, onTest }: } } - const parsedHeaders = !isStdio && Object.keys(headers).length > 0 ? headers : undefined; - const parsedEnv = Object.keys(env).length > 0 ? env : undefined; + const parsedHeaders = !isStdio && Object.keys(headers).length > 0 + ? (headers as Record) + : undefined; + const parsedEnv = Object.keys(env).length > 0 + ? (env as Record) + : undefined; return { transport, command: isStdio ? resolvedCommand : undefined, @@ -143,20 +170,16 @@ export function MCPFormDialog({ open, onOpenChange, server, onSubmit, onTest }: } }; - const handleSubmit = async () => { - if (!name.trim() || !transport) { - setError(t("form.errors.nameRequired")); - return; - } - if (!isValidSlug(name.trim())) { + const handleSubmit = rhfHandleSubmit(async (data) => { + if (!isValidSlug(data.name.trim())) { setError(t("form.errors.nameSlug")); return; } - if (isStdio && !command.trim()) { + if (isStdio && !data.command.trim()) { setError(t("form.errors.commandRequired")); return; } - if (!isStdio && !url.trim()) { + if (!isStdio && !data.url.trim()) { setError(t("form.errors.urlRequired")); return; } @@ -166,13 +189,13 @@ export function MCPFormDialog({ open, onOpenChange, server, onSubmit, onTest }: try { const conn = buildConnectionData(); await onSubmit({ - name: name.trim(), - display_name: displayName.trim() || undefined, + name: data.name.trim(), + display_name: data.displayName.trim() || undefined, ...conn, - tool_prefix: toolPrefix.trim() || undefined, - timeout_sec: timeout, - settings: { require_user_credentials: requireUserCreds }, - enabled, + tool_prefix: data.toolPrefix.trim() || undefined, + timeout_sec: data.timeout, + settings: { require_user_credentials: data.requireUserCreds }, + enabled: data.enabled, }); onOpenChange(false); } catch (err: unknown) { @@ -180,7 +203,7 @@ export function MCPFormDialog({ open, onOpenChange, server, onSubmit, onTest }: } finally { setLoading(false); } - }; + }); return ( !loading && onOpenChange(v)}> @@ -192,13 +215,18 @@ export function MCPFormDialog({ open, onOpenChange, server, onSubmit, onTest }:
- setName(slugify(e.target.value))} placeholder="my-mcp-server" /> + setValue("name", slugify(e.target.value))} + placeholder="my-mcp-server" + />

{t("form.nameHint")}

- setDisplayName(e.target.value)} placeholder={t("form.displayNamePlaceholder")} /> +
@@ -210,7 +238,7 @@ export function MCPFormDialog({ open, onOpenChange, server, onSubmit, onTest }: type="button" variant={transport === tr.value ? "default" : "outline"} size="sm" - onClick={() => setTransport(tr.value)} + onClick={() => setValue("transport", tr.value)} > {tr.label} @@ -222,24 +250,24 @@ export function MCPFormDialog({ open, onOpenChange, server, onSubmit, onTest }: <>
- setCommand(e.target.value)} placeholder="npx" className="font-mono" /> +
- setArgs(e.target.value)} placeholder={t("form.argsPlaceholder")} className="font-mono" /> +
) : ( <>
- setUrl(e.target.value)} placeholder="http://localhost:3001/sse" className="font-mono" /> +
setValue("headers", v)} keyPlaceholder={t("form.headerKeyPlaceholder")} valuePlaceholder={t("form.headerValuePlaceholder")} addLabel={t("form.addHeader")} @@ -253,7 +281,7 @@ export function MCPFormDialog({ open, onOpenChange, server, onSubmit, onTest }: setValue("env", v)} keyPlaceholder={t("form.envKeyPlaceholder")} valuePlaceholder={t("form.envValuePlaceholder")} addLabel={t("form.addVariable")} @@ -265,24 +293,36 @@ export function MCPFormDialog({ open, onOpenChange, server, onSubmit, onTest }:
mcp_ - setToolPrefix(e.target.value.replace(/[^a-z0-9_]/g, ""))} placeholder={name.replace(/-/g, "_") || "auto"} className="rounded-l-none font-mono" /> + setValue("toolPrefix", e.target.value.replace(/[^a-z0-9_]/g, ""))} + placeholder={name.replace(/-/g, "_") || "auto"} + className="rounded-l-none font-mono" + />

{t("form.toolPrefixHint")} Tools: mcp_{prefix}__{tool}

- setTimeout(Number(e.target.value))} min={1} /> + setValue("timeout", Number(e.target.value))} + min={1} + />
- + setValue("enabled", v)} />
- + setValue("requireUserCreds", v)} />

{t("form.requireUserCredentialsHint")}

diff --git a/ui/web/src/pages/mcp/mcp-user-credentials-dialog.tsx b/ui/web/src/pages/mcp/mcp-user-credentials-dialog.tsx index 81fd1af9..d782b5db 100644 --- a/ui/web/src/pages/mcp/mcp-user-credentials-dialog.tsx +++ b/ui/web/src/pages/mcp/mcp-user-credentials-dialog.tsx @@ -1,5 +1,7 @@ import { useState, useEffect } from "react"; import { useTranslation } from "react-i18next"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; import { KeyRound, Loader2 } from "lucide-react"; import { Dialog, @@ -20,6 +22,7 @@ import { useAuthStore } from "@/stores/use-auth-store"; import { useTenants } from "@/hooks/use-tenants"; import i18next from "i18next"; import type { MCPServerData, MCPUserCredentialStatus, MCPUserCredentialInput } from "./hooks/use-mcp"; +import { mcpUserCredentialsSchema, type MCPUserCredentialsFormData } from "@/schemas/mcp-credentials.schema"; /** Header keys whose values should be masked. */ const SENSITIVE_HEADER_RE = /^(authorization|bearer)|(key|secret|token|password|credential)/i; @@ -56,20 +59,24 @@ export function MCPUserCredentialsDialog({ currentTenant?.role === "owner" || currentTenant?.role === "admin"; + // UI-only state const [selectedUserId, setSelectedUserId] = useState(currentUserId); - // Separate search text from selected value — onChange fires on every keystroke, - // but we only want to load credentials when user actually selects from dropdown const [userSearchText, setUserSearchText] = useState(""); - const [status, setStatus] = useState(null); const [loadingStatus, setLoadingStatus] = useState(false); const [saving, setSaving] = useState(false); const [deleting, setDeleting] = useState(false); + const [initialLoad, setInitialLoad] = useState(true); - const [apiKey, setApiKey] = useState(""); - const [headers, setHeaders] = useState>({}); - const [env, setEnv] = useState>({}); + const form = useForm({ + resolver: zodResolver(mcpUserCredentialsSchema), + mode: "onChange", + defaultValues: { apiKey: "", headers: {}, env: {} }, + }); + const { register, watch, setValue, reset } = form; + const headers = watch("headers") as Record; + const env = watch("env") as Record; // Reset state when dialog opens useEffect(() => { @@ -80,15 +87,9 @@ export function MCPUserCredentialsDialog({ } }, [open, currentUserId]); - // Track whether this is the initial load (show full spinner) vs user switch (keep form visible) - const [initialLoad, setInitialLoad] = useState(true); - useEffect(() => { if (!open) return; - setApiKey(""); - setHeaders({}); - setEnv({}); - // Only show full-page spinner on initial dialog open, not on user switch + reset({ apiKey: "", headers: {}, env: {} }); if (initialLoad) { setStatus(null); setLoadingStatus(true); @@ -103,10 +104,11 @@ export function MCPUserCredentialsDialog({ const handleSave = async () => { setSaving(true); try { + const data = form.getValues(); const creds: MCPUserCredentialInput = {}; - if (apiKey.trim()) creds.api_key = apiKey.trim(); - if (Object.keys(headers).length > 0) creds.headers = headers; - if (Object.keys(env).length > 0) creds.env = env; + if (data.apiKey.trim()) creds.api_key = data.apiKey.trim(); + if (Object.keys(data.headers).length > 0) creds.headers = data.headers as Record; + if (Object.keys(data.env).length > 0) creds.env = data.env as Record; const targetUser = canManageUsers ? selectedUserId : undefined; await onSetCredentials(server.id, creds, targetUser); toast.success(i18next.t("mcp:userCredentials.saved")); @@ -196,19 +198,18 @@ export function MCPUserCredentialsDialog({ setApiKey(e.target.value)} placeholder={t("userCredentials.apiKeyPlaceholder")} className="text-base md:text-sm font-mono" + {...register("apiKey")} />
- {/* Headers — KeyValueEditor with sensitive masking */} + {/* Headers */}
setValue("headers", v)} keyPlaceholder="Header" valuePlaceholder="Value" addLabel={t("userCredentials.addHeader")} @@ -216,12 +217,12 @@ export function MCPUserCredentialsDialog({ />
- {/* Env vars — KeyValueEditor with sensitive masking */} + {/* Env vars */}
setValue("env", v)} keyPlaceholder="ENV_KEY" valuePlaceholder="value" addLabel={t("userCredentials.addEnv")} diff --git a/ui/web/src/pages/memory/memory-create-dialog.tsx b/ui/web/src/pages/memory/memory-create-dialog.tsx index 520a7911..3bf4927f 100644 --- a/ui/web/src/pages/memory/memory-create-dialog.tsx +++ b/ui/web/src/pages/memory/memory-create-dialog.tsx @@ -1,6 +1,8 @@ import { useState, useEffect } from "react"; import { formatUserLabel } from "@/lib/format-user-label"; import { useTranslation } from "react-i18next"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; import { Dialog, DialogContent, @@ -16,6 +18,7 @@ import { Switch } from "@/components/ui/switch"; import { useAgents } from "@/pages/agents/hooks/use-agents"; import { useMemoryDocuments } from "./hooks/use-memory"; import type { AgentData } from "@/types/agent"; +import { memoryCreateSchema, type MemoryCreateFormData } from "@/schemas/memory.schema"; interface MemoryCreateDialogProps { open: boolean; @@ -31,16 +34,31 @@ export function MemoryCreateDialog({ open, onOpenChange, agentId: parentAgentId, const { t } = useTranslation("memory"); const { t: tc } = useTranslation("common"); const { agents } = useAgents(); - const [selectedAgentId, setSelectedAgentId] = useState(""); - const [path, setPath] = useState(""); - const [content, setContent] = useState(""); - const [scopeMode, setScopeMode] = useState<"global" | "existing" | "custom">("global"); - const [selectedUserId, setSelectedUserId] = useState(""); - const [customUserId, setCustomUserId] = useState(""); - const [autoIndex, setAutoIndex] = useState(true); + + // UI-only state const [loading, setLoading] = useState(false); const [error, setError] = useState(""); + const form = useForm({ + resolver: zodResolver(memoryCreateSchema), + mode: "onChange", + defaultValues: { + selectedAgentId: "", + path: "", + content: "", + scopeMode: "global", + selectedUserId: "", + customUserId: "", + autoIndex: true, + }, + }); + + const { register, watch, setValue, reset } = form; + const selectedAgentId = watch("selectedAgentId"); + const scopeMode = watch("scopeMode"); + const selectedUserId = watch("selectedUserId"); + const autoIndex = watch("autoIndex"); + const effectiveAgentId = selectedAgentId || parentAgentId || ""; const { createDocument, indexDocument } = useMemoryDocuments({ agentId: effectiveAgentId || undefined }); @@ -48,33 +66,37 @@ export function MemoryCreateDialog({ open, onOpenChange, agentId: parentAgentId, useEffect(() => { if (open) { - setSelectedAgentId(parentAgentId || ""); - setPath(""); - setContent(""); - setScopeMode("global"); - setSelectedUserId(""); - setCustomUserId(""); - setAutoIndex(true); + reset({ + selectedAgentId: parentAgentId || "", + path: "", + content: "", + scopeMode: "global", + selectedUserId: "", + customUserId: "", + autoIndex: true, + }); setError(""); } - }, [open, parentAgentId]); + }, [open, parentAgentId, reset]); const resolvedUserId = (): string | undefined => { - if (scopeMode === "global") return undefined; - if (scopeMode === "existing") return selectedUserId || undefined; - return customUserId.trim() || undefined; + const data = form.getValues(); + if (data.scopeMode === "global") return undefined; + if (data.scopeMode === "existing") return data.selectedUserId || undefined; + return data.customUserId.trim() || undefined; }; const handleSubmit = async () => { + const data = form.getValues(); if (!effectiveAgentId) { setError(t("createDialog.agentRequired") ?? "Please select an agent"); return; } - if (!path.trim()) { + if (!data.path.trim()) { setError(t("createDialog.pathRequired") ?? "Path is required"); return; } - if (!content.trim()) { + if (!data.content.trim()) { setError(t("createDialog.contentRequired") ?? "Content is required"); return; } @@ -83,9 +105,9 @@ export function MemoryCreateDialog({ open, onOpenChange, agentId: parentAgentId, setError(""); try { const uid = resolvedUserId(); - await createDocument(path.trim(), content, uid); - if (autoIndex) { - await indexDocument(path.trim(), uid); + await createDocument(data.path.trim(), data.content, uid); + if (data.autoIndex) { + await indexDocument(data.path.trim(), uid); } onOpenChange(false); } catch (err) { @@ -109,7 +131,7 @@ export function MemoryCreateDialog({ open, onOpenChange, agentId: parentAgentId, setSelectedUserId(e.target.value)} + onChange={(e) => setValue("selectedUserId", e.target.value)} className="h-9 rounded-md border bg-background px-3 text-base md:text-sm" > @@ -171,10 +193,9 @@ export function MemoryCreateDialog({ open, onOpenChange, agentId: parentAgentId, )} {scopeMode === "custom" && ( setCustomUserId(e.target.value)} placeholder="e.g. group:telegram:-100123456" className="font-mono text-sm" + {...register("customUserId")} /> )}

@@ -187,10 +208,9 @@ export function MemoryCreateDialog({ open, onOpenChange, agentId: parentAgentId, setPath(e.target.value)} placeholder={t("createDialog.pathPlaceholder")} className="font-mono text-sm" + {...register("path")} />

@@ -199,16 +219,15 @@ export function MemoryCreateDialog({ open, onOpenChange, agentId: parentAgentId,