Skip to content

Commit 5a5abe3

Browse files
committed
fix(web): make the account section state and action obvious
Show the sign-in state with a status dot, split the provider and the model into separate labelled fields, and label the primary button for what it opens instead of borrowing the provider dialog title.
1 parent 2826517 commit 5a5abe3

4 files changed

Lines changed: 131 additions & 6 deletions

File tree

.changeset/web-account-section.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@pymodel/pythinker-code": patch
3+
---
4+
5+
Show the sign-in state, the provider, and the model as separate fields in the web settings account section, and label the button for what it opens.

apps/pythinker-web/src/components/settings/pages/GeneralPage.vue

Lines changed: 78 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { computed, onMounted, onUnmounted, ref } from 'vue';
33
import { useI18n } from 'vue-i18n';
44
import type { ColorScheme, Theme } from '../../../composables/usePythinkerWebClient';
55
6-
defineProps<{
6+
const props = defineProps<{
77
theme: Theme;
88
colorScheme: ColorScheme;
99
uiFontSize: number;
@@ -28,6 +28,17 @@ const desktopAutoUpdate = ref(true);
2828
const desktopUpdateState = ref<DesktopUpdateState>();
2929
let removeDesktopUpdateListener: (() => void) | undefined;
3030
31+
const accountDetails = computed(() => {
32+
const value = props.accountModel;
33+
if (!value) return null;
34+
const separator = value.indexOf('/');
35+
return {
36+
raw: value,
37+
provider: separator === -1 ? value : value.slice(0, separator),
38+
model: separator === -1 ? value : value.slice(separator + 1),
39+
};
40+
});
41+
3142
const desktopStatusText = computed(() => {
3243
const state = desktopUpdateState.value;
3344
if (state === undefined) return '';
@@ -184,15 +195,28 @@ onUnmounted(() => {
184195
</div>
185196
</section>
186197

187-
<section class="sec">
198+
<section class="sec account-section">
188199
<h3 class="sec-title">{{ t('settings.account') }}</h3>
189-
<div class="row">
190-
<span class="rlabel">{{ authReady ? t('sidebar.signedIn') : t('sidebar.notSignedIn') }}</span>
191-
<span v-if="authReady && accountModel" class="rvalue" :title="accountModel">{{ accountModel }}</span>
200+
<div class="account-status">
201+
<span class="dot" :class="authReady ? 's-connected' : 's-unconfigured'" aria-hidden="true" />
202+
<span>{{ authReady ? t('sidebar.signedIn') : t('sidebar.notSignedIn') }}</span>
192203
</div>
204+
<div v-if="authReady && accountDetails" class="account-details">
205+
<div class="account-detail">
206+
<span class="account-label">{{ t('providers.provider') }}</span>
207+
<span class="account-value account-provider" :title="accountDetails.raw">{{ accountDetails.provider }}</span>
208+
</div>
209+
<div class="account-detail">
210+
<span class="account-label">{{ t('providers.model') }}</span>
211+
<span class="account-value account-model" :title="accountDetails.raw">{{ accountDetails.model }}</span>
212+
</div>
213+
</div>
214+
<p id="account-action-description" class="account-action-note">
215+
{{ t(authReady ? 'providers.manageDescription' : 'providers.signInDescription') }}
216+
</p>
193217
<div class="actions">
218+
<button type="button" class="act signin" aria-describedby="account-action-description" @click="emit('login')">{{ t('providers.manage') }}</button>
194219
<button type="button" class="act" @click="emit('openOnboarding')">{{ t('onboarding.reopen') }}</button>
195-
<button type="button" class="act signin" @click="emit('login')">{{ t('providers.title') }}</button>
196220
</div>
197221
</section>
198222
</section>
@@ -238,4 +262,52 @@ onUnmounted(() => {
238262
.opt:hover { color: var(--ink); }
239263
.opt.on { background: var(--soft); color: var(--blue2); font-weight: 600; }
240264
.actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
265+
.account-status {
266+
display: flex;
267+
align-items: center;
268+
gap: 8px;
269+
color: var(--ink);
270+
font-family: var(--sans);
271+
font-size: calc(var(--ui-font-size) - 1px);
272+
}
273+
.account-details {
274+
display: grid;
275+
grid-template-columns: repeat(2, minmax(0, 1fr));
276+
gap: 8px;
277+
margin-top: 10px;
278+
}
279+
.account-detail {
280+
min-width: 0;
281+
padding: 8px 10px;
282+
border: 1px solid var(--line);
283+
border-radius: var(--r-sm);
284+
background: var(--panel);
285+
}
286+
.account-label {
287+
display: block;
288+
margin-bottom: 3px;
289+
color: var(--muted);
290+
font-family: var(--mono);
291+
font-size: var(--ui-font-size-xs);
292+
letter-spacing: 0.05em;
293+
text-transform: uppercase;
294+
}
295+
.account-value {
296+
display: block;
297+
overflow: hidden;
298+
color: var(--ink);
299+
font-family: var(--mono);
300+
font-size: calc(var(--ui-font-size) - 1px);
301+
text-overflow: ellipsis;
302+
white-space: nowrap;
303+
}
304+
.account-action-note {
305+
margin: 10px 0 0;
306+
color: var(--muted);
307+
font-size: calc(var(--ui-font-size) - 1px);
308+
}
309+
310+
@media (max-width: 640px) {
311+
.account-details { grid-template-columns: 1fr; }
312+
}
241313
</style>

apps/pythinker-web/src/i18n/locales/en/providers.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
export default {
22
dialogLabel: 'Manage providers',
3+
manage: 'Manage providers',
4+
manageDescription: 'Open provider setup to add or change providers.',
5+
signInDescription: 'Open provider setup to sign in.',
6+
provider: 'Provider',
7+
model: 'Model',
38
title: 'Provider management',
49
close: 'Close (Esc)',
510
loading: 'Loading providers…',

apps/pythinker-web/test/settings-pane.test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,49 @@ describe('SettingsPane desktop updates', () => {
352352
});
353353
});
354354

355+
describe('SettingsPane general account section', () => {
356+
it('shows signed-in status and separates the provider from the model', () => {
357+
const rawModel = 'openai-codex/gpt-5.6-luna';
358+
const panel = mountPane('general', { accountModel: rawModel }).get('#settings-panel-general');
359+
360+
expect(panel.get('.account-status').text()).toContain('Signed in');
361+
expect(panel.get('.account-status .dot').classes()).toContain('s-connected');
362+
expect(panel.get('.account-provider').text()).toBe('openai-codex');
363+
expect(panel.get('.account-model').text()).toBe('gpt-5.6-luna');
364+
expect(panel.get('.account-provider').attributes('title')).toBe(rawModel);
365+
expect(panel.get('.account-model').attributes('title')).toBe(rawModel);
366+
});
367+
368+
it('shows the full value when the account model has no provider separator', () => {
369+
const rawModel = 'legacy-model';
370+
const panel = mountPane('general', { accountModel: rawModel }).get('#settings-panel-general');
371+
372+
expect(panel.get('.account-provider').text()).toBe(rawModel);
373+
expect(panel.get('.account-model').text()).toBe(rawModel);
374+
});
375+
376+
it('shows the sign-in affordance when the account is not ready', () => {
377+
const panel = mountPane('general', { authReady: false, accountModel: null }).get('#settings-panel-general');
378+
379+
expect(panel.get('.account-status').text()).toContain('Not signed in');
380+
expect(panel.get('.account-status .dot').classes()).toContain('s-unconfigured');
381+
expect(panel.get('.account-action-note').text()).toBe('Open provider setup to sign in.');
382+
expect(panel.get('button.signin').text()).toBe('Manage providers');
383+
});
384+
385+
it('emits login from the primary action and onboarding from the secondary action', async () => {
386+
const wrapper = mountPane('general', { authReady: false, accountModel: null });
387+
const panel = wrapper.get('#settings-panel-general');
388+
const buttons = panel.findAll('button.act');
389+
390+
await buttons[0]!.trigger('click');
391+
await buttons[1]!.trigger('click');
392+
393+
expect(wrapper.emitted('login')).toEqual([[]]);
394+
expect(wrapper.emitted('openOnboarding')).toEqual([[]]);
395+
});
396+
});
397+
355398
describe('SettingsPane agent page', () => {
356399
it('keeps a configured default the catalog no longer offers', () => {
357400
// Without a matching option the browser shows its first one, which reads

0 commit comments

Comments
 (0)