macOS: show friendly device names in Instances
This commit is contained in:
69
apps/macos/Sources/Clawdis/DeviceModelCatalog.swift
Normal file
69
apps/macos/Sources/Clawdis/DeviceModelCatalog.swift
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct DevicePresentation: Sendable {
|
||||||
|
let title: String
|
||||||
|
let symbol: String?
|
||||||
|
}
|
||||||
|
|
||||||
|
enum DeviceModelCatalog {
|
||||||
|
static func presentation(deviceFamily: String?, modelIdentifier: String?) -> DevicePresentation? {
|
||||||
|
let family = (deviceFamily ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
let model = (modelIdentifier ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
|
||||||
|
let modelEntry = model.isEmpty ? nil : modelIdentifierTable[model]
|
||||||
|
let symbol = modelEntry?.symbol ?? fallbackSymbol(for: family, modelIdentifier: model)
|
||||||
|
|
||||||
|
let title = if let name = modelEntry?.name, !name.isEmpty {
|
||||||
|
name
|
||||||
|
} else if !family.isEmpty, !model.isEmpty {
|
||||||
|
"\(family) (\(model))"
|
||||||
|
} else if !family.isEmpty {
|
||||||
|
family
|
||||||
|
} else if !model.isEmpty {
|
||||||
|
model
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
|
||||||
|
if title.isEmpty { return nil }
|
||||||
|
return DevicePresentation(title: title, symbol: symbol)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func fallbackSymbol(for familyRaw: String, modelIdentifier: String) -> String? {
|
||||||
|
let family = familyRaw.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
if family.isEmpty { return nil }
|
||||||
|
switch family.lowercased() {
|
||||||
|
case "ipad":
|
||||||
|
return "ipad"
|
||||||
|
case "iphone":
|
||||||
|
return "iphone"
|
||||||
|
case "mac":
|
||||||
|
return "laptopcomputer"
|
||||||
|
case "android":
|
||||||
|
// Prefer tablet glyph when we know it's an Android tablet. (No attempt to infer phone/tablet here.)
|
||||||
|
return "cpu"
|
||||||
|
case "linux":
|
||||||
|
return "cpu"
|
||||||
|
default:
|
||||||
|
return "cpu"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct ModelEntry: Sendable {
|
||||||
|
let name: String
|
||||||
|
let symbol: String?
|
||||||
|
}
|
||||||
|
|
||||||
|
// Friendly model names for a small set of known identifiers.
|
||||||
|
// Extend this table as needed; unknown identifiers fall back to the raw value.
|
||||||
|
private static let modelIdentifierTable: [String: ModelEntry] = [
|
||||||
|
// iPad
|
||||||
|
"iPad16,5": .init(name: "iPad Pro 11-inch (M4)", symbol: "ipad"),
|
||||||
|
"iPad16,6": .init(name: "iPad Pro 13-inch (M4)", symbol: "ipad"),
|
||||||
|
|
||||||
|
// Mac
|
||||||
|
"Mac16,6": .init(name: "MacBook Pro (14-inch, 2024)", symbol: "laptopcomputer"),
|
||||||
|
"Mac16,8": .init(name: "MacBook Pro (16-inch, 2024)", symbol: "laptopcomputer"),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
@@ -70,10 +70,11 @@ struct InstancesSettings: View {
|
|||||||
if let platform = inst.platform, let prettyPlatform = self.prettyPlatform(platform) {
|
if let platform = inst.platform, let prettyPlatform = self.prettyPlatform(platform) {
|
||||||
self.label(icon: self.platformIcon(platform), text: prettyPlatform)
|
self.label(icon: self.platformIcon(platform), text: prettyPlatform)
|
||||||
}
|
}
|
||||||
if let deviceText = self.deviceDescription(inst),
|
if let device = DeviceModelCatalog.presentation(
|
||||||
let deviceIcon = self.deviceIcon(inst)
|
deviceFamily: inst.deviceFamily,
|
||||||
|
modelIdentifier: inst.modelIdentifier)
|
||||||
{
|
{
|
||||||
self.label(icon: deviceIcon, text: deviceText)
|
self.label(icon: device.symbol, text: device.title)
|
||||||
}
|
}
|
||||||
self.label(icon: "clock", text: inst.lastInputDescription)
|
self.label(icon: "clock", text: inst.lastInputDescription)
|
||||||
if let mode = inst.mode { self.label(icon: "network", text: mode) }
|
if let mode = inst.mode { self.label(icon: "network", text: mode) }
|
||||||
@@ -94,9 +95,11 @@ struct InstancesSettings: View {
|
|||||||
.padding(.vertical, 6)
|
.padding(.vertical, 6)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func label(icon: String, text: String) -> some View {
|
private func label(icon: String?, text: String) -> some View {
|
||||||
HStack(spacing: 4) {
|
HStack(spacing: 4) {
|
||||||
Image(systemName: icon).foregroundStyle(.secondary).font(.caption)
|
if let icon {
|
||||||
|
Image(systemName: icon).foregroundStyle(.secondary).font(.caption)
|
||||||
|
}
|
||||||
Text(text)
|
Text(text)
|
||||||
}
|
}
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
@@ -120,28 +123,6 @@ struct InstancesSettings: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func deviceIcon(_ inst: InstanceInfo) -> String? {
|
|
||||||
let family = inst.deviceFamily?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
|
||||||
if family.isEmpty { return nil }
|
|
||||||
switch family.lowercased() {
|
|
||||||
case "ipad":
|
|
||||||
return "ipad"
|
|
||||||
case "iphone":
|
|
||||||
return "iphone"
|
|
||||||
case "mac":
|
|
||||||
return "laptopcomputer"
|
|
||||||
default:
|
|
||||||
return "cpu"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func deviceDescription(_ inst: InstanceInfo) -> String? {
|
|
||||||
let family = inst.deviceFamily?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
|
||||||
let model = inst.modelIdentifier?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
|
||||||
if !family.isEmpty, !model.isEmpty { return "\(family) (\(model))" }
|
|
||||||
if !model.isEmpty { return model }
|
|
||||||
return family.isEmpty ? nil : family
|
|
||||||
}
|
|
||||||
private func prettyPlatform(_ raw: String) -> String? {
|
private func prettyPlatform(_ raw: String) -> String? {
|
||||||
let (prefix, version) = self.parsePlatform(raw)
|
let (prefix, version) = self.parsePlatform(raw)
|
||||||
if prefix.isEmpty { return nil }
|
if prefix.isEmpty { return nil }
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { AgentTool, AgentToolResult } from "@mariozechner/pi-ai";
|
import type { AgentTool, AgentToolResult } from "@mariozechner/pi-ai";
|
||||||
import type { TSchema } from "@sinclair/typebox";
|
|
||||||
import { codingTools, readTool } from "@mariozechner/pi-coding-agent";
|
import { codingTools, readTool } from "@mariozechner/pi-coding-agent";
|
||||||
|
import type { TSchema } from "@sinclair/typebox";
|
||||||
|
|
||||||
import { detectMime } from "../media/mime.js";
|
import { detectMime } from "../media/mime.js";
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user