mirror of
https://github.com/wu736139669/hapi.git
synced 2026-11-08 01:43:07 +00:00
Build the app and notification extension for Mac Catalyst with the Mac idiom, so Macs get a 1:1 native window instead of the scaled iPad app. The Mac build keeps the run.hapi.app bundle ID and adds Mac entitlements (sandbox, network, microphone, user-selected files, Mac APNs). - Always use the split on Mac with a sidebar list style, denser rows, 14pt reading text, a hidden duplicate titlebar title, a minimum window size and page-sized sheets; open web links in the default browser and hide the unavailable live QR scanner. - Add menu bar / hardware-keyboard commands: New Session, Settings, Toggle Sidebar, a Session menu (settings, files, rename, export, stop, previous/next session) and a guide link. - Composer: Return sends on Mac (Shift/Option-Return for a newline, IME untouched, first suggestion picked); dropped files and pasted images or copied files become attachments on all platforms. - CI builds Mac Catalyst and runs the navigation, split and transcript geometry tests there; README documents the Mac build.
104 lines
3.8 KiB
Swift
104 lines
3.8 KiB
Swift
import SwiftUI
|
||
|
||
/// Actions the focused home window offers to the menu bar (Mac) and the
|
||
/// hardware-keyboard shortcut overlay (iPad).
|
||
struct HomeCommandActions {
|
||
var newSession: () -> Void
|
||
var openSettings: () -> Void
|
||
/// Nil when the window has no sidebar to toggle (iPhone stack).
|
||
var toggleSidebar: (() -> Void)?
|
||
/// Selects the row above (-1) or below (+1) the open session.
|
||
var selectAdjacentSession: ((Int) -> Void)?
|
||
}
|
||
|
||
/// Actions of the chat shown in the focused window.
|
||
struct ChatCommandActions {
|
||
var openSessionSettings: () -> Void
|
||
var openFiles: () -> Void
|
||
var rename: () -> Void
|
||
var export: (() -> Void)?
|
||
/// Nil unless the agent is mid-turn.
|
||
var stop: (() -> Void)?
|
||
}
|
||
|
||
private struct HomeCommandActionsKey: FocusedValueKey {
|
||
typealias Value = HomeCommandActions
|
||
}
|
||
|
||
private struct ChatCommandActionsKey: FocusedValueKey {
|
||
typealias Value = ChatCommandActions
|
||
}
|
||
|
||
extension FocusedValues {
|
||
var homeCommands: HomeCommandActions? {
|
||
get { self[HomeCommandActionsKey.self] }
|
||
set { self[HomeCommandActionsKey.self] = newValue }
|
||
}
|
||
|
||
var chatCommands: ChatCommandActions? {
|
||
get { self[ChatCommandActionsKey.self] }
|
||
set { self[ChatCommandActionsKey.self] = newValue }
|
||
}
|
||
}
|
||
|
||
struct HapiCommands: Commands {
|
||
@FocusedValue(\.homeCommands) private var home
|
||
@FocusedValue(\.chatCommands) private var chat
|
||
@Environment(\.openURL) private var openURL
|
||
|
||
private static let guideURL = URL(string: "https://hapi.run/docs/guide/native-apps")
|
||
|
||
var body: some Commands {
|
||
CommandGroup(replacing: .newItem) {
|
||
Button("New Session") { home?.newSession() }
|
||
.keyboardShortcut("n")
|
||
.disabled(home == nil)
|
||
}
|
||
CommandGroup(replacing: .appSettings) {
|
||
Button("Settings…") { home?.openSettings() }
|
||
.keyboardShortcut(",")
|
||
.disabled(home == nil)
|
||
}
|
||
// The composer is plain text; Format › Bold/Italic would also
|
||
// shadow ⌘I.
|
||
CommandGroup(replacing: .textFormatting) {}
|
||
// Not a document app and no customizable toolbar.
|
||
CommandGroup(replacing: .saveItem) {}
|
||
CommandGroup(replacing: .importExport) {}
|
||
CommandGroup(replacing: .toolbar) {
|
||
Button("Toggle Sidebar") { home?.toggleSidebar?() }
|
||
.keyboardShortcut("s", modifiers: [.command, .control])
|
||
.disabled(home?.toggleSidebar == nil)
|
||
}
|
||
CommandGroup(replacing: .help) {
|
||
if let url = Self.guideURL {
|
||
Button("HAPI Guide") { openURL(url) }
|
||
}
|
||
}
|
||
CommandMenu("Session") {
|
||
Button("Session Settings…") { chat?.openSessionSettings() }
|
||
.keyboardShortcut("i")
|
||
.disabled(chat == nil)
|
||
Button("Session Files") { chat?.openFiles() }
|
||
.keyboardShortcut("o", modifiers: [.command, .shift])
|
||
.disabled(chat == nil)
|
||
Divider()
|
||
Button("Rename…") { chat?.rename() }
|
||
.disabled(chat == nil)
|
||
Button("Export Conversation…") { chat?.export?() }
|
||
.disabled(chat?.export == nil)
|
||
Divider()
|
||
Button("Stop") { chat?.stop?() }
|
||
.keyboardShortcut(".")
|
||
.disabled(chat?.stop == nil)
|
||
Divider()
|
||
Button("Previous Session") { home?.selectAdjacentSession?(-1) }
|
||
.keyboardShortcut(.upArrow, modifiers: [.command, .option])
|
||
.disabled(home?.selectAdjacentSession == nil)
|
||
Button("Next Session") { home?.selectAdjacentSession?(1) }
|
||
.keyboardShortcut(.downArrow, modifiers: [.command, .option])
|
||
.disabled(home?.selectAdjacentSession == nil)
|
||
}
|
||
}
|
||
}
|