@nextclaw/ui 0.5.29 → 0.5.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/assets/ChannelsList-DKiedAFa.js +1 -0
- package/dist/assets/{ChatPage-DI2euxZy.js → ChatPage-BwNLpkSy.js} +1 -1
- package/dist/assets/{CronConfig-DAlt-x5i.js → CronConfig-DS4SLvdJ.js} +1 -1
- package/dist/assets/{DocBrowser-TrMsdXgx.js → DocBrowser-GX0i3zMz.js} +1 -1
- package/dist/assets/{MarketplacePage-Dwm527F7.js → MarketplacePage-Cfary3pL.js} +1 -1
- package/dist/assets/{ModelConfig-srggzgfA.js → ModelConfig-DqA1Fpq1.js} +1 -1
- package/dist/assets/ProvidersList-Bp8j2dHz.js +1 -0
- package/dist/assets/{RuntimeConfig-CLbdKAlo.js → RuntimeConfig-BPFqqOmo.js} +1 -1
- package/dist/assets/{SecretsConfig-DXCdR0Be.js → SecretsConfig-817qoOc8.js} +1 -1
- package/dist/assets/{SessionsConfig-iKpz3Sts.js → SessionsConfig-DSz76nPB.js} +1 -1
- package/dist/assets/{card-CVj65Dvi.js → card-DsXZnE66.js} +1 -1
- package/dist/assets/{dialog-lK79rlAw.js → dialog-CM-OB-VU.js} +2 -2
- package/dist/assets/index-BDnLuH5V.js +2 -0
- package/dist/assets/index-yypHrk9r.css +1 -0
- package/dist/assets/{label-l-fECYi3.js → label-BryQW6Hh.js} +1 -1
- package/dist/assets/logos-DpQfWN7T.js +1 -0
- package/dist/assets/{page-layout-BghxFaNt.js → page-layout-CljvdClt.js} +1 -1
- package/dist/assets/{switch-B4yFzIbc.js → switch-DK9xeFoj.js} +1 -1
- package/dist/assets/{tabs-custom-B4q02QSV.js → tabs-custom-BWSgRC2i.js} +1 -1
- package/dist/assets/{useConfig-C9k3TmQk.js → useConfig-Cr9T194r.js} +1 -1
- package/dist/assets/{useConfirmDialog-C20D5SYn.js → useConfirmDialog-CeUaLKWy.js} +1 -1
- package/dist/assets/{vendor-H2M3a_4Z.js → vendor-CrNxGKd2.js} +77 -72
- package/dist/index.html +3 -3
- package/package.json +1 -1
- package/src/components/config/ProviderForm.tsx +198 -143
- package/src/components/config/ProvidersList.tsx +140 -80
- package/src/components/ui/status-dot.tsx +1 -1
- package/src/lib/i18n.ts +10 -1
- package/src/stores/ui.store.ts +0 -9
- package/dist/assets/ChannelsList-DEr4kE7H.js +0 -1
- package/dist/assets/ProvidersList-8kFCDiqC.js +0 -1
- package/dist/assets/action-link-w4jS8X9q.js +0 -1
- package/dist/assets/index-BXgULtdk.js +0 -2
- package/dist/assets/index-B_OeEGic.css +0 -1
package/dist/index.html
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
|
|
7
7
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
8
8
|
<title>NextClaw - 系统配置</title>
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
10
|
-
<link rel="modulepreload" crossorigin href="/assets/vendor-
|
|
11
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-BDnLuH5V.js"></script>
|
|
10
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-CrNxGKd2.js">
|
|
11
|
+
<link rel="stylesheet" crossorigin href="/assets/index-yypHrk9r.css">
|
|
12
12
|
</head>
|
|
13
13
|
|
|
14
14
|
<body>
|
package/package.json
CHANGED
|
@@ -1,27 +1,57 @@
|
|
|
1
|
-
import { useEffect, useState } from 'react';
|
|
1
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import { useConfig, useConfigMeta, useConfigSchema, useUpdateProvider } from '@/hooks/useConfig';
|
|
3
|
-
import { useUiStore } from '@/stores/ui.store';
|
|
4
|
-
import {
|
|
5
|
-
Dialog,
|
|
6
|
-
DialogContent,
|
|
7
|
-
DialogHeader,
|
|
8
|
-
DialogTitle,
|
|
9
|
-
DialogDescription,
|
|
10
|
-
DialogFooter,
|
|
11
|
-
} from '@/components/ui/dialog';
|
|
12
3
|
import { Button } from '@/components/ui/button';
|
|
13
4
|
import { Input } from '@/components/ui/input';
|
|
14
5
|
import { Label } from '@/components/ui/label';
|
|
15
6
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
|
16
7
|
import { MaskedInput } from '@/components/common/MaskedInput';
|
|
17
8
|
import { KeyValueEditor } from '@/components/common/KeyValueEditor';
|
|
9
|
+
import { StatusDot } from '@/components/ui/status-dot';
|
|
18
10
|
import { t } from '@/lib/i18n';
|
|
19
11
|
import { hintForPath } from '@/lib/config-hints';
|
|
20
12
|
import type { ProviderConfigUpdate } from '@/api/types';
|
|
21
|
-
import { KeyRound, Globe, Hash } from 'lucide-react';
|
|
13
|
+
import { KeyRound, Globe, Hash, RotateCcw } from 'lucide-react';
|
|
22
14
|
|
|
23
|
-
|
|
24
|
-
|
|
15
|
+
type WireApiType = 'auto' | 'chat' | 'responses';
|
|
16
|
+
|
|
17
|
+
type ProviderFormProps = {
|
|
18
|
+
providerName?: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function normalizeHeaders(input: Record<string, string> | null | undefined): Record<string, string> | null {
|
|
22
|
+
if (!input) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
const entries = Object.entries(input)
|
|
26
|
+
.map(([key, value]) => [key.trim(), value] as const)
|
|
27
|
+
.filter(([key]) => key.length > 0);
|
|
28
|
+
if (entries.length === 0) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
return Object.fromEntries(entries);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function headersEqual(
|
|
35
|
+
left: Record<string, string> | null | undefined,
|
|
36
|
+
right: Record<string, string> | null | undefined
|
|
37
|
+
): boolean {
|
|
38
|
+
const a = normalizeHeaders(left);
|
|
39
|
+
const b = normalizeHeaders(right);
|
|
40
|
+
if (a === null && b === null) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
if (!a || !b) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
const aEntries = Object.entries(a).sort(([ak], [bk]) => ak.localeCompare(bk));
|
|
47
|
+
const bEntries = Object.entries(b).sort(([ak], [bk]) => ak.localeCompare(bk));
|
|
48
|
+
if (aEntries.length !== bEntries.length) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
return aEntries.every(([key, value], index) => key === bEntries[index][0] && value === bEntries[index][1]);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function ProviderForm({ providerName }: ProviderFormProps) {
|
|
25
55
|
const { data: config } = useConfig();
|
|
26
56
|
const { data: meta } = useConfigMeta();
|
|
27
57
|
const { data: schema } = useConfigSchema();
|
|
@@ -30,174 +60,199 @@ export function ProviderForm() {
|
|
|
30
60
|
const [apiKey, setApiKey] = useState('');
|
|
31
61
|
const [apiBase, setApiBase] = useState('');
|
|
32
62
|
const [extraHeaders, setExtraHeaders] = useState<Record<string, string> | null>(null);
|
|
33
|
-
const [wireApi, setWireApi] = useState<
|
|
63
|
+
const [wireApi, setWireApi] = useState<WireApiType>('auto');
|
|
34
64
|
|
|
35
|
-
const providerName = providerModal.provider;
|
|
36
65
|
const providerSpec = meta?.providers.find((p) => p.name === providerName);
|
|
37
66
|
const providerConfig = providerName ? config?.providers[providerName] : null;
|
|
38
67
|
const uiHints = schema?.uiHints;
|
|
68
|
+
|
|
39
69
|
const apiKeyHint = providerName ? hintForPath(`providers.${providerName}.apiKey`, uiHints) : undefined;
|
|
40
70
|
const apiBaseHint = providerName ? hintForPath(`providers.${providerName}.apiBase`, uiHints) : undefined;
|
|
41
71
|
const extraHeadersHint = providerName ? hintForPath(`providers.${providerName}.extraHeaders`, uiHints) : undefined;
|
|
42
72
|
const wireApiHint = providerName ? hintForPath(`providers.${providerName}.wireApi`, uiHints) : undefined;
|
|
43
73
|
|
|
74
|
+
const providerTitle = providerSpec?.displayName || providerName || t('providersSelectPlaceholder');
|
|
75
|
+
const defaultApiBase = providerSpec?.defaultApiBase || '';
|
|
76
|
+
const currentApiBase = providerConfig?.apiBase || defaultApiBase;
|
|
77
|
+
const currentHeaders = normalizeHeaders(providerConfig?.extraHeaders || null);
|
|
78
|
+
const currentWireApi = (providerConfig?.wireApi || providerSpec?.defaultWireApi || 'auto') as WireApiType;
|
|
79
|
+
|
|
44
80
|
useEffect(() => {
|
|
45
|
-
if (
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
setWireApi(nextWireApi as 'auto' | 'chat' | 'responses');
|
|
81
|
+
if (!providerName) {
|
|
82
|
+
setApiKey('');
|
|
83
|
+
setApiBase('');
|
|
84
|
+
setExtraHeaders(null);
|
|
85
|
+
setWireApi('auto');
|
|
86
|
+
return;
|
|
52
87
|
}
|
|
53
|
-
|
|
88
|
+
|
|
89
|
+
setApiKey('');
|
|
90
|
+
setApiBase(currentApiBase);
|
|
91
|
+
setExtraHeaders(providerConfig?.extraHeaders || null);
|
|
92
|
+
setWireApi(currentWireApi);
|
|
93
|
+
}, [providerName, currentApiBase, providerConfig?.extraHeaders, currentWireApi]);
|
|
94
|
+
|
|
95
|
+
const hasChanges = useMemo(() => {
|
|
96
|
+
if (!providerName) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
const apiKeyChanged = apiKey.trim().length > 0;
|
|
100
|
+
const apiBaseChanged = apiBase.trim() !== currentApiBase.trim();
|
|
101
|
+
const headersChanged = !headersEqual(extraHeaders, currentHeaders);
|
|
102
|
+
const wireApiChanged = providerSpec?.supportsWireApi ? wireApi !== currentWireApi : false;
|
|
103
|
+
|
|
104
|
+
return apiKeyChanged || apiBaseChanged || headersChanged || wireApiChanged;
|
|
105
|
+
}, [
|
|
106
|
+
providerName,
|
|
107
|
+
apiKey,
|
|
108
|
+
apiBase,
|
|
109
|
+
currentApiBase,
|
|
110
|
+
extraHeaders,
|
|
111
|
+
currentHeaders,
|
|
112
|
+
providerSpec?.supportsWireApi,
|
|
113
|
+
wireApi,
|
|
114
|
+
currentWireApi
|
|
115
|
+
]);
|
|
116
|
+
|
|
117
|
+
const resetToDefault = () => {
|
|
118
|
+
setApiKey('');
|
|
119
|
+
setApiBase(defaultApiBase);
|
|
120
|
+
setExtraHeaders(null);
|
|
121
|
+
setWireApi((providerSpec?.defaultWireApi || 'auto') as WireApiType);
|
|
122
|
+
};
|
|
54
123
|
|
|
55
124
|
const handleSubmit = (e: React.FormEvent) => {
|
|
56
125
|
e.preventDefault();
|
|
57
126
|
|
|
127
|
+
if (!providerName) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
58
131
|
const payload: ProviderConfigUpdate = {};
|
|
132
|
+
const trimmedApiKey = apiKey.trim();
|
|
133
|
+
const trimmedApiBase = apiBase.trim();
|
|
134
|
+
const normalizedHeaders = normalizeHeaders(extraHeaders);
|
|
59
135
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
payload.apiKey = apiKey;
|
|
136
|
+
if (trimmedApiKey.length > 0) {
|
|
137
|
+
payload.apiKey = trimmedApiKey;
|
|
63
138
|
}
|
|
64
139
|
|
|
65
|
-
if (
|
|
66
|
-
payload.apiBase =
|
|
140
|
+
if (trimmedApiBase !== currentApiBase.trim()) {
|
|
141
|
+
payload.apiBase = trimmedApiBase.length > 0 && trimmedApiBase !== defaultApiBase ? trimmedApiBase : null;
|
|
67
142
|
}
|
|
68
143
|
|
|
69
|
-
if (
|
|
70
|
-
payload.extraHeaders =
|
|
144
|
+
if (!headersEqual(normalizedHeaders, currentHeaders)) {
|
|
145
|
+
payload.extraHeaders = normalizedHeaders;
|
|
71
146
|
}
|
|
72
147
|
|
|
73
|
-
if (providerSpec?.supportsWireApi) {
|
|
74
|
-
|
|
75
|
-
providerConfig?.wireApi || providerSpec.defaultWireApi || 'auto';
|
|
76
|
-
if (wireApi !== currentWireApi) {
|
|
77
|
-
payload.wireApi = wireApi;
|
|
78
|
-
}
|
|
148
|
+
if (providerSpec?.supportsWireApi && wireApi !== currentWireApi) {
|
|
149
|
+
payload.wireApi = wireApi;
|
|
79
150
|
}
|
|
80
151
|
|
|
81
|
-
|
|
152
|
+
updateProvider.mutate({ provider: providerName, data: payload });
|
|
153
|
+
};
|
|
82
154
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
155
|
+
if (!providerName || !providerSpec || !providerConfig) {
|
|
156
|
+
return (
|
|
157
|
+
<div className="flex min-h-[520px] items-center justify-center rounded-2xl border border-gray-200/70 bg-white px-6 text-center xl:h-[calc(100vh-180px)] xl:min-h-[600px] xl:max-h-[860px]">
|
|
158
|
+
<div>
|
|
159
|
+
<h3 className="text-base font-semibold text-gray-900">{t('providersSelectTitle')}</h3>
|
|
160
|
+
<p className="mt-2 text-sm text-gray-500">{t('providersSelectDescription')}</p>
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
86
163
|
);
|
|
87
|
-
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const statusLabel = providerConfig.apiKeySet ? t('statusReady') : t('statusSetup');
|
|
88
167
|
|
|
89
168
|
return (
|
|
90
|
-
<
|
|
91
|
-
<
|
|
92
|
-
<
|
|
93
|
-
<div className="
|
|
94
|
-
<
|
|
95
|
-
|
|
96
|
-
</div>
|
|
97
|
-
<div>
|
|
98
|
-
<DialogTitle>{providerSpec?.displayName || providerName}</DialogTitle>
|
|
99
|
-
<DialogDescription>{t('providerFormDescription')}</DialogDescription>
|
|
100
|
-
</div>
|
|
169
|
+
<div className="flex min-h-[520px] flex-col rounded-2xl border border-gray-200/70 bg-white shadow-card xl:h-[calc(100vh-180px)] xl:min-h-[600px] xl:max-h-[860px]">
|
|
170
|
+
<div className="border-b border-gray-100 px-6 py-5">
|
|
171
|
+
<div className="flex flex-wrap items-center justify-between gap-3">
|
|
172
|
+
<div className="min-w-0">
|
|
173
|
+
<h3 className="truncate text-lg font-semibold text-gray-900">{providerTitle}</h3>
|
|
174
|
+
<p className="mt-1 text-sm text-gray-500">{t('providerFormDescription')}</p>
|
|
101
175
|
</div>
|
|
102
|
-
|
|
176
|
+
<StatusDot status={providerConfig.apiKeySet ? 'ready' : 'setup'} label={statusLabel} />
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
103
179
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
className="rounded-xl"
|
|
122
|
-
/>
|
|
123
|
-
</div>
|
|
124
|
-
|
|
125
|
-
<div className="space-y-2.5">
|
|
126
|
-
<Label htmlFor="apiBase" className="text-sm font-medium text-gray-900 flex items-center gap-2">
|
|
127
|
-
<Globe className="h-3.5 w-3.5 text-gray-500" />
|
|
128
|
-
{apiBaseHint?.label ?? t('apiBase')}
|
|
129
|
-
</Label>
|
|
130
|
-
<Input
|
|
131
|
-
id="apiBase"
|
|
132
|
-
type="text"
|
|
133
|
-
value={apiBase}
|
|
134
|
-
onChange={(e) => setApiBase(e.target.value)}
|
|
135
|
-
placeholder={
|
|
136
|
-
providerSpec?.defaultApiBase ||
|
|
137
|
-
apiBaseHint?.placeholder ||
|
|
138
|
-
'https://api.example.com'
|
|
139
|
-
}
|
|
140
|
-
className="rounded-xl"
|
|
141
|
-
/>
|
|
142
|
-
{apiBaseHint?.help && (
|
|
143
|
-
<p className="text-xs text-gray-500">{apiBaseHint.help}</p>
|
|
144
|
-
)}
|
|
145
|
-
</div>
|
|
180
|
+
<form onSubmit={handleSubmit} className="flex min-h-0 flex-1 flex-col">
|
|
181
|
+
<div className="min-h-0 flex-1 space-y-6 overflow-y-auto px-6 py-5">
|
|
182
|
+
<div className="space-y-2.5">
|
|
183
|
+
<Label htmlFor="apiKey" className="flex items-center gap-2 text-sm font-medium text-gray-900">
|
|
184
|
+
<KeyRound className="h-3.5 w-3.5 text-gray-500" />
|
|
185
|
+
{apiKeyHint?.label ?? t('apiKey')}
|
|
186
|
+
</Label>
|
|
187
|
+
<MaskedInput
|
|
188
|
+
id="apiKey"
|
|
189
|
+
value={apiKey}
|
|
190
|
+
isSet={providerConfig.apiKeySet}
|
|
191
|
+
onChange={(e) => setApiKey(e.target.value)}
|
|
192
|
+
placeholder={providerConfig.apiKeySet ? t('apiKeySet') : apiKeyHint?.placeholder ?? t('enterApiKey')}
|
|
193
|
+
className="rounded-xl"
|
|
194
|
+
/>
|
|
195
|
+
<p className="text-xs text-gray-500">{t('leaveBlankToKeepUnchanged')}</p>
|
|
196
|
+
</div>
|
|
146
197
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
: option === 'responses'
|
|
163
|
-
? t('wireApiResponses')
|
|
164
|
-
: t('wireApiAuto')}
|
|
165
|
-
</SelectItem>
|
|
166
|
-
))}
|
|
167
|
-
</SelectContent>
|
|
168
|
-
</Select>
|
|
169
|
-
</div>
|
|
170
|
-
)}
|
|
198
|
+
<div className="space-y-2.5">
|
|
199
|
+
<Label htmlFor="apiBase" className="flex items-center gap-2 text-sm font-medium text-gray-900">
|
|
200
|
+
<Globe className="h-3.5 w-3.5 text-gray-500" />
|
|
201
|
+
{apiBaseHint?.label ?? t('apiBase')}
|
|
202
|
+
</Label>
|
|
203
|
+
<Input
|
|
204
|
+
id="apiBase"
|
|
205
|
+
type="text"
|
|
206
|
+
value={apiBase}
|
|
207
|
+
onChange={(e) => setApiBase(e.target.value)}
|
|
208
|
+
placeholder={defaultApiBase || apiBaseHint?.placeholder || 'https://api.example.com'}
|
|
209
|
+
className="rounded-xl"
|
|
210
|
+
/>
|
|
211
|
+
<p className="text-xs text-gray-500">{apiBaseHint?.help || t('providerApiBaseHelp')}</p>
|
|
212
|
+
</div>
|
|
171
213
|
|
|
214
|
+
{providerSpec.supportsWireApi && (
|
|
172
215
|
<div className="space-y-2.5">
|
|
173
|
-
<Label className="text-sm font-medium text-gray-900
|
|
216
|
+
<Label htmlFor="wireApi" className="flex items-center gap-2 text-sm font-medium text-gray-900">
|
|
174
217
|
<Hash className="h-3.5 w-3.5 text-gray-500" />
|
|
175
|
-
{
|
|
218
|
+
{wireApiHint?.label ?? t('wireApi')}
|
|
176
219
|
</Label>
|
|
177
|
-
<
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
220
|
+
<Select value={wireApi} onValueChange={(v) => setWireApi(v as WireApiType)}>
|
|
221
|
+
<SelectTrigger className="rounded-xl">
|
|
222
|
+
<SelectValue />
|
|
223
|
+
</SelectTrigger>
|
|
224
|
+
<SelectContent>
|
|
225
|
+
{(providerSpec.wireApiOptions || ['auto', 'chat', 'responses']).map((option) => (
|
|
226
|
+
<SelectItem key={option} value={option}>
|
|
227
|
+
{option === 'chat' ? t('wireApiChat') : option === 'responses' ? t('wireApiResponses') : t('wireApiAuto')}
|
|
228
|
+
</SelectItem>
|
|
229
|
+
))}
|
|
230
|
+
</SelectContent>
|
|
231
|
+
</Select>
|
|
232
|
+
{wireApiHint?.help && <p className="text-xs text-gray-500">{wireApiHint.help}</p>}
|
|
181
233
|
</div>
|
|
234
|
+
)}
|
|
235
|
+
|
|
236
|
+
<div className="space-y-2.5">
|
|
237
|
+
<Label className="flex items-center gap-2 text-sm font-medium text-gray-900">
|
|
238
|
+
<Hash className="h-3.5 w-3.5 text-gray-500" />
|
|
239
|
+
{extraHeadersHint?.label ?? t('extraHeaders')}
|
|
240
|
+
</Label>
|
|
241
|
+
<KeyValueEditor value={extraHeaders} onChange={setExtraHeaders} />
|
|
242
|
+
<p className="text-xs text-gray-500">{extraHeadersHint?.help || t('providerExtraHeadersHelp')}</p>
|
|
182
243
|
</div>
|
|
244
|
+
</div>
|
|
183
245
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
>
|
|
196
|
-
{updateProvider.isPending ? t('saving') : t('save')}
|
|
197
|
-
</Button>
|
|
198
|
-
</DialogFooter>
|
|
199
|
-
</form>
|
|
200
|
-
</DialogContent>
|
|
201
|
-
</Dialog>
|
|
246
|
+
<div className="flex flex-wrap items-center justify-between gap-3 border-t border-gray-100 px-6 py-4">
|
|
247
|
+
<Button type="button" variant="outline" onClick={resetToDefault}>
|
|
248
|
+
<RotateCcw className="mr-2 h-4 w-4" />
|
|
249
|
+
{t('resetToDefault')}
|
|
250
|
+
</Button>
|
|
251
|
+
<Button type="submit" disabled={updateProvider.isPending || !hasChanges}>
|
|
252
|
+
{updateProvider.isPending ? t('saving') : hasChanges ? t('save') : t('unchanged')}
|
|
253
|
+
</Button>
|
|
254
|
+
</div>
|
|
255
|
+
</form>
|
|
256
|
+
</div>
|
|
202
257
|
);
|
|
203
258
|
}
|