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
80 changes: 80 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
name: CI

on:
push:
branches: [main]
pull_request:
workflow_dispatch:

# Cancel in-progress runs for the same ref when new commits are pushed.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
test:
name: Build & Test (SwiftPM · macOS)
runs-on: macos-15
steps:
- name: Checkout
uses: actions/checkout@v4

# The package declares swift-tools-version 6.2, so it needs a Swift 6.2+
# toolchain (Xcode 26+). latest-stable tracks that going forward.
- name: Select latest stable Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest-stable

- name: Show Swift version
run: swift --version

- name: Resolve dependencies
run: swift package resolve

- name: Build
run: swift build
Comment thread
ronaldmannak marked this conversation as resolved.

- name: Run tests
run: swift test

build-example:
name: Build Example App (Xcode · macOS)
runs-on: macos-15
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Select latest stable Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest-stable

- name: Show versions
run: |
swift --version
xcodebuild -version

# Resolve as a distinct step so dependency-resolution failures (the
# BonjourPico / PicoMarkdownView remote packages, or the in-repo
# OpenResponses package) are easy to spot separately from compilation.
- name: Resolve package dependencies
run: >
xcodebuild -resolvePackageDependencies
-project ResponsesExample/ResponsesExample.xcodeproj
-scheme ResponsesExample
-clonedSourcePackagesDirPath .spm

- name: Build for macOS
run: |
xcodebuild build \
-project ResponsesExample/ResponsesExample.xcodeproj \
-scheme ResponsesExample \
-destination 'platform=macOS' \
-clonedSourcePackagesDirPath .spm \
-skipMacroValidation \
-skipPackagePluginValidation \
CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY=""
45 changes: 25 additions & 20 deletions ResponsesExample/ResponsesExample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@

/* Begin PBXBuildFile section */
F8B183562E9ECE55007F778C /* PicoMarkdownView in Frameworks */ = {isa = PBXBuildFile; productRef = F8B183552E9ECE55007F778C /* PicoMarkdownView */; };
F8E2A9E02E92E3FC00EFF610 /* PicoResponsesCore in Frameworks */ = {isa = PBXBuildFile; productRef = F8E2A9DF2E92E3FC00EFF610 /* PicoResponsesCore */; };
F8E2A9E22E92E3FC00EFF610 /* PicoResponsesSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = F8E2A9E12E92E3FC00EFF610 /* PicoResponsesSwiftUI */; };
F8E2A9E02E92E3FC00EFF610 /* OpenResponses in Frameworks */ = {isa = PBXBuildFile; productRef = F8E2A9DF2E92E3FC00EFF610 /* OpenResponses */; };
F8E2A9E22E92E3FC00EFF610 /* OpenResponsesSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = F8E2A9E12E92E3FC00EFF610 /* OpenResponsesSwiftUI */; };
F8E2A9F32E94376500EFF610 /* BonjourPico in Frameworks */ = {isa = PBXBuildFile; productRef = F8E2A9F22E94376500EFF610 /* BonjourPico */; };
/* End PBXBuildFile section */

Expand All @@ -32,8 +32,8 @@
files = (
F8E2A9F32E94376500EFF610 /* BonjourPico in Frameworks */,
F8B183562E9ECE55007F778C /* PicoMarkdownView in Frameworks */,
F8E2A9E22E92E3FC00EFF610 /* PicoResponsesSwiftUI in Frameworks */,
F8E2A9E02E92E3FC00EFF610 /* PicoResponsesCore in Frameworks */,
F8E2A9E22E92E3FC00EFF610 /* OpenResponsesSwiftUI in Frameworks */,
F8E2A9E02E92E3FC00EFF610 /* OpenResponses in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -76,8 +76,8 @@
);
name = ResponsesExample;
packageProductDependencies = (
F8E2A9DF2E92E3FC00EFF610 /* PicoResponsesCore */,
F8E2A9E12E92E3FC00EFF610 /* PicoResponsesSwiftUI */,
F8E2A9DF2E92E3FC00EFF610 /* OpenResponses */,
F8E2A9E12E92E3FC00EFF610 /* OpenResponsesSwiftUI */,
F8E2A9F22E94376500EFF610 /* BonjourPico */,
F8B183552E9ECE55007F778C /* PicoMarkdownView */,
);
Expand Down Expand Up @@ -110,9 +110,9 @@
mainGroup = F8E2A99F2E92D91600EFF610;
minimizedProjectReferenceProxies = 1;
packageReferences = (
F8E2A9DE2E92E3FC00EFF610 /* XCLocalSwiftPackageReference "../../PicoResponses" */,
F8E2A9DE2E92E3FC00EFF610 /* XCLocalSwiftPackageReference ".." */,
F8E2A9F12E94376500EFF610 /* XCRemoteSwiftPackageReference "BonjourPico" */,
F8B183542E9ECE55007F778C /* XCLocalSwiftPackageReference "../../PicoMarkdownView" */,
F8B183542E9ECE55007F778C /* XCRemoteSwiftPackageReference "PicoMarkdownView" */,
);
preferredProjectObjectVersion = 77;
productRefGroup = F8E2A9A92E92D91600EFF610 /* Products */;
Expand Down Expand Up @@ -393,39 +393,44 @@
/* End XCConfigurationList section */

/* Begin XCLocalSwiftPackageReference section */
F8B183542E9ECE55007F778C /* XCLocalSwiftPackageReference "../../PicoMarkdownView" */ = {
F8E2A9DE2E92E3FC00EFF610 /* XCLocalSwiftPackageReference ".." */ = {
isa = XCLocalSwiftPackageReference;
relativePath = ../../PicoMarkdownView;
};
F8E2A9DE2E92E3FC00EFF610 /* XCLocalSwiftPackageReference "../../PicoResponses" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = ../../PicoResponses;
relativePath = ..;
};
/* End XCLocalSwiftPackageReference section */

/* Begin XCRemoteSwiftPackageReference section */
F8B183542E9ECE55007F778C /* XCRemoteSwiftPackageReference "PicoMarkdownView" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/PicoMLX/PicoMarkdownView";
requirement = {
branch = main;
kind = branch;
};
};
F8E2A9F12E94376500EFF610 /* XCRemoteSwiftPackageReference "BonjourPico" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/PicoMLX/BonjourPico";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 0.0.1;
branch = main;
kind = branch;
};
};
/* End XCRemoteSwiftPackageReference section */

/* Begin XCSwiftPackageProductDependency section */
F8B183552E9ECE55007F778C /* PicoMarkdownView */ = {
isa = XCSwiftPackageProductDependency;
package = F8B183542E9ECE55007F778C /* XCRemoteSwiftPackageReference "PicoMarkdownView" */;
productName = PicoMarkdownView;
};
F8E2A9DF2E92E3FC00EFF610 /* PicoResponsesCore */ = {
F8E2A9DF2E92E3FC00EFF610 /* OpenResponses */ = {
isa = XCSwiftPackageProductDependency;
productName = PicoResponsesCore;
productName = OpenResponses;
};
F8E2A9E12E92E3FC00EFF610 /* PicoResponsesSwiftUI */ = {
F8E2A9E12E92E3FC00EFF610 /* OpenResponsesSwiftUI */ = {
isa = XCSwiftPackageProductDependency;
productName = PicoResponsesSwiftUI;
productName = OpenResponsesSwiftUI;
};
F8E2A9F22E94376500EFF610 /* BonjourPico */ = {
isa = XCSwiftPackageProductDependency;
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2600"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F8E2A9A72E92D91600EFF610"
BuildableName = "ResponsesExample.app"
BlueprintName = "ResponsesExample"
ReferencedContainer = "container:ResponsesExample.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F8E2A9A72E92D91600EFF610"
BuildableName = "ResponsesExample.app"
BlueprintName = "ResponsesExample"
ReferencedContainer = "container:ResponsesExample.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F8E2A9A72E92D91600EFF610"
BuildableName = "ResponsesExample.app"
BlueprintName = "ResponsesExample"
ReferencedContainer = "container:ResponsesExample.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
48 changes: 40 additions & 8 deletions ResponsesExample/ResponsesExample/SelectServerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,16 @@ struct SelectServerView: View {

@State var bonjourPico = BonjourPico()
@Binding var server: (URL, String?, [String])?

// Local snapshot of discovered servers. Mirrors `bonjourPico.endpoints` while scanning,
// but is kept (not cleared) when the user stops scanning so the list stays selectable.
@State private var discoveredServers: [BonjourEndpoint] = []

var body: some View {
VStack {
List {
Section("Local Pico AI Servers") {
if bonjourPico.servers.isEmpty {
if discoveredServers.isEmpty {
VStack(alignment: .leading) {
Text("No Pico AI servers found on this network")
Group {
Expand All @@ -29,10 +33,17 @@ struct SelectServerView: View {
.foregroundStyle(.secondary)
}
}
ForEach(bonjourPico.servers, id: \.self) { server in
Button("\(server.name)") {
guard let baseURL = URL(string: "http://\(server.ipAddress):\(server.port)") else {
print("Invalid url: http://\(server.ipAddress):\(server.port)")
ForEach(discoveredServers) { endpoint in
Button("\(endpoint.displayName)") {
// Prefer the Bonjour host name (recommended for connecting); fall back
// to an advertised IP. Bracket IPv6 literals so the URL host stays valid.
guard let rawHost = endpoint.hostName ?? endpoint.ipAddresses.first else {
print("Invalid url for \(endpoint.displayName)")
return
}
let host = rawHost.contains(":") ? "[\(rawHost)]" : rawHost
guard let baseURL = URL(string: "http://\(host):\(endpoint.port)") else {
print("Invalid url for \(endpoint.displayName)")
return
}
let apiRoot = baseURL.appendingPathComponent("v1")
Expand Down Expand Up @@ -66,13 +77,34 @@ struct SelectServerView: View {
.buttonStyle(.plain)

Button(bonjourPico.isScanning ? "Stop scanning for Pico AI Servers" : "Scan for Pico AI Servers") {
bonjourPico.startStop()
Task {
if bonjourPico.isScanning {
await bonjourPico.stopScanning()
Comment thread
ronaldmannak marked this conversation as resolved.
} else {
await startScan()
}
}
}
}
.padding()
.onAppear {
bonjourPico.startStop()
.task {
await startScan()
}
.onChange(of: bonjourPico.endpoints) { _, endpoints in
// Mirror live results while scanning. stopScanning() sets isScanning = false
// before clearing endpoints, so the cleared snapshot is ignored here and the
// last discovered list stays visible (and selectable) after the scan stops.
if bonjourPico.isScanning {
Comment thread
ronaldmannak marked this conversation as resolved.
discoveredServers = endpoints
}
}
}

/// Starts a fresh scan. Clears the kept snapshot first so a new scan that finds nothing
/// doesn't leave stale servers from a previous (stopped) scan visible and selectable.
private func startScan() async {
discoveredServers = []
try? await bonjourPico.startScanning()
}
}

Expand Down
Loading