Merge pull request #24 from Askhz/fix/model-login-sync

feat(logo): show "Not logged in" state and provider display name in logo
This commit is contained in:
geroge 2026-04-11 11:45:44 +08:00 committed by GitHub
commit cb6108610d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 73 additions and 11 deletions

View File

@ -7,7 +7,7 @@ import { useAppState } from '../../state/AppState.js';
import { getEffortSuffix } from '../../utils/effort.js';
import { truncate } from '../../utils/format.js';
import { isFullscreenEnvEnabled } from '../../utils/fullscreen.js';
import { formatModelAndBilling, getLogoDisplayData, truncatePath } from '../../utils/logoV2Utils.js';
import { formatModelAndBilling, getLogoDisplayData, isNotLoggedIn, truncatePath } from '../../utils/logoV2Utils.js';
import { renderModelSetting } from '../../utils/model/model.js';
import { OffscreenFreeze } from '../OffscreenFreeze.js';
import { AnimatedClawd } from './AnimatedClawd.js';
@ -23,7 +23,10 @@ export function CondensedLogo(): ReactNode {
const { columns } = useTerminalSize();
const agent = useAppState(s => s.agent);
const effortValue = useAppState(s => s.effortValue);
// Subscribe to authVersion to re-render after login/logout
useAppState(s => s.authVersion);
const model = useMainLoopModel();
const notLoggedIn = isNotLoggedIn();
const modelDisplayName = renderModelSetting(model);
const { version, cwd, billingType, agentName: agentNameFromSettings } = getLogoDisplayData();
@ -92,7 +95,9 @@ export function CondensedLogo(): ReactNode {
</Text>{' '}
<Text dimColor>v{truncatedVersion}</Text>
</Text>
{shouldSplit ? (
{notLoggedIn ? (
<Text dimColor>Not logged in</Text>
) : shouldSplit ? (
<>
<Text dimColor>{truncatedModel}</Text>
<Text dimColor>{truncatedBilling}</Text>

View File

@ -11,6 +11,7 @@ import {
getRecentActivitySync,
getRecentReleaseNotesSync,
getLogoDisplayData,
isNotLoggedIn,
} from '../../utils/logoV2Utils.js';
import { truncate } from '../../utils/format.js';
import { getDisplayPath } from '../../utils/file.js';
@ -82,6 +83,8 @@ export function LogoV2(): React.ReactNode {
const showOverageCreditUpsell = useShowOverageCreditUpsell();
const agent = useAppState(s => s.agent);
const effortValue = useAppState(s => s.effortValue);
// Subscribe to authVersion to re-render after login/logout
useAppState(s => s.authVersion);
const config = getGlobalConfig();
@ -136,6 +139,7 @@ export function LogoV2(): React.ReactNode {
}, [showOverageCreditUpsell, showOnboarding, showGuestPassesUpsell, isCondensedMode]);
const model = useMainLoopModel();
const notLoggedIn = isNotLoggedIn();
const fullModelDisplayName = renderModelSetting(model);
const { version, cwd, billingType, agentName: agentNameFromSettings } = getLogoDisplayData();
// Prefer AppState.agent (set from --agent CLI flag) over settings
@ -247,8 +251,8 @@ export function LogoV2(): React.ReactNode {
<Box marginY={1}>
<Clawd />
</Box>
<Text dimColor>{modelDisplayName}</Text>
<Text dimColor>{billingType}</Text>
<Text dimColor>{notLoggedIn ? 'Not logged in' : modelDisplayName}</Text>
{!notLoggedIn && <Text dimColor>{billingType}</Text>}
<Text dimColor>{agentName ? `@${agentName} · ${truncatedCwd}` : truncatedCwd}</Text>
</Box>
</OffscreenFreeze>
@ -269,8 +273,9 @@ export function LogoV2(): React.ReactNode {
}
const welcomeMessage = formatWelcomeMessage(username);
const modelLine =
!process.env.IS_DEMO && config.oauthAccount?.organizationName
const modelLine = notLoggedIn
? 'Not logged in'
: !process.env.IS_DEMO && config.oauthAccount?.organizationName
? `${modelDisplayName} · ${billingType} · ${config.oauthAccount.organizationName}`
: `${modelDisplayName} · ${billingType}`;
// Calculate cwd width accounting for agent name if present

View File

@ -1,7 +1,8 @@
import { getDirectConnectServerUrl, getSessionId } from '../bootstrap/state.js'
import { stringWidth } from '@anthropic/ink'
import type { LogOption } from '../types/logs.js'
import { getSubscriptionName, isClaudeAISubscriber } from './auth.js'
import { getAnthropicApiKey } from './auth.js'
import { hasCoStrictCredentialsSync } from '../costrict/provider/credentials.js'
import { getCwd } from './cwd.js'
import { getDisplayPath } from './file.js'
import {
@ -9,6 +10,7 @@ import {
truncateToWidth,
truncateToWidthNoEllipsis,
} from './format.js'
import { getAPIProvider, getProviderDisplayName } from './model/providers.js'
import { getStoredChangelogFromMemory, parseChangelog } from './releaseNotes.js'
import { gt } from './semver.js'
import { loadMessageLogs } from './sessionStorage.js'
@ -237,12 +239,33 @@ export function formatReleaseNoteForDisplay(
}
/**
* Gets the common logo display data used by both LogoV2 and CondensedLogo
* Check if user is not logged in
* User is considered logged in if they have CoStrict credentials or API key
*/
export function isNotLoggedIn(): boolean {
// 1. CoStrict login (credentials file exists)
if (hasCoStrictCredentialsSync()) return false
const settings = getInitialSettings()
// 2. Any provider configured via modelType (包括 anthropic/openai/bedrock 等)
// 用户通过 /login 选择 provider 后会设置 modelType
if (settings.modelType) {
return false
}
// 3. Anthropic API Key configured (legacy way without modelType)
if (getAnthropicApiKey() !== null) return false
// None of the above - not logged in
return true
}
export function getLogoDisplayData(): {
version: string
cwd: string
billingType: string
isLoggedIn: boolean
agentName: string | undefined
} {
const version = process.env.DEMO_VERSION ?? MACRO.VERSION
@ -253,15 +276,18 @@ export function getLogoDisplayData(): {
const cwd = serverUrl
? `${displayPath} in ${serverUrl.replace(/^https?:\/\//, '')}`
: displayPath
const billingType = isClaudeAISubscriber()
? getSubscriptionName()
: 'API Usage Billing'
const loggedIn = !isNotLoggedIn()
const provider = getAPIProvider()
const providerName = getProviderDisplayName(provider)
const billingType = loggedIn ? providerName : 'Not logged in'
const agentName = getInitialSettings().agent
return {
version,
cwd,
billingType,
isLoggedIn: loggedIn,
agentName,
}
}

View File

@ -35,6 +35,32 @@ export function getAPIProviderForStatsig(): AnalyticsMetadata_I_VERIFIED_THIS_IS
return getAPIProvider() as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
}
/**
* Get a human-readable display name for the current API provider
*/
export function getProviderDisplayName(provider: APIProvider): string {
switch (provider) {
case 'firstParty':
return 'Anthropic'
case 'bedrock':
return 'AWS Bedrock'
case 'vertex':
return 'Google Vertex'
case 'foundry':
return 'Foundry'
case 'openai':
return 'OpenAI'
case 'gemini':
return 'Google Gemini'
case 'grok':
return 'xAI Grok'
case 'costrict':
return 'CoStrict'
default:
return provider
}
}
/**
* Check if ANTHROPIC_BASE_URL is a first-party Anthropic API URL.
* Returns true if not set (default API) or points to api.anthropic.com