agentscope / examples /web_ui /frontend /src /components /dialog /EditCredentialDialog.tsx
ghostdrive1's picture
Upload folder using huggingface_hub
0b9dc2e verified
Raw
History Blame Contribute Delete
3.89 kB
import { CircleAlert, Loader2, Save } from 'lucide-react';
import { useState, useEffect } from 'react';
import { credentialApi } from '@/api';
import type { CredentialRecord, CredentialSchema } from '@/api';
import { SchemaForm, type SchemaFormValue } from '@/components/form/SchemaForm';
import { Button } from '@/components/ui/button';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter,
} from '@/components/ui/dialog';
import { useCredentials } from '@/hooks/useCredentials';
import { useTranslation } from '@/i18n/useI18n';
interface Props {
open: boolean;
onOpenChange: (open: boolean) => void;
credential: CredentialRecord;
onUpdated?: () => void;
}
export function EditCredentialDialog({ open, onOpenChange, credential, onUpdated }: Props) {
const { update } = useCredentials();
const { t } = useTranslation();
const [schema, setSchema] = useState<CredentialSchema | null>(null);
const [loadingSchema, setLoadingSchema] = useState(false);
const [values, setValues] = useState<Record<string, SchemaFormValue>>({});
const [submitting, setSubmitting] = useState(false);
const type = credential.data.type as string | undefined;
useEffect(() => {
if (!open || !type) return;
setLoadingSchema(true);
credentialApi
.schemas()
.then((res) => {
const matched = res.schemas.find(
(s) => (s.properties.type?.const as string) === type,
);
setSchema(matched ?? null);
// Pre-fill values from existing credential data (excluding writeOnly fields)
if (matched) {
const prefill: Record<string, SchemaFormValue> = {};
for (const [key, prop] of Object.entries(matched.properties)) {
if (key === 'id' || key === 'type' || prop.const !== undefined) continue;
if (prop.writeOnly) continue; // don't pre-fill secrets like api_key
const existing = credential.data[key];
if (existing !== undefined) {
prefill[key] = existing as SchemaFormValue;
}
}
setValues(prefill);
}
})
.finally(() => setLoadingSchema(false));
}, [open, type, credential.data]);
const handleSubmit = async () => {
if (!schema) return;
setSubmitting(true);
try {
const data: Record<string, unknown> = { ...credential.data };
for (const [key, prop] of Object.entries(schema.properties)) {
if (key === 'id' || key === 'type' || prop.const !== undefined) continue;
const val = values[key];
if (val !== undefined && val !== '') data[key] = val;
}
await update(credential.id, { data });
onOpenChange(false);
onUpdated?.();
} finally {
setSubmitting(false);
}
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="!w-[500px] !max-w-[500px]">
<DialogHeader>
<DialogTitle>{t('dialog-credential-edit.title')}</DialogTitle>
<DialogDescription>{t('dialog-credential-edit.description')}</DialogDescription>
</DialogHeader>
{loadingSchema ? (
<p className="text-muted-foreground text-sm">{t('common.loading')}</p>
) : schema ? (
<SchemaForm
schema={schema}
values={values}
onChange={(key, val) => setValues((prev) => ({ ...prev, [key]: val }))}
/>
) : null}
<DialogFooter>
<Button
variant="ghost"
onClick={() => onOpenChange(false)}
disabled={submitting}
>
<CircleAlert className="size-3.5" />
{t('common.cancel')}
</Button>
<Button onClick={handleSubmit} disabled={submitting || !schema}>
{submitting ? (
<Loader2 className="size-3.5 animate-spin" />
) : (
<Save className="size-3.5" />
)}
{submitting ? t('common.saving') : t('common.save')}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}