Skip to content

Commit af22226

Browse files
committed
Add edit provider settings feature
1 parent d147704 commit af22226

File tree

1 file changed

+165
-29
lines changed

1 file changed

+165
-29
lines changed

app/settings/page.tsx

Lines changed: 165 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ export default function AgentSettings() {
7171
// Provider settings state
7272
const [settings, setSettings] = useState<Record<string, string>>({});
7373
const [error, setError] = useState<ErrorState>(null);
74+
75+
// Edit provider state
76+
const [editingProvider, setEditingProvider] = useState<string | null>(null);
77+
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false);
7478

7579
// Wallet state
7680
const [walletData, setWalletData] = useState<WalletKeys | null>(null);
@@ -91,6 +95,9 @@ export default function AgentSettings() {
9195
};
9296
}
9397

98+
console.log('Agent settings:', agentData.agent.settings);
99+
console.log('All providers:', providerData.map(p => ({ name: p.name, settings: p.settings?.map(s => s.name) })));
100+
94101
const connected = providerData.filter((provider) => {
95102
// Skip providers without settings
96103
if (!provider.settings?.length) return false;
@@ -103,16 +110,29 @@ export default function AgentSettings() {
103110
return isSensitive && agentData.agent?.settings.some((s) => s.name === setting.name);
104111
});
105112

113+
console.log(`Provider ${provider.name}:`, {
114+
hasSettings: !!provider.settings?.length,
115+
relevantSettings: relevantSettings.map(s => s.name),
116+
relevantCount: relevantSettings.length
117+
});
118+
106119
// If no relevant settings found, provider is not connected
107120
if (relevantSettings.length === 0) return false;
108121

109122
// Check if ALL relevant settings are HIDDEN
110-
return relevantSettings.every((setting) => {
123+
const isConnected = relevantSettings.every((setting) => {
111124
const agentSetting = agentData.agent?.settings.find((s) => s.name === setting.name);
112-
return agentSetting && agentSetting.value === 'HIDDEN';
125+
const isHidden = agentSetting && agentSetting.value === 'HIDDEN';
126+
console.log(` Setting ${setting.name}: agent value = "${agentSetting?.value}", is hidden = ${isHidden}`);
127+
return isHidden;
113128
});
129+
130+
console.log(`Provider ${provider.name} is connected:`, isConnected);
131+
return isConnected;
114132
});
115133

134+
console.log('Connected providers:', connected.map(p => p.name));
135+
116136
return {
117137
connected,
118138
available: providerData.filter((provider) => !connected.includes(provider)),
@@ -133,11 +153,20 @@ export default function AgentSettings() {
133153
const handleSaveSettings = async (extensionName: string, settings: Record<string, string>) => {
134154
try {
135155
setError(null);
156+
157+
// Filter out HIDDEN and empty values
158+
const filteredSettings = Object.entries(settings).reduce((acc, [key, value]) => {
159+
if (value && value !== 'HIDDEN' && value.trim() !== '') {
160+
acc[key] = value;
161+
}
162+
return acc;
163+
}, {} as Record<string, string>);
164+
136165
const response = await axios.put<{ status: number; data: any }>(
137166
`${process.env.NEXT_PUBLIC_AGIXT_SERVER}/api/agent/${agent_name}`,
138167
{
139168
agent_name: agent_name,
140-
settings: settings,
169+
settings: filteredSettings,
141170
} as ExtensionSettings,
142171
{
143172
headers: {
@@ -150,34 +179,68 @@ export default function AgentSettings() {
150179
if (response.status === 200) {
151180
setError({
152181
type: 'success',
153-
message: 'Extension connected successfully!',
182+
message: 'Extension updated successfully!',
183+
});
184+
setEditingProvider(null);
185+
setIsEditDialogOpen(false);
186+
187+
// Show toast notification for success
188+
const isEditing = editingProvider !== null;
189+
toast({
190+
title: 'Success',
191+
description: `${extensionName} ${isEditing ? 'updated' : 'connected'} successfully!`,
154192
});
155-
window.location.reload();
193+
194+
// Just refresh the data, don't reload the whole page
195+
mutateAgent();
156196
}
157197
} catch (error: any) {
158198
setError({
159199
type: 'error',
160-
message: error.response?.data?.detail || error.message || 'Failed to connect extension',
200+
message: error.response?.data?.detail || error.message || 'Failed to update extension',
161201
});
162202
}
163203
mutateAgent();
164204
};
165205

166206
// Handler for disconnecting provider
167207
const handleDisconnect = async (name: string) => {
168-
const extension = providerData?.find((ext) => ext.name === name);
169-
if (!extension) return;
170-
171-
const emptySettings = extension.settings
172-
.filter((setting) => {
173-
return ['API_KEY', 'SECRET', 'PASSWORD', 'TOKEN'].some((keyword) =>
174-
setting.name.replaceAll('TOKENS', '').includes(keyword),
175-
);
176-
})
177-
.reduce((acc, setting) => {
178-
return { ...acc, [setting.name]: '' };
179-
}, {});
180-
await handleSaveSettings(extension.name, emptySettings);
208+
try {
209+
setError(null);
210+
211+
console.log('Disconnecting provider:', name);
212+
console.log('Using agent name:', agent_name);
213+
console.log('Full URL:', `${process.env.NEXT_PUBLIC_AGIXT_SERVER}/v1/agent/${agent_name}/provider/${name}`);
214+
215+
const response = await axios.delete(
216+
`${process.env.NEXT_PUBLIC_AGIXT_SERVER}/v1/agent/${agent_name}/provider/${name}`,
217+
{
218+
headers: {
219+
'Content-Type': 'application/json',
220+
Authorization: getCookie('jwt'),
221+
},
222+
},
223+
);
224+
225+
console.log('Disconnect response:', response);
226+
227+
if (response.status === 200) {
228+
toast({
229+
title: 'Success',
230+
description: `${name} disconnected successfully!`,
231+
});
232+
// Just refresh the data, don't reload the whole page
233+
mutateAgent();
234+
}
235+
} catch (error: any) {
236+
console.error('Failed to disconnect provider:', error);
237+
console.error('Error response:', error.response);
238+
toast({
239+
title: 'Error',
240+
description: error.response?.data?.detail || error.response?.data?.message || 'Failed to disconnect provider. Please try again.',
241+
variant: 'destructive',
242+
});
243+
}
181244
};
182245

183246
// Agent creation handler
@@ -612,15 +675,85 @@ export default function AgentSettings() {
612675
<p className='text-sm text-muted-foreground'>Connected</p>
613676
</div>
614677
</div>
615-
<Button
616-
variant='outline'
617-
size={isMobile ? 'sm' : 'default'}
618-
className={cn('gap-2', isMobile ? 'px-2' : '')}
619-
onClick={() => handleDisconnect(provider.name)}
620-
>
621-
<Unlink className='w-4 h-4' />
622-
{!isMobile && 'Disconnect'}
623-
</Button>
678+
<div className='flex gap-2'>
679+
<Dialog open={isEditDialogOpen && editingProvider === provider.name} onOpenChange={setIsEditDialogOpen}>
680+
<DialogTrigger asChild>
681+
<Button
682+
variant='outline'
683+
size={isMobile ? 'sm' : 'default'}
684+
className={cn('gap-2', isMobile ? 'px-2' : '')}
685+
onClick={() => {
686+
setEditingProvider(provider.name);
687+
setIsEditDialogOpen(true);
688+
// Initialize settings with current agent settings values, showing HIDDEN for protected values
689+
const currentSettings = provider.settings.reduce((acc: Record<string, string>, setting) => {
690+
const agentSetting = agentData?.agent?.settings.find((s) => s.name === setting.name);
691+
acc[setting.name] = agentSetting?.value || setting.value as string;
692+
return acc;
693+
}, {});
694+
setSettings(currentSettings);
695+
}}
696+
>
697+
<LuPencil className='w-4 h-4' />
698+
{!isMobile && 'Edit'}
699+
</Button>
700+
</DialogTrigger>
701+
<DialogContent className={cn('sm:max-w-[425px]', isMobile ? 'w-[90%] p-4' : '')}>
702+
<DialogHeader>
703+
<DialogTitle>Edit {provider.name}</DialogTitle>
704+
<DialogDescription>
705+
Update the credentials for this service. Leave fields as "HIDDEN" to keep existing values, or clear them to remove the setting.
706+
</DialogDescription>
707+
</DialogHeader>
708+
709+
<div className='grid gap-4 py-4'>
710+
{provider.settings.map((prov) => (
711+
<div key={prov.name} className='grid gap-2'>
712+
<Label htmlFor={`edit-${prov.name}`}>{prov.name}</Label>
713+
<Input
714+
id={`edit-${prov.name}`}
715+
type={
716+
prov.name.toLowerCase().includes('key') || prov.name.toLowerCase().includes('password')
717+
? 'password'
718+
: 'text'
719+
}
720+
value={settings[prov.name] || ''}
721+
onChange={(e) =>
722+
setSettings((prev) => ({
723+
...prev,
724+
[prov.name]: e.target.value,
725+
}))
726+
}
727+
placeholder={settings[prov.name] === 'HIDDEN' ? 'Leave as HIDDEN to keep current value' : `Enter ${prov.name.toLowerCase()}`}
728+
/>
729+
</div>
730+
))}
731+
</div>
732+
733+
<DialogFooter>
734+
<DialogClose asChild>
735+
<Button variant='outline' onClick={() => setIsEditDialogOpen(false)}>Cancel</Button>
736+
</DialogClose>
737+
<Button onClick={() => handleSaveSettings(provider.name, settings)}>Update Provider</Button>
738+
</DialogFooter>
739+
740+
{error && editingProvider === provider.name && (
741+
<Alert variant={error.type === 'success' ? 'default' : 'destructive'}>
742+
<AlertDescription>{error.message}</AlertDescription>
743+
</Alert>
744+
)}
745+
</DialogContent>
746+
</Dialog>
747+
<Button
748+
variant='outline'
749+
size={isMobile ? 'sm' : 'default'}
750+
className={cn('gap-2', isMobile ? 'px-2' : '')}
751+
onClick={() => handleDisconnect(provider.name)}
752+
>
753+
<Unlink className='w-4 h-4' />
754+
{!isMobile && 'Disconnect'}
755+
</Button>
756+
</div>
624757
</div>
625758
<div className='text-sm text-muted-foreground'>
626759
<MarkdownBlock content={provider.description} />
@@ -696,10 +829,13 @@ export default function AgentSettings() {
696829
</div>
697830

698831
<DialogFooter>
832+
<DialogClose asChild>
833+
<Button variant='outline'>Cancel</Button>
834+
</DialogClose>
699835
<Button onClick={() => handleSaveSettings(provider.name, settings)}>Connect Provider</Button>
700836
</DialogFooter>
701837

702-
{error && (
838+
{error && !editingProvider && (
703839
<Alert variant={error.type === 'success' ? 'default' : 'destructive'}>
704840
<AlertDescription>{error.message}</AlertDescription>
705841
</Alert>

0 commit comments

Comments
 (0)