@sriinnu/runic 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/LICENSE +1 -1
- package/README.md +47 -29
- package/RuniciOS/Core/RunicTypographyiOS.swift +24 -0
- package/RuniciOS/Views/ProviderDetailView.swift +9 -9
- package/RuniciOS/Views/ProviderListView.swift +10 -10
- package/RuniciOS/Widgets/LargeWidget.swift +14 -14
- package/RuniciOS/Widgets/LiveActivity.swift +19 -19
- package/RuniciOS/Widgets/LockScreenWidget.swift +15 -15
- package/RuniciOS/Widgets/MediumWidget.swift +10 -10
- package/RuniciOS/Widgets/RunicWidget.swift +5 -5
- package/RuniciOS/Widgets/SmallWidget.swift +9 -9
- package/Scripts/package_app.sh +2 -2
- package/Sources/Runic/About.swift +3 -3
- package/Sources/Runic/AppNotifications.swift +5 -3
- package/Sources/Runic/Controllers/StatusItemController+Actions.swift +17 -4
- package/Sources/Runic/Controllers/StatusItemController+Animation.swift +5 -4
- package/Sources/Runic/Controllers/StatusItemController+ChartSubmenus.swift +30 -16
- package/Sources/Runic/Controllers/StatusItemController+CustomProviders.swift +6 -3
- package/Sources/Runic/Controllers/StatusItemController+Menu.swift +47 -14
- package/Sources/Runic/Controllers/StatusItemController+MenuCards.swift +6 -4
- package/Sources/Runic/Controllers/StatusItemController+MenuHelpers.swift +40 -12
- package/Sources/Runic/Controllers/StatusItemController.swift +43 -43
- package/Sources/Runic/Core/BudgetNotificationManager.swift +2 -1
- package/Sources/Runic/Core/Login/ClaudeLoginRunner.swift +2 -2
- package/Sources/Runic/Core/Login/CodexLoginRunner.swift +1 -1
- package/Sources/Runic/Core/Login/CursorLoginRunner.swift +4 -4
- package/Sources/Runic/Core/Login/GeminiLoginRunner.swift +3 -6
- package/Sources/Runic/Core/Rendering/IconRenderer.swift +91 -85
- package/Sources/Runic/Core/Rendering/IconView.swift +3 -1
- package/Sources/Runic/Core/RunicColors.swift +6 -6
- package/Sources/Runic/Core/RunicSpacing.swift +7 -7
- package/Sources/Runic/Core/RunicTypography.swift +206 -0
- package/Sources/Runic/Core/Stores/AuggieTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/AzureOpenAITokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/CerebrasTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/CohereTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/CopilotTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/CustomProviderStore.swift +10 -10
- package/Sources/Runic/Core/Stores/DeepSeekTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/FireworksTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/GroqTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/KimiTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/MiniMaxCookieHeaderStore.swift +10 -36
- package/Sources/Runic/Core/Stores/MiniMaxGroupIDStore.swift +10 -36
- package/Sources/Runic/Core/Stores/MiniMaxTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/MistralTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/OpenRouterTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/PerplexityTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/ProviderToggleStore.swift +1 -1
- package/Sources/Runic/Core/Stores/QwenTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/SambaNovaTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/SettingsStore.swift +99 -52
- package/Sources/Runic/Core/Stores/TogetherTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/UsageStore+Accessors.swift +19 -6
- package/Sources/Runic/Core/Stores/UsageStore+TokenCost.swift +1 -1
- package/Sources/Runic/Core/Stores/UsageStore+WidgetSnapshot.swift +1 -1
- package/Sources/Runic/Core/Stores/UsageStore.swift +234 -167
- package/Sources/Runic/Core/Stores/XAITokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/ZaiTokenStore.swift +10 -36
- package/Sources/Runic/Core/UsageExporter.swift +8 -4
- package/Sources/Runic/ProviderRegistry.swift +1 -1
- package/Sources/Runic/Providers/Antigravity/AntigravityProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Auggie/AuggieProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Azure/AzureProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Bedrock/BedrockProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Cerebras/CerebrasProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Codex/CodexProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Cohere/CohereProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Cursor/CursorProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/DeepSeek/DeepSeekProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Factory/FactoryProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Fireworks/FireworksProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Gemini/GeminiProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Groq/GroqProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Kimi/KimiProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/MiniMax/MiniMaxProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Mistral/MistralProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/OpenRouter/OpenRouterProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Perplexity/PerplexityProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/SambaNova/SambaNovaProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Shared/ProviderImplementation.swift +5 -2
- package/Sources/Runic/Providers/Shared/ProviderImplementationRegistry.swift +1 -1
- package/Sources/Runic/Providers/Shared/ProviderSettingsDescriptors.swift +1 -1
- package/Sources/Runic/Providers/Together/TogetherProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/XAI/XAIProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Zai/ZaiProviderImplementation.swift +1 -1
- package/Sources/Runic/Resources/Fonts/Caveat-Bold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/Caveat-Regular.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/FiraCode-Bold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/FiraCode-Light.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/FiraCode-Medium.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/FiraCode-Regular.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/FiraCode-SemiBold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/IBMPlexMono-Bold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/IBMPlexMono-Medium.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/IBMPlexMono-Regular.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/IBMPlexMono-SemiBold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/Inconsolata-Bold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/Inconsolata-Medium.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/Inconsolata-Regular.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/Inconsolata-SemiBold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/JetBrainsMono-Bold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/JetBrainsMono-Medium.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/JetBrainsMono-Regular.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/JetBrainsMono-SemiBold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/SpaceMono-Bold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/SpaceMono-Regular.ttf +0 -0
- package/Sources/Runic/Resources/provider-context-windows.json +20 -0
- package/Sources/Runic/RunicApp.swift +46 -36
- package/Sources/Runic/SessionQuotaNotifications.swift +2 -2
- package/Sources/Runic/UsagePaceText.swift +1 -1
- package/Sources/Runic/UsageProgressBar.swift +13 -9
- package/Sources/Runic/Utilities/LoadingPattern.swift +4 -2
- package/Sources/Runic/Utilities/PerformanceConstants.swift +28 -28
- package/Sources/Runic/Views/Menu/AlertsMenuView.swift +19 -21
- package/Sources/Runic/Views/Menu/CostComparativeChartMenuView.swift +19 -19
- package/Sources/Runic/Views/Menu/CostHistoryChartMenuView.swift +7 -7
- package/Sources/Runic/Views/Menu/CreditsHistoryChartMenuView.swift +5 -5
- package/Sources/Runic/Views/Menu/EfficiencyMetricsMenuView.swift +29 -30
- package/Sources/Runic/Views/Menu/HeroStatCardsMenuView.swift +20 -17
- package/Sources/Runic/Views/Menu/HourlyActivityChartMenuView.swift +14 -14
- package/Sources/Runic/Views/Menu/InlineUsageChartView.swift +15 -20
- package/Sources/Runic/Views/Menu/MenuCardEntranceModifier.swift +3 -4
- package/Sources/Runic/Views/Menu/MenuCardSidebarView.swift +5 -25
- package/Sources/Runic/Views/Menu/MenuCardSkeletonView.swift +1 -1
- package/Sources/Runic/Views/Menu/MenuCardView.swift +120 -131
- package/Sources/Runic/Views/Menu/MenuContent.swift +11 -8
- package/Sources/Runic/Views/Menu/MenuDescriptor.swift +4 -2
- package/Sources/Runic/Views/Menu/MenuHighlightStyle.swift +1 -8
- package/Sources/Runic/Views/Menu/ModelBreakdownMenuView.swift +20 -14
- package/Sources/Runic/Views/Menu/OverviewMenuView.swift +198 -58
- package/Sources/Runic/Views/Menu/ProjectBreakdownMenuView.swift +16 -11
- package/Sources/Runic/Views/Menu/ProjectBudgetMenuView.swift +27 -27
- package/Sources/Runic/Views/Menu/ProviderTabBarView.swift +4 -6
- package/Sources/Runic/Views/Menu/SubscriptionUtilizationChartMenuView.swift +24 -11
- package/Sources/Runic/Views/Menu/UsageBreakdownChartMenuView.swift +5 -5
- package/Sources/Runic/Views/Menu/UsageHeatmapMenuView.swift +7 -8
- package/Sources/Runic/Views/Menu/UsageTimelineChartMenuView.swift +28 -76
- package/Sources/Runic/Views/Menu/UsageWindowComparisonChartMenuView.swift +8 -7
- package/Sources/Runic/Views/Menu/WeeklyActivityChartMenuView.swift +7 -7
- package/Sources/Runic/Views/Performance/PerformanceDashboardView.swift +50 -47
- package/Sources/Runic/Views/Preferences/LiquidComponents.swift +15 -14
- package/Sources/Runic/Views/Preferences/PreferencesAboutPane.swift +7 -7
- package/Sources/Runic/Views/Preferences/PreferencesAdvancedPane.swift +27 -27
- package/Sources/Runic/Views/Preferences/PreferencesAlertsPane.swift +38 -42
- package/Sources/Runic/Views/Preferences/PreferencesAnalyticsPane.swift +39 -43
- package/Sources/Runic/Views/Preferences/PreferencesBudgetsPane.swift +21 -21
- package/Sources/Runic/Views/Preferences/PreferencesComponents.swift +7 -7
- package/Sources/Runic/Views/Preferences/PreferencesCustomProvidersPane.swift +49 -50
- package/Sources/Runic/Views/Preferences/PreferencesDebugPane.swift +10 -10
- package/Sources/Runic/Views/Preferences/PreferencesGeneralPane.swift +40 -21
- package/Sources/Runic/Views/Preferences/PreferencesHelpPane.swift +7 -7
- package/Sources/Runic/Views/Preferences/PreferencesIntegrationsPane.swift +108 -33
- package/Sources/Runic/Views/Preferences/PreferencesPerformancePane.swift +26 -23
- package/Sources/Runic/Views/Preferences/PreferencesProvidersPane.swift +271 -226
- package/Sources/Runic/Views/Preferences/PreferencesSelection.swift +6 -2
- package/Sources/Runic/Views/Preferences/PreferencesSyncPane.swift +7 -5
- package/Sources/Runic/Views/Preferences/PreferencesView.swift +38 -31
- package/Sources/Runic/Views/Rating/QualityRatingView.swift +11 -11
- package/Sources/Runic/Views/Team/TeamDashboardView.swift +29 -24
- package/Sources/Runic/Views/Team/TeamInviteSheet.swift +16 -16
- package/Sources/Runic/Views/Team/TeamManagementView.swift +50 -49
- package/Sources/Runic/Views/Team/TeamMemberRow.swift +8 -8
- package/Sources/RunicCLI/CLIEntry.swift +83 -42
- package/Sources/RunicCLI/CLIRenderer.swift +1 -1
- package/Sources/RunicCLI/Commands/EnhancedUsageCommand.swift +60 -60
- package/Sources/RunicCLI/Commands/ModelsCommand.swift +45 -44
- package/Sources/RunicCLI/Commands/ProjectsCommand.swift +83 -69
- package/Sources/RunicCLI/Commands/ResetCommand.swift +50 -49
- package/Sources/RunicCLI/Commands/UsageLedgerSourceFactory.swift +31 -32
- package/Sources/RunicClaudeWebProbe/main.swift +1 -1
- package/Sources/RunicCore/AlertRuleStore.swift +20 -20
- package/Sources/RunicCore/BrowserCookieImportOrder.swift +3 -3
- package/Sources/RunicCore/CostUsageModels.swift +1 -1
- package/Sources/RunicCore/CustomProviders/CustomProviderModels.swift +29 -30
- package/Sources/RunicCore/CustomProviders/GenericProviderFetcher.swift +80 -71
- package/Sources/RunicCore/CustomProviders/ProviderTemplates.swift +53 -109
- package/Sources/RunicCore/Host/PTY/TTYCommandRunner.swift +3 -3
- package/Sources/RunicCore/Integrations/GitHubIntegration.swift +3 -3
- package/Sources/RunicCore/Logging/OSLogLogHandler.swift +6 -6
- package/Sources/RunicCore/Models/EnhancedUsageModels.swift +47 -48
- package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardBrowserCookieImporter.swift +1 -1
- package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardFetcher.swift +1 -1
- package/Sources/RunicCore/Performance/PerformanceModels.swift +4 -4
- package/Sources/RunicCore/Performance/PerformanceStorageImpl.swift +81 -64
- package/Sources/RunicCore/Policy/UsagePolicyEngine.swift +2 -2
- package/Sources/RunicCore/ProjectBudgetStore.swift +15 -12
- package/Sources/RunicCore/Providers/Antigravity/AntigravityProviderDescriptor.swift +4 -2
- package/Sources/RunicCore/Providers/Antigravity/AntigravityStatusProbe.swift +2 -2
- package/Sources/RunicCore/Providers/Auggie/AuggieProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Auggie/AuggieUsageFetcher.swift +3 -3
- package/Sources/RunicCore/Providers/Azure/AzureProviderDescriptor.swift +8 -8
- package/Sources/RunicCore/Providers/Azure/AzureUsageFetcher.swift +5 -3
- package/Sources/RunicCore/Providers/Bedrock/BedrockProviderDescriptor.swift +6 -6
- package/Sources/RunicCore/Providers/Bedrock/BedrockUsageFetcher.swift +3 -7
- package/Sources/RunicCore/Providers/Cerebras/CerebrasProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Cerebras/CerebrasUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthUsageFetcher.swift +3 -3
- package/Sources/RunicCore/Providers/Claude/ClaudeProviderDescriptor.swift +4 -2
- package/Sources/RunicCore/Providers/Claude/ClaudeStatusProbe.swift +4 -4
- package/Sources/RunicCore/Providers/Claude/ClaudeUsageDataSource.swift +3 -1
- package/Sources/RunicCore/Providers/Claude/ClaudeWeb/ClaudeWebAPIFetcher.swift +1 -1
- package/Sources/RunicCore/Providers/Codex/CodexOAuth/CodexOAuthCredentials.swift +1 -0
- package/Sources/RunicCore/Providers/Codex/CodexOAuth/CodexOAuthUsageFetcher.swift +12 -1
- package/Sources/RunicCore/Providers/Codex/CodexProviderDescriptor.swift +4 -2
- package/Sources/RunicCore/Providers/Codex/CodexUsageDataSource.swift +3 -1
- package/Sources/RunicCore/Providers/Codex/CodexWebDashboardStrategy.swift +5 -3
- package/Sources/RunicCore/Providers/Cohere/CohereProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Cohere/CohereUsageFetcher.swift +3 -3
- package/Sources/RunicCore/Providers/Copilot/CopilotGitHubCLITokenReader.swift +7 -4
- package/Sources/RunicCore/Providers/Copilot/CopilotProviderDescriptor.swift +4 -4
- package/Sources/RunicCore/Providers/Copilot/CopilotUsageFetcher.swift +5 -3
- package/Sources/RunicCore/Providers/Copilot/CopilotVSCodeTokenReader.swift +9 -10
- package/Sources/RunicCore/Providers/Cursor/CursorProviderDescriptor.swift +4 -2
- package/Sources/RunicCore/Providers/Cursor/CursorStatusProbe.swift +1 -0
- package/Sources/RunicCore/Providers/DeepSeek/DeepSeekProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/DeepSeek/DeepSeekUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/Factory/FactoryLocalStorageImporter.swift +4 -4
- package/Sources/RunicCore/Providers/Factory/FactoryProviderDescriptor.swift +4 -2
- package/Sources/RunicCore/Providers/Factory/FactoryStatusProbe.swift +3 -2
- package/Sources/RunicCore/Providers/Fireworks/FireworksProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Fireworks/FireworksUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/Gemini/GeminiProviderDescriptor.swift +4 -2
- package/Sources/RunicCore/Providers/Gemini/GeminiStatusProbe.swift +6 -2
- package/Sources/RunicCore/Providers/Groq/GroqProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Groq/GroqUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/Kimi/KimiProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Kimi/KimiUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/MiniMax/MiniMaxProviderDescriptor.swift +10 -5
- package/Sources/RunicCore/Providers/MiniMax/MiniMaxUsageFetcher.swift +8 -11
- package/Sources/RunicCore/Providers/MiniMax/MiniMaxWebUsageFetcher.swift +10 -10
- package/Sources/RunicCore/Providers/Mistral/MistralProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Mistral/MistralUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/OpenRouter/OpenRouterProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/OpenRouter/OpenRouterUsageFetcher.swift +25 -16
- package/Sources/RunicCore/Providers/Perplexity/PerplexityProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Perplexity/PerplexityUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/ProviderFetchPlan.swift +2 -2
- package/Sources/RunicCore/Providers/ProviderTokenResolver.swift +2 -2
- package/Sources/RunicCore/Providers/Qwen/QwenProviderDescriptor.swift +5 -3
- package/Sources/RunicCore/Providers/Qwen/QwenUsageStats.swift +27 -16
- package/Sources/RunicCore/Providers/SambaNova/SambaNovaProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/SambaNova/SambaNovaUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/Together/TogetherProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Together/TogetherUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/VertexAI/VertexAIProviderDescriptor.swift +5 -5
- package/Sources/RunicCore/Providers/VertexAI/VertexAISettingsReader.swift +1 -1
- package/Sources/RunicCore/Providers/VertexAI/VertexAIUsageFetcher.swift +3 -3
- package/Sources/RunicCore/Providers/XAI/XAIProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/XAI/XAIUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/Zai/ZaiProviderDescriptor.swift +4 -2
- package/Sources/RunicCore/Providers/Zai/ZaiUsageStats.swift +51 -30
- package/Sources/RunicCore/Sync/BackgroundSyncManager.swift +76 -77
- package/Sources/RunicCore/Sync/SyncConflictResolver.swift +55 -58
- package/Sources/RunicCore/Sync/SyncProtocol.swift +21 -22
- package/Sources/RunicCore/Sync/SyncRecord.swift +46 -49
- package/Sources/RunicCore/Sync/SyncUsageExample.swift +44 -50
- package/Sources/RunicCore/Sync/TeamSyncRecords.swift +94 -101
- package/Sources/RunicCore/Sync/iCloudSyncEngine.swift +51 -46
- package/Sources/RunicCore/Team/TeamModels.swift +111 -110
- package/Sources/RunicCore/Team/TeamStore.swift +126 -109
- package/Sources/RunicCore/UsageFetcher.swift +2 -2
- package/Sources/RunicCore/UsageFormatter.swift +25 -25
- package/Sources/RunicCore/UsageLedger/ClaudeUsageLogSource.swift +141 -29
- package/Sources/RunicCore/UsageLedger/CodexUsageLogSource.swift +59 -5
- package/Sources/RunicCore/UsageLedger/LedgerCache.swift +161 -0
- package/Sources/RunicCore/UsageLedger/OTelGenAILedgerAdapter.swift +1 -1
- package/Sources/RunicCore/UsageLedger/UsageLedgerAggregator.swift +40 -25
- package/Sources/RunicCore/UsageLedger/UsageLedgerInsightsAdvisor.swift +1 -1
- package/Sources/RunicCore/UsageLedger/UsageLedgerProjectIdentity.swift +55 -16
- package/Sources/RunicCore/Vendored/CostUsage/CostUsageCache.swift +6 -6
- package/Sources/RunicCore/Vendored/CostUsage/CostUsageJsonl.swift +1 -1
- package/Sources/RunicCore/Vendored/CostUsage/CostUsagePricing.swift +2 -2
- package/Sources/RunicCore/Vendored/CostUsage/CostUsageScanner.swift +5 -5
- package/Tests/RunicTests/AppDelegateTests.swift +8 -3
- package/Tests/RunicTests/BrowserCookieOrderLabelTests.swift +2 -3
- package/Tests/RunicTests/CLIArgumentParsingTests.swift +3 -4
- package/Tests/RunicTests/CLICostTests.swift +6 -7
- package/Tests/RunicTests/CLIProviderSelectionTests.swift +2 -3
- package/Tests/RunicTests/CLISnapshotTests.swift +3 -4
- package/Tests/RunicTests/CLIWebFallbackTests.swift +3 -4
- package/Tests/RunicTests/ClaudeOAuthTests.swift +11 -12
- package/Tests/RunicTests/ClaudeResilienceTests.swift +3 -4
- package/Tests/RunicTests/ClaudeUsageTests.swift +20 -21
- package/Tests/RunicTests/CodexOAuthTests.swift +7 -8
- package/Tests/RunicTests/CodexUsageLogSourceTests.swift +1 -3
- package/Tests/RunicTests/CopilotTokenResolverTests.swift +5 -6
- package/Tests/RunicTests/CopilotVSCodeTokenReaderTests.swift +4 -5
- package/Tests/RunicTests/CostUsageDecodingTests.swift +15 -17
- package/Tests/RunicTests/CostUsagePricingTests.swift +5 -7
- package/Tests/RunicTests/CostUsageScannerTests.swift +10 -12
- package/Tests/RunicTests/CursorStatusProbeTests.swift +15 -16
- package/Tests/RunicTests/FactoryStatusProbeTests.swift +3 -5
- package/Tests/RunicTests/GeminiLoginAlertTests.swift +3 -4
- package/Tests/RunicTests/GeminiStatusProbeAPITests.swift +11 -11
- package/Tests/RunicTests/GeminiStatusProbePlanTests.swift +8 -8
- package/Tests/RunicTests/GeminiStatusProbeTests.swift +20 -21
- package/Tests/RunicTests/GoogleWorkspaceStatusTests.swift +2 -3
- package/Tests/RunicTests/InstallOriginTests.swift +1 -2
- package/Tests/RunicTests/KeyboardShortcutsBundleTests.swift +2 -2
- package/Tests/RunicTests/LiveAccountTests.swift +3 -3
- package/Tests/RunicTests/LoadingPatternTests.swift +3 -4
- package/Tests/RunicTests/MenuCardModelTests.swift +33 -34
- package/Tests/RunicTests/MiniMaxCookieHeaderStoreTestSupport.swift +4 -1
- package/Tests/RunicTests/MiniMaxGroupIDStoreTestSupport.swift +4 -1
- package/Tests/RunicTests/MiniMaxParsingTests.swift +4 -5
- package/Tests/RunicTests/MiniMaxTokenStoreTestSupport.swift +4 -1
- package/Tests/RunicTests/OTelGenAILedgerAdapterTests.swift +4 -6
- package/Tests/RunicTests/OpenAIDashboardBrowserCookieImporterTests.swift +1 -2
- package/Tests/RunicTests/OpenAIDashboardFetcherCreditsWaitTests.swift +5 -6
- package/Tests/RunicTests/OpenAIDashboardOffscreenHostTests.swift +2 -3
- package/Tests/RunicTests/OpenAIDashboardParserTests.swift +12 -13
- package/Tests/RunicTests/OpenAIWebAccountSwitchTests.swift +17 -6
- package/Tests/RunicTests/OpenRouterTokenStoreTestSupport.swift +52 -13
- package/Tests/RunicTests/PathBuilderTests.swift +14 -15
- package/Tests/RunicTests/ProviderFetchPipelineTests.swift +3 -4
- package/Tests/RunicTests/ProviderIconResourcesTests.swift +1 -2
- package/Tests/RunicTests/ProviderMetadataStatusLinkTests.swift +1 -2
- package/Tests/RunicTests/ProviderSettingsDescriptorTests.swift +53 -19
- package/Tests/RunicTests/ProviderToggleStoreTests.swift +10 -11
- package/Tests/RunicTests/RunicTests.swift +6 -7
- package/Tests/RunicTests/SessionQuotaNotificationLogicTests.swift +5 -6
- package/Tests/RunicTests/SettingsStoreTests.swift +149 -45
- package/Tests/RunicTests/StatusItemAnimationTests.swift +8 -3
- package/Tests/RunicTests/StatusMenuTests.swift +63 -22
- package/Tests/RunicTests/StatusProbeTests.swift +28 -29
- package/Tests/RunicTests/SubprocessRunnerTests.swift +1 -3
- package/Tests/RunicTests/SubscriptionDetectionTests.swift +8 -9
- package/Tests/RunicTests/TTYCommandRunnerTests.swift +8 -9
- package/Tests/RunicTests/TeamShowbackExporterTests.swift +2 -4
- package/Tests/RunicTests/TextParsingTests.swift +1 -2
- package/Tests/RunicTests/UsageFormatterTests.swift +15 -16
- package/Tests/RunicTests/UsageLedgerAnomalyDetectionTests.swift +12 -14
- package/Tests/RunicTests/UsageLedgerForecastTests.swift +4 -6
- package/Tests/RunicTests/UsageLedgerInsightsAdvisorTests.swift +7 -9
- package/Tests/RunicTests/UsageLedgerProjectNameTests.swift +6 -8
- package/Tests/RunicTests/UsagePaceTests.swift +5 -6
- package/Tests/RunicTests/UsagePaceTextTests.swift +8 -9
- package/Tests/RunicTests/UsagePolicyEngineTests.swift +3 -5
- package/Tests/RunicTests/WidgetSnapshotTests.swift +1 -3
- package/Tests/RunicTests/ZaiProviderTests.swift +3 -5
- package/Tests/RunicTests/ZaiTokenStoreTestSupport.swift +4 -1
- package/appcast.xml +36 -37
- package/package.json +1 -1
- package/runic.png +0 -0
- package/runic.svg +15 -0
- package/version.env +2 -2
|
@@ -225,7 +225,9 @@ private enum ProviderInsightsComposer {
|
|
|
225
225
|
help: self.budgetHelpText(topProjectSpendForecast)))
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
-
if let today = self
|
|
228
|
+
if let today = self
|
|
229
|
+
.tokenWindowValue(tokens: tokenSnapshot?.sessionTokens, cost: tokenSnapshot?.sessionCostUSD)
|
|
230
|
+
{
|
|
229
231
|
rows.append(ProviderInsightLine(id: "today", label: "Today", value: today))
|
|
230
232
|
}
|
|
231
233
|
|
|
@@ -247,14 +249,14 @@ private enum ProviderInsightsComposer {
|
|
|
247
249
|
}
|
|
248
250
|
|
|
249
251
|
static func coverageSummaryLabel(for provider: UsageProvider, store: UsageStore) -> String? {
|
|
250
|
-
|
|
252
|
+
self.effectiveCoverage(for: provider, store: store).summaryLabel
|
|
251
253
|
}
|
|
252
254
|
|
|
253
255
|
static func effectiveCoverage(
|
|
254
256
|
for provider: UsageProvider,
|
|
255
257
|
store: UsageStore) -> ProviderUsageCoverage
|
|
256
258
|
{
|
|
257
|
-
|
|
259
|
+
self.effectiveCoverage(
|
|
258
260
|
provider: provider,
|
|
259
261
|
metadataCoverage: store.metadata(for: provider).usageCoverage,
|
|
260
262
|
topModel: store.ledgerTopModel(for: provider),
|
|
@@ -277,13 +279,13 @@ private enum ProviderInsightsComposer {
|
|
|
277
279
|
{
|
|
278
280
|
let hasModelBreakdown =
|
|
279
281
|
metadataCoverage.supportsModelBreakdown
|
|
280
|
-
|
|
281
|
-
|
|
282
|
+
|| (topModel?.provider == provider)
|
|
283
|
+
|| !modelBreakdown.isEmpty
|
|
282
284
|
|
|
283
285
|
let hasProjectAttribution =
|
|
284
286
|
metadataCoverage.supportsProjectAttribution
|
|
285
|
-
|
|
286
|
-
|
|
287
|
+
|| (topProject?.provider == provider)
|
|
288
|
+
|| !projectBreakdown.isEmpty
|
|
287
289
|
|
|
288
290
|
let hasTokenMetrics = metadataCoverage.supportsTokenMetrics
|
|
289
291
|
|| snapshot?.providerCost != nil
|
|
@@ -457,21 +459,21 @@ private enum ProviderInsightsComposer {
|
|
|
457
459
|
|
|
458
460
|
private static func projectSourceLabel(_ source: UsageLedgerProjectNameSource) -> String {
|
|
459
461
|
switch source {
|
|
460
|
-
case .projectName:
|
|
461
|
-
case .projectID:
|
|
462
|
-
case .inferredFromPath:
|
|
463
|
-
case .inferredFromName:
|
|
464
|
-
case .budgetOverride:
|
|
465
|
-
case .unknown:
|
|
462
|
+
case .projectName: "project name"
|
|
463
|
+
case .projectID: "project id"
|
|
464
|
+
case .inferredFromPath: "path-derived"
|
|
465
|
+
case .inferredFromName: "name-derived"
|
|
466
|
+
case .budgetOverride: "budget override"
|
|
467
|
+
case .unknown: "unknown"
|
|
466
468
|
}
|
|
467
469
|
}
|
|
468
470
|
|
|
469
471
|
private static func projectConfidenceLabel(_ confidence: UsageLedgerProjectNameConfidence) -> String {
|
|
470
472
|
switch confidence {
|
|
471
|
-
case .high:
|
|
472
|
-
case .medium:
|
|
473
|
-
case .low:
|
|
474
|
-
case .none:
|
|
473
|
+
case .high: "high"
|
|
474
|
+
case .medium: "medium"
|
|
475
|
+
case .low: "low"
|
|
476
|
+
case .none: "none"
|
|
475
477
|
}
|
|
476
478
|
}
|
|
477
479
|
|
|
@@ -480,10 +482,10 @@ private enum ProviderInsightsComposer {
|
|
|
480
482
|
return nil
|
|
481
483
|
}
|
|
482
484
|
|
|
483
|
-
var hash: UInt64 =
|
|
485
|
+
var hash: UInt64 = 0xCBF2_9CE4_8422_2325
|
|
484
486
|
for byte in projectID.lowercased().utf8 {
|
|
485
487
|
hash ^= UInt64(byte)
|
|
486
|
-
hash = hash &*
|
|
488
|
+
hash = hash &* 0x100_0000_01B3
|
|
487
489
|
}
|
|
488
490
|
return String(format: "%08llx", hash)
|
|
489
491
|
}
|
|
@@ -542,7 +544,8 @@ private enum ProviderInsightsComposer {
|
|
|
542
544
|
|
|
543
545
|
private static func projectBudgetValue(_ forecast: UsageLedgerSpendForecast?) -> String? {
|
|
544
546
|
guard let forecast, let limit = forecast.budgetLimitUSD, limit > 0 else { return nil }
|
|
545
|
-
let name = self
|
|
547
|
+
let name = self
|
|
548
|
+
.shortProjectName(forecast.projectName?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "Top project")
|
|
546
549
|
if let eta = forecast.budgetETAInDays {
|
|
547
550
|
return "\(name) · \(self.budgetBreachETAText(days: eta))"
|
|
548
551
|
}
|
|
@@ -574,7 +577,7 @@ private enum ProviderInsightsComposer {
|
|
|
574
577
|
guard let forecast, let limit = forecast.budgetLimitUSD, limit > 0 else { return nil }
|
|
575
578
|
var lines = [
|
|
576
579
|
"Budget limit: \(UsageFormatter.usdString(limit))",
|
|
577
|
-
"Projected 30d: \(UsageFormatter.usdString(forecast.projected30DayCostUSD))"
|
|
580
|
+
"Projected 30d: \(UsageFormatter.usdString(forecast.projected30DayCostUSD))",
|
|
578
581
|
]
|
|
579
582
|
if let eta = forecast.budgetETAInDays {
|
|
580
583
|
lines.append(self.budgetBreachETAText(days: eta))
|
|
@@ -649,7 +652,7 @@ private enum ProviderInsightsComposer {
|
|
|
649
652
|
"Spend today: \(UsageFormatter.usdString(spend.todayValue))",
|
|
650
653
|
"Baseline (\(anomaly.baselineDays)d avg): \(UsageFormatter.usdString(spend.baselineAverage))",
|
|
651
654
|
"Increase: +\(percent)%",
|
|
652
|
-
"Severity: \(spend.severity.label)"
|
|
655
|
+
"Severity: \(spend.severity.label)",
|
|
653
656
|
]
|
|
654
657
|
if let explanation = anomaly.explanation {
|
|
655
658
|
lines.append(contentsOf: explanation.details.prefix(2))
|
|
@@ -660,13 +663,12 @@ private enum ProviderInsightsComposer {
|
|
|
660
663
|
private static func fetchHealthValue(_ attempts: [ProviderFetchAttempt]) -> String? {
|
|
661
664
|
guard !attempts.isEmpty else { return nil }
|
|
662
665
|
let rendered = attempts.prefix(3).map { attempt in
|
|
663
|
-
let status
|
|
664
|
-
|
|
665
|
-
status = "unavailable"
|
|
666
|
+
let status = if !attempt.wasAvailable {
|
|
667
|
+
"unavailable"
|
|
666
668
|
} else if self.trimmed(attempt.errorDescription) != nil {
|
|
667
|
-
|
|
669
|
+
"failed"
|
|
668
670
|
} else {
|
|
669
|
-
|
|
671
|
+
"ok"
|
|
670
672
|
}
|
|
671
673
|
return "\(self.fetchStrategyLabel(attempt)) \(status)"
|
|
672
674
|
}
|
|
@@ -712,13 +714,13 @@ private enum ProviderInsightsComposer {
|
|
|
712
714
|
|
|
713
715
|
private static func fetchKindLabel(_ kind: ProviderFetchKind) -> String {
|
|
714
716
|
switch kind {
|
|
715
|
-
case .cli:
|
|
716
|
-
case .web:
|
|
717
|
-
case .oauth:
|
|
718
|
-
case .apiToken:
|
|
719
|
-
case .api:
|
|
720
|
-
case .localProbe:
|
|
721
|
-
case .webDashboard:
|
|
717
|
+
case .cli: "cli"
|
|
718
|
+
case .web: "web"
|
|
719
|
+
case .oauth: "oauth"
|
|
720
|
+
case .apiToken: "api"
|
|
721
|
+
case .api: "api"
|
|
722
|
+
case .localProbe: "local"
|
|
723
|
+
case .webDashboard: "web"
|
|
722
724
|
}
|
|
723
725
|
}
|
|
724
726
|
|
|
@@ -730,7 +732,7 @@ private enum ProviderInsightsComposer {
|
|
|
730
732
|
|
|
731
733
|
private static func windowModelsValue(_ snapshot: UsageSnapshot?) -> String? {
|
|
732
734
|
guard let snapshot else { return nil }
|
|
733
|
-
let windows = [snapshot.primary, snapshot.secondary, snapshot.tertiary].compactMap
|
|
735
|
+
let windows = [snapshot.primary, snapshot.secondary, snapshot.tertiary].compactMap(\.self)
|
|
734
736
|
guard !windows.isEmpty else { return nil }
|
|
735
737
|
|
|
736
738
|
var seen: Set<String> = []
|
|
@@ -764,7 +766,9 @@ struct ProvidersPane: View {
|
|
|
764
766
|
@State private var activeConfirmation: ProviderSettingsConfirmationState?
|
|
765
767
|
@State private var sidebarSelection: UsageProvider?
|
|
766
768
|
|
|
767
|
-
private var providers: [UsageProvider] {
|
|
769
|
+
private var providers: [UsageProvider] {
|
|
770
|
+
self.settings.orderedProviders()
|
|
771
|
+
}
|
|
768
772
|
|
|
769
773
|
var body: some View {
|
|
770
774
|
Group {
|
|
@@ -835,10 +839,10 @@ struct ProvidersPane: View {
|
|
|
835
839
|
|
|
836
840
|
VStack(alignment: .leading, spacing: RunicSpacing.xxs) {
|
|
837
841
|
Text("Built-in providers")
|
|
838
|
-
.font(.subheadline.weight(.semibold))
|
|
842
|
+
.font(RunicFont.subheadline.weight(.semibold))
|
|
839
843
|
.foregroundStyle(.primary)
|
|
840
844
|
Text("Switch to sidebar layout for per-day history cards and model or project drill-down details.")
|
|
841
|
-
.font(.caption)
|
|
845
|
+
.font(RunicFont.caption)
|
|
842
846
|
.foregroundStyle(.secondary)
|
|
843
847
|
.fixedSize(horizontal: false, vertical: true)
|
|
844
848
|
}
|
|
@@ -848,14 +852,12 @@ struct ProvidersPane: View {
|
|
|
848
852
|
.padding(ProviderListMetrics.listHeaderPadding)
|
|
849
853
|
.background(
|
|
850
854
|
RoundedRectangle(cornerRadius: ProviderListMetrics.listHeaderCornerRadius, style: .continuous)
|
|
851
|
-
.fill(Color(nsColor: .controlBackgroundColor).opacity(ProviderListMetrics.listHeaderBackgroundOpacity))
|
|
852
|
-
)
|
|
855
|
+
.fill(Color(nsColor: .controlBackgroundColor).opacity(ProviderListMetrics.listHeaderBackgroundOpacity)))
|
|
853
856
|
.overlay(
|
|
854
857
|
RoundedRectangle(cornerRadius: ProviderListMetrics.listHeaderCornerRadius, style: .continuous)
|
|
855
858
|
.strokeBorder(
|
|
856
859
|
Color(nsColor: .separatorColor).opacity(ProviderListMetrics.listHeaderBorderOpacity),
|
|
857
|
-
lineWidth: 1)
|
|
858
|
-
)
|
|
860
|
+
lineWidth: 1))
|
|
859
861
|
.padding(.horizontal, ProviderListMetrics.contentInset)
|
|
860
862
|
}
|
|
861
863
|
|
|
@@ -903,7 +905,7 @@ struct ProvidersPane: View {
|
|
|
903
905
|
onCopyError: { text in self.copyToPasteboard(text) })
|
|
904
906
|
} else {
|
|
905
907
|
Text("Select a provider")
|
|
906
|
-
.font(.title3)
|
|
908
|
+
.font(RunicFont.title3)
|
|
907
909
|
.foregroundStyle(.secondary)
|
|
908
910
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
909
911
|
}
|
|
@@ -1237,7 +1239,7 @@ private struct ProviderListBrandIcon: View {
|
|
|
1237
1239
|
RoundedRectangle(cornerRadius: RunicCornerRadius.sm, style: .continuous)
|
|
1238
1240
|
.fill(brandColor.opacity(0.18))
|
|
1239
1241
|
Text(initial)
|
|
1240
|
-
.font(.system(size: ProviderListMetrics.iconSize * 0.5, weight: .bold
|
|
1242
|
+
.font(RunicFont.system(size: ProviderListMetrics.iconSize * 0.5, weight: .bold))
|
|
1241
1243
|
.foregroundStyle(brandColor)
|
|
1242
1244
|
}
|
|
1243
1245
|
.frame(width: ProviderListMetrics.iconSize, height: ProviderListMetrics.iconSize)
|
|
@@ -1257,7 +1259,7 @@ private struct ProviderInsightsView: View {
|
|
|
1257
1259
|
columns: [
|
|
1258
1260
|
GridItem(
|
|
1259
1261
|
.adaptive(minimum: ProviderListMetrics.providerInsightsGridItemMinWidth),
|
|
1260
|
-
spacing: ProviderListMetrics.providerInsightsChipSpacing)
|
|
1262
|
+
spacing: ProviderListMetrics.providerInsightsChipSpacing),
|
|
1261
1263
|
],
|
|
1262
1264
|
alignment: .leading,
|
|
1263
1265
|
spacing: ProviderListMetrics.providerInsightsChipSpacing)
|
|
@@ -1272,8 +1274,7 @@ private struct ProviderInsightsView: View {
|
|
|
1272
1274
|
RoundedRectangle(
|
|
1273
1275
|
cornerRadius: ProviderListMetrics.providerInsightsCardCornerRadius,
|
|
1274
1276
|
style: .continuous)
|
|
1275
|
-
.fill(Color(nsColor: .controlBackgroundColor).opacity(0.42))
|
|
1276
|
-
)
|
|
1277
|
+
.fill(Color(nsColor: .controlBackgroundColor).opacity(0.42)))
|
|
1277
1278
|
.overlay {
|
|
1278
1279
|
RoundedRectangle(
|
|
1279
1280
|
cornerRadius: ProviderListMetrics.providerInsightsCardCornerRadius,
|
|
@@ -1288,31 +1289,29 @@ private struct ProviderInsightChip: View {
|
|
|
1288
1289
|
|
|
1289
1290
|
var body: some View {
|
|
1290
1291
|
VStack(alignment: .leading, spacing: RunicSpacing.xxxs) {
|
|
1291
|
-
Text(line.label.uppercased())
|
|
1292
|
-
.font(.caption2.weight(.semibold))
|
|
1292
|
+
Text(self.line.label.uppercased())
|
|
1293
|
+
.font(RunicFont.caption2.weight(.semibold))
|
|
1293
1294
|
.tracking(0.2)
|
|
1294
1295
|
.foregroundStyle(.tertiary)
|
|
1295
|
-
Text(line.value)
|
|
1296
|
-
.font(.caption)
|
|
1296
|
+
Text(self.line.value)
|
|
1297
|
+
.font(RunicFont.caption)
|
|
1297
1298
|
.foregroundStyle(.secondary)
|
|
1298
1299
|
.lineLimit(2)
|
|
1299
1300
|
.fixedSize(horizontal: false, vertical: true)
|
|
1300
1301
|
.textSelection(.enabled)
|
|
1301
|
-
.help(line.help ?? "")
|
|
1302
|
+
.help(self.line.help ?? "")
|
|
1302
1303
|
}
|
|
1303
1304
|
.padding(ProviderListMetrics.providerInsightsChipPadding)
|
|
1304
1305
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
1305
1306
|
.background(
|
|
1306
1307
|
RoundedRectangle(cornerRadius: ProviderListMetrics.providerInsightsChipCornerRadius, style: .continuous)
|
|
1307
|
-
.fill(Color(nsColor: .textBackgroundColor).opacity(0.45))
|
|
1308
|
-
)
|
|
1308
|
+
.fill(Color(nsColor: .textBackgroundColor).opacity(0.45)))
|
|
1309
1309
|
.overlay(
|
|
1310
1310
|
RoundedRectangle(cornerRadius: ProviderListMetrics.providerInsightsChipCornerRadius, style: .continuous)
|
|
1311
|
-
.strokeBorder(Color(nsColor: .separatorColor).opacity(0.2), lineWidth: 1)
|
|
1312
|
-
)
|
|
1313
|
-
.
|
|
1314
|
-
.
|
|
1315
|
-
.accessibilityHint(line.help ?? "")
|
|
1311
|
+
.strokeBorder(Color(nsColor: .separatorColor).opacity(0.2), lineWidth: 1))
|
|
1312
|
+
.help(self.line.help ?? "")
|
|
1313
|
+
.accessibilityLabel("\(self.line.label): \(self.line.value)")
|
|
1314
|
+
.accessibilityHint(self.line.help ?? "")
|
|
1316
1315
|
}
|
|
1317
1316
|
}
|
|
1318
1317
|
|
|
@@ -1350,10 +1349,10 @@ private struct ProviderListProviderRowView: View {
|
|
|
1350
1349
|
ProviderListBrandIcon(provider: self.provider)
|
|
1351
1350
|
VStack(alignment: .leading, spacing: RunicSpacing.xxs) {
|
|
1352
1351
|
Text(metadata.displayName)
|
|
1353
|
-
.font(.headline.weight(.semibold))
|
|
1352
|
+
.font(RunicFont.headline.weight(.semibold))
|
|
1354
1353
|
.foregroundStyle(.primary)
|
|
1355
1354
|
Text(self.subtitle)
|
|
1356
|
-
.font(.footnote)
|
|
1355
|
+
.font(RunicFont.footnote)
|
|
1357
1356
|
.foregroundStyle(.secondary)
|
|
1358
1357
|
.fixedSize(horizontal: false, vertical: true)
|
|
1359
1358
|
}
|
|
@@ -1363,7 +1362,7 @@ private struct ProviderListProviderRowView: View {
|
|
|
1363
1362
|
HStack(alignment: .center, spacing: RunicSpacing.xs) {
|
|
1364
1363
|
self.sourceBadge
|
|
1365
1364
|
Text(self.statusLabel)
|
|
1366
|
-
.font(.caption2)
|
|
1365
|
+
.font(RunicFont.caption2)
|
|
1367
1366
|
.foregroundStyle(.tertiary)
|
|
1368
1367
|
.lineLimit(1)
|
|
1369
1368
|
|
|
@@ -1372,7 +1371,7 @@ private struct ProviderListProviderRowView: View {
|
|
|
1372
1371
|
.controlSize(.small)
|
|
1373
1372
|
.alignmentGuide(.firstTextBaseline) { d in d[VerticalAlignment.center] }
|
|
1374
1373
|
Text("Refreshing…")
|
|
1375
|
-
.font(.caption2)
|
|
1374
|
+
.font(RunicFont.caption2)
|
|
1376
1375
|
.foregroundStyle(.secondary)
|
|
1377
1376
|
} else {
|
|
1378
1377
|
self.usageStatusBadge
|
|
@@ -1420,7 +1419,7 @@ private struct ProviderListProviderRowView: View {
|
|
|
1420
1419
|
let (color, backgroundColor) = self.usageStatusColors
|
|
1421
1420
|
|
|
1422
1421
|
return Text(self.usageStatus.text)
|
|
1423
|
-
.font(.caption2.weight(.medium))
|
|
1422
|
+
.font(RunicFont.caption2.weight(.medium))
|
|
1424
1423
|
.padding(.horizontal, ProviderListMetrics.statusBadgePaddingH)
|
|
1425
1424
|
.padding(.vertical, ProviderListMetrics.statusBadgePaddingV)
|
|
1426
1425
|
.background(Capsule(style: .continuous).fill(backgroundColor))
|
|
@@ -1429,24 +1428,23 @@ private struct ProviderListProviderRowView: View {
|
|
|
1429
1428
|
|
|
1430
1429
|
private var sourceBadge: some View {
|
|
1431
1430
|
Text(self.sourceLabel)
|
|
1432
|
-
.font(.caption2.weight(.medium))
|
|
1431
|
+
.font(RunicFont.caption2.weight(.medium))
|
|
1433
1432
|
.foregroundStyle(.secondary)
|
|
1434
1433
|
.padding(.horizontal, RunicSpacing.xs)
|
|
1435
1434
|
.padding(.vertical, RunicSpacing.xxxs)
|
|
1436
1435
|
.background(
|
|
1437
1436
|
Capsule(style: .continuous)
|
|
1438
|
-
.fill(Color(nsColor: .controlBackgroundColor).opacity(0.7))
|
|
1439
|
-
)
|
|
1437
|
+
.fill(Color(nsColor: .controlBackgroundColor).opacity(0.7)))
|
|
1440
1438
|
}
|
|
1441
1439
|
|
|
1442
1440
|
private var usageStatusColors: (Color, Color) {
|
|
1443
1441
|
switch self.usageStatus.style {
|
|
1444
1442
|
case .success:
|
|
1445
|
-
|
|
1443
|
+
(.green, Color.green.opacity(0.15))
|
|
1446
1444
|
case .error:
|
|
1447
|
-
|
|
1445
|
+
(.red, Color.red.opacity(0.15))
|
|
1448
1446
|
case .neutral:
|
|
1449
|
-
|
|
1447
|
+
(.secondary, Color(nsColor: .controlBackgroundColor))
|
|
1450
1448
|
}
|
|
1451
1449
|
}
|
|
1452
1450
|
|
|
@@ -1516,7 +1514,6 @@ private struct ProviderListSectionDividerView: View {
|
|
|
1516
1514
|
}
|
|
1517
1515
|
|
|
1518
1516
|
extension View {
|
|
1519
|
-
@ViewBuilder
|
|
1520
1517
|
fileprivate func providerSectionDivider(isVisible: Bool) -> some View {
|
|
1521
1518
|
overlay(alignment: .bottom) {
|
|
1522
1519
|
if isVisible {
|
|
@@ -1541,9 +1538,9 @@ private struct ProviderListToggleRowView: View {
|
|
|
1541
1538
|
VStack(alignment: .leading, spacing: RunicSpacing.sm) {
|
|
1542
1539
|
VStack(alignment: .leading, spacing: RunicSpacing.xxs) {
|
|
1543
1540
|
Text(self.toggle.title)
|
|
1544
|
-
.font(.callout.weight(.semibold))
|
|
1541
|
+
.font(RunicFont.callout.weight(.semibold))
|
|
1545
1542
|
Text(self.toggle.subtitle)
|
|
1546
|
-
.font(.footnote)
|
|
1543
|
+
.font(RunicFont.footnote)
|
|
1547
1544
|
.foregroundStyle(.secondary)
|
|
1548
1545
|
.fixedSize(horizontal: false, vertical: true)
|
|
1549
1546
|
}
|
|
@@ -1551,7 +1548,7 @@ private struct ProviderListToggleRowView: View {
|
|
|
1551
1548
|
if self.toggle.binding.wrappedValue {
|
|
1552
1549
|
if let status = self.toggle.statusText?(), !status.isEmpty {
|
|
1553
1550
|
Text(status)
|
|
1554
|
-
.font(.caption)
|
|
1551
|
+
.font(RunicFont.caption)
|
|
1555
1552
|
.foregroundStyle(.secondary)
|
|
1556
1553
|
.lineLimit(4)
|
|
1557
1554
|
.fixedSize(horizontal: false, vertical: true)
|
|
@@ -1559,8 +1556,7 @@ private struct ProviderListToggleRowView: View {
|
|
|
1559
1556
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
1560
1557
|
.background(
|
|
1561
1558
|
RoundedRectangle(cornerRadius: RunicCornerRadius.sm, style: .continuous)
|
|
1562
|
-
.fill(Color(nsColor: .controlBackgroundColor).opacity(0.55))
|
|
1563
|
-
)
|
|
1559
|
+
.fill(Color(nsColor: .controlBackgroundColor).opacity(0.55)))
|
|
1564
1560
|
.overlay {
|
|
1565
1561
|
RoundedRectangle(cornerRadius: RunicCornerRadius.sm, style: .continuous)
|
|
1566
1562
|
.strokeBorder(Color(nsColor: .separatorColor).opacity(0.2), lineWidth: 1)
|
|
@@ -1607,7 +1603,8 @@ private struct ProviderListToggleRowView: View {
|
|
|
1607
1603
|
|
|
1608
1604
|
private var supplementalCardBackground: some View {
|
|
1609
1605
|
RoundedRectangle(cornerRadius: ProviderListMetrics.providerCardCornerRadius, style: .continuous)
|
|
1610
|
-
.fill(Color(nsColor: .controlBackgroundColor)
|
|
1606
|
+
.fill(Color(nsColor: .controlBackgroundColor)
|
|
1607
|
+
.opacity(ProviderListMetrics.supplementalCardBackgroundOpacity))
|
|
1611
1608
|
}
|
|
1612
1609
|
|
|
1613
1610
|
private var supplementalCardBorderColor: Color {
|
|
@@ -1629,9 +1626,9 @@ private struct ProviderListFieldRowView: View {
|
|
|
1629
1626
|
VStack(alignment: .leading, spacing: RunicSpacing.sm) {
|
|
1630
1627
|
VStack(alignment: .leading, spacing: RunicSpacing.xxs) {
|
|
1631
1628
|
Text(self.field.title)
|
|
1632
|
-
.font(.callout.weight(.semibold))
|
|
1629
|
+
.font(RunicFont.callout.weight(.semibold))
|
|
1633
1630
|
Text(self.field.subtitle)
|
|
1634
|
-
.font(.footnote)
|
|
1631
|
+
.font(RunicFont.footnote)
|
|
1635
1632
|
.foregroundStyle(.secondary)
|
|
1636
1633
|
.fixedSize(horizontal: false, vertical: true)
|
|
1637
1634
|
}
|
|
@@ -1640,12 +1637,12 @@ private struct ProviderListFieldRowView: View {
|
|
|
1640
1637
|
case .plain:
|
|
1641
1638
|
TextField(self.field.placeholder ?? "", text: self.field.binding)
|
|
1642
1639
|
.textFieldStyle(.roundedBorder)
|
|
1643
|
-
.font(.callout)
|
|
1640
|
+
.font(RunicFont.callout)
|
|
1644
1641
|
.frame(maxWidth: ProviderListMetrics.fieldMaxWidth, alignment: .leading)
|
|
1645
1642
|
case .secure:
|
|
1646
1643
|
SecureField(self.field.placeholder ?? "", text: self.field.binding)
|
|
1647
1644
|
.textFieldStyle(.roundedBorder)
|
|
1648
|
-
.font(.callout)
|
|
1645
|
+
.font(RunicFont.callout)
|
|
1649
1646
|
.frame(maxWidth: ProviderListMetrics.fieldMaxWidth, alignment: .leading)
|
|
1650
1647
|
}
|
|
1651
1648
|
|
|
@@ -1677,7 +1674,8 @@ private struct ProviderListFieldRowView: View {
|
|
|
1677
1674
|
|
|
1678
1675
|
private var supplementalCardBackground: some View {
|
|
1679
1676
|
RoundedRectangle(cornerRadius: ProviderListMetrics.providerCardCornerRadius, style: .continuous)
|
|
1680
|
-
.fill(Color(nsColor: .controlBackgroundColor)
|
|
1677
|
+
.fill(Color(nsColor: .controlBackgroundColor)
|
|
1678
|
+
.opacity(ProviderListMetrics.supplementalCardBackgroundOpacity))
|
|
1681
1679
|
}
|
|
1682
1680
|
|
|
1683
1681
|
private var supplementalCardBorderColor: Color {
|
|
@@ -1697,7 +1695,7 @@ extension View {
|
|
|
1697
1695
|
}
|
|
1698
1696
|
}
|
|
1699
1697
|
|
|
1700
|
-
private struct ProviderErrorDisplay
|
|
1698
|
+
private struct ProviderErrorDisplay {
|
|
1701
1699
|
let preview: String
|
|
1702
1700
|
let full: String
|
|
1703
1701
|
}
|
|
@@ -1705,7 +1703,9 @@ private struct ProviderErrorDisplay: Sendable {
|
|
|
1705
1703
|
@MainActor
|
|
1706
1704
|
private struct ProviderListScrollInsetFixer: NSViewRepresentable {
|
|
1707
1705
|
private final class HitTestIgnoringView: NSView {
|
|
1708
|
-
override func hitTest(_ point: NSPoint) -> NSView? {
|
|
1706
|
+
override func hitTest(_ point: NSPoint) -> NSView? {
|
|
1707
|
+
nil
|
|
1708
|
+
}
|
|
1709
1709
|
}
|
|
1710
1710
|
|
|
1711
1711
|
func makeNSView(context: Context) -> NSView {
|
|
@@ -1721,12 +1721,14 @@ private struct ProviderListScrollInsetFixer: NSViewRepresentable {
|
|
|
1721
1721
|
let zeroInsets = NSEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
|
|
1722
1722
|
let currentContentInsets = scrollView.contentInsets
|
|
1723
1723
|
if currentContentInsets.top != 0 || currentContentInsets.left != 0 ||
|
|
1724
|
-
|
|
1724
|
+
currentContentInsets.bottom != 0 || currentContentInsets.right != 0
|
|
1725
|
+
{
|
|
1725
1726
|
scrollView.contentInsets = zeroInsets
|
|
1726
1727
|
}
|
|
1727
1728
|
let currentScrollerInsets = scrollView.scrollerInsets
|
|
1728
1729
|
if currentScrollerInsets.top != 0 || currentScrollerInsets.left != 0 ||
|
|
1729
|
-
|
|
1730
|
+
currentScrollerInsets.bottom != 0 || currentScrollerInsets.right != 0
|
|
1731
|
+
{
|
|
1730
1732
|
scrollView.scrollerInsets = zeroInsets
|
|
1731
1733
|
}
|
|
1732
1734
|
}
|
|
@@ -1756,11 +1758,11 @@ private struct ProviderErrorView: View {
|
|
|
1756
1758
|
VStack(alignment: .leading, spacing: RunicSpacing.xs) {
|
|
1757
1759
|
HStack(alignment: .center, spacing: RunicSpacing.xs) {
|
|
1758
1760
|
Image(systemName: "exclamationmark.triangle.fill")
|
|
1759
|
-
.font(.caption)
|
|
1761
|
+
.font(RunicFont.caption)
|
|
1760
1762
|
.foregroundStyle(.orange)
|
|
1761
1763
|
.alignmentGuide(.firstTextBaseline) { d in d[VerticalAlignment.center] }
|
|
1762
1764
|
Text(self.title)
|
|
1763
|
-
.font(.callout.weight(.semibold))
|
|
1765
|
+
.font(RunicFont.callout.weight(.semibold))
|
|
1764
1766
|
.foregroundStyle(.orange)
|
|
1765
1767
|
Spacer()
|
|
1766
1768
|
Button {
|
|
@@ -1768,9 +1770,9 @@ private struct ProviderErrorView: View {
|
|
|
1768
1770
|
} label: {
|
|
1769
1771
|
HStack(alignment: .center, spacing: RunicSpacing.xxs) {
|
|
1770
1772
|
Image(systemName: "doc.on.doc")
|
|
1771
|
-
.font(.caption)
|
|
1773
|
+
.font(RunicFont.caption)
|
|
1772
1774
|
Text("Copy")
|
|
1773
|
-
.font(.caption)
|
|
1775
|
+
.font(RunicFont.caption)
|
|
1774
1776
|
}
|
|
1775
1777
|
}
|
|
1776
1778
|
.buttonStyle(.bordered)
|
|
@@ -1780,7 +1782,7 @@ private struct ProviderErrorView: View {
|
|
|
1780
1782
|
}
|
|
1781
1783
|
|
|
1782
1784
|
Text(self.display.preview)
|
|
1783
|
-
.font(.callout)
|
|
1785
|
+
.font(RunicFont.callout)
|
|
1784
1786
|
.foregroundStyle(.secondary)
|
|
1785
1787
|
.lineLimit(3)
|
|
1786
1788
|
.fixedSize(horizontal: false, vertical: true)
|
|
@@ -1788,19 +1790,18 @@ private struct ProviderErrorView: View {
|
|
|
1788
1790
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
1789
1791
|
.background(
|
|
1790
1792
|
RoundedRectangle(cornerRadius: ProviderListMetrics.errorCardCornerRadius, style: .continuous)
|
|
1791
|
-
.fill(Color.orange.opacity(0.08))
|
|
1792
|
-
)
|
|
1793
|
+
.fill(Color.orange.opacity(0.08)))
|
|
1793
1794
|
|
|
1794
1795
|
if self.display.preview != self.display.full {
|
|
1795
1796
|
Button(self.isExpanded ? "Hide details" : "Show details") { self.isExpanded.toggle() }
|
|
1796
1797
|
.buttonStyle(.link)
|
|
1797
|
-
.font(.callout)
|
|
1798
|
+
.font(RunicFont.callout)
|
|
1798
1799
|
}
|
|
1799
1800
|
|
|
1800
1801
|
if self.isExpanded {
|
|
1801
1802
|
ScrollView(.horizontal, showsIndicators: true) {
|
|
1802
1803
|
Text(self.display.full)
|
|
1803
|
-
.font(.
|
|
1804
|
+
.font(RunicFont.caption)
|
|
1804
1805
|
.foregroundStyle(.primary)
|
|
1805
1806
|
.textSelection(.enabled)
|
|
1806
1807
|
.fixedSize(horizontal: false, vertical: true)
|
|
@@ -1809,19 +1810,16 @@ private struct ProviderErrorView: View {
|
|
|
1809
1810
|
.frame(maxHeight: 200)
|
|
1810
1811
|
.background(
|
|
1811
1812
|
RoundedRectangle(cornerRadius: ProviderListMetrics.errorCardCornerRadius, style: .continuous)
|
|
1812
|
-
.fill(Color(nsColor: .textBackgroundColor))
|
|
1813
|
-
)
|
|
1813
|
+
.fill(Color(nsColor: .textBackgroundColor)))
|
|
1814
1814
|
.overlay(
|
|
1815
1815
|
RoundedRectangle(cornerRadius: ProviderListMetrics.errorCardCornerRadius, style: .continuous)
|
|
1816
|
-
.stroke(Color.orange.opacity(0.3), lineWidth: 1)
|
|
1817
|
-
)
|
|
1816
|
+
.stroke(Color.orange.opacity(0.3), lineWidth: 1))
|
|
1818
1817
|
}
|
|
1819
1818
|
}
|
|
1820
1819
|
.padding(ProviderListMetrics.errorCardPadding)
|
|
1821
1820
|
.background(
|
|
1822
1821
|
RoundedRectangle(cornerRadius: ProviderListMetrics.errorCardCornerRadius, style: .continuous)
|
|
1823
|
-
.fill(Color.orange.opacity(0.03))
|
|
1824
|
-
)
|
|
1822
|
+
.fill(Color.orange.opacity(0.03)))
|
|
1825
1823
|
}
|
|
1826
1824
|
}
|
|
1827
1825
|
|
|
@@ -1865,7 +1863,7 @@ private struct ProviderSidebarRow: View {
|
|
|
1865
1863
|
.frame(width: 20, height: 20)
|
|
1866
1864
|
}
|
|
1867
1865
|
Text(self.store.metadata(for: self.provider).displayName)
|
|
1868
|
-
.font(.body)
|
|
1866
|
+
.font(RunicFont.body)
|
|
1869
1867
|
.foregroundStyle(self.isEnabled ? .primary : .secondary)
|
|
1870
1868
|
.lineLimit(1)
|
|
1871
1869
|
.layoutPriority(1)
|
|
@@ -1901,14 +1899,15 @@ private struct ProviderSidebarSectionCard<Content: View>: View {
|
|
|
1901
1899
|
RoundedRectangle(
|
|
1902
1900
|
cornerRadius: ProviderListMetrics.sidebarCardCornerRadius,
|
|
1903
1901
|
style: .continuous)
|
|
1904
|
-
.fill(Color(nsColor: .controlBackgroundColor)
|
|
1905
|
-
|
|
1902
|
+
.fill(Color(nsColor: .controlBackgroundColor)
|
|
1903
|
+
.opacity(ProviderListMetrics.sidebarCardBackgroundOpacity)))
|
|
1906
1904
|
.overlay(
|
|
1907
1905
|
RoundedRectangle(
|
|
1908
1906
|
cornerRadius: ProviderListMetrics.sidebarCardCornerRadius,
|
|
1909
1907
|
style: .continuous)
|
|
1910
|
-
.strokeBorder(
|
|
1911
|
-
|
|
1908
|
+
.strokeBorder(
|
|
1909
|
+
Color(nsColor: .separatorColor).opacity(ProviderListMetrics.sidebarCardBorderOpacity),
|
|
1910
|
+
lineWidth: 1))
|
|
1912
1911
|
}
|
|
1913
1912
|
}
|
|
1914
1913
|
|
|
@@ -1921,7 +1920,7 @@ private struct ProviderHistoryNavigationButton: View {
|
|
|
1921
1920
|
var body: some View {
|
|
1922
1921
|
Button(action: self.action) {
|
|
1923
1922
|
Image(systemName: self.systemName)
|
|
1924
|
-
.font(.caption.weight(.semibold))
|
|
1923
|
+
.font(RunicFont.caption.weight(.semibold))
|
|
1925
1924
|
.foregroundStyle(self.enabled ? .secondary : .tertiary)
|
|
1926
1925
|
.frame(width: 26, height: 26)
|
|
1927
1926
|
}
|
|
@@ -1935,10 +1934,9 @@ private struct ProviderHistoryNavigationButton: View {
|
|
|
1935
1934
|
.strokeBorder(
|
|
1936
1935
|
Color(
|
|
1937
1936
|
nsColor: .separatorColor).opacity(self.enabled ? 0.28 : 0.1),
|
|
1938
|
-
lineWidth: 1))
|
|
1939
|
-
)
|
|
1937
|
+
lineWidth: 1)))
|
|
1940
1938
|
.help(self.help)
|
|
1941
|
-
.accessibilityLabel(help)
|
|
1939
|
+
.accessibilityLabel(self.help)
|
|
1942
1940
|
}
|
|
1943
1941
|
}
|
|
1944
1942
|
|
|
@@ -1947,7 +1945,7 @@ private struct ProviderSidebarSectionHeader: View {
|
|
|
1947
1945
|
|
|
1948
1946
|
var body: some View {
|
|
1949
1947
|
Text(self.title)
|
|
1950
|
-
.font(.subheadline.weight(.semibold))
|
|
1948
|
+
.font(RunicFont.subheadline.weight(.semibold))
|
|
1951
1949
|
.foregroundStyle(.secondary)
|
|
1952
1950
|
}
|
|
1953
1951
|
}
|
|
@@ -1961,12 +1959,12 @@ private struct ProviderSidebarKeyValueRow: View {
|
|
|
1961
1959
|
var body: some View {
|
|
1962
1960
|
HStack(alignment: .firstTextBaseline, spacing: RunicSpacing.xs) {
|
|
1963
1961
|
Text("\(self.label):")
|
|
1964
|
-
.font(.caption)
|
|
1962
|
+
.font(RunicFont.caption)
|
|
1965
1963
|
.foregroundStyle(.tertiary)
|
|
1966
1964
|
.frame(width: ProviderListMetrics.sidebarStatusLabelWidth, alignment: .leading)
|
|
1967
1965
|
if let helpText = self.helpText {
|
|
1968
1966
|
Text(self.value)
|
|
1969
|
-
.font(.caption)
|
|
1967
|
+
.font(RunicFont.caption)
|
|
1970
1968
|
.foregroundStyle(.secondary)
|
|
1971
1969
|
.lineLimit(2)
|
|
1972
1970
|
.fixedSize(horizontal: false, vertical: true)
|
|
@@ -1974,7 +1972,7 @@ private struct ProviderSidebarKeyValueRow: View {
|
|
|
1974
1972
|
.help(helpText)
|
|
1975
1973
|
} else {
|
|
1976
1974
|
Text(self.value)
|
|
1977
|
-
.font(.caption)
|
|
1975
|
+
.font(RunicFont.caption)
|
|
1978
1976
|
.foregroundStyle(.secondary)
|
|
1979
1977
|
.lineLimit(2)
|
|
1980
1978
|
.fixedSize(horizontal: false, vertical: true)
|
|
@@ -1993,11 +1991,11 @@ private struct ProviderSidebarMetricChip: View {
|
|
|
1993
1991
|
var body: some View {
|
|
1994
1992
|
VStack(alignment: .leading, spacing: RunicSpacing.xxxs) {
|
|
1995
1993
|
Text(self.title)
|
|
1996
|
-
.font(.caption2.weight(.semibold))
|
|
1994
|
+
.font(RunicFont.caption2.weight(.semibold))
|
|
1997
1995
|
.foregroundStyle(.tertiary)
|
|
1998
1996
|
.textCase(.uppercase)
|
|
1999
1997
|
Text(self.value)
|
|
2000
|
-
.font(.caption.weight(.medium))
|
|
1998
|
+
.font(RunicFont.caption.weight(.medium))
|
|
2001
1999
|
.foregroundStyle(.secondary)
|
|
2002
2000
|
.lineLimit(2)
|
|
2003
2001
|
.fixedSize(horizontal: false, vertical: true)
|
|
@@ -2010,15 +2008,14 @@ private struct ProviderSidebarMetricChip: View {
|
|
|
2010
2008
|
RoundedRectangle(
|
|
2011
2009
|
cornerRadius: ProviderListMetrics.sidebarMicroCardCornerRadius,
|
|
2012
2010
|
style: .continuous)
|
|
2013
|
-
.fill(Color(nsColor: .textBackgroundColor)
|
|
2014
|
-
|
|
2011
|
+
.fill(Color(nsColor: .textBackgroundColor)
|
|
2012
|
+
.opacity(ProviderListMetrics.sidebarMicroCardBackgroundOpacity)))
|
|
2015
2013
|
.overlay(
|
|
2016
2014
|
RoundedRectangle(
|
|
2017
2015
|
cornerRadius: ProviderListMetrics.sidebarMicroCardCornerRadius,
|
|
2018
2016
|
style: .continuous)
|
|
2019
2017
|
.strokeBorder(Color(
|
|
2020
|
-
nsColor: .separatorColor).opacity(ProviderListMetrics.sidebarMicroCardBorderOpacity), lineWidth: 1)
|
|
2021
|
-
)
|
|
2018
|
+
nsColor: .separatorColor).opacity(ProviderListMetrics.sidebarMicroCardBorderOpacity), lineWidth: 1))
|
|
2022
2019
|
.help(self.helpText ?? "")
|
|
2023
2020
|
}
|
|
2024
2021
|
}
|
|
@@ -2027,7 +2024,9 @@ private enum ProviderDetailSubview: String, CaseIterable, Identifiable {
|
|
|
2027
2024
|
case overview = "Overview"
|
|
2028
2025
|
case history = "History"
|
|
2029
2026
|
|
|
2030
|
-
var id: String {
|
|
2027
|
+
var id: String {
|
|
2028
|
+
self.rawValue
|
|
2029
|
+
}
|
|
2031
2030
|
}
|
|
2032
2031
|
|
|
2033
2032
|
private enum ProviderHistoryMetricMode: String, CaseIterable, Identifiable {
|
|
@@ -2035,7 +2034,9 @@ private enum ProviderHistoryMetricMode: String, CaseIterable, Identifiable {
|
|
|
2035
2034
|
case cost = "Cost"
|
|
2036
2035
|
case requests = "Requests"
|
|
2037
2036
|
|
|
2038
|
-
var id: String {
|
|
2037
|
+
var id: String {
|
|
2038
|
+
self.rawValue
|
|
2039
|
+
}
|
|
2039
2040
|
}
|
|
2040
2041
|
|
|
2041
2042
|
private enum ProviderHistoryDayDetailMode: String, CaseIterable, Identifiable {
|
|
@@ -2043,7 +2044,9 @@ private enum ProviderHistoryDayDetailMode: String, CaseIterable, Identifiable {
|
|
|
2043
2044
|
case models = "Models"
|
|
2044
2045
|
case projects = "Projects"
|
|
2045
2046
|
|
|
2046
|
-
var id: String {
|
|
2047
|
+
var id: String {
|
|
2048
|
+
self.rawValue
|
|
2049
|
+
}
|
|
2047
2050
|
}
|
|
2048
2051
|
|
|
2049
2052
|
@MainActor
|
|
@@ -2059,7 +2062,7 @@ private struct ProviderHistoryCalendarDayCell: View {
|
|
|
2059
2062
|
Button(action: self.action) {
|
|
2060
2063
|
VStack(alignment: .leading, spacing: RunicSpacing.xxxs) {
|
|
2061
2064
|
Text("\(self.dayNumber)")
|
|
2062
|
-
.font(.caption.weight(self.isSelected ? .semibold : .regular))
|
|
2065
|
+
.font(RunicFont.caption.weight(self.isSelected ? .semibold : .regular))
|
|
2063
2066
|
.foregroundStyle(self.isInMonth ? .primary : .tertiary)
|
|
2064
2067
|
Spacer(minLength: 0)
|
|
2065
2068
|
if self.hasActivity {
|
|
@@ -2076,12 +2079,10 @@ private struct ProviderHistoryCalendarDayCell: View {
|
|
|
2076
2079
|
.frame(maxWidth: .infinity, minHeight: 46, alignment: .topLeading)
|
|
2077
2080
|
.background(
|
|
2078
2081
|
RoundedRectangle(cornerRadius: RunicCornerRadius.sm, style: .continuous)
|
|
2079
|
-
.fill(self.backgroundColor)
|
|
2080
|
-
)
|
|
2082
|
+
.fill(self.backgroundColor))
|
|
2081
2083
|
.overlay(
|
|
2082
2084
|
RoundedRectangle(cornerRadius: RunicCornerRadius.sm, style: .continuous)
|
|
2083
|
-
.strokeBorder(self.borderColor, lineWidth: self.isSelected ? 1.5 : 1)
|
|
2084
|
-
)
|
|
2085
|
+
.strokeBorder(self.borderColor, lineWidth: self.isSelected ? 1.5 : 1))
|
|
2085
2086
|
.opacity(self.isInMonth ? 1 : 0.52)
|
|
2086
2087
|
}
|
|
2087
2088
|
.buttonStyle(.plain)
|
|
@@ -2142,9 +2143,9 @@ private struct ProviderSidebarDetailView: View {
|
|
|
2142
2143
|
ProviderListBrandIcon(provider: self.provider)
|
|
2143
2144
|
VStack(alignment: .leading, spacing: RunicSpacing.xxs) {
|
|
2144
2145
|
Text(self.store.metadata(for: self.provider).displayName)
|
|
2145
|
-
.font(.title2.weight(.semibold))
|
|
2146
|
+
.font(RunicFont.title2.weight(.semibold))
|
|
2146
2147
|
Text(self.subtitle)
|
|
2147
|
-
.font(.caption)
|
|
2148
|
+
.font(RunicFont.caption)
|
|
2148
2149
|
.foregroundStyle(.secondary)
|
|
2149
2150
|
}
|
|
2150
2151
|
Spacer()
|
|
@@ -2207,7 +2208,7 @@ private struct ProviderSidebarDetailView: View {
|
|
|
2207
2208
|
|
|
2208
2209
|
if let diagnosticsCopyStatus {
|
|
2209
2210
|
Text(diagnosticsCopyStatus)
|
|
2210
|
-
.font(.caption2)
|
|
2211
|
+
.font(RunicFont.caption2)
|
|
2211
2212
|
.foregroundStyle(.tertiary)
|
|
2212
2213
|
}
|
|
2213
2214
|
}
|
|
@@ -2231,11 +2232,11 @@ private struct ProviderSidebarDetailView: View {
|
|
|
2231
2232
|
if !topModelLines.isEmpty {
|
|
2232
2233
|
VStack(alignment: .leading, spacing: RunicSpacing.xxs) {
|
|
2233
2234
|
Text(self.modelSectionTitle)
|
|
2234
|
-
.font(.caption2.weight(.semibold))
|
|
2235
|
+
.font(RunicFont.caption2.weight(.semibold))
|
|
2235
2236
|
.foregroundStyle(.secondary)
|
|
2236
2237
|
ForEach(Array(topModelLines.enumerated()), id: \.offset) { index, line in
|
|
2237
2238
|
Text("\(index + 1). \(line)")
|
|
2238
|
-
.font(.caption)
|
|
2239
|
+
.font(RunicFont.caption)
|
|
2239
2240
|
.foregroundStyle(.secondary)
|
|
2240
2241
|
.textSelection(.enabled)
|
|
2241
2242
|
}
|
|
@@ -2246,11 +2247,11 @@ private struct ProviderSidebarDetailView: View {
|
|
|
2246
2247
|
if !topProjectLines.isEmpty {
|
|
2247
2248
|
VStack(alignment: .leading, spacing: RunicSpacing.xxs) {
|
|
2248
2249
|
Text("Projects")
|
|
2249
|
-
.font(.caption2.weight(.semibold))
|
|
2250
|
+
.font(RunicFont.caption2.weight(.semibold))
|
|
2250
2251
|
.foregroundStyle(.secondary)
|
|
2251
2252
|
ForEach(Array(topProjectLines.enumerated()), id: \.offset) { index, line in
|
|
2252
2253
|
Text("\(index + 1). \(line)")
|
|
2253
|
-
.font(.caption)
|
|
2254
|
+
.font(RunicFont.caption)
|
|
2254
2255
|
.foregroundStyle(.secondary)
|
|
2255
2256
|
.textSelection(.enabled)
|
|
2256
2257
|
}
|
|
@@ -2280,19 +2281,19 @@ private struct ProviderSidebarDetailView: View {
|
|
|
2280
2281
|
ForEach(self.settingsFields) { field in
|
|
2281
2282
|
VStack(alignment: .leading, spacing: RunicSpacing.xxs) {
|
|
2282
2283
|
Text(field.title)
|
|
2283
|
-
.font(.body.weight(.medium))
|
|
2284
|
+
.font(RunicFont.body.weight(.medium))
|
|
2284
2285
|
Text(field.subtitle)
|
|
2285
|
-
.font(.caption)
|
|
2286
|
+
.font(RunicFont.caption)
|
|
2286
2287
|
.foregroundStyle(.secondary)
|
|
2287
2288
|
switch field.kind {
|
|
2288
2289
|
case .plain:
|
|
2289
2290
|
TextField(field.placeholder ?? "", text: field.binding)
|
|
2290
2291
|
.textFieldStyle(.roundedBorder)
|
|
2291
|
-
.font(.callout)
|
|
2292
|
+
.font(RunicFont.callout)
|
|
2292
2293
|
case .secure:
|
|
2293
2294
|
SecureField(field.placeholder ?? "", text: field.binding)
|
|
2294
2295
|
.textFieldStyle(.roundedBorder)
|
|
2295
|
-
.font(.callout)
|
|
2296
|
+
.font(RunicFont.callout)
|
|
2296
2297
|
}
|
|
2297
2298
|
self.fieldActions(field.actions)
|
|
2298
2299
|
}
|
|
@@ -2310,9 +2311,9 @@ private struct ProviderSidebarDetailView: View {
|
|
|
2310
2311
|
Toggle(isOn: toggle.binding) {
|
|
2311
2312
|
VStack(alignment: .leading, spacing: RunicSpacing.xxs) {
|
|
2312
2313
|
Text(toggle.title)
|
|
2313
|
-
.font(.body.weight(.medium))
|
|
2314
|
+
.font(RunicFont.body.weight(.medium))
|
|
2314
2315
|
Text(toggle.subtitle)
|
|
2315
|
-
.font(.caption)
|
|
2316
|
+
.font(RunicFont.caption)
|
|
2316
2317
|
.foregroundStyle(.secondary)
|
|
2317
2318
|
}
|
|
2318
2319
|
}
|
|
@@ -2321,7 +2322,7 @@ private struct ProviderSidebarDetailView: View {
|
|
|
2321
2322
|
if toggle.binding.wrappedValue {
|
|
2322
2323
|
if let status = toggle.statusText?(), !status.isEmpty {
|
|
2323
2324
|
Text(status)
|
|
2324
|
-
.font(.caption)
|
|
2325
|
+
.font(RunicFont.caption)
|
|
2325
2326
|
.foregroundStyle(.secondary)
|
|
2326
2327
|
.padding(RunicSpacing.xs)
|
|
2327
2328
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
@@ -2331,7 +2332,7 @@ private struct ProviderSidebarDetailView: View {
|
|
|
2331
2332
|
style: .continuous)
|
|
2332
2333
|
.fill(Color(
|
|
2333
2334
|
nsColor: .controlBackgroundColor).opacity(
|
|
2334
|
-
|
|
2335
|
+
ProviderListMetrics.sidebarMicroCardBackgroundOpacity)))
|
|
2335
2336
|
}
|
|
2336
2337
|
self.toggleActions(toggle.actions)
|
|
2337
2338
|
}
|
|
@@ -2364,7 +2365,7 @@ private struct ProviderSidebarDetailView: View {
|
|
|
2364
2365
|
private var statusBadge: some View {
|
|
2365
2366
|
let (color, bg) = self.statusColors
|
|
2366
2367
|
return Text(self.usageStatus.text)
|
|
2367
|
-
.font(.caption2.weight(.medium))
|
|
2368
|
+
.font(RunicFont.caption2.weight(.medium))
|
|
2368
2369
|
.padding(.horizontal, RunicSpacing.xs)
|
|
2369
2370
|
.padding(.vertical, RunicSpacing.xxs)
|
|
2370
2371
|
.background(bg)
|
|
@@ -2374,9 +2375,9 @@ private struct ProviderSidebarDetailView: View {
|
|
|
2374
2375
|
|
|
2375
2376
|
private var statusColors: (Color, Color) {
|
|
2376
2377
|
switch self.usageStatus.style {
|
|
2377
|
-
case .success:
|
|
2378
|
-
case .error:
|
|
2379
|
-
case .neutral:
|
|
2378
|
+
case .success: (.green, Color.green.opacity(0.15))
|
|
2379
|
+
case .error: (.red, Color.red.opacity(0.15))
|
|
2380
|
+
case .neutral: (.secondary, Color(nsColor: .controlBackgroundColor))
|
|
2380
2381
|
}
|
|
2381
2382
|
}
|
|
2382
2383
|
|
|
@@ -2392,19 +2393,21 @@ private struct ProviderSidebarDetailView: View {
|
|
|
2392
2393
|
ProviderHistoryNavigationButton(
|
|
2393
2394
|
systemName: "chevron.left",
|
|
2394
2395
|
enabled: true,
|
|
2395
|
-
help: "Previous month")
|
|
2396
|
+
help: "Previous month")
|
|
2397
|
+
{
|
|
2396
2398
|
self.shiftHistoryMonth(by: -1)
|
|
2397
2399
|
}
|
|
2398
2400
|
|
|
2399
2401
|
Text(self.historyMonthTitle)
|
|
2400
|
-
.font(.caption.weight(.semibold))
|
|
2402
|
+
.font(RunicFont.caption.weight(.semibold))
|
|
2401
2403
|
.foregroundStyle(.secondary)
|
|
2402
2404
|
.frame(minWidth: 130, alignment: .leading)
|
|
2403
2405
|
|
|
2404
2406
|
ProviderHistoryNavigationButton(
|
|
2405
2407
|
systemName: "chevron.right",
|
|
2406
2408
|
enabled: self.canShiftHistoryForward,
|
|
2407
|
-
help: "Next month")
|
|
2409
|
+
help: "Next month")
|
|
2410
|
+
{
|
|
2408
2411
|
self.shiftHistoryMonth(by: 1)
|
|
2409
2412
|
}
|
|
2410
2413
|
|
|
@@ -2424,14 +2427,14 @@ private struct ProviderSidebarDetailView: View {
|
|
|
2424
2427
|
ProgressView()
|
|
2425
2428
|
.controlSize(.small)
|
|
2426
2429
|
Text("Loading history…")
|
|
2427
|
-
.font(.caption)
|
|
2430
|
+
.font(RunicFont.caption)
|
|
2428
2431
|
.foregroundStyle(.secondary)
|
|
2429
2432
|
}
|
|
2430
2433
|
.padding(.vertical, RunicSpacing.xs)
|
|
2431
2434
|
} else if let snapshot = self.historySnapshot {
|
|
2432
2435
|
if !snapshot.isSupported {
|
|
2433
2436
|
Text(snapshot.note ?? "History is not available for this provider yet.")
|
|
2434
|
-
.font(.caption)
|
|
2437
|
+
.font(RunicFont.caption)
|
|
2435
2438
|
.foregroundStyle(.secondary)
|
|
2436
2439
|
.padding(.vertical, RunicSpacing.xs)
|
|
2437
2440
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
@@ -2439,16 +2442,18 @@ private struct ProviderSidebarDetailView: View {
|
|
|
2439
2442
|
RoundedRectangle(
|
|
2440
2443
|
cornerRadius: ProviderListMetrics.sidebarMicroCardCornerRadius,
|
|
2441
2444
|
style: .continuous)
|
|
2442
|
-
.fill(Color(nsColor: .controlBackgroundColor)
|
|
2443
|
-
|
|
2445
|
+
.fill(Color(nsColor: .controlBackgroundColor)
|
|
2446
|
+
.opacity(ProviderListMetrics.sidebarCardBackgroundOpacity)))
|
|
2444
2447
|
} else {
|
|
2445
2448
|
if let note = snapshot.note, !note.isEmpty {
|
|
2446
2449
|
Text(note)
|
|
2447
|
-
.font(.caption2)
|
|
2450
|
+
.font(RunicFont.caption2)
|
|
2448
2451
|
.foregroundStyle(.tertiary)
|
|
2449
2452
|
}
|
|
2450
|
-
Text(snapshot.days
|
|
2451
|
-
.
|
|
2453
|
+
Text(snapshot.days
|
|
2454
|
+
.count == 1 ? "1 active day in \(self.historyMonthTitle)." :
|
|
2455
|
+
"\(snapshot.days.count) active days in \(self.historyMonthTitle).")
|
|
2456
|
+
.font(RunicFont.caption2)
|
|
2452
2457
|
.foregroundStyle(.secondary)
|
|
2453
2458
|
.padding(.bottom, RunicSpacing.xxs)
|
|
2454
2459
|
|
|
@@ -2457,7 +2462,7 @@ private struct ProviderSidebarDetailView: View {
|
|
|
2457
2462
|
}
|
|
2458
2463
|
} else {
|
|
2459
2464
|
Text("History is empty for this period.")
|
|
2460
|
-
.font(.caption)
|
|
2465
|
+
.font(RunicFont.caption)
|
|
2461
2466
|
.foregroundStyle(.secondary)
|
|
2462
2467
|
.padding(.vertical, RunicSpacing.xs)
|
|
2463
2468
|
}
|
|
@@ -2465,10 +2470,10 @@ private struct ProviderSidebarDetailView: View {
|
|
|
2465
2470
|
if let historyError = self.historyError {
|
|
2466
2471
|
VStack(alignment: .leading, spacing: RunicSpacing.xxs) {
|
|
2467
2472
|
Text("History load failed")
|
|
2468
|
-
.font(.caption.weight(.semibold))
|
|
2473
|
+
.font(RunicFont.caption.weight(.semibold))
|
|
2469
2474
|
.foregroundStyle(.red)
|
|
2470
2475
|
Text(historyError)
|
|
2471
|
-
.font(.caption)
|
|
2476
|
+
.font(RunicFont.caption)
|
|
2472
2477
|
.foregroundStyle(.secondary)
|
|
2473
2478
|
.textSelection(.enabled)
|
|
2474
2479
|
Button("Retry") {
|
|
@@ -2483,18 +2488,17 @@ private struct ProviderSidebarDetailView: View {
|
|
|
2483
2488
|
RoundedRectangle(
|
|
2484
2489
|
cornerRadius: ProviderListMetrics.sidebarMicroCardCornerRadius,
|
|
2485
2490
|
style: .continuous)
|
|
2486
|
-
.fill(Color.red.opacity(0.06))
|
|
2487
|
-
)
|
|
2491
|
+
.fill(Color.red.opacity(0.06)))
|
|
2488
2492
|
.overlay(
|
|
2489
2493
|
RoundedRectangle(
|
|
2490
2494
|
cornerRadius: ProviderListMetrics.sidebarMicroCardCornerRadius,
|
|
2491
2495
|
style: .continuous)
|
|
2492
|
-
.strokeBorder(Color.red.opacity(0.28), lineWidth: 1)
|
|
2493
|
-
)
|
|
2496
|
+
.strokeBorder(Color.red.opacity(0.28), lineWidth: 1))
|
|
2494
2497
|
}
|
|
2495
2498
|
|
|
2496
|
-
Text(
|
|
2497
|
-
.
|
|
2499
|
+
Text(
|
|
2500
|
+
"Local aggregated history only. Prompts, cookies, API keys, and raw payloads are never shown here.")
|
|
2501
|
+
.font(RunicFont.caption2)
|
|
2498
2502
|
.foregroundStyle(.tertiary)
|
|
2499
2503
|
}
|
|
2500
2504
|
}
|
|
@@ -2503,22 +2507,32 @@ private struct ProviderSidebarDetailView: View {
|
|
|
2503
2507
|
private var historyCalendarGrid: some View {
|
|
2504
2508
|
VStack(alignment: .leading, spacing: RunicSpacing.xs) {
|
|
2505
2509
|
let weekdays = self.weekdaySymbols
|
|
2506
|
-
LazyVGrid(
|
|
2510
|
+
LazyVGrid(
|
|
2511
|
+
columns: Array(repeating: GridItem(.flexible(), spacing: RunicSpacing.xxs), count: 7),
|
|
2512
|
+
spacing: RunicSpacing.xxs)
|
|
2513
|
+
{
|
|
2507
2514
|
ForEach(weekdays, id: \.self) { symbol in
|
|
2508
2515
|
Text(symbol)
|
|
2509
|
-
.font(.caption2.weight(.semibold))
|
|
2516
|
+
.font(RunicFont.caption2.weight(.semibold))
|
|
2510
2517
|
.foregroundStyle(.tertiary)
|
|
2511
2518
|
.frame(maxWidth: .infinity)
|
|
2512
2519
|
}
|
|
2513
2520
|
}
|
|
2514
2521
|
|
|
2515
|
-
LazyVGrid(
|
|
2522
|
+
LazyVGrid(
|
|
2523
|
+
columns: Array(repeating: GridItem(.flexible(), spacing: RunicSpacing.xxs), count: 7),
|
|
2524
|
+
spacing: RunicSpacing.xxs)
|
|
2525
|
+
{
|
|
2516
2526
|
ForEach(self.calendarDaysForMonth, id: \.self) { day in
|
|
2517
|
-
let inMonth = self.historyCalendar.isDate(
|
|
2527
|
+
let inMonth = self.historyCalendar.isDate(
|
|
2528
|
+
day,
|
|
2529
|
+
equalTo: self.historyMonthStart,
|
|
2530
|
+
toGranularity: .month)
|
|
2518
2531
|
let normalizedDay = self.historyCalendar.startOfDay(for: day)
|
|
2519
2532
|
let summary = self.historySummaryByDay[normalizedDay]
|
|
2520
2533
|
let dayNumber = self.historyCalendar.component(.day, from: day)
|
|
2521
|
-
let isSelected = self.historySelectedDay
|
|
2534
|
+
let isSelected = self.historySelectedDay
|
|
2535
|
+
.map { self.historyCalendar.isDate($0, inSameDayAs: day) } ?? false
|
|
2522
2536
|
ProviderHistoryCalendarDayCell(
|
|
2523
2537
|
dayNumber: dayNumber,
|
|
2524
2538
|
isInMonth: inMonth,
|
|
@@ -2526,7 +2540,7 @@ private struct ProviderSidebarDetailView: View {
|
|
|
2526
2540
|
hasActivity: summary != nil,
|
|
2527
2541
|
intensity: self.historyIntensity(for: summary),
|
|
2528
2542
|
action: { self.historySelectedDay = normalizedDay })
|
|
2529
|
-
|
|
2543
|
+
.help(self.historyDayHelp(for: day, summary: summary))
|
|
2530
2544
|
}
|
|
2531
2545
|
}
|
|
2532
2546
|
}
|
|
@@ -2537,7 +2551,7 @@ private struct ProviderSidebarDetailView: View {
|
|
|
2537
2551
|
VStack(alignment: .leading, spacing: RunicSpacing.sm) {
|
|
2538
2552
|
HStack(alignment: .firstTextBaseline, spacing: RunicSpacing.xs) {
|
|
2539
2553
|
Text("Day details")
|
|
2540
|
-
.font(.caption.weight(.semibold))
|
|
2554
|
+
.font(RunicFont.caption.weight(.semibold))
|
|
2541
2555
|
.foregroundStyle(.tertiary)
|
|
2542
2556
|
.textCase(.uppercase)
|
|
2543
2557
|
.tracking(0.25)
|
|
@@ -2556,7 +2570,7 @@ private struct ProviderSidebarDetailView: View {
|
|
|
2556
2570
|
|
|
2557
2571
|
if let selected = self.selectedHistoryDaySummary {
|
|
2558
2572
|
Text(selected.dayStart.formatted(.dateTime.weekday(.wide).month(.abbreviated).day().year()))
|
|
2559
|
-
.font(.caption.weight(.semibold))
|
|
2573
|
+
.font(RunicFont.caption.weight(.semibold))
|
|
2560
2574
|
.foregroundStyle(.secondary)
|
|
2561
2575
|
|
|
2562
2576
|
if self.historyDayDetailMode == .summary {
|
|
@@ -2586,8 +2600,9 @@ private struct ProviderSidebarDetailView: View {
|
|
|
2586
2600
|
}
|
|
2587
2601
|
|
|
2588
2602
|
if self.hasModelBreakdown, let topModel = selected.topModel {
|
|
2589
|
-
Text(
|
|
2590
|
-
.
|
|
2603
|
+
Text(
|
|
2604
|
+
"Top model: \(self.usageLine(title: UsageFormatter.modelDisplayName(topModel.model), totals: topModel.totals, requests: topModel.entryCount, model: topModel.model))")
|
|
2605
|
+
.font(RunicFont.caption)
|
|
2591
2606
|
.foregroundStyle(.secondary)
|
|
2592
2607
|
.textSelection(.enabled)
|
|
2593
2608
|
}
|
|
@@ -2595,7 +2610,7 @@ private struct ProviderSidebarDetailView: View {
|
|
|
2595
2610
|
if self.hasProjectAttribution, let topProject = selected.topProject {
|
|
2596
2611
|
let project = self.projectDisplay(topProject)
|
|
2597
2612
|
Text("Top project: \(project.title)")
|
|
2598
|
-
.font(.caption)
|
|
2613
|
+
.font(RunicFont.caption)
|
|
2599
2614
|
.foregroundStyle(.secondary)
|
|
2600
2615
|
.textSelection(.enabled)
|
|
2601
2616
|
.help(project.helpText ?? "")
|
|
@@ -2605,23 +2620,28 @@ private struct ProviderSidebarDetailView: View {
|
|
|
2605
2620
|
if self.historyDayDetailMode == .models {
|
|
2606
2621
|
if self.hasModelBreakdown, !selected.modelSummaries.isEmpty {
|
|
2607
2622
|
Text("Models used")
|
|
2608
|
-
.font(.caption2.weight(.medium))
|
|
2623
|
+
.font(RunicFont.caption2.weight(.medium))
|
|
2609
2624
|
.foregroundStyle(.secondary)
|
|
2610
|
-
ForEach(
|
|
2625
|
+
ForEach(
|
|
2626
|
+
Array(selected.modelSummaries.prefix(12).enumerated()),
|
|
2627
|
+
id: \.offset)
|
|
2628
|
+
{ _, summary in
|
|
2611
2629
|
Text("• \(self.historyModelLine(summary))")
|
|
2612
|
-
.font(.caption2)
|
|
2630
|
+
.font(RunicFont.caption2)
|
|
2613
2631
|
.foregroundStyle(.tertiary)
|
|
2614
2632
|
.textSelection(.enabled)
|
|
2615
2633
|
.help(self.historyModelLine(summary))
|
|
2616
2634
|
}
|
|
2617
2635
|
} else if self.hasModelBreakdown, !selected.modelsUsed.isEmpty {
|
|
2618
2636
|
Text("Models used: \(self.renderedModelsList(selected.modelsUsed).joined(separator: ", "))")
|
|
2619
|
-
.font(.caption2)
|
|
2637
|
+
.font(RunicFont.caption2)
|
|
2620
2638
|
.foregroundStyle(.tertiary)
|
|
2621
2639
|
.textSelection(.enabled)
|
|
2622
2640
|
} else {
|
|
2623
|
-
Text(self
|
|
2624
|
-
.
|
|
2641
|
+
Text(self
|
|
2642
|
+
.hasModelBreakdown ? "No models recorded for this day." :
|
|
2643
|
+
"Model attribution is not available for this provider.")
|
|
2644
|
+
.font(RunicFont.caption)
|
|
2625
2645
|
.foregroundStyle(.tertiary)
|
|
2626
2646
|
}
|
|
2627
2647
|
}
|
|
@@ -2629,28 +2649,34 @@ private struct ProviderSidebarDetailView: View {
|
|
|
2629
2649
|
if self.historyDayDetailMode == .projects {
|
|
2630
2650
|
if self.hasProjectAttribution, !selected.projectSummaries.isEmpty {
|
|
2631
2651
|
Text("Top projects")
|
|
2632
|
-
.font(.caption2.weight(.medium))
|
|
2652
|
+
.font(RunicFont.caption2.weight(.medium))
|
|
2633
2653
|
.foregroundStyle(.secondary)
|
|
2634
|
-
ForEach(
|
|
2654
|
+
ForEach(
|
|
2655
|
+
Array(selected.projectSummaries.prefix(12).enumerated()),
|
|
2656
|
+
id: \.offset)
|
|
2657
|
+
{ _, summary in
|
|
2635
2658
|
let project = self.projectDisplay(summary)
|
|
2636
2659
|
Text("• \(self.historyProjectLine(summary))")
|
|
2637
|
-
.font(.caption2)
|
|
2660
|
+
.font(RunicFont.caption2)
|
|
2638
2661
|
.foregroundStyle(.tertiary)
|
|
2639
2662
|
.textSelection(.enabled)
|
|
2640
2663
|
.help(project.helpText ?? "")
|
|
2641
2664
|
}
|
|
2642
2665
|
} else {
|
|
2643
|
-
Text(self
|
|
2644
|
-
.
|
|
2666
|
+
Text(self
|
|
2667
|
+
.hasProjectAttribution ? "No projects recorded for this day." :
|
|
2668
|
+
"Project attribution is not available for this provider.")
|
|
2669
|
+
.font(RunicFont.caption)
|
|
2645
2670
|
.foregroundStyle(.tertiary)
|
|
2646
2671
|
}
|
|
2647
2672
|
}
|
|
2648
2673
|
} else {
|
|
2649
|
-
Text(self.historySelectedDay
|
|
2650
|
-
.
|
|
2674
|
+
Text(self.historySelectedDay?
|
|
2675
|
+
.formatted(.dateTime.weekday(.wide).month(.abbreviated).day().year()) ?? "No day selected")
|
|
2676
|
+
.font(RunicFont.caption.weight(.semibold))
|
|
2651
2677
|
.foregroundStyle(.secondary)
|
|
2652
2678
|
Text("No recorded activity for this day.")
|
|
2653
|
-
.font(.caption)
|
|
2679
|
+
.font(RunicFont.caption)
|
|
2654
2680
|
.foregroundStyle(.tertiary)
|
|
2655
2681
|
}
|
|
2656
2682
|
}
|
|
@@ -2720,11 +2746,11 @@ private struct ProviderSidebarDetailView: View {
|
|
|
2720
2746
|
private func historyMetricValue(for day: ProviderHistoryDaySnapshot) -> Double {
|
|
2721
2747
|
switch self.historyMetricMode {
|
|
2722
2748
|
case .tokens:
|
|
2723
|
-
|
|
2749
|
+
Double(day.totals.totalTokens)
|
|
2724
2750
|
case .cost:
|
|
2725
|
-
|
|
2751
|
+
max(0, day.totals.costUSD ?? 0)
|
|
2726
2752
|
case .requests:
|
|
2727
|
-
|
|
2753
|
+
Double(day.requestCount)
|
|
2728
2754
|
}
|
|
2729
2755
|
}
|
|
2730
2756
|
|
|
@@ -2887,21 +2913,21 @@ private struct ProviderSidebarDetailView: View {
|
|
|
2887
2913
|
|
|
2888
2914
|
private func projectSourceLabel(_ source: UsageLedgerProjectNameSource) -> String {
|
|
2889
2915
|
switch source {
|
|
2890
|
-
case .projectName:
|
|
2891
|
-
case .projectID:
|
|
2892
|
-
case .inferredFromPath:
|
|
2893
|
-
case .inferredFromName:
|
|
2894
|
-
case .budgetOverride:
|
|
2895
|
-
case .unknown:
|
|
2916
|
+
case .projectName: "project name"
|
|
2917
|
+
case .projectID: "project id"
|
|
2918
|
+
case .inferredFromPath: "path-derived"
|
|
2919
|
+
case .inferredFromName: "name-derived"
|
|
2920
|
+
case .budgetOverride: "budget override"
|
|
2921
|
+
case .unknown: "unknown"
|
|
2896
2922
|
}
|
|
2897
2923
|
}
|
|
2898
2924
|
|
|
2899
2925
|
private func projectConfidenceLabel(_ confidence: UsageLedgerProjectNameConfidence) -> String {
|
|
2900
2926
|
switch confidence {
|
|
2901
|
-
case .high:
|
|
2902
|
-
case .medium:
|
|
2903
|
-
case .low:
|
|
2904
|
-
case .none:
|
|
2927
|
+
case .high: "high"
|
|
2928
|
+
case .medium: "medium"
|
|
2929
|
+
case .low: "low"
|
|
2930
|
+
case .none: "none"
|
|
2905
2931
|
}
|
|
2906
2932
|
}
|
|
2907
2933
|
|
|
@@ -2909,10 +2935,10 @@ private struct ProviderSidebarDetailView: View {
|
|
|
2909
2935
|
guard let projectID = projectID?.trimmingCharacters(in: .whitespacesAndNewlines), !projectID.isEmpty else {
|
|
2910
2936
|
return nil
|
|
2911
2937
|
}
|
|
2912
|
-
var hash: UInt64 =
|
|
2938
|
+
var hash: UInt64 = 0xCBF2_9CE4_8422_2325
|
|
2913
2939
|
for byte in projectID.lowercased().utf8 {
|
|
2914
2940
|
hash ^= UInt64(byte)
|
|
2915
|
-
hash = hash &*
|
|
2941
|
+
hash = hash &* 0x100_0000_01B3
|
|
2916
2942
|
}
|
|
2917
2943
|
return String(format: "%08llx", hash)
|
|
2918
2944
|
}
|
|
@@ -2963,17 +2989,31 @@ private struct ProviderSidebarDetailView: View {
|
|
|
2963
2989
|
let hasModelBreakdown = self.hasModelBreakdown
|
|
2964
2990
|
let hasProjectAttribution = self.hasProjectAttribution
|
|
2965
2991
|
|
|
2966
|
-
if let today = self
|
|
2967
|
-
|
|
2992
|
+
if let today = self
|
|
2993
|
+
.tokenWindowValue(tokens: tokenSnapshot?.sessionTokens, cost: tokenSnapshot?.sessionCostUSD)
|
|
2994
|
+
{
|
|
2995
|
+
items.append(QuickMetricItem(
|
|
2996
|
+
id: "today",
|
|
2997
|
+
title: "Today",
|
|
2998
|
+
value: today,
|
|
2999
|
+
helpText: "Session cost and tokens."))
|
|
2968
3000
|
}
|
|
2969
3001
|
if let last30 = self.tokenWindowValue(
|
|
2970
3002
|
tokens: tokenSnapshot?.last30DaysTokens,
|
|
2971
3003
|
cost: tokenSnapshot?.last30DaysCostUSD)
|
|
2972
3004
|
{
|
|
2973
|
-
items.append(QuickMetricItem(
|
|
3005
|
+
items.append(QuickMetricItem(
|
|
3006
|
+
id: "30d",
|
|
3007
|
+
title: "30d",
|
|
3008
|
+
value: last30,
|
|
3009
|
+
helpText: "Last 30 days cost and tokens."))
|
|
2974
3010
|
}
|
|
2975
3011
|
if let spend = self.providerSpendValue(snapshot?.providerCost) {
|
|
2976
|
-
items.append(QuickMetricItem(
|
|
3012
|
+
items.append(QuickMetricItem(
|
|
3013
|
+
id: "spend",
|
|
3014
|
+
title: "Spend",
|
|
3015
|
+
value: spend,
|
|
3016
|
+
helpText: "Provider billing usage."))
|
|
2977
3017
|
}
|
|
2978
3018
|
if hasModelBreakdown, let topModel = self.store.ledgerTopModel(for: self.provider) {
|
|
2979
3019
|
let modelName = UsageFormatter.modelDisplayName(topModel.model)
|
|
@@ -3176,7 +3216,7 @@ private struct ProviderSidebarDetailView: View {
|
|
|
3176
3216
|
|
|
3177
3217
|
private func labeledQuotaWindows(from snapshot: UsageSnapshot?) -> [(label: String, window: RateWindow)] {
|
|
3178
3218
|
guard let snapshot else { return [] }
|
|
3179
|
-
let windows = [snapshot.primary, snapshot.secondary, snapshot.tertiary].compactMap
|
|
3219
|
+
let windows = [snapshot.primary, snapshot.secondary, snapshot.tertiary].compactMap(\.self)
|
|
3180
3220
|
var seen: Set<String> = []
|
|
3181
3221
|
var labeled: [(label: String, window: RateWindow)] = []
|
|
3182
3222
|
|
|
@@ -3246,7 +3286,9 @@ private struct ProviderSidebarDetailView: View {
|
|
|
3246
3286
|
|
|
3247
3287
|
private func retryCount(from attempts: [ProviderFetchAttempt]) -> Int {
|
|
3248
3288
|
guard !attempts.isEmpty else { return 0 }
|
|
3249
|
-
if let successIndex = attempts
|
|
3289
|
+
if let successIndex = attempts
|
|
3290
|
+
.firstIndex(where: { $0.wasAvailable && self.trimmed($0.errorDescription) == nil })
|
|
3291
|
+
{
|
|
3250
3292
|
return max(0, successIndex)
|
|
3251
3293
|
}
|
|
3252
3294
|
return max(0, attempts.count - 1)
|
|
@@ -3267,13 +3309,13 @@ private struct ProviderSidebarDetailView: View {
|
|
|
3267
3309
|
|
|
3268
3310
|
private static func fetchKindLabel(_ kind: ProviderFetchKind) -> String {
|
|
3269
3311
|
switch kind {
|
|
3270
|
-
case .cli:
|
|
3271
|
-
case .web:
|
|
3272
|
-
case .oauth:
|
|
3273
|
-
case .apiToken:
|
|
3274
|
-
case .api:
|
|
3275
|
-
case .localProbe:
|
|
3276
|
-
case .webDashboard:
|
|
3312
|
+
case .cli: "cli"
|
|
3313
|
+
case .web: "web"
|
|
3314
|
+
case .oauth: "oauth"
|
|
3315
|
+
case .apiToken: "api"
|
|
3316
|
+
case .api: "api"
|
|
3317
|
+
case .localProbe: "local"
|
|
3318
|
+
case .webDashboard: "web"
|
|
3277
3319
|
}
|
|
3278
3320
|
}
|
|
3279
3321
|
|
|
@@ -3321,9 +3363,13 @@ private struct ProviderSidebarDetailView: View {
|
|
|
3321
3363
|
lines.append("- primary_resets_at: \(iso.string(from: reset))")
|
|
3322
3364
|
}
|
|
3323
3365
|
if let cost = snapshot.providerCost {
|
|
3324
|
-
lines
|
|
3366
|
+
lines
|
|
3367
|
+
.append(
|
|
3368
|
+
"- provider_spend_used: \(UsageFormatter.currencyString(cost.used, currencyCode: cost.currencyCode))")
|
|
3325
3369
|
if cost.limit > 0 {
|
|
3326
|
-
lines
|
|
3370
|
+
lines
|
|
3371
|
+
.append(
|
|
3372
|
+
"- provider_spend_limit: \(UsageFormatter.currencyString(cost.limit, currencyCode: cost.currencyCode))")
|
|
3327
3373
|
}
|
|
3328
3374
|
if let period = self.trimmed(cost.period) {
|
|
3329
3375
|
lines.append("- provider_spend_period: \(period)")
|
|
@@ -3337,13 +3383,12 @@ private struct ProviderSidebarDetailView: View {
|
|
|
3337
3383
|
lines.append("- none")
|
|
3338
3384
|
} else {
|
|
3339
3385
|
for attempt in attempts {
|
|
3340
|
-
let state
|
|
3341
|
-
|
|
3342
|
-
state = "unavailable"
|
|
3386
|
+
let state = if !attempt.wasAvailable {
|
|
3387
|
+
"unavailable"
|
|
3343
3388
|
} else if let error = self.trimmed(attempt.errorDescription) {
|
|
3344
|
-
|
|
3389
|
+
"failed: \(error)"
|
|
3345
3390
|
} else {
|
|
3346
|
-
|
|
3391
|
+
"ok"
|
|
3347
3392
|
}
|
|
3348
3393
|
lines.append("- \(attempt.strategyID) [\(Self.fetchKindLabel(attempt.kind))] \(state)")
|
|
3349
3394
|
}
|