feat(provider): support Codex subscriptions with ChatGPT sign-in (#2513)

* feat(provider): support Codex subscriptions with ChatGPT sign-in

* style: format Codex live integration test

* fix(provider): preserve Codex identity in temporary model tests

* fix(web): portal provider selector without dialog overflow

* fix(web): allow native scrolling in provider dropdown

* fix(provider): surface safe Codex quota and upstream errors

* fix(web): provide reliable Codex copy feedback in dialogs

* feat(provider): confirm cascade deletion from edit dialog

* fix(persistence): discard connections after failed commit

* fix(web): polish provider loading and confirmation motion

---------

Co-authored-by: dadachann <185672915+dadachann@users.noreply.github.com>
This commit is contained in:
Hyu
2026-09-06 23:31:02 +08:00
committed by GitHub
parent ec63978ecf
commit 0f216a0d4d
52 changed files with 5490 additions and 360 deletions
@@ -486,6 +486,7 @@ export default function ModelsPanel({
// Get the provider info
const provider = providers.find((p) => p.uuid === providerUuid);
const providerData = {
uuid: providerUuid,
requester: provider?.requester || '',
base_url: provider?.base_url || '',
api_keys: provider?.api_keys || [],
@@ -495,7 +496,7 @@ export default function ModelsPanel({
await httpClient.testLLMModel('_', {
uuid: '',
name,
provider_uuid: '',
provider_uuid: providerUuid,
provider: providerData,
abilities,
reasoning_config: reasoningConfig,
@@ -505,7 +506,7 @@ export default function ModelsPanel({
await httpClient.testEmbeddingModel('_', {
uuid: '',
name,
provider_uuid: '',
provider_uuid: providerUuid,
provider: providerData,
extra_args: extraArgsObj,
} as never);
@@ -513,7 +514,7 @@ export default function ModelsPanel({
await httpClient.testRerankModel('_', {
uuid: '',
name,
provider_uuid: '',
provider_uuid: providerUuid,
provider: providerData,
extra_args: extraArgsObj,
} as never);
@@ -536,6 +537,29 @@ export default function ModelsPanel({
expandedProviders.forEach((uuid) => loadProviderModels(uuid));
}
async function handleProviderDeleted(providerUuid: string) {
setProviders((prev) =>
prev.filter((provider) => provider.uuid !== providerUuid),
);
setProviderModels((prev) => {
const next = { ...prev };
delete next[providerUuid];
return next;
});
setExpandedProviders((prev) => {
const next = new Set(prev);
next.delete(providerUuid);
return next;
});
await Promise.all([
loadProviders(),
...Array.from(expandedProviders)
.filter((uuid) => uuid !== providerUuid)
.map((uuid) => loadProviderModels(uuid)),
]);
setProviderFormOpen(false);
}
function renderProviderCard(
provider: ModelProvider,
isLangBotModels: boolean = false,
@@ -666,8 +690,14 @@ export default function ModelsPanel({
)}
</PanelBody>
<Dialog open={providerFormOpen} onOpenChange={setProviderFormOpen}>
<DialogContent className="w-full max-w-[calc(100%-2rem)] p-4 sm:max-w-[600px] sm:p-6">
<Dialog
open={providerFormOpen}
onOpenChange={(open) => {
if (!open) handleFormClose();
else setProviderFormOpen(true);
}}
>
<DialogContent className="w-full max-w-[calc(100%-2rem)] max-h-[calc(100dvh-2rem)] overflow-y-auto p-4 sm:max-w-[600px] sm:p-6">
<DialogHeader>
<DialogTitle>
{editingProviderId
@@ -675,11 +705,15 @@ export default function ModelsPanel({
: t('models.addProvider')}
</DialogTitle>
</DialogHeader>
<ProviderForm
providerId={editingProviderId || undefined}
onFormSubmit={handleFormClose}
onFormCancel={() => setProviderFormOpen(false)}
/>
{providerFormOpen && (
<ProviderForm
key={editingProviderId || 'new'}
providerId={editingProviderId || undefined}
onFormSubmit={handleFormClose}
onFormCancel={handleFormClose}
onProviderDeleted={canManage ? handleProviderDeleted : undefined}
/>
)}
</DialogContent>
</Dialog>
</>