From f6bca7003ae8a2963f9ddb4900c9fa46d77d44ed Mon Sep 17 00:00:00 2001 From: viettranx Date: Fri, 3 Apr 2026 23:26:26 +0700 Subject: [PATCH] =?UTF-8?q?refactor(ui):=20gap=20closure=20=E2=80=94=20ext?= =?UTF-8?q?ract=20form=20field=20groups=20from=20bloated=20RHF=20dialogs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Split 4 RHF form dialogs that grew after Phase 3 Zod refactoring: - channel-instance-form-dialog: 445 → 292 (+ form-step 196) - cli-credential-form-dialog: 417 → 277 (+ binary-fields 151, scope-fields 72) - mcp-form-dialog: 359 → 209 (+ connection-fields 100, settings-fields 95) - agent-create-dialog: 351 → 165 (+ identity-model-fields 166, description 90) Skipped: heartbeat-config (already decomposed), provider-form (no 50+ line boundary). Files >300 lines: 20 → 16. Build passes. --- .../src/pages/agents/agent-create-dialog.tsx | 244 +++--------------- .../agents/agent-description-section.tsx | 90 +++++++ .../agent-identity-and-model-fields.tsx | 166 ++++++++++++ .../channels/channel-instance-form-dialog.tsx | 209 ++------------- .../channels/channel-instance-form-step.tsx | 196 ++++++++++++++ .../channels/telegram-group-overrides.tsx | 2 +- .../cli-credential-binary-fields.tsx | 151 +++++++++++ .../cli-credential-form-dialog.tsx | 180 ++----------- .../cli-credential-scope-fields.tsx | 72 ++++++ .../src/pages/mcp/mcp-connection-fields.tsx | 100 +++++++ ui/web/src/pages/mcp/mcp-form-dialog.tsx | 224 +++------------- ui/web/src/pages/mcp/mcp-settings-fields.tsx | 95 +++++++ 12 files changed, 985 insertions(+), 744 deletions(-) create mode 100644 ui/web/src/pages/agents/agent-description-section.tsx create mode 100644 ui/web/src/pages/agents/agent-identity-and-model-fields.tsx create mode 100644 ui/web/src/pages/channels/channel-instance-form-step.tsx create mode 100644 ui/web/src/pages/cli-credentials/cli-credential-binary-fields.tsx create mode 100644 ui/web/src/pages/cli-credentials/cli-credential-scope-fields.tsx create mode 100644 ui/web/src/pages/mcp/mcp-connection-fields.tsx create mode 100644 ui/web/src/pages/mcp/mcp-settings-fields.tsx diff --git a/ui/web/src/pages/agents/agent-create-dialog.tsx b/ui/web/src/pages/agents/agent-create-dialog.tsx index de74d7d4..235b5a19 100644 --- a/ui/web/src/pages/agents/agent-create-dialog.tsx +++ b/ui/web/src/pages/agents/agent-create-dialog.tsx @@ -1,8 +1,7 @@ -import { useMemo, useEffect } from "react"; -import { useForm, Controller } from "react-hook-form"; +import { useMemo, useEffect, useState } from "react"; +import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { useTranslation } from "react-i18next"; -import { ChevronRight } from "lucide-react"; import { Dialog, DialogContent, @@ -11,26 +10,14 @@ import { DialogFooter, } from "@/components/ui/dialog"; 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 { Switch } from "@/components/ui/switch"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { Combobox } from "@/components/ui/combobox"; import type { AgentData } from "@/types/agent"; -import { slugify } from "@/lib/slug"; import { useProviders } from "@/pages/providers/hooks/use-providers"; import { useProviderModels } from "@/pages/providers/hooks/use-provider-models"; import { useProviderVerify } from "@/pages/providers/hooks/use-provider-verify"; import { useAgentPresets } from "./agent-presets"; import { agentCreateSchema, type AgentCreateFormData } from "@/schemas/agent.schema"; -import { useState } from "react"; +import { AgentIdentityAndModelFields } from "./agent-identity-and-model-fields"; +import { AgentDescriptionSection } from "./agent-description-section"; interface AgentCreateDialogProps { open: boolean; @@ -60,14 +47,15 @@ export function AgentCreateDialog({ open, onOpenChange, onCreate }: AgentCreateD }, }); - const { register, control, handleSubmit, watch, setValue, reset, formState: { errors } } = form; + const { handleSubmit, watch, setValue, reset, formState: { errors } } = form; const provider = watch("provider"); const model = watch("model"); const agentType = watch("agentType"); + const agentKey = watch("agentKey"); + const displayName = watch("displayName"); const enabledProviders = providers.filter((p) => p.enabled); - const selectedProvider = useMemo( () => enabledProviders.find((p) => p.name === provider), [enabledProviders, provider], @@ -76,12 +64,8 @@ export function AgentCreateDialog({ open, onOpenChange, onCreate }: AgentCreateD const { models, loading: modelsLoading } = useProviderModels(selectedProviderId); const { verify, verifying, result: verifyResult, reset: resetVerify } = useProviderVerify(); - // Reset verification when provider or model changes - useEffect(() => { - resetVerify(); - }, [provider, model, resetVerify]); + useEffect(() => { resetVerify(); }, [provider, model, resetVerify]); - // Reset form when dialog closes useEffect(() => { if (!open) { reset(); @@ -95,12 +79,6 @@ export function AgentCreateDialog({ open, onOpenChange, onCreate }: AgentCreateD await verify(selectedProviderId, model.trim()); }; - const handleVerifyAndCreate = async () => { - if (!selectedProviderId || !model.trim()) return; - const res = await verify(selectedProviderId, model.trim()); - if (res?.valid) await handleSubmitForm(form.getValues()); - }; - const handleSubmitForm = async (data: AgentCreateFormData) => { setLoading(true); setSubmitError(""); @@ -125,15 +103,17 @@ export function AgentCreateDialog({ open, onOpenChange, onCreate }: AgentCreateD } }; + const handleVerifyAndCreate = async () => { + if (!selectedProviderId || !model.trim()) return; + const res = await verify(selectedProviderId, model.trim()); + if (res?.valid) await handleSubmitForm(form.getValues()); + }; + const handleProviderChange = (value: string) => { setValue("provider", value, { shouldValidate: true }); setValue("model", "", { shouldValidate: false }); }; - const displayName = watch("displayName"); - const agentKey = watch("agentKey"); - - // Derived submit button state const canCreate = !!agentKey && !!displayName && !!provider && !!model && !errors.agentKey && !errors.displayName && (agentType !== "predefined" || !!watch("description")?.trim()); @@ -144,185 +124,22 @@ export function AgentCreateDialog({ open, onOpenChange, onCreate }: AgentCreateD {t("create.title")} +
-
-
- -
- - { - register("displayName").onBlur(e); - const name = e.target.value.trim(); - if (name && !form.getFieldState("agentKey").isDirty) { - setValue("agentKey", slugify(name), { shouldValidate: true }); - } - }} - placeholder={t("create.displayNamePlaceholder")} - /> -
-
-
- - { - setValue("agentKey", slugify(e.target.value), { shouldValidate: true }); - }} - placeholder={t("create.agentKeyPlaceholder")} - /> - {errors.agentKey ? ( -

{errors.agentKey.message}

- ) : ( -

{t("create.agentKeyHint")}

- )} -
-
-
-
- - {enabledProviders.length > 0 ? ( - ( - - )} - /> - ) : ( - - )} -
-
- -
-
- ( - setValue("model", v, { shouldValidate: true })} - options={models.map((m) => ({ value: m.id, label: m.name }))} - placeholder={modelsLoading ? t("create.loadingModels") : t("create.enterOrSelectModel")} - /> - )} - /> -
- -
- {verifyResult && ( -

- {verifyResult.valid ? t("create.modelVerified") : verifyResult.error || t("create.verificationFailed")} -

- )} - {!verifyResult && provider && !modelsLoading && models.length === 0 && ( -

{t("create.noModelsHint")}

- )} -
-
- {agentType === "predefined" ? ( -
- -
- {agentPresets.map((preset) => ( - - ))} -
-