From 67d2cf5649d90259eefd351287b9a5dcf9f93e53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Sun, 7 Jun 2026 13:52:42 -0400 Subject: [PATCH] Split oversized renderer components --- .../src/renderer/src/app/Onboarding.tsx | 202 +---------------- .../src/app/OnboardingProviderStep.tsx | 208 ++++++++++++++++++ .../settings/ProviderConnectionDialog.tsx | 164 ++++++++++++++ .../src/pages/settings/ProvidersPanel.tsx | 197 +---------------- .../src/pages/settings/provider-secret.ts | 14 ++ 5 files changed, 392 insertions(+), 393 deletions(-) create mode 100644 AgentCompany-New/src/renderer/src/app/OnboardingProviderStep.tsx create mode 100644 AgentCompany-New/src/renderer/src/pages/settings/ProviderConnectionDialog.tsx create mode 100644 AgentCompany-New/src/renderer/src/pages/settings/provider-secret.ts diff --git a/AgentCompany-New/src/renderer/src/app/Onboarding.tsx b/AgentCompany-New/src/renderer/src/app/Onboarding.tsx index 918e6119..7c23dd1b 100644 --- a/AgentCompany-New/src/renderer/src/app/Onboarding.tsx +++ b/AgentCompany-New/src/renderer/src/app/Onboarding.tsx @@ -1,16 +1,16 @@ -import { Fragment, useEffect, useMemo, useState, type FormEvent } from "react"; +import { useMemo, useState, type FormEvent } from "react"; import { PROVIDER_AUTH_TYPE, type AppSnapshot, type ProviderCatalogEntry, } from "../../../shared/domain"; import { Button, Field, Icon, IconButton, Pill } from "../shared/ui"; -import { ProviderOAuthConnect } from "../shared/ProviderOAuthConnect"; import type { SnapshotActions } from "./useSnapshot"; +import { ProviderStep } from "./OnboardingProviderStep"; type Step = "welcome" | "provider" | "company" | "mode" | "mission"; -interface Draft { +export interface Draft { companyName: string; companyDescription: string; employeeName: string; @@ -234,202 +234,6 @@ function WelcomeStep({ onNext }: { onNext: () => void }) { ); } -function ProviderStep({ - providers, - draft, - onChange, - onBack, - onNext, -}: { - providers: ProviderCatalogEntry[]; - draft: Draft; - onChange: (patch: Partial) => void; - onBack: () => void; - onNext: () => void; -}) { - const selected = providers.find((entry) => entry.key === draft.providerKey) ?? providers[0]; - if (!selected) throw new Error("No provider available for onboarding."); - - return ( -
-

Provider

-

- Pick the runtime for the founding CEO. You can attach more providers in Settings later. -

-
- {providers.map((provider) => ( - - - {provider.key === selected.key && ( - - )} - - ))} -
- -
- ); -} - -function ProviderCredentialFields({ - selected, - draft, - onChange, -}: { - selected: ProviderCatalogEntry; - draft: Draft; - onChange: (patch: Partial) => void; -}) { - const isOAuthLike = selected.authType === "oauth_pkce" || selected.authType === "account_subscription"; - const isCustom = Boolean(selected.customEndpoint); - const live = selected.supportsLiveModels; - const [fetched, setFetched] = useState(null); - const [loading, setLoading] = useState(false); - const [loadError, setLoadError] = useState(null); - - const hasCredential = isOAuthLike - ? Boolean(draft.providerSecret.trim()) - : isCustom - ? Boolean(draft.providerBaseUrl.trim() && draft.providerSecret.trim()) - : Boolean(draft.providerSecret.trim()); - - const loadModels = async (secret: string) => { - setLoading(true); - setLoadError(null); - const result = await window.agentCompany.listProviderModels({ providerKey: selected.key, secret }); - setLoading(false); - if (!result.ok) { - setLoadError(result.error.message); - return; - } - setFetched(result.data); - if (result.data[0]) onChange({ providerModel: result.data[0].id }); - }; - - const loadTypedModels = () => { - const secret = isCustom - ? JSON.stringify({ baseUrl: draft.providerBaseUrl.trim(), secret: draft.providerSecret.trim() }) - : draft.providerSecret.trim(); - void loadModels(secret); - }; - - // Curated providers (no live endpoint) show their fixed dropdown once a - // credential is present; seed the selection with the provider default. - const showCuratedList = !live && hasCredential && selected.models.length > 0; - useEffect(() => { - if (showCuratedList && !draft.providerModel) { - onChange({ providerModel: selected.defaultModel ?? selected.models[0].id }); - } - }, [showCuratedList, draft.providerModel, selected, onChange]); - - return ( -
- {selected.credentialHelp && ( -
{selected.credentialHelp}
- )} - {isOAuthLike ? ( - ({ providerKey: selected.key })} - onComplete={(secret) => { - onChange({ providerSecret: secret ?? "" }); - if (secret && live) void loadModels(secret); - }} - /> - ) : isCustom ? ( - <> - - onChange({ providerBaseUrl: event.target.value })} - /> - - - onChange({ providerSecret: event.target.value })} - /> - - - ) : ( - - onChange({ providerSecret: event.target.value, providerModel: "" })} - /> - - )} - {!isOAuthLike && live && ( - - )} - {loadError &&
{loadError}
} - {(hasCredential || isCustom) && ( - - {fetched || showCuratedList ? ( - - ) : ( - onChange({ providerModel: event.target.value })} - /> - )} - - )} -
- ); -} - -function modelPlaceholder(live: boolean, isOAuthLike: boolean, loading: boolean): string { - if (!live) return "Enter the model id"; - if (loading || isOAuthLike) return "Loading…"; - return "Click “Get models”, or type a model id"; -} - -function authBadge(authType: string): string { - if (authType === "api_key") return "API key"; - if (authType === "oauth_pkce") return "OAuth"; - if (authType === "account_subscription") return "Account"; - if (authType === "cloud") return "Cloud"; - return authType; -} - function CompanyStep({ draft, onChange, onBack, onNext }: StepProps) { const chooseWorkspace = () => { void window.agentCompany.pickDirectory().then((result) => { diff --git a/AgentCompany-New/src/renderer/src/app/OnboardingProviderStep.tsx b/AgentCompany-New/src/renderer/src/app/OnboardingProviderStep.tsx new file mode 100644 index 00000000..59e38135 --- /dev/null +++ b/AgentCompany-New/src/renderer/src/app/OnboardingProviderStep.tsx @@ -0,0 +1,208 @@ +import { Fragment, useEffect, useState } from "react"; +import type { ProviderCatalogEntry } from "../../../shared/domain"; +import { ProviderOAuthConnect } from "../shared/ProviderOAuthConnect"; +import { Button, Field, Pill } from "../shared/ui"; +import type { Draft } from "./Onboarding"; + +export function ProviderStep({ + providers, + draft, + onChange, + onBack, + onNext, +}: { + providers: ProviderCatalogEntry[]; + draft: Draft; + onChange: (patch: Partial) => void; + onBack: () => void; + onNext: () => void; +}) { + const selected = providers.find((entry) => entry.key === draft.providerKey) ?? providers[0]; + if (!selected) throw new Error("No provider available for onboarding."); + + return ( +
+

Provider

+

+ Pick the runtime for the founding CEO. You can attach more providers in Settings later. +

+
+ {providers.map((provider) => ( + + + {provider.key === selected.key && ( + + )} + + ))} +
+ +
+ ); +} + +function ProviderCredentialFields({ + selected, + draft, + onChange, +}: { + selected: ProviderCatalogEntry; + draft: Draft; + onChange: (patch: Partial) => void; +}) { + const isOAuthLike = selected.authType === "oauth_pkce" || selected.authType === "account_subscription"; + const isCustom = Boolean(selected.customEndpoint); + const live = selected.supportsLiveModels; + const [fetched, setFetched] = useState(null); + const [loading, setLoading] = useState(false); + const [loadError, setLoadError] = useState(null); + + const hasCredential = isOAuthLike + ? Boolean(draft.providerSecret.trim()) + : isCustom + ? Boolean(draft.providerBaseUrl.trim() && draft.providerSecret.trim()) + : Boolean(draft.providerSecret.trim()); + + const loadModels = async (secret: string) => { + setLoading(true); + setLoadError(null); + const result = await window.agentCompany.listProviderModels({ providerKey: selected.key, secret }); + setLoading(false); + if (!result.ok) { + setLoadError(result.error.message); + return; + } + setFetched(result.data); + if (result.data[0]) onChange({ providerModel: result.data[0].id }); + }; + + const loadTypedModels = () => { + const secret = isCustom + ? JSON.stringify({ baseUrl: draft.providerBaseUrl.trim(), secret: draft.providerSecret.trim() }) + : draft.providerSecret.trim(); + void loadModels(secret); + }; + + const showCuratedList = !live && hasCredential && selected.models.length > 0; + useEffect(() => { + if (showCuratedList && !draft.providerModel) { + onChange({ providerModel: selected.defaultModel ?? selected.models[0].id }); + } + }, [showCuratedList, draft.providerModel, selected, onChange]); + + return ( +
+ {selected.credentialHelp && ( +
{selected.credentialHelp}
+ )} + {isOAuthLike ? ( + ({ providerKey: selected.key })} + onComplete={(secret) => { + onChange({ providerSecret: secret ?? "" }); + if (secret && live) void loadModels(secret); + }} + /> + ) : isCustom ? ( + <> + + onChange({ providerBaseUrl: event.target.value })} + /> + + + onChange({ providerSecret: event.target.value })} + /> + + + ) : ( + + onChange({ providerSecret: event.target.value, providerModel: "" })} + /> + + )} + {!isOAuthLike && live && ( + + )} + {loadError &&
{loadError}
} + {(hasCredential || isCustom) && ( + + {fetched || showCuratedList ? ( + + ) : ( + onChange({ providerModel: event.target.value })} + /> + )} + + )} +
+ ); +} + +function Actions({ onBack, onNext }: { onBack: () => void; onNext: () => void }) { + return ( +
+ + +
+ ); +} + +function modelPlaceholder(live: boolean, isOAuthLike: boolean, loading: boolean): string { + if (!live) return "Enter the model id"; + if (loading || isOAuthLike) return "Loading..."; + return "Click Get models, or type a model id"; +} + +function authBadge(authType: string): string { + if (authType === "api_key") return "API key"; + if (authType === "oauth_pkce") return "OAuth"; + if (authType === "account_subscription") return "Account"; + if (authType === "cloud") return "Cloud"; + return authType; +} diff --git a/AgentCompany-New/src/renderer/src/pages/settings/ProviderConnectionDialog.tsx b/AgentCompany-New/src/renderer/src/pages/settings/ProviderConnectionDialog.tsx new file mode 100644 index 00000000..491e7f09 --- /dev/null +++ b/AgentCompany-New/src/renderer/src/pages/settings/ProviderConnectionDialog.tsx @@ -0,0 +1,164 @@ +import { useState } from "react"; +import type { ProviderCatalogEntry, ProviderRecord } from "../../../../shared/domain"; +import { ProviderOAuthConnect } from "../../shared/ProviderOAuthConnect"; +import { Field } from "../../shared/ui"; + +interface CloudFields { + baseUrl: string; + region: string; + projectId: string; + deployment: string; +} + +const CLOUD_DEFAULTS: CloudFields = { baseUrl: "", region: "", projectId: "", deployment: "" }; + +export function ProviderConnectFields({ + provider, + entry, +}: { + provider: ProviderRecord; + entry: ProviderCatalogEntry; +}) { + if (entry.authType === "api_key") { + return ( + <> +

{entry.credentialHelp}

+ + + + ); + } + if (entry.customEndpoint) { + return ; + } + return ; +} + +export function ProviderOAuthFields({ + orgId, + employeeId, + provider, + entry, + onConnected, +}: { + orgId: string; + employeeId: string; + provider: ProviderRecord; + entry: ProviderCatalogEntry; + onConnected: () => void; +}) { + const [model, setModel] = useState(provider.model ?? entry.defaultModel ?? entry.models[0]?.id ?? ""); + return ( + <> +

{entry.credentialHelp}

+ + {entry.models.length ? ( + + ) : ( + setModel(event.target.value)} /> + )} + + ({ providerKey: entry.key, orgId, providerId: provider.id, employeeId, model })} + onComplete={() => onConnected()} + /> + + ); +} + +function CompatFieldsForm({ entry, provider }: { entry: ProviderCatalogEntry; provider: ProviderRecord }) { + const [baseUrl, setBaseUrl] = useState(""); + return ( + <> +

{entry.credentialHelp}

+ + setBaseUrl(event.target.value)} + /> + + + + + ); +} + +function ModelPicker({ entry, provider }: { entry: ProviderCatalogEntry; provider: ProviderRecord }) { + const [fetched, setFetched] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [value, setValue] = useState(provider.model ?? entry.defaultModel ?? entry.models[0]?.id ?? ""); + const models = fetched ?? entry.models; + const connected = entry.supportsLiveModels && Boolean(provider.credentialId) && provider.enabled && provider.authState === "configured"; + + const refresh = async () => { + setLoading(true); + setError(null); + const result = await window.agentCompany.listProviderModels({ providerKey: entry.key, orgId: provider.orgId, providerId: provider.id }); + setLoading(false); + if (!result.ok) { + setError(result.error.message); + return; + } + setFetched(result.data); + if (result.data[0] && !result.data.some((model) => model.id === value)) setValue(result.data[0].id); + }; + + return ( + + {models.length ? ( + + ) : ( + setValue(event.target.value)} /> + )} + {connected && ( + + )} + {error &&
{error}
} +
+ ); +} + +function CloudFieldsForm({ entry, provider }: { entry: ProviderCatalogEntry; provider: ProviderRecord }) { + const [state, setState] = useState(CLOUD_DEFAULTS); + const update = (patch: Partial) => setState((current) => ({ ...current, ...patch })); + return ( + <> +

{entry.credentialHelp}

+ + update({ baseUrl: event.target.value })} /> + + + update({ region: event.target.value })} /> + + + update({ projectId: event.target.value })} /> + + + update({ deployment: event.target.value })} /> + + + + + + + + + ); +} diff --git a/AgentCompany-New/src/renderer/src/pages/settings/ProvidersPanel.tsx b/AgentCompany-New/src/renderer/src/pages/settings/ProvidersPanel.tsx index d8988a6c..83819ee4 100644 --- a/AgentCompany-New/src/renderer/src/pages/settings/ProvidersPanel.tsx +++ b/AgentCompany-New/src/renderer/src/pages/settings/ProvidersPanel.tsx @@ -1,7 +1,6 @@ import { useState, type FormEvent } from "react"; -import { Button, Field, Pill } from "../../shared/ui"; +import { Button, Pill } from "../../shared/ui"; import { Dialog } from "../../shared/Dialog"; -import { ProviderOAuthConnect } from "../../shared/ProviderOAuthConnect"; import type { AgentRecord, ProviderCatalogEntry, @@ -14,6 +13,8 @@ import type { import { PROVIDER_DATA_POLICY } from "../../../../shared/domain"; import { ProviderLogo } from "./ProviderLogo"; import { ProviderRequestPanel } from "./ProviderRequestPanel"; +import { ProviderConnectFields, ProviderOAuthFields } from "./ProviderConnectionDialog"; +import { composeSecret } from "./provider-secret"; import type { SnapshotActions } from "../../app/useSnapshot"; interface ProvidersPanelProps { @@ -29,14 +30,6 @@ interface ProvidersPanelProps { actions: SnapshotActions; } -interface CloudFields { - baseUrl: string; - region: string; - projectId: string; - deployment: string; -} - -const CLOUD_DEFAULTS: CloudFields = { baseUrl: "", region: "", projectId: "", deployment: "" }; const POLICY_GRID = "minmax(150px, 1.2fr) minmax(118px, 0.8fr) minmax(140px, 1fr) 112px 124px 92px"; type WorkspaceScope = "organization" | "workspace"; @@ -272,190 +265,6 @@ function ProviderPolicyRow({ ); } -function ProviderConnectFields({ - provider, - entry, -}: { - provider: ProviderRecord; - entry: ProviderCatalogEntry; -}) { - if (entry.authType === "api_key") { - return ( - <> -

{entry.credentialHelp}

- - - - ); - } - if (entry.customEndpoint) { - return ; - } - return ; -} - -function CompatFieldsForm({ entry, provider }: { entry: ProviderCatalogEntry; provider: ProviderRecord }) { - const [baseUrl, setBaseUrl] = useState(""); - return ( - <> -

{entry.credentialHelp}

- - setBaseUrl(event.target.value)} - /> - - - - - ); -} - -function ProviderOAuthFields({ - orgId, - employeeId, - provider, - entry, - onConnected, -}: { - orgId: string; - employeeId: string; - provider: ProviderRecord; - entry: ProviderCatalogEntry; - onConnected: () => void; -}) { - const [model, setModel] = useState(provider.model ?? entry.defaultModel ?? entry.models[0]?.id ?? ""); - return ( - <> -

{entry.credentialHelp}

- - {entry.models.length ? ( - - ) : ( - setModel(event.target.value)} /> - )} - - ({ providerKey: entry.key, orgId, providerId: provider.id, employeeId, model })} - onComplete={() => onConnected()} - /> - - ); -} - -function ModelPicker({ entry, provider }: { entry: ProviderCatalogEntry; provider: ProviderRecord }) { - const [fetched, setFetched] = useState(null); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - const [value, setValue] = useState(provider.model ?? entry.defaultModel ?? entry.models[0]?.id ?? ""); - const models = fetched ?? entry.models; - const connected = entry.supportsLiveModels && Boolean(provider.credentialId) && provider.enabled && provider.authState === "configured"; - - const refresh = async () => { - setLoading(true); - setError(null); - const result = await window.agentCompany.listProviderModels({ providerKey: entry.key, orgId: provider.orgId, providerId: provider.id }); - setLoading(false); - if (!result.ok) { - setError(result.error.message); - return; - } - setFetched(result.data); - if (result.data[0] && !result.data.some((model) => model.id === value)) setValue(result.data[0].id); - }; - - return ( - - {models.length ? ( - - ) : ( - setValue(event.target.value)} /> - )} - {connected && ( - - )} - {error &&
{error}
} -
- ); -} - -function CloudFieldsForm({ entry, provider }: { entry: ProviderCatalogEntry; provider: ProviderRecord }) { - const [state, setState] = useState(CLOUD_DEFAULTS); - const update = (patch: Partial) => setState((current) => ({ ...current, ...patch })); - return ( - <> -

{entry.credentialHelp}

- - update({ baseUrl: event.target.value })} - /> - - - update({ region: event.target.value })} - /> - - - update({ projectId: event.target.value })} - /> - - - update({ deployment: event.target.value })} - /> - - - - - - - - - ); -} - -function composeSecret(entry: ProviderCatalogEntry, form: FormData): string { - const raw = String(form.get("secret") ?? "").trim(); - if (entry.authType !== "cloud") return raw; - const payload = { - baseUrl: String(form.get("cloudBaseUrl") ?? "").trim(), - region: String(form.get("cloudRegion") ?? "").trim(), - projectId: String(form.get("cloudProject") ?? "").trim(), - deployment: String(form.get("cloudDeployment") ?? "").trim(), - secret: raw, - }; - return JSON.stringify(payload); -} - function ProviderRow({ provider, authProfile, diff --git a/AgentCompany-New/src/renderer/src/pages/settings/provider-secret.ts b/AgentCompany-New/src/renderer/src/pages/settings/provider-secret.ts new file mode 100644 index 00000000..7aad244e --- /dev/null +++ b/AgentCompany-New/src/renderer/src/pages/settings/provider-secret.ts @@ -0,0 +1,14 @@ +import type { ProviderCatalogEntry } from "../../../../shared/domain"; + +export function composeSecret(entry: ProviderCatalogEntry, form: FormData): string { + const raw = String(form.get("secret") ?? "").trim(); + if (entry.authType !== "cloud") return raw; + const payload = { + baseUrl: String(form.get("cloudBaseUrl") ?? "").trim(), + region: String(form.get("cloudRegion") ?? "").trim(), + projectId: String(form.get("cloudProject") ?? "").trim(), + deployment: String(form.get("cloudDeployment") ?? "").trim(), + secret: raw, + }; + return JSON.stringify(payload); +}