| |
| import * as React from 'react' |
| import { Box, Text, color } from '../../ink.js' |
| import { useTerminalSize } from '../../hooks/useTerminalSize.js' |
| import { stringWidth } from '../../ink/stringWidth.js' |
| import { |
| getLayoutMode, |
| calculateLayoutDimensions, |
| calculateOptimalLeftWidth, |
| formatWelcomeMessage, |
| truncatePath, |
| getRecentActivitySync, |
| getRecentReleaseNotesSync, |
| getLogoDisplayData, |
| } from '../../utils/logoV2Utils.js' |
| import { truncate } from '../../utils/format.js' |
| import { getDisplayPath } from '../../utils/file.js' |
| import { Clawd } from './Clawd.js' |
| import { FeedColumn } from './FeedColumn.js' |
| import { |
| createRecentActivityFeed, |
| createWhatsNewFeed, |
| createProjectOnboardingFeed, |
| createGuestPassesFeed, |
| } from './feedConfigs.js' |
| import { getGlobalConfig, saveGlobalConfig } from 'src/utils/config.js' |
| import { resolveThemeSetting } from 'src/utils/systemTheme.js' |
| import { getInitialSettings } from 'src/utils/settings/settings.js' |
| import { |
| isDebugMode, |
| isDebugToStdErr, |
| getDebugLogPath, |
| } from 'src/utils/debug.js' |
| import { useEffect, useState } from 'react' |
| import { |
| getSteps, |
| shouldShowProjectOnboarding, |
| incrementProjectOnboardingSeenCount, |
| } from '../../projectOnboardingState.js' |
| import { CondensedLogo } from './CondensedLogo.js' |
| import { OffscreenFreeze } from '../OffscreenFreeze.js' |
| import { checkForReleaseNotesSync } from '../../utils/releaseNotes.js' |
| import { getDumpPromptsPath } from 'src/services/api/dumpPrompts.js' |
| import { isEnvTruthy } from 'src/utils/envUtils.js' |
| import { |
| getStartupPerfLogPath, |
| isDetailedProfilingEnabled, |
| } from 'src/utils/startupProfiler.js' |
| import { EmergencyTip } from './EmergencyTip.js' |
| import { VoiceModeNotice } from './VoiceModeNotice.js' |
| import { Opus1mMergeNotice } from './Opus1mMergeNotice.js' |
| import { feature } from 'bun:bundle' |
|
|
| |
| |
| |
| |
| |
| |
| const ChannelsNoticeModule = |
| feature('KAIROS') || feature('KAIROS_CHANNELS') |
| ? (require('./ChannelsNotice.js') as typeof import('./ChannelsNotice.js')) |
| : null |
| |
| import { SandboxManager } from 'src/utils/sandbox/sandbox-adapter.js' |
| import { |
| useShowGuestPassesUpsell, |
| incrementGuestPassesSeenCount, |
| } from './GuestPassesUpsell.js' |
| import { |
| useShowOverageCreditUpsell, |
| incrementOverageCreditUpsellSeenCount, |
| createOverageCreditFeed, |
| } from './OverageCreditUpsell.js' |
| import { plural } from '../../utils/stringUtils.js' |
| import { useAppState } from '../../state/AppState.js' |
| import { getEffortSuffix } from '../../utils/effort.js' |
| import { useMainLoopModel } from '../../hooks/useMainLoopModel.js' |
| import { renderModelSetting } from '../../utils/model/model.js' |
|
|
| const LEFT_PANEL_MAX_WIDTH = 50 |
|
|
| export function LogoV2(): React.ReactNode { |
| const activities = getRecentActivitySync() |
| const username = getGlobalConfig().oauthAccount?.displayName ?? '' |
|
|
| const { columns } = useTerminalSize() |
| const showOnboarding = shouldShowProjectOnboarding() |
| const showSandboxStatus = SandboxManager.isSandboxingEnabled() |
| const showGuestPassesUpsell = useShowGuestPassesUpsell() |
| const showOverageCreditUpsell = useShowOverageCreditUpsell() |
| const agent = useAppState(s => s.agent) |
| const effortValue = useAppState(s => s.effortValue) |
|
|
| const config = getGlobalConfig() |
|
|
| let changelog: string[] |
| try { |
| changelog = getRecentReleaseNotesSync(3) |
| } catch { |
| changelog = [] |
| } |
|
|
| |
| |
| |
| const [announcement] = useState(() => { |
| const announcements = getInitialSettings().companyAnnouncements |
| if (!announcements || announcements.length === 0) return undefined |
| return config.numStartups === 1 |
| ? announcements[0] |
| : announcements[Math.floor(Math.random() * announcements.length)] |
| }) |
| const { hasReleaseNotes } = checkForReleaseNotesSync( |
| config.lastReleaseNotesSeen, |
| ) |
|
|
| useEffect(() => { |
| const currentConfig = getGlobalConfig() |
| if (currentConfig.lastReleaseNotesSeen === MACRO.VERSION) { |
| return |
| } |
| saveGlobalConfig(current => { |
| if (current.lastReleaseNotesSeen === MACRO.VERSION) return current |
| return { ...current, lastReleaseNotesSeen: MACRO.VERSION } |
| }) |
| if (showOnboarding) { |
| incrementProjectOnboardingSeenCount() |
| } |
| }, [config, showOnboarding]) |
|
|
| |
| |
| |
| const isCondensedMode = |
| !hasReleaseNotes && |
| !showOnboarding && |
| !isEnvTruthy(process.env.CLAUDE_CODE_FORCE_FULL_LOGO) |
|
|
| useEffect(() => { |
| if (showGuestPassesUpsell && !showOnboarding && !isCondensedMode) { |
| incrementGuestPassesSeenCount() |
| } |
| }, [showGuestPassesUpsell, showOnboarding, isCondensedMode]) |
|
|
| useEffect(() => { |
| if ( |
| showOverageCreditUpsell && |
| !showOnboarding && |
| !showGuestPassesUpsell && |
| !isCondensedMode |
| ) { |
| incrementOverageCreditUpsellSeenCount() |
| } |
| }, [ |
| showOverageCreditUpsell, |
| showOnboarding, |
| showGuestPassesUpsell, |
| isCondensedMode, |
| ]) |
|
|
| const model = useMainLoopModel() |
| const fullModelDisplayName = renderModelSetting(model) |
| const { |
| version, |
| cwd, |
| billingType, |
| agentName: agentNameFromSettings, |
| } = getLogoDisplayData() |
| |
| const agentName = agent ?? agentNameFromSettings |
| |
| const effortSuffix = getEffortSuffix(model, effortValue) |
| const modelDisplayName = truncate( |
| fullModelDisplayName + effortSuffix, |
| LEFT_PANEL_MAX_WIDTH - 20, |
| ) |
|
|
| |
| if ( |
| !hasReleaseNotes && |
| !showOnboarding && |
| !isEnvTruthy(process.env.CLAUDE_CODE_FORCE_FULL_LOGO) |
| ) { |
| return ( |
| <> |
| <CondensedLogo /> |
| <VoiceModeNotice /> |
| <Opus1mMergeNotice /> |
| {ChannelsNoticeModule && <ChannelsNoticeModule.ChannelsNotice />} |
| {isDebugMode() && ( |
| <Box paddingLeft={2} flexDirection="column"> |
| <Text color="warning">Debug mode enabled</Text> |
| <Text dimColor> |
| Logging to: {isDebugToStdErr() ? 'stderr' : getDebugLogPath()} |
| </Text> |
| </Box> |
| )} |
| <EmergencyTip /> |
| {process.env.CLAUDE_CODE_TMUX_SESSION && ( |
| <Box paddingLeft={2} flexDirection="column"> |
| <Text dimColor> |
| tmux session: {process.env.CLAUDE_CODE_TMUX_SESSION} |
| </Text> |
| <Text dimColor> |
| {process.env.CLAUDE_CODE_TMUX_PREFIX_CONFLICTS |
| ? `Detach: ${process.env.CLAUDE_CODE_TMUX_PREFIX} ${process.env.CLAUDE_CODE_TMUX_PREFIX} d (press prefix twice - Claude uses ${process.env.CLAUDE_CODE_TMUX_PREFIX})` |
| : `Detach: ${process.env.CLAUDE_CODE_TMUX_PREFIX} d`} |
| </Text> |
| </Box> |
| )} |
| {announcement && ( |
| <Box paddingLeft={2} flexDirection="column"> |
| {!process.env.IS_DEMO && config.oauthAccount?.organizationName && ( |
| <Text dimColor> |
| Message from {config.oauthAccount.organizationName}: |
| </Text> |
| )} |
| <Text>{announcement}</Text> |
| </Box> |
| )} |
| {"external" === 'ant' && !process.env.DEMO_VERSION && ( |
| <Box paddingLeft={2} flexDirection="column"> |
| <Text dimColor>Use /issue to report model behavior issues</Text> |
| </Box> |
| )} |
| {"external" === 'ant' && !process.env.DEMO_VERSION && ( |
| <Box paddingLeft={2} flexDirection="column"> |
| <Text color="warning">[ANT-ONLY] Logs:</Text> |
| <Text dimColor> |
| API calls: {getDisplayPath(getDumpPromptsPath())} |
| </Text> |
| <Text dimColor> |
| Debug logs: {getDisplayPath(getDebugLogPath())} |
| </Text> |
| {isDetailedProfilingEnabled() && ( |
| <Text dimColor> |
| Startup Perf: {getDisplayPath(getStartupPerfLogPath())} |
| </Text> |
| )} |
| </Box> |
| )} |
| {"external" === 'ant' && <GateOverridesWarning />} |
| {"external" === 'ant' && <ExperimentEnrollmentNotice />} |
| </> |
| ) |
| } |
|
|
| |
| const layoutMode = getLayoutMode(columns) |
|
|
| const userTheme = resolveThemeSetting(getGlobalConfig().theme) |
| const borderTitle = ` ${color('clawd_body', userTheme)('Codev')} ${color('inactive', userTheme)(`v${version}`)} ` |
| const compactBorderTitle = color('clawd_body', userTheme)(' Codev ') |
|
|
| |
| if (layoutMode === 'compact') { |
| const layoutWidth = 4 |
| let welcomeMessage = formatWelcomeMessage(username) |
| if (stringWidth(welcomeMessage) > columns - layoutWidth) { |
| welcomeMessage = formatWelcomeMessage(null) |
| } |
|
|
| |
| const separator = ' · ' |
| const atPrefix = '@' |
| const cwdAvailableWidth = agentName |
| ? columns - |
| layoutWidth - |
| atPrefix.length - |
| stringWidth(agentName) - |
| separator.length |
| : columns - layoutWidth |
| const truncatedCwd = truncatePath(cwd, Math.max(cwdAvailableWidth, 10)) |
| |
| |
| |
| return ( |
| <> |
| <OffscreenFreeze> |
| <Box |
| flexDirection="column" |
| borderStyle="round" |
| borderColor="clawd_body" |
| borderText={{ |
| content: compactBorderTitle, |
| position: 'top', |
| align: 'start', |
| offset: 1, |
| }} |
| paddingX={1} |
| paddingY={1} |
| alignItems="center" |
| width={columns} |
| > |
| <Text bold>{welcomeMessage}</Text> |
| <Box marginY={1}> |
| <Clawd /> |
| </Box> |
| <Text dimColor>{modelDisplayName}</Text> |
| <Text dimColor>{billingType}</Text> |
| <Text dimColor> |
| {agentName ? `@${agentName} · ${truncatedCwd}` : truncatedCwd} |
| </Text> |
| </Box> |
| </OffscreenFreeze> |
| <VoiceModeNotice /> |
| <Opus1mMergeNotice /> |
| {ChannelsNoticeModule && <ChannelsNoticeModule.ChannelsNotice />} |
| {showSandboxStatus && ( |
| <Box marginTop={1} flexDirection="column"> |
| <Text color="warning"> |
| Your bash commands will be sandboxed. Disable with /sandbox. |
| </Text> |
| </Box> |
| )} |
| {"external" === 'ant' && <GateOverridesWarning />} |
| {"external" === 'ant' && <ExperimentEnrollmentNotice />} |
| </> |
| ) |
| } |
|
|
| const welcomeMessage = formatWelcomeMessage(username) |
| const modelLine = |
| !process.env.IS_DEMO && config.oauthAccount?.organizationName |
| ? `${modelDisplayName} · ${billingType} · ${config.oauthAccount.organizationName}` |
| : `${modelDisplayName} · ${billingType}` |
| |
| const cwdSeparator = ' · ' |
| const cwdAtPrefix = '@' |
| const cwdAvailableWidth = agentName |
| ? LEFT_PANEL_MAX_WIDTH - |
| cwdAtPrefix.length - |
| stringWidth(agentName) - |
| cwdSeparator.length |
| : LEFT_PANEL_MAX_WIDTH |
| const truncatedCwd = truncatePath(cwd, Math.max(cwdAvailableWidth, 10)) |
| const cwdLine = agentName ? `@${agentName} · ${truncatedCwd}` : truncatedCwd |
| const optimalLeftWidth = calculateOptimalLeftWidth( |
| welcomeMessage, |
| cwdLine, |
| modelLine, |
| ) |
|
|
| |
| const { leftWidth, rightWidth } = calculateLayoutDimensions( |
| columns, |
| layoutMode, |
| optimalLeftWidth, |
| ) |
|
|
| return ( |
| <> |
| <OffscreenFreeze> |
| <Box |
| flexDirection="column" |
| borderStyle="round" |
| borderColor="clawd_body" |
| borderText={{ |
| content: borderTitle, |
| position: 'top', |
| align: 'start', |
| offset: 3, |
| }} |
| > |
| {/* Main content */} |
| <Box |
| flexDirection={layoutMode === 'horizontal' ? 'row' : 'column'} |
| paddingX={1} |
| gap={1} |
| > |
| {/* Left Panel */} |
| <Box |
| flexDirection="column" |
| width={leftWidth} |
| justifyContent="space-between" |
| alignItems="center" |
| minHeight={9} |
| > |
| <Box marginTop={1}> |
| <Text bold>{welcomeMessage}</Text> |
| </Box> |
| |
| <Clawd /> |
| |
| <Box flexDirection="column" alignItems="center"> |
| <Text dimColor>{modelLine}</Text> |
| <Text dimColor>{cwdLine}</Text> |
| </Box> |
| </Box> |
| |
| {/* Vertical divider */} |
| {layoutMode === 'horizontal' && ( |
| <Box |
| height="100%" |
| borderStyle="single" |
| borderColor="clawd_body" |
| borderDimColor |
| borderTop={false} |
| borderBottom={false} |
| borderLeft={false} |
| /> |
| )} |
| |
| {/* Right Panel - Project Onboarding or Recent Activity and What's New */} |
| {layoutMode === 'horizontal' && ( |
| <FeedColumn |
| feeds={ |
| showOnboarding |
| ? [ |
| createProjectOnboardingFeed(getSteps()), |
| createRecentActivityFeed(activities), |
| ] |
| : showGuestPassesUpsell |
| ? [ |
| createRecentActivityFeed(activities), |
| createGuestPassesFeed(), |
| ] |
| : showOverageCreditUpsell |
| ? [ |
| createRecentActivityFeed(activities), |
| createOverageCreditFeed(), |
| ] |
| : [ |
| createRecentActivityFeed(activities), |
| createWhatsNewFeed(changelog), |
| ] |
| } |
| maxWidth={rightWidth} |
| /> |
| )} |
| </Box> |
| </Box> |
| </OffscreenFreeze> |
| <VoiceModeNotice /> |
| <Opus1mMergeNotice /> |
| {ChannelsNoticeModule && <ChannelsNoticeModule.ChannelsNotice />} |
| {isDebugMode() && ( |
| <Box paddingLeft={2} flexDirection="column"> |
| <Text color="warning">Debug mode enabled</Text> |
| <Text dimColor> |
| Logging to: {isDebugToStdErr() ? 'stderr' : getDebugLogPath()} |
| </Text> |
| </Box> |
| )} |
| <EmergencyTip /> |
| {process.env.CLAUDE_CODE_TMUX_SESSION && ( |
| <Box paddingLeft={2} flexDirection="column"> |
| <Text dimColor> |
| tmux session: {process.env.CLAUDE_CODE_TMUX_SESSION} |
| </Text> |
| <Text dimColor> |
| {process.env.CLAUDE_CODE_TMUX_PREFIX_CONFLICTS |
| ? `Detach: ${process.env.CLAUDE_CODE_TMUX_PREFIX} ${process.env.CLAUDE_CODE_TMUX_PREFIX} d (press prefix twice - Claude uses ${process.env.CLAUDE_CODE_TMUX_PREFIX})` |
| : `Detach: ${process.env.CLAUDE_CODE_TMUX_PREFIX} d`} |
| </Text> |
| </Box> |
| )} |
| {announcement && ( |
| <Box paddingLeft={2} flexDirection="column"> |
| {!process.env.IS_DEMO && config.oauthAccount?.organizationName && ( |
| <Text dimColor> |
| Message from {config.oauthAccount.organizationName}: |
| </Text> |
| )} |
| <Text>{announcement}</Text> |
| </Box> |
| )} |
| {showSandboxStatus && ( |
| <Box paddingLeft={2} flexDirection="column"> |
| <Text color="warning"> |
| Your bash commands will be sandboxed. Disable with /sandbox. |
| </Text> |
| </Box> |
| )} |
| {"external" === 'ant' && !process.env.DEMO_VERSION && ( |
| <Box paddingLeft={2} flexDirection="column"> |
| <Text dimColor>Use /issue to report model behavior issues</Text> |
| </Box> |
| )} |
| {"external" === 'ant' && !process.env.DEMO_VERSION && ( |
| <Box paddingLeft={2} flexDirection="column"> |
| <Text color="warning">[ANT-ONLY] Logs:</Text> |
| <Text dimColor> |
| API calls: {getDisplayPath(getDumpPromptsPath())} |
| </Text> |
| <Text dimColor>Debug logs: {getDisplayPath(getDebugLogPath())}</Text> |
| {isDetailedProfilingEnabled() && ( |
| <Text dimColor> |
| Startup Perf: {getDisplayPath(getStartupPerfLogPath())} |
| </Text> |
| )} |
| </Box> |
| )} |
| {"external" === 'ant' && <GateOverridesWarning />} |
| {"external" === 'ant' && <ExperimentEnrollmentNotice />} |
| </> |
| ) |
| } |
|
|
|
|