mirror of
https://github.com/agent0ai/agent-zero.git
synced 2026-10-19 15:33:54 +00:00
Show success feedback when copying OAuth device codes
Reuse the shared button-feedback helper to briefly show a green checkmark after copying a sign-in code, while preserving existing notifications and automatic reset. Cover successful copies, clipboard failures, and missing codes in the reconnect regression test; update plugin documentation. Validation: 218 OAuth tests and 3 plugin scan tests passed, along with the Node reconnect test and a live UI check of clipboard contents, success styling, and icon reset.
This commit is contained in:
parent
935fb5cdcc
commit
186acbc58c
5 changed files with 20 additions and 8 deletions
|
|
@ -27,7 +27,7 @@
|
|||
- Provider cards and model slot actions must be driven by backend provider status. Do not reintroduce hardcoded frontend provider lists or fallback provider catalogs.
|
||||
- OAuth account surfaces in settings, discovery, and onboarding must use the provider registry/status summary rather than Codex-only frontend state.
|
||||
- OAuth settings pending-auth controls such as device codes, manual callback input, and provider setup fields must render inline under the relevant provider row, not as a detached section below all providers.
|
||||
- Device challenge codes use a selectable standalone box and an accessible copy button, reusing the shared clipboard helper and OAuth notifications for feedback.
|
||||
- Device challenge codes use a selectable standalone box and an accessible copy button, reusing the shared clipboard and button-feedback helpers: successful copies briefly show a green checkmark and retain OAuth notifications.
|
||||
- OAuth device-code polling must honor provider `interval`, `expires_at`, and `slow_down` updates; do not poll immediately or keep a stale fixed interval after a provider asks the client to slow down.
|
||||
- OAuth settings model slots must keep provider choice editable per slot, list only connected OAuth account providers, and persist the selected provider IDs into `chat_model.provider` and `utility_model.provider`.
|
||||
- When exactly one OAuth provider is connected, use it as an unsaved default only for empty slots or slots already using that provider. A different saved provider must keep the explicit `Choose connected provider` prompt until the user opts into the switch.
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ OAuth-backed model providers do not require users to enter API keys. Agent Zero
|
|||
### Codex/ChatGPT (`codex_oauth`)
|
||||
|
||||
- Uses the existing Codex device-code flow.
|
||||
- Shows the sign-in code in a selectable box with a copy button.
|
||||
- Shows the sign-in code in a selectable box with a copy button that briefly turns into a green checkmark after copying.
|
||||
- Writes Codex-compatible credentials to an Agent Zero-owned `auth.json` file.
|
||||
- Refreshes local tokens when needed.
|
||||
- If a refresh token expires or is revoked, shows an **Open OAuth settings** toast. Choose **Reconnect** on the Codex row and complete sign-in, then retry your request. No manual disconnect is needed; cancelling sign-in preserves the saved credentials and model choices.
|
||||
|
|
|
|||
|
|
@ -166,7 +166,7 @@
|
|||
<span>Enter this code</span>
|
||||
<div class="oauth-device-code">
|
||||
<code x-text="$store.oauthConfig.providerDevice(card.provider_id)?.user_code"></code>
|
||||
<button class="oauth-icon-button" type="button" title="Copy sign-in code" aria-label="Copy sign-in code" @click="$store.oauthConfig.copyDeviceCode(card.provider_id)">
|
||||
<button class="oauth-icon-button" type="button" title="Copy sign-in code" aria-label="Copy sign-in code" @click="$store.oauthConfig.copyDeviceCode(card.provider_id, $el)">
|
||||
<x-icon name="content_copy"></x-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -855,6 +855,11 @@
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
.oauth-icon-button.success {
|
||||
color: #4caf50;
|
||||
border-color: currentColor;
|
||||
}
|
||||
|
||||
.oauth-model-config {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { createStore } from "/js/AlpineStore.js";
|
||||
import { callJsonApi, fetchApi } from "/js/api.js";
|
||||
import { copyToClipboard } from "/components/messages/action-buttons/simple-action-buttons.js";
|
||||
import { copyToClipboard, showButtonFeedback } from "/components/messages/action-buttons/simple-action-buttons.js";
|
||||
import { store as modelConfigStore } from "/plugins/_model_config/webui/model-config-store.js";
|
||||
import {
|
||||
store as notificationStore,
|
||||
|
|
@ -311,11 +311,12 @@ export const store = createStore("oauthConfig", {
|
|||
return this.devices[String(providerId || "")] || null;
|
||||
},
|
||||
|
||||
async copyDeviceCode(providerId) {
|
||||
async copyDeviceCode(providerId, button = null) {
|
||||
const code = this.providerDevice(providerId)?.user_code;
|
||||
if (!code) return;
|
||||
try {
|
||||
await copyToClipboard(code);
|
||||
if (button) showButtonFeedback(button, true, "content_copy");
|
||||
void toastFrontendSuccess("Sign-in code copied.", "OAuth Connections");
|
||||
} catch {
|
||||
void toastFrontendError("Could not copy the code. Select it and copy manually.", "OAuth Connections");
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ const requests = [];
|
|||
const timers = new Map();
|
||||
const dismissed = [];
|
||||
const copied = [];
|
||||
const buttonFeedback = [];
|
||||
const copyButton = {};
|
||||
const feedback = [];
|
||||
let clipboardDenied = false;
|
||||
const source = readFileSync(new URL('../plugins/_oauth/webui/oauth-config-store.js', import.meta.url), 'utf8')
|
||||
|
|
@ -29,6 +31,7 @@ const store = vm.runInNewContext(`${source}\nstore`, {
|
|||
if (clipboardDenied) throw new Error('Permission denied');
|
||||
copied.push(code);
|
||||
},
|
||||
showButtonFeedback: (...args) => buttonFeedback.push(args),
|
||||
notificationStore: { dismissToast: id => dismissed.push(id) },
|
||||
});
|
||||
const provider = 'codex_oauth';
|
||||
|
|
@ -41,20 +44,23 @@ await store.connectProvider(provider);
|
|||
assert.equal(store.providerPrimaryLabel(provider), 'Waiting');
|
||||
assert.equal(store.providerDetailOpen(provider), true);
|
||||
assert.equal(store.providerDevice(provider).user_code, 'TEST');
|
||||
await store.copyDeviceCode(provider);
|
||||
await store.copyDeviceCode(provider, copyButton);
|
||||
assert.deepEqual(copied, ['TEST']);
|
||||
assert.deepEqual(buttonFeedback, [[copyButton, true, 'content_copy']]);
|
||||
assert.equal(feedback.pop(), 'Sign-in code copied.');
|
||||
clipboardDenied = true;
|
||||
await store.copyDeviceCode(provider);
|
||||
await store.copyDeviceCode(provider, copyButton);
|
||||
assert.equal(feedback.pop(), 'Could not copy the code. Select it and copy manually.');
|
||||
assert.equal(buttonFeedback.length, 1);
|
||||
assert.equal(timers.size, 1);
|
||||
await store.connectProvider(provider);
|
||||
assert.equal(requests.length, 1);
|
||||
store.cancelConnect(provider);
|
||||
assert.equal(timers.size, 0);
|
||||
assert.equal(store.providerDevice(provider), null);
|
||||
await store.copyDeviceCode(provider);
|
||||
await store.copyDeviceCode(provider, copyButton);
|
||||
assert.equal(copied.length, 1);
|
||||
assert.equal(buttonFeedback.length, 1);
|
||||
assert.equal(feedback.length, 0);
|
||||
assert.equal(store.providerConnected(provider), true);
|
||||
assert.equal(store.modelConfig.chat_model.name, 'chosen-model');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue