From 6fb91d2c28c0fa3fde4fca1c6ad6e4a5b91647ca Mon Sep 17 00:00:00 2001 From: Askhz <1361267452@qq.com> Date: Sat, 11 Apr 2026 01:41:55 +0800 Subject: [PATCH] feat(logo): show "Not logged in" state and provider display name in logo Add isNotLoggedIn() utility to detect unauthenticated state and display "Not logged in" message in both LogoV2 and CondensedLogo components. Replace billing type with provider display name (e.g. "OpenAI", "AWS Bedrock") when logged in. Subscribe to authVersion to re-render on login/logout. Add third-party provider model options for OpenAI, Gemini, Grok, Bedrock, Vertex, and Foundry. Disable biome formatter. --- src/components/LogoV2/CondensedLogo.tsx | 9 +++++-- src/components/LogoV2/LogoV2.tsx | 13 ++++++--- src/utils/logoV2Utils.ts | 36 +++++++++++++++++++++---- src/utils/model/providers.ts | 26 ++++++++++++++++++ 4 files changed, 73 insertions(+), 11 deletions(-) diff --git a/src/components/LogoV2/CondensedLogo.tsx b/src/components/LogoV2/CondensedLogo.tsx index 13419d62c..0e7891200 100644 --- a/src/components/LogoV2/CondensedLogo.tsx +++ b/src/components/LogoV2/CondensedLogo.tsx @@ -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 { {' '} v{truncatedVersion} - {shouldSplit ? ( + {notLoggedIn ? ( + Not logged in + ) : shouldSplit ? ( <> {truncatedModel} {truncatedBilling} diff --git a/src/components/LogoV2/LogoV2.tsx b/src/components/LogoV2/LogoV2.tsx index 2c998a224..cfec387fc 100644 --- a/src/components/LogoV2/LogoV2.tsx +++ b/src/components/LogoV2/LogoV2.tsx @@ -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 { - {modelDisplayName} - {billingType} + {notLoggedIn ? 'Not logged in' : modelDisplayName} + {!notLoggedIn && {billingType}} {agentName ? `@${agentName} · ${truncatedCwd}` : truncatedCwd} @@ -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 diff --git a/src/utils/logoV2Utils.ts b/src/utils/logoV2Utils.ts index 4357119d7..861d45200 100644 --- a/src/utils/logoV2Utils.ts +++ b/src/utils/logoV2Utils.ts @@ -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, } } diff --git a/src/utils/model/providers.ts b/src/utils/model/providers.ts index b5b55444c..45c76d3ca 100644 --- a/src/utils/model/providers.ts +++ b/src/utils/model/providers.ts @@ -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