iOS remote: transcript fidelity + mid-session controls & model catalog
Bring NucleicRemote closer to desktop parity in two areas (the core sync loop was already at parity — shared protocol, control scope). Transcript fidelity (iOS): a client-side TranscriptProjection coalesces streaming text by messageID and folds each tool call's lifecycle (start/deltas/complete/result/fileChange) into one expandable card — fixing the duplicate started+completed rows. Adds Markdown bubbles, the gold Orchestra card for Task/Agent spawns, and the previously-dropped usage/cost, rate-limit, file-change, turn-boundary and session-started rows, plus a context-window % header badge. Mid-session controls + model catalog (protocol/host/iOS): project the host ModelCatalog over the wire as WireModelCatalog (in Welcome); add 5 control-scope setters (setSessionModel/Effort/Auto/AutoShip/ShipBranch) backed by the existing AppStore.mutateSession + SessionController hooks; enrich WireSessionSummary with model/effort/auto/autoShip/shipBranch/ contextInputTokens (all forward-compatible). The composer gains a model picker and a catalog-driven effort menu (per-backend caps: Codex→xhigh, Grok→auto), and the session header gains a model/effort/auto/autoship control bar. Tests: CBOR round-trips for the new messages, Welcome.modelCatalog, the new summary fields, forward-compat decode of old bytes, and the setters reaching the host. Verified in the Simulator (NUCLEIC_DEMO=1). Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
e3b6ccc297
commit
e6ef59e830
@@ -8,12 +8,14 @@ struct StartChatComposer: View {
|
||||
@State private var projectID: ProjectID?
|
||||
@State private var draft = ""
|
||||
@State private var auto = false
|
||||
@State private var model: String?
|
||||
@State private var effort = MobileEfforts.fallback
|
||||
|
||||
private var projects: [WireProject] { store.dashboard.projects }
|
||||
private var selected: WireProject? {
|
||||
projects.first { $0.id == projectID } ?? projects.first
|
||||
}
|
||||
private var controlled: Bool { selected?.isNucleicControlled ?? false }
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 10) {
|
||||
@@ -28,7 +30,6 @@ struct StartChatComposer: View {
|
||||
.font(.subheadline)
|
||||
}
|
||||
Spacer()
|
||||
EffortMenu(effort: $effort, controlled: selected?.isNucleicControlled ?? false)
|
||||
Toggle(isOn: $auto) {
|
||||
Label("Auto", systemImage: auto ? "bolt.fill" : "bolt.slash")
|
||||
}
|
||||
@@ -36,14 +37,20 @@ struct StartChatComposer: View {
|
||||
.tint(Palette.accent)
|
||||
.font(.caption)
|
||||
}
|
||||
HStack {
|
||||
// The model also selects the backend, so the home composer offers every provider.
|
||||
ModelMenu(model: $model, catalog: store.modelCatalog, backend: nil)
|
||||
Spacer()
|
||||
EffortMenu(effort: $effort, catalog: store.modelCatalog, modelSKU: model, controlled: controlled)
|
||||
}
|
||||
HStack(alignment: .bottom, spacing: 8) {
|
||||
TextField("Describe a task…", text: $draft, axis: .vertical)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
.lineLimit(1...5)
|
||||
.orchestraGlow(active: MobileEfforts.isOrchestra(effort) && (selected?.isNucleicControlled ?? false))
|
||||
.orchestraGlow(active: MobileEfforts.isOrchestra(effort) && controlled)
|
||||
Button {
|
||||
if let project = selected {
|
||||
store.startChat(in: project.id, message: draft, effort: effort, auto: auto)
|
||||
store.startChat(in: project.id, message: draft, model: model, effort: effort, auto: auto)
|
||||
draft = ""
|
||||
}
|
||||
} label: {
|
||||
@@ -56,6 +63,14 @@ struct StartChatComposer: View {
|
||||
}
|
||||
}
|
||||
.card()
|
||||
// Switching to a model with a lower effort cap can't leave an unsupported level selected.
|
||||
.onChange(of: model) { _, newModel in
|
||||
let levels = store.modelCatalog.offeredEfforts(forModel: newModel)
|
||||
let sentinel = store.modelCatalog.orchestraSentinelOrFallback
|
||||
if effort.caseInsensitiveCompare(sentinel) != .orderedSame, !levels.contains(effort) {
|
||||
effort = levels.last ?? MobileEfforts.fallback
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user