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.
This commit is contained in:
Askhz 2026-04-11 01:41:55 +08:00
parent afee7d3ed2
commit 6fb91d2c28
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 { getEffortSuffix } from '../../utils/effort.js';
import { truncate } from '../../utils/format.js'; import { truncate } from '../../utils/format.js';
import { isFullscreenEnvEnabled } from '../../utils/fullscreen.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 { renderModelSetting } from '../../utils/model/model.js';
import { OffscreenFreeze } from '../OffscreenFreeze.js'; import { OffscreenFreeze } from '../OffscreenFreeze.js';
import { AnimatedClawd } from './AnimatedClawd.js'; import { AnimatedClawd } from './AnimatedClawd.js';
@ -23,7 +23,10 @@ export function CondensedLogo(): ReactNode {
const { columns } = useTerminalSize(); const { columns } = useTerminalSize();
const agent = useAppState(s => s.agent); const agent = useAppState(s => s.agent);
const effortValue = useAppState(s => s.effortValue); const effortValue = useAppState(s => s.effortValue);
// Subscribe to authVersion to re-render after login/logout
useAppState(s => s.authVersion);
const model = useMainLoopModel(); const model = useMainLoopModel();
const notLoggedIn = isNotLoggedIn();
const modelDisplayName = renderModelSetting(model); const modelDisplayName = renderModelSetting(model);
const { version, cwd, billingType, agentName: agentNameFromSettings } = getLogoDisplayData(); const { version, cwd, billingType, agentName: agentNameFromSettings } = getLogoDisplayData();
@ -92,7 +95,9 @@ export function CondensedLogo(): ReactNode {
</Text>{' '} </Text>{' '}
<Text dimColor>v{truncatedVersion}</Text> <Text dimColor>v{truncatedVersion}</Text>
</Text> </Text>
{shouldSplit ? ( {notLoggedIn ? (
<Text dimColor>Not logged in</Text>
) : shouldSplit ? (
<> <>
<Text dimColor>{truncatedModel}</Text> <Text dimColor>{truncatedModel}</Text>
<Text dimColor>{truncatedBilling}</Text> <Text dimColor>{truncatedBilling}</Text>

View File

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

View File

@ -1,7 +1,8 @@
import { getDirectConnectServerUrl, getSessionId } from '../bootstrap/state.js' import { getDirectConnectServerUrl, getSessionId } from '../bootstrap/state.js'
import { stringWidth } from '@anthropic/ink' import { stringWidth } from '@anthropic/ink'
import type { LogOption } from '../types/logs.js' 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 { getCwd } from './cwd.js'
import { getDisplayPath } from './file.js' import { getDisplayPath } from './file.js'
import { import {
@ -9,6 +10,7 @@ import {
truncateToWidth, truncateToWidth,
truncateToWidthNoEllipsis, truncateToWidthNoEllipsis,
} from './format.js' } from './format.js'
import { getAPIProvider, getProviderDisplayName } from './model/providers.js'
import { getStoredChangelogFromMemory, parseChangelog } from './releaseNotes.js' import { getStoredChangelogFromMemory, parseChangelog } from './releaseNotes.js'
import { gt } from './semver.js' import { gt } from './semver.js'
import { loadMessageLogs } from './sessionStorage.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(): { export function getLogoDisplayData(): {
version: string version: string
cwd: string cwd: string
billingType: string billingType: string
isLoggedIn: boolean
agentName: string | undefined agentName: string | undefined
} { } {
const version = process.env.DEMO_VERSION ?? MACRO.VERSION const version = process.env.DEMO_VERSION ?? MACRO.VERSION
@ -253,15 +276,18 @@ export function getLogoDisplayData(): {
const cwd = serverUrl const cwd = serverUrl
? `${displayPath} in ${serverUrl.replace(/^https?:\/\//, '')}` ? `${displayPath} in ${serverUrl.replace(/^https?:\/\//, '')}`
: displayPath : displayPath
const billingType = isClaudeAISubscriber()
? getSubscriptionName() const loggedIn = !isNotLoggedIn()
: 'API Usage Billing' const provider = getAPIProvider()
const providerName = getProviderDisplayName(provider)
const billingType = loggedIn ? providerName : 'Not logged in'
const agentName = getInitialSettings().agent const agentName = getInitialSettings().agent
return { return {
version, version,
cwd, cwd,
billingType, billingType,
isLoggedIn: loggedIn,
agentName, 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 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. * 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 * Returns true if not set (default API) or points to api.anthropic.com