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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 1.0.1

- The window opens at 1280×820 on first launch instead of at its minimum width, which squeezed the Discover screen and the chat.
- The sidebar keeps at least 240 pt and the main area at least 820 pt (chat 560 + settings inspector 260). The window can't shrink below the sum, so neither side gets compressed.
- Discover's model badges move to a second row when the pane is narrow instead of breaking mid-word.

## 1.0.0

First public release.
Expand Down
2 changes: 1 addition & 1 deletion Sources/MLXEngine/Version.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/// The MLX Runner release version. The release workflow refuses a tag that doesn't match it,
/// and scripts/bundle.sh writes it into the app's Info.plist.
public enum MLXRunnerVersion {
public static let current = "1.0.0"
public static let current = "1.0.1"
public static let repository = "BackendStack21/mlx-runner"
public static let homepage = "https://mlx-runner.21no.de"
}
1 change: 1 addition & 0 deletions Sources/MLXRunner/ChatView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ struct ChatView: View {
Composer(conversationID: conversationID)
}
}
.frame(minWidth: WindowLayout.chatMin)
.inspector(isPresented: $state.showInspector) {
InspectorView(conversationID: conversationID)
.inspectorColumnWidth(min: 260, ideal: 290, max: 360)
Expand Down
16 changes: 15 additions & 1 deletion Sources/MLXRunner/DevSnapshot.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,21 @@ enum DevSnapshot {
]
Task { @MainActor in
loadShowcaseModel(state)
try? await Task.sleep(nanoseconds: 3_000_000_000)
try? await Task.sleep(nanoseconds: 1_000_000_000)
// MLXRUNNER_SNAPSHOT_SIZE=WxH asks for that content size, clamped to the window's minimum
// (setContentSize doesn't enforce it), so a tiny size shows the most compressed layout a
// user can reach.
if let window = NSApp.windows.first(where: { $0.isVisible && $0.contentView != nil }) {
let opened = window.frame.size
if let parts = env["MLXRUNNER_SNAPSHOT_SIZE"]?.split(separator: "x", omittingEmptySubsequences: false),
parts.count == 2, let w = Double(parts[0]), let h = Double(parts[1]),
w.isFinite, h.isFinite, w > 0, h > 0 {
let min = window.contentMinSize
window.setContentSize(NSSize(width: max(w, min.width), height: max(h, min.height)))
}
FileHandle.standardError.write(Data("snapshot window: opened \(opened), now \(window.frame.size), contentMinSize \(window.contentMinSize)\n".utf8))
}
try? await Task.sleep(nanoseconds: 2_000_000_000)
for (name, select) in screens {
select()
try? await Task.sleep(nanoseconds: 2_500_000_000)
Expand Down
30 changes: 21 additions & 9 deletions Sources/MLXRunner/DiscoverView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ struct DiscoverView: View {
Divider()
list
}
.frame(minWidth: 380, idealWidth: 440)
.frame(minWidth: 340, idealWidth: 420)

Group {
if let id = selected {
Expand All @@ -37,7 +37,7 @@ struct DiscoverView: View {
}
}
}
.frame(minWidth: 420, maxWidth: .infinity, maxHeight: .infinity)
.frame(minWidth: 400, maxWidth: .infinity, maxHeight: .infinity)
}
.navigationTitle("Discover")
.task { await search() }
Expand Down Expand Up @@ -188,13 +188,13 @@ struct ModelDetailView: View {
.textSelection(.enabled)
}
if let d = details {
HStack(spacing: 6) {
if let t = d.modelType { Badge(text: t, color: d.isSupported ? Theme.accent2 : .orange, systemImage: "cpu") }
if let b = d.bits { Badge(text: "\(b)-bit", color: Theme.accent, systemImage: "square.stack.3d.down.right") }
Badge(text: Fmt.bytes(d.downloadSize), color: .secondary, systemImage: "internaldrive")
if let l = d.license { Badge(text: l, color: .secondary, systemImage: "doc.text") }
Badge(text: Fmt.count(d.downloads), color: .secondary, systemImage: "arrow.down.circle")
Badge(text: Fmt.count(d.likes), color: .secondary, systemImage: "heart")
// One row when it fits, two when the pane is narrow.
ViewThatFits(in: .horizontal) {
HStack(spacing: 6) { modelBadges(d); statBadges(d) }
VStack(alignment: .leading, spacing: 6) {
HStack(spacing: 6) { modelBadges(d) }
HStack(spacing: 6) { statBadges(d) }
}
}
actionCard(d)
} else if let error {
Expand All @@ -218,6 +218,18 @@ struct ModelDetailView: View {
}
}

@ViewBuilder private func modelBadges(_ d: HubModelDetails) -> some View {
if let t = d.modelType { Badge(text: t, color: d.isSupported ? Theme.accent2 : .orange, systemImage: "cpu") }
if let b = d.bits { Badge(text: "\(b)-bit", color: Theme.accent, systemImage: "square.stack.3d.down.right") }
Badge(text: Fmt.bytes(d.downloadSize), color: .secondary, systemImage: "internaldrive")
}

@ViewBuilder private func statBadges(_ d: HubModelDetails) -> some View {
if let l = d.license { Badge(text: l, color: .secondary, systemImage: "doc.text") }
Badge(text: Fmt.count(d.downloads), color: .secondary, systemImage: "arrow.down.circle")
Badge(text: Fmt.count(d.likes), color: .secondary, systemImage: "heart")
}

@ViewBuilder
private func actionCard(_ d: HubModelDetails) -> some View {
let installed = ModelStore.isInstalled(d.id) || state.localModels.contains { $0.repo == d.id }
Expand Down
2 changes: 1 addition & 1 deletion Sources/MLXRunner/Theme.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ struct Badge: View {
var body: some View {
HStack(spacing: 3) {
if let systemImage { Image(systemName: systemImage).font(.system(size: 9, weight: .bold)) }
Text(text).font(.system(size: 10, weight: .semibold, design: .rounded))
Text(text).font(.system(size: 10, weight: .semibold, design: .rounded)).lineLimit(1)
}
.padding(.horizontal, 7)
.padding(.vertical, 3)
Expand Down
22 changes: 19 additions & 3 deletions Sources/MLXRunner/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,16 @@ struct MLXRunnerApp: App {
RootView()
.environment(state)
.environment(updates)
.frame(minWidth: 980, minHeight: 640)
.frame(minWidth: WindowLayout.windowMinWidth, minHeight: 640)
.onAppear {
NSApp.activate(ignoringOtherApps: true)
DevSnapshot.runIfRequested(state)
updates.start() // no-op in DevSnapshot mode or when already started
}
}
// First launch has no saved frame: open roomy instead of at the minimum width.
.defaultSize(width: 1280, height: 820)
.windowResizability(.contentMinSize)
.windowStyle(.hiddenTitleBar)
.windowToolbarStyle(.unified)
.commands {
Expand Down Expand Up @@ -63,14 +66,26 @@ struct MLXRunnerApp: App {
}
}

/// Window and column minimums. The window minimum is the sum of what the columns need, so
/// shrinking the window can never squeeze the sidebar or a detail screen below its own minimum.
enum WindowLayout {
static let sidebarMin: CGFloat = 240
static let sidebarMax: CGFloat = 340
/// The conversation column alone.
static let chatMin: CGFloat = 560
/// Chat with the inspector open (560 + 260); also fits Discover's two panes (340 + 400 + divider).
static let detailMin: CGFloat = chatMin + 260
static let windowMinWidth: CGFloat = sidebarMin + detailMin
}

struct RootView: View {
@Environment(AppState.self) private var state

var body: some View {
@Bindable var state = state
NavigationSplitView {
SidebarView()
.navigationSplitViewColumnWidth(min: 220, ideal: 250, max: 320)
.navigationSplitViewColumnWidth(min: WindowLayout.sidebarMin, ideal: 260, max: WindowLayout.sidebarMax)
} detail: {
ZStack {
switch state.selection {
Expand All @@ -86,7 +101,8 @@ struct RootView: View {
WelcomeView()
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.frame(minWidth: WindowLayout.detailMin, maxWidth: .infinity, maxHeight: .infinity)
.navigationSplitViewColumnWidth(min: WindowLayout.detailMin, ideal: 1020)
}
.modifier(SafeLinkHandling())
.alert("Chat history", isPresented: Binding(
Expand Down
Loading