Files
AgentCompany/src/renderer/components/settings/forms/VertexForm.tsx
T
ZacharyZhang-NY 4a120f4fc6 feat(settings): drive providers panel from pi-mono catalog
ProvidersPanel reads all 26 CONNECTOR_CATALOG entries and groups them by
auth kind (OAuth / API key / Azure / AWS / Vertex). ProviderRow
dispatches to the matching dedicated form component per auth kind. AWS,
Azure, Vertex forms rewritten with Tailwind classes and a shared field
style module so the Settings pane renders with the same aesthetic as the
rest of the app.
2026-04-17 02:19:56 -04:00

116 lines
3.1 KiB
TypeScript

import { useState } from "react";
import {
FIELD_INPUT,
FIELD_LABEL,
FORM_BUTTON,
FORM_BUTTON_SECONDARY,
FORM_ERROR,
FORM_FIELDSET,
} from "./form-shared";
interface Props {
connectorId: string;
onSaved?: () => void;
}
export function VertexForm({ connectorId, onSaved }: Props) {
const [project, setProject] = useState("");
const [location, setLocation] = useState("");
const [credentialsPath, setCredentialsPath] = useState("");
const [busy, setBusy] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleSubmit = async () => {
const desktop = window.agentCompany;
if (!desktop) return;
if (project.trim().length === 0 || location.trim().length === 0) {
setError("Project and location are required.");
return;
}
setBusy(true);
setError(null);
const result = await desktop.saveCredential({
connectorId,
payload: {
kind: "gcp_adc",
value: {
project: project.trim(),
location: location.trim(),
credentialsPath: credentialsPath.trim() || undefined,
},
},
});
setBusy(false);
if (!result.ok) {
setError(result.error.message);
return;
}
onSaved?.();
};
const pickCredentialsFile = async () => {
const desktop = window.agentCompany;
if (!desktop) return;
const result = await desktop.pickDirectory();
if (result.ok && result.data) setCredentialsPath(result.data);
};
return (
<form
onSubmit={(event) => {
event.preventDefault();
void handleSubmit();
}}
className={FORM_FIELDSET}
>
<div>
<label className={FIELD_LABEL}>GCP project id</label>
<input
type="text"
value={project}
onChange={(event) => setProject(event.target.value)}
disabled={busy}
className={FIELD_INPUT}
placeholder="my-gcp-project"
/>
</div>
<div>
<label className={FIELD_LABEL}>Region / location</label>
<input
type="text"
value={location}
onChange={(event) => setLocation(event.target.value)}
disabled={busy}
className={FIELD_INPUT}
placeholder="us-central1"
/>
</div>
<div>
<label className={FIELD_LABEL}>Credentials JSON path (optional — leave blank for ADC)</label>
<div className="mt-1 flex gap-2">
<input
type="text"
value={credentialsPath}
onChange={(event) => setCredentialsPath(event.target.value)}
disabled={busy}
className={FIELD_INPUT}
placeholder="/path/to/service-account.json"
/>
<button
type="button"
onClick={() => void pickCredentialsFile()}
disabled={busy}
className={FORM_BUTTON_SECONDARY}
>
Browse
</button>
</div>
</div>
{error ? <div className={FORM_ERROR}>{error}</div> : null}
<button type="submit" disabled={busy} className={FORM_BUTTON}>
{busy ? "Saving…" : "Save"}
</button>
</form>
);
}