Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 20 additions & 10 deletions Cotabby/Support/OnboardingTemplateRecommender.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,17 @@ import Foundation
/// inputs (`HardwareCapability`, Apple Intelligence availability) so the onboarding UI can stay a
/// thin renderer and the decisions can be unit-tested without a host.
enum OnboardingTemplateRecommender {
/// Below this much memory, the Powerful template's ~5 GB model leaves too little headroom (the
/// resident model plus OS would dominate an 8 GB machine), so it is disabled rather than offered
/// as a trap. Chosen above 8 so stock 8 GB Macs are excluded while any 12 GB+ config is allowed.
static let powerfulDisableBelowGigabytes = 10.0
/// Between the disable floor and this ceiling, Powerful is allowed but flagged as potentially slow.
/// Below this much memory, the Powerful tier's model leaves too little headroom and is disabled
/// rather than offered as a trap. The base-model tiers are far smaller than the old ~5 GB models,
/// so the floor is 8: only sub-8 GB Macs (effectively pre-Apple-Silicon) are excluded, while every
/// 8 GB+ machine may run it. Sizes for the copy are read from the catalog, not hardcoded here.
static let powerfulDisableBelowGigabytes = 8.0
/// Between the disable floor and this ceiling, Powerful is allowed but flagged as potentially slow
/// under memory pressure from other apps. 16 GB and up is treated as comfortable.
static let powerfulWarnBelowGigabytes = 16.0
/// Below this, the Everyday open-source path (~3 GB model) is flagged as potentially slow. Only
/// relevant when Apple Intelligence is unavailable; the Apple Intelligence path has no such cost.
/// Below this, the Everyday open-source tier is flagged as potentially slow, and it is also the
/// cutoff below which Quick becomes the recommended default. Only relevant when Apple Intelligence
/// is unavailable; the Apple Intelligence path has no per-tier memory cost.
static let everydayWarnBelowGigabytes = 8.0

/// Resolves the model and behavior flags for a template under an explicitly chosen engine.
Expand Down Expand Up @@ -63,14 +66,14 @@ enum OnboardingTemplateRecommender {
break
case .everyday:
if gigabytes < everydayWarnBelowGigabytes {
warning = "Uses a ~3 GB model, which may run slowly on this Mac."
warning = "Uses a \(modelSizeLabel(for: template)) model, which may run slowly on this Mac."
}
case .powerful:
if gigabytes < powerfulDisableBelowGigabytes {
isDisabled = true
warning = "Needs more memory than this Mac has (uses a ~5 GB model)."
warning = "Needs more memory than this Mac has (uses a \(modelSizeLabel(for: template)) model)."
} else if gigabytes < powerfulWarnBelowGigabytes {
warning = "Uses a ~5 GB model; may run slowly with less than 16 GB of memory."
warning = "Uses a \(modelSizeLabel(for: template)) model; may run slowly with less than 16 GB of memory."

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The model size is now derived from the catalog, but the memory ceiling "16 GB" is still hardcoded in this warning string. The PR description claims "no hardcoded sizes remain in the recommender," yet this literal can drift if powerfulWarnBelowGigabytes is ever adjusted. Deriving it from the constant keeps the copy and the logic in sync.

Suggested change
warning = "Uses a \(modelSizeLabel(for: template)) model; may run slowly with less than 16 GB of memory."
warning = "Uses a \(modelSizeLabel(for: template)) model; may run slowly with less than \(Int(powerfulWarnBelowGigabytes)) GB of memory."

Fix in Codex Fix in Claude Code

}
}
}
Expand Down Expand Up @@ -103,4 +106,11 @@ enum OnboardingTemplateRecommender {
private static func downloadableModel(filename: String) -> DownloadableRuntimeModel? {
RuntimeModelCatalog.downloadableModels.first { $0.filename == filename }
}

/// Human-readable size of the GGUF a template installs, read from the catalog so warning copy stays
/// in sync with the actual model instead of a hardcoded number. Falls back to a generic phrase if
/// the filename is missing from the catalog.
private static func modelSizeLabel(for template: OnboardingTemplate) -> String {
downloadableModel(filename: template.openSourceModelFilename)?.approximateSizeLabel ?? "local"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The fallback string "local" produces grammatically awkward warning copy — e.g. "Uses a local model, which may run slowly…" — if a template's openSourceModelFilename is absent from the catalog. A phrase that completes the sentence naturally would be less confusing to users who might see this in edge cases.

Suggested change
downloadableModel(filename: template.openSourceModelFilename)?.approximateSizeLabel ?? "local"
downloadableModel(filename: template.openSourceModelFilename)?.approximateSizeLabel ?? "large"

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Codex Fix in Claude Code

}
}
9 changes: 7 additions & 2 deletions CotabbyTests/OnboardingTemplateRecommenderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,11 @@ final class OnboardingTemplateRecommenderTests: XCTestCase {
// MARK: - availability gating (Open Source engine)

func testPowerfulDisabledOnLowMemoryMacOpenSource() {
// Sub-8 GB Macs (effectively pre-Apple-Silicon) cannot comfortably hold the model, so Powerful
// is excluded there.
let availability = OnboardingTemplateRecommender.availability(
for: .powerful,
hardware: hardware(gigabytes: 8),
hardware: hardware(gigabytes: 6),
engine: .llamaOpenSource
)

Expand All @@ -71,9 +73,12 @@ final class OnboardingTemplateRecommenderTests: XCTestCase {
}

func testPowerfulWarnsBetweenDisableFloorAndComfortCeiling() {
// 8 GB is the disable floor: allowed, not excluded, but still flagged below the 16 GB comfort
// ceiling. This pins that a stock 8 GB Mac can run the Powerful base model (the smaller
// base-model tiers no longer need the old 10 GB floor).
let availability = OnboardingTemplateRecommender.availability(
for: .powerful,
hardware: hardware(gigabytes: 12),
hardware: hardware(gigabytes: 8),
engine: .llamaOpenSource
)

Expand Down