Support configuring effort alongside model

This commit is contained in:
Ian Gulliver
2026-10-23 08:40:00 -07:00
parent 6bbae3fffe
commit d6d9dd86d9
40 changed files with 805 additions and 541 deletions
+19 -8
View File
@@ -16,6 +16,7 @@ final class StepNode: Identifiable {
var kind: Kind
var name = ""
var model = ""
var effort = ""
var prompt = ""
var weight = 0.0
var agents: [TeamMember]?
@@ -42,6 +43,7 @@ final class StepNode: Identifiable {
self.init(kind: .stage)
name = step.stage ?? ""
model = step.model ?? ""
effort = step.effort ?? ""
prompt = step.prompt ?? ""
agents = step.agents
}
@@ -52,7 +54,7 @@ final class StepNode: Identifiable {
let w = parent?.kind == .random ? weight : nil
return switch kind {
case .stage:
WorkflowStep(stage: name, model: agents == nil ? model : nil, prompt: prompt, weight: w, agents: agents)
WorkflowStep(stage: name, model: agents == nil ? model : nil, effort: agents == nil && !effort.isEmpty ? effort : nil, prompt: prompt, weight: w, agents: agents)
case .order:
WorkflowStep(order: children.map(\.step), weight: w)
case .parallel:
@@ -90,7 +92,7 @@ final class StepNode: Identifiable {
}
@MainActor
private func newStage(_ root: StepNode, _ model: String) -> StepNode {
private func newStage(_ root: StepNode, _ model: String, _ effort: String) -> StepNode {
let names = Set(root.stageNames())
var n = names.count + 1
while names.contains("Stage \(n)") {
@@ -99,6 +101,7 @@ private func newStage(_ root: StepNode, _ model: String) -> StepNode {
let s = StepNode(kind: .stage)
s.name = "Stage \(n)"
s.model = model
s.effort = effort
s.open = true
return s
}
@@ -251,7 +254,7 @@ struct WorkflowEditor: View {
}
.padding(.top, 12)
Button("Add stage") {
root.insert(newStage(root, store.defaultModel), at: root.children.count)
root.insert(newStage(root, store.defaultModel, store.defaultEffort), at: root.children.count)
}
.buttonStyle(SmallLinkButtonStyle())
.padding(.top, 8)
@@ -285,7 +288,7 @@ struct WorkflowEditor: View {
private func load() async {
guard let original else {
let r = StepNode(kind: .order)
r.insert(newStage(r, store.defaultModel), at: 0)
r.insert(newStage(r, store.defaultModel, store.defaultEffort), at: 0)
root = r
return
}
@@ -355,7 +358,7 @@ private struct StepRow: View {
if let agents = node.agents {
Text("team of \(agents.count)").foregroundStyle(Palette.muted).fixedSize()
} else {
ModelPicker(model: $node.model)
ModelPicker(model: $node.model, effort: $node.effort)
}
case .order:
Text("in order").foregroundStyle(Palette.muted)
@@ -389,7 +392,8 @@ private struct StepRow: View {
TextField("Agent name", text: Binding(get: { node.agents?[i].name ?? "" }, set: { node.agents?[i].name = $0 }))
.textFieldStyle(.plain)
.font(Fonts.font(13, weight: 700))
ModelPicker(model: Binding(get: { node.agents?[i].model ?? "" }, set: { node.agents?[i].model = $0 }))
ModelPicker(model: Binding(get: { node.agents?[i].model ?? "" }, set: { node.agents?[i].model = $0 }),
effort: Binding(get: { node.agents?[i].effort ?? "" }, set: { node.agents?[i].effort = $0.isEmpty ? nil : $0 }))
}
.padding(.top, 4)
PromptEditor(text: Binding(get: { node.agents?[i].prompt ?? "" }, set: { node.agents?[i].prompt = $0 }))
@@ -443,7 +447,7 @@ private struct StepRow: View {
private let groupKinds: [(StepNode.Kind, String)] = [(.order, "in order"), (.parallel, "in parallel"), (.random, "one at random")]
private func stage() -> StepNode {
newStage(root, store.defaultModel)
newStage(root, store.defaultModel, store.defaultEffort)
}
private func addAfter() {
@@ -528,10 +532,17 @@ private struct WeightField: View {
struct ModelPicker: View {
@Binding var model: String
@Binding var effort: String
var body: some View {
let list = model.isEmpty || models.contains(model) ? models : [model] + models
ChoiceMenu(value: $model, options: list.map { ($0, $0) }, placeholder: "model")
HStack(spacing: 6) {
ChoiceMenu(value: $model, options: list.map { ($0, $0) }, placeholder: "model")
ChoiceMenu(value: $effort, options: efforts(model).map { ($0, effortLabel($0)) })
}
.onChange(of: model) {
effort = effortFor(model, effort)
}
}
}