composer changes
This commit is contained in:
@@ -18,7 +18,7 @@ struct StartChatComposer: View {
|
|||||||
/// project page's "+", so the chat always lands in that project.
|
/// project page's "+", so the chat always lands in that project.
|
||||||
var lockedProject: WireProject? = nil
|
var lockedProject: WireProject? = nil
|
||||||
@State private var projectID: ProjectID?
|
@State private var projectID: ProjectID?
|
||||||
@State private var auto = false
|
@State private var autoOverride: Bool?
|
||||||
@State private var model: String?
|
@State private var model: String?
|
||||||
@State private var effort = MobileEfforts.fallback
|
@State private var effort = MobileEfforts.fallback
|
||||||
// Advanced options (the Mac new-session sheet's base branch + worktree fields).
|
// Advanced options (the Mac new-session sheet's base branch + worktree fields).
|
||||||
@@ -31,6 +31,11 @@ struct StartChatComposer: View {
|
|||||||
lockedProject ?? (projects.first { $0.id == projectID } ?? projects.first)
|
lockedProject ?? (projects.first { $0.id == projectID } ?? projects.first)
|
||||||
}
|
}
|
||||||
private var controlled: Bool { selected?.isNucleicControlled ?? false }
|
private var controlled: Bool { selected?.isNucleicControlled ?? false }
|
||||||
|
/// Auto-approve for the chat about to start: the user's choice off-control, but forced on for a
|
||||||
|
/// Nucleic Control project. The host locks Auto on for those chats regardless of what the phone
|
||||||
|
/// sends (they run autonomously — nvrsion needs it: see `AppStore.createSession`), so the
|
||||||
|
/// composer shows it locked on to match the Mac rather than offering a toggle that wouldn't take.
|
||||||
|
private var effectiveAuto: Bool { controlled || (autoOverride ?? false) }
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: 10) {
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
@@ -51,12 +56,17 @@ struct StartChatComposer: View {
|
|||||||
.font(.subheadline).foregroundStyle(.secondary)
|
.font(.subheadline).foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
Toggle(isOn: $auto) {
|
Toggle(isOn: Binding(get: { effectiveAuto }, set: { autoOverride = $0 })) {
|
||||||
Label("Auto", systemImage: auto ? "bolt.fill" : "bolt.slash")
|
Label("Auto", systemImage: effectiveAuto ? "bolt.fill" : "bolt.slash")
|
||||||
}
|
}
|
||||||
.toggleStyle(.button)
|
.toggleStyle(.button)
|
||||||
.tint(Palette.accent)
|
.tint(Palette.accent)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
|
// Locked on for a Nucleic Control project — those chats always run autonomously.
|
||||||
|
.disabled(controlled)
|
||||||
|
.help(controlled
|
||||||
|
? "Auto-approve is always on for Nucleic Control chats — they run autonomously."
|
||||||
|
: "Auto-approve safe actions; destructive ones still ask.")
|
||||||
}
|
}
|
||||||
HStack {
|
HStack {
|
||||||
ModelMenu(model: $model, catalog: store.modelCatalog, backend: nil)
|
ModelMenu(model: $model, catalog: store.modelCatalog, backend: nil)
|
||||||
@@ -104,7 +114,7 @@ struct StartChatComposer: View {
|
|||||||
store.startChat(
|
store.startChat(
|
||||||
in: project.id, message: draft, model: model, effort: effort,
|
in: project.id, message: draft, model: model, effort: effort,
|
||||||
baseBranch: branch.isEmpty ? nil : branch,
|
baseBranch: branch.isEmpty ? nil : branch,
|
||||||
useWorktree: useWorktree, auto: auto)
|
useWorktree: useWorktree, auto: effectiveAuto)
|
||||||
draft = ""
|
draft = ""
|
||||||
onStart?()
|
onStart?()
|
||||||
}
|
}
|
||||||
@@ -123,6 +133,10 @@ struct StartChatComposer: View {
|
|||||||
.padding(.horizontal, 14)
|
.padding(.horizontal, 14)
|
||||||
.padding(.vertical, 12)
|
.padding(.vertical, 12)
|
||||||
.glassSurface(cornerRadius: 26)
|
.glassSurface(cornerRadius: 26)
|
||||||
|
// Prefill the base branch with the project's default so it actually appears (mirroring the
|
||||||
|
// Mac composer's `effectiveBranch`), rather than sitting as an empty placeholder.
|
||||||
|
.onAppear(perform: syncToProject)
|
||||||
|
.onChange(of: projectID) { _, _ in syncToProject() }
|
||||||
// Switching to a model with a lower effort cap can't leave an unsupported level selected.
|
// Switching to a model with a lower effort cap can't leave an unsupported level selected.
|
||||||
.onChange(of: model) { _, newModel in
|
.onChange(of: model) { _, newModel in
|
||||||
let levels = store.modelCatalog.offeredEfforts(forModel: newModel)
|
let levels = store.modelCatalog.offeredEfforts(forModel: newModel)
|
||||||
@@ -132,6 +146,16 @@ struct StartChatComposer: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sync the composer to the freshly-selected project: show its default branch as the base, and
|
||||||
|
/// resolve a carried-over Orchestra selection down to a real level when the new project isn't
|
||||||
|
/// under Nucleic Control (Orchestra can't run there — mirrors the Mac's `effectiveEffort`).
|
||||||
|
private func syncToProject() {
|
||||||
|
baseBranch = selected?.defaultBranch ?? ""
|
||||||
|
if MobileEfforts.isOrchestra(effort), !controlled {
|
||||||
|
effort = store.modelCatalog.offeredEfforts(forModel: model).last ?? MobileEfforts.fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The floating "+" start-chat affordance: a Liquid Glass circle in the lower-right that reveals
|
/// The floating "+" start-chat affordance: a Liquid Glass circle in the lower-right that reveals
|
||||||
|
|||||||
Reference in New Issue
Block a user