codex-flow 2.1.13__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- codex_flow/__init__.py +28 -0
- codex_flow/__main__.py +9 -0
- codex_flow/cli.py +242 -0
- codex_flow/data/LICENSE +21 -0
- codex_flow/data/README.en.md +303 -0
- codex_flow/data/README.md +305 -0
- codex_flow/data/VERSION +1 -0
- codex_flow/data/apps/chatgpt-mcp/README.md +86 -0
- codex_flow/data/apps/chatgpt-mcp/__init__.py +1 -0
- codex_flow/data/apps/chatgpt-mcp/adapter.py +458 -0
- codex_flow/data/apps/chatgpt-mcp/server.py +358 -0
- codex_flow/data/apps/chatgpt-mcp/widget.html +927 -0
- codex_flow/data/apps/macos-overlay/README.en.md +121 -0
- codex_flow/data/apps/macos-overlay/README.md +123 -0
- codex_flow/data/apps/macos-overlay/Sources/Controllers/OverlayRuntimeState.swift +126 -0
- codex_flow/data/apps/macos-overlay/Sources/Controllers/OverlayScreenGeometry.swift +82 -0
- codex_flow/data/apps/macos-overlay/Sources/Controllers/OverlayWindowController.swift +1052 -0
- codex_flow/data/apps/macos-overlay/Sources/Localization.swift +197 -0
- codex_flow/data/apps/macos-overlay/Sources/Models/TelemetryData.swift +1557 -0
- codex_flow/data/apps/macos-overlay/Sources/Services/AccountSnapshotService.swift +1101 -0
- codex_flow/data/apps/macos-overlay/Sources/Services/FlowPilotInstanceLock.swift +153 -0
- codex_flow/data/apps/macos-overlay/Sources/Services/IPCServer.swift +298 -0
- codex_flow/data/apps/macos-overlay/Sources/Services/TelemetryQueryEngine.swift +800 -0
- codex_flow/data/apps/macos-overlay/Sources/Services/TelemetryWatcher.swift +135 -0
- codex_flow/data/apps/macos-overlay/Sources/Services/UpdateService.swift +610 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/AccountView.swift +610 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/AnalyticsView.swift +566 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/AutostartView.swift +293 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/BubbleView.swift +317 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/HistoryView.swift +1124 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/HoverRevealText.swift +165 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/InspectorSkillsToolsView.swift +121 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/LogoView.swift +182 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/SleekSwitch.swift +117 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/StrategyModeView.swift +561 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/SummaryView.swift +1273 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/UpdateView.swift +352 -0
- codex_flow/data/apps/macos-overlay/Sources/main.swift +340 -0
- codex_flow/data/apps/macos-overlay/Tests/OverlayScreenGeometryTests.swift +163 -0
- codex_flow/data/apps/macos-overlay/Tests/TelemetryPhase1ContractTests.swift +357 -0
- codex_flow/data/apps/macos-overlay/Tests/TelemetryQueryEngineConcurrencyTests.swift +221 -0
- codex_flow/data/apps/macos-overlay/Tests/TelemetryQuotaSelectionTests.swift +158 -0
- codex_flow/data/apps/macos-overlay/Tests/TelemetryWorkerTokenTests.swift +122 -0
- codex_flow/data/apps/macos-overlay/build.sh +75 -0
- codex_flow/data/benchmark/corpus.json +103 -0
- codex_flow/data/benchmark/manifest.example.json +41 -0
- codex_flow/data/benchmark/manifest.schema.json +137 -0
- codex_flow/data/benchmark/prices/gpt-5.6-2026-08-30.json +5 -0
- codex_flow/data/benchmark/profiles.json +90 -0
- codex_flow/data/benchmark/schema.json +77 -0
- codex_flow/data/benchmark/tasks.json +50 -0
- codex_flow/data/completions/codex-flow.bash +34 -0
- codex_flow/data/completions/codex-flow.zsh +52 -0
- codex_flow/data/glama.json +6 -0
- codex_flow/data/install-release.ps1 +126 -0
- codex_flow/data/install-release.sh +155 -0
- codex_flow/data/install.ps1 +349 -0
- codex_flow/data/install.sh +362 -0
- codex_flow/data/policy/benchmark.toml +49 -0
- codex_flow/data/policy/defaults.toml +70 -0
- codex_flow/data/scripts/analyze-benchmark.py +510 -0
- codex_flow/data/scripts/benchmark-local.py +171 -0
- codex_flow/data/scripts/check-recommendation.py +277 -0
- codex_flow/data/scripts/doctor.py +449 -0
- codex_flow/data/scripts/generate-release-manifest.py +74 -0
- codex_flow/data/scripts/localization.py +192 -0
- codex_flow/data/scripts/manage-hooks.py +448 -0
- codex_flow/data/scripts/manage-instructions.py +389 -0
- codex_flow/data/scripts/manage-shell.py +151 -0
- codex_flow/data/scripts/materialize-corpus.py +193 -0
- codex_flow/data/scripts/menu.py +646 -0
- codex_flow/data/scripts/migrations/0001_update_settings.py +80 -0
- codex_flow/data/scripts/package-release.py +132 -0
- codex_flow/data/scripts/render-benchmark-report.py +292 -0
- codex_flow/data/scripts/run-benchmark.py +829 -0
- codex_flow/data/scripts/strategies/__init__.py +28 -0
- codex_flow/data/scripts/strategies/balanced.py +115 -0
- codex_flow/data/scripts/strategies/base.py +363 -0
- codex_flow/data/scripts/strategies/efficient.py +158 -0
- codex_flow/data/scripts/strategies/lifecycle_runtime.py +590 -0
- codex_flow/data/scripts/strategies/quality.py +209 -0
- codex_flow/data/scripts/strategies/speed.py +108 -0
- codex_flow/data/scripts/strategies/task_budget_runtime.py +644 -0
- codex_flow/data/scripts/strategies/task_phase_runtime.py +341 -0
- codex_flow/data/scripts/strategies/work_unit_runtime.py +421 -0
- codex_flow/data/scripts/strategy_runtime.py +1091 -0
- codex_flow/data/scripts/telemetry.py +400 -0
- codex_flow/data/scripts/telemetry_core/__init__.py +192 -0
- codex_flow/data/scripts/telemetry_core/app_server.py +1192 -0
- codex_flow/data/scripts/telemetry_core/collector.py +1247 -0
- codex_flow/data/scripts/telemetry_core/common.py +421 -0
- codex_flow/data/scripts/telemetry_core/latency.py +593 -0
- codex_flow/data/scripts/telemetry_core/query.py +427 -0
- codex_flow/data/scripts/telemetry_core/quota_ledger.py +598 -0
- codex_flow/data/scripts/telemetry_core/render.py +460 -0
- codex_flow/data/scripts/telemetry_core/repair.py +223 -0
- codex_flow/data/scripts/ui.py +266 -0
- codex_flow/data/scripts/update-homebrew-formula.py +146 -0
- codex_flow/data/scripts/update_runtime_config.py +134 -0
- codex_flow/data/scripts/updater.py +1718 -0
- codex_flow/data/smithery.yaml +18 -0
- codex_flow/data/templates/agents/worker-explorer.toml +24 -0
- codex_flow/data/templates/agents/worker-implementer.toml +49 -0
- codex_flow/data/templates/agents/worker-reviewer.toml +25 -0
- codex_flow/data/templates/flow-pilot-instructions.md +35 -0
- codex_flow/data/templates/skills/flow-pilot/SKILL.md +577 -0
- codex_flow/mcp.py +35 -0
- codex_flow-2.1.13.dist-info/METADATA +342 -0
- codex_flow-2.1.13.dist-info/RECORD +113 -0
- codex_flow-2.1.13.dist-info/WHEEL +5 -0
- codex_flow-2.1.13.dist-info/entry_points.txt +3 -0
- codex_flow-2.1.13.dist-info/licenses/LICENSE +21 -0
- codex_flow-2.1.13.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
/// A compact text label that keeps the normal layout truncated, but reveals the
|
|
5
|
+
/// complete value in a small native popover after a short hover dwell.
|
|
6
|
+
///
|
|
7
|
+
/// Privacy mode intentionally keeps the original text layout and applies the
|
|
8
|
+
/// same 4.5pt blur used by the earlier showcase pipeline. A drawing group bakes
|
|
9
|
+
/// the blur into an offscreen texture so AppKit-backed screenshots preserve the
|
|
10
|
+
/// visual mask instead of degrading it into placeholder bars.
|
|
11
|
+
public struct HoverRevealText: View {
|
|
12
|
+
public let text: String
|
|
13
|
+
public let fullText: String?
|
|
14
|
+
public let font: Font
|
|
15
|
+
public let foregroundColor: Color
|
|
16
|
+
public let lineLimit: Int?
|
|
17
|
+
public let truncationMode: Text.TruncationMode
|
|
18
|
+
public let privacyBlur: Bool
|
|
19
|
+
public let alignment: TextAlignment
|
|
20
|
+
public let popoverWidth: CGFloat
|
|
21
|
+
|
|
22
|
+
@State private var isPresented = false
|
|
23
|
+
@State private var hoverGeneration = 0
|
|
24
|
+
|
|
25
|
+
public init(
|
|
26
|
+
_ text: String,
|
|
27
|
+
font: Font = .system(size: 10),
|
|
28
|
+
foregroundColor: Color = .white,
|
|
29
|
+
lineLimit: Int? = 1,
|
|
30
|
+
truncationMode: Text.TruncationMode = .tail,
|
|
31
|
+
privacyBlur: Bool = false,
|
|
32
|
+
alignment: TextAlignment = .leading,
|
|
33
|
+
popoverWidth: CGFloat = 340,
|
|
34
|
+
fullText: String? = nil
|
|
35
|
+
) {
|
|
36
|
+
self.text = text
|
|
37
|
+
self.fullText = fullText
|
|
38
|
+
self.font = font
|
|
39
|
+
self.foregroundColor = foregroundColor
|
|
40
|
+
self.lineLimit = lineLimit
|
|
41
|
+
self.truncationMode = truncationMode
|
|
42
|
+
self.privacyBlur = privacyBlur
|
|
43
|
+
self.alignment = alignment
|
|
44
|
+
self.popoverWidth = popoverWidth
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public var body: some View {
|
|
48
|
+
Group {
|
|
49
|
+
if privacyBlur {
|
|
50
|
+
Text(text)
|
|
51
|
+
.font(font)
|
|
52
|
+
.foregroundColor(foregroundColor)
|
|
53
|
+
.lineLimit(lineLimit)
|
|
54
|
+
.truncationMode(truncationMode)
|
|
55
|
+
.multilineTextAlignment(alignment)
|
|
56
|
+
.blur(radius: 4.5)
|
|
57
|
+
.drawingGroup(opaque: false, colorMode: .linear)
|
|
58
|
+
.accessibilityLabel(Text("Private content"))
|
|
59
|
+
} else {
|
|
60
|
+
Text(text)
|
|
61
|
+
.font(font)
|
|
62
|
+
.foregroundColor(foregroundColor)
|
|
63
|
+
.lineLimit(lineLimit)
|
|
64
|
+
.truncationMode(truncationMode)
|
|
65
|
+
.multilineTextAlignment(alignment)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
.contentShape(Rectangle())
|
|
69
|
+
.onHover(perform: handleSourceHover)
|
|
70
|
+
.popover(isPresented: $isPresented, arrowEdge: .bottom) {
|
|
71
|
+
ZStack {
|
|
72
|
+
Color(red: 0.055, green: 0.062, blue: 0.085)
|
|
73
|
+
.ignoresSafeArea()
|
|
74
|
+
|
|
75
|
+
ScrollView(.vertical, showsIndicators: true) {
|
|
76
|
+
expandedText
|
|
77
|
+
.font(font)
|
|
78
|
+
.foregroundColor(.white.opacity(0.92))
|
|
79
|
+
.multilineTextAlignment(.leading)
|
|
80
|
+
.textSelection(.enabled)
|
|
81
|
+
.fixedSize(horizontal: false, vertical: true)
|
|
82
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
83
|
+
.padding(10)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
.frame(width: min(max(popoverWidth, 220), 520))
|
|
87
|
+
.frame(maxHeight: 260)
|
|
88
|
+
.background(Color(red: 0.055, green: 0.062, blue: 0.085))
|
|
89
|
+
.preferredColorScheme(.dark)
|
|
90
|
+
.onHover(perform: handlePopoverHover)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/// Native Foundation Markdown parsing keeps headings, lists, fenced code,
|
|
95
|
+
/// inline code, and links selectable without introducing a WebView or HTML.
|
|
96
|
+
/// A malformed value falls back to the exact source text. Link attributes
|
|
97
|
+
/// are retained only for http/https URLs so local files and script schemes
|
|
98
|
+
/// can never become clickable from telemetry.
|
|
99
|
+
@ViewBuilder
|
|
100
|
+
private var expandedText: some View {
|
|
101
|
+
let content = fullText ?? text
|
|
102
|
+
if let markdown = Self.parseMarkdown(content) {
|
|
103
|
+
Text(markdown)
|
|
104
|
+
} else {
|
|
105
|
+
Text(content)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private static func parseMarkdown(_ source: String) -> AttributedString? {
|
|
110
|
+
do {
|
|
111
|
+
var value = try AttributedString(
|
|
112
|
+
markdown: source,
|
|
113
|
+
options: .init(interpretedSyntax: .full)
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
for run in value.runs {
|
|
117
|
+
guard let link = run.link else { continue }
|
|
118
|
+
let scheme = link.scheme?.lowercased()
|
|
119
|
+
guard scheme == "http" || scheme == "https" else {
|
|
120
|
+
value[run.range].link = nil
|
|
121
|
+
continue
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return value
|
|
125
|
+
} catch {
|
|
126
|
+
return nil
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private func handleSourceHover(_ inside: Bool) {
|
|
131
|
+
hoverGeneration += 1
|
|
132
|
+
let generation = hoverGeneration
|
|
133
|
+
|
|
134
|
+
let content = fullText ?? text
|
|
135
|
+
guard !privacyBlur, !content.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
|
|
136
|
+
isPresented = false
|
|
137
|
+
return
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
let delay = inside ? 0.22 : 0.25
|
|
141
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
|
|
142
|
+
guard generation == hoverGeneration else { return }
|
|
143
|
+
withAnimation(.easeInOut(duration: 0.12)) {
|
|
144
|
+
isPresented = inside
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private func handlePopoverHover(_ inside: Bool) {
|
|
150
|
+
hoverGeneration += 1
|
|
151
|
+
let generation = hoverGeneration
|
|
152
|
+
|
|
153
|
+
if inside {
|
|
154
|
+
isPresented = true
|
|
155
|
+
return
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.16) {
|
|
159
|
+
guard generation == hoverGeneration else { return }
|
|
160
|
+
withAnimation(.easeInOut(duration: 0.12)) {
|
|
161
|
+
isPresented = false
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
|
|
3
|
+
/// Keeps the existing Skills / MCP visibility in the refreshed Inspector while
|
|
4
|
+
/// using the same hover-to-reveal behavior as the rest of the app.
|
|
5
|
+
public struct InspectorSkillsToolsView: View {
|
|
6
|
+
public let run: TaskRun
|
|
7
|
+
@State private var expanded = false
|
|
8
|
+
|
|
9
|
+
public init(run: TaskRun) {
|
|
10
|
+
self.run = run
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
private var skills: [SkillUsage] { run.skillsUsed ?? [] }
|
|
14
|
+
private var tools: [ToolCallInfo] { run.toolsUsed ?? [] }
|
|
15
|
+
|
|
16
|
+
public var body: some View {
|
|
17
|
+
VStack(alignment: .leading, spacing: 6) {
|
|
18
|
+
Button {
|
|
19
|
+
withAnimation(.easeInOut(duration: 0.15)) {
|
|
20
|
+
expanded.toggle()
|
|
21
|
+
}
|
|
22
|
+
} label: {
|
|
23
|
+
HStack(spacing: 4) {
|
|
24
|
+
Image(systemName: "square.grid.2x2.fill")
|
|
25
|
+
.font(.system(size: 8))
|
|
26
|
+
.foregroundColor(.purple)
|
|
27
|
+
Text(L("Skills & Tools", "技能与工具"))
|
|
28
|
+
.font(.system(size: 8.8, weight: .bold, design: .rounded))
|
|
29
|
+
.foregroundColor(.white.opacity(0.7))
|
|
30
|
+
Spacer()
|
|
31
|
+
Text(L("\(skills.count) skills · \(tools.count) tools", "\(skills.count) 个技能 · \(tools.count) 个工具"))
|
|
32
|
+
.font(.system(size: 7.3, weight: .medium))
|
|
33
|
+
.foregroundColor(.white.opacity(0.38))
|
|
34
|
+
Image(systemName: expanded ? "chevron.up" : "chevron.down")
|
|
35
|
+
.font(.system(size: 7, weight: .bold))
|
|
36
|
+
.foregroundColor(.white.opacity(0.35))
|
|
37
|
+
}
|
|
38
|
+
.contentShape(Rectangle())
|
|
39
|
+
}
|
|
40
|
+
.buttonStyle(.plain)
|
|
41
|
+
|
|
42
|
+
if expanded {
|
|
43
|
+
if !skills.isEmpty {
|
|
44
|
+
compactSection(title: L("Skills", "技能"), icon: "sparkles", tint: .purple) {
|
|
45
|
+
ForEach(skills) { skill in
|
|
46
|
+
compactItem(
|
|
47
|
+
text: skill.name,
|
|
48
|
+
trailing: skill.count.map { "×\($0)" },
|
|
49
|
+
tint: .purple
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if !tools.isEmpty {
|
|
56
|
+
compactSection(title: L("Tools", "工具"), icon: "wrench.and.screwdriver.fill", tint: .teal) {
|
|
57
|
+
ForEach(tools) { tool in
|
|
58
|
+
compactItem(
|
|
59
|
+
text: tool.displayName,
|
|
60
|
+
trailing: tool.count.map { "×\($0)" },
|
|
61
|
+
tint: tool.isMcp == true ? .cyan : .teal
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
.padding(8)
|
|
69
|
+
.background(
|
|
70
|
+
RoundedRectangle(cornerRadius: 9)
|
|
71
|
+
.fill(Color.white.opacity(0.035))
|
|
72
|
+
.overlay(
|
|
73
|
+
RoundedRectangle(cornerRadius: 9)
|
|
74
|
+
.stroke(Color.white.opacity(0.07), lineWidth: 0.7)
|
|
75
|
+
)
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private func compactSection<Content: View>(
|
|
80
|
+
title: String,
|
|
81
|
+
icon: String,
|
|
82
|
+
tint: Color,
|
|
83
|
+
@ViewBuilder content: () -> Content
|
|
84
|
+
) -> some View {
|
|
85
|
+
VStack(alignment: .leading, spacing: 4) {
|
|
86
|
+
Label(title, systemImage: icon)
|
|
87
|
+
.font(.system(size: 7.6, weight: .semibold))
|
|
88
|
+
.foregroundColor(tint.opacity(0.85))
|
|
89
|
+
|
|
90
|
+
VStack(spacing: 3) {
|
|
91
|
+
content()
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private func compactItem(text: String, trailing: String?, tint: Color) -> some View {
|
|
97
|
+
HStack(spacing: 5) {
|
|
98
|
+
Circle()
|
|
99
|
+
.fill(tint.opacity(0.85))
|
|
100
|
+
.frame(width: 4, height: 4)
|
|
101
|
+
|
|
102
|
+
HoverRevealText(
|
|
103
|
+
text,
|
|
104
|
+
font: .system(size: 8, weight: .medium, design: .monospaced),
|
|
105
|
+
foregroundColor: .white.opacity(0.68),
|
|
106
|
+
lineLimit: 1,
|
|
107
|
+
popoverWidth: 380
|
|
108
|
+
)
|
|
109
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
110
|
+
|
|
111
|
+
if let trailing {
|
|
112
|
+
Text(trailing)
|
|
113
|
+
.font(.system(size: 7.2, weight: .bold, design: .monospaced))
|
|
114
|
+
.foregroundColor(tint.opacity(0.75))
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
.padding(.horizontal, 6)
|
|
118
|
+
.padding(.vertical, 3)
|
|
119
|
+
.background(RoundedRectangle(cornerRadius: 6).fill(Color.white.opacity(0.022)))
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
import AppKit
|
|
3
|
+
|
|
4
|
+
public struct FlowPilotLogoView: View {
|
|
5
|
+
public var size: CGFloat
|
|
6
|
+
public var showGlow: Bool
|
|
7
|
+
public var withBolt: Bool
|
|
8
|
+
public var text: String
|
|
9
|
+
|
|
10
|
+
public init(
|
|
11
|
+
size: CGFloat = 256,
|
|
12
|
+
showGlow: Bool = true,
|
|
13
|
+
withBolt: Bool = true,
|
|
14
|
+
text: String = "FlowPilot"
|
|
15
|
+
) {
|
|
16
|
+
self.size = size
|
|
17
|
+
self.showGlow = showGlow
|
|
18
|
+
self.withBolt = withBolt
|
|
19
|
+
self.text = text
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private var circleDiameter: CGFloat { size * 0.76 }
|
|
23
|
+
private var beaconSize: CGFloat { circleDiameter * 0.138 }
|
|
24
|
+
private var sparklesSize: CGFloat { circleDiameter * 0.31 }
|
|
25
|
+
private var beaconOffset: CGFloat { circleDiameter * 0.35 }
|
|
26
|
+
|
|
27
|
+
public var body: some View {
|
|
28
|
+
ZStack {
|
|
29
|
+
// 1. Dark Frosted Glass Inner Body
|
|
30
|
+
Circle()
|
|
31
|
+
.fill(
|
|
32
|
+
RadialGradient(
|
|
33
|
+
colors: [
|
|
34
|
+
Color(red: 0.15, green: 0.17, blue: 0.24).opacity(0.96),
|
|
35
|
+
Color(red: 0.07, green: 0.08, blue: 0.12).opacity(0.98)
|
|
36
|
+
],
|
|
37
|
+
center: .center,
|
|
38
|
+
startRadius: circleDiameter * 0.08,
|
|
39
|
+
endRadius: circleDiameter * 0.5
|
|
40
|
+
)
|
|
41
|
+
)
|
|
42
|
+
.overlay(
|
|
43
|
+
Circle()
|
|
44
|
+
.stroke(
|
|
45
|
+
LinearGradient(
|
|
46
|
+
colors: [
|
|
47
|
+
Color.white.opacity(0.28),
|
|
48
|
+
Color.white.opacity(0.08),
|
|
49
|
+
Color.cyan.opacity(0.20)
|
|
50
|
+
],
|
|
51
|
+
startPoint: .topLeading,
|
|
52
|
+
endPoint: .bottomTrailing
|
|
53
|
+
),
|
|
54
|
+
lineWidth: max(1.0, circleDiameter * 0.015)
|
|
55
|
+
)
|
|
56
|
+
)
|
|
57
|
+
.frame(width: circleDiameter, height: circleDiameter)
|
|
58
|
+
|
|
59
|
+
// 2. Dynamic Neon Rainbow Border Glow
|
|
60
|
+
Circle()
|
|
61
|
+
.strokeBorder(
|
|
62
|
+
AngularGradient(
|
|
63
|
+
colors: [
|
|
64
|
+
Color(red: 0.0, green: 0.85, blue: 1.0), // Cyan
|
|
65
|
+
Color(red: 0.35, green: 0.45, blue: 1.0), // Indigo
|
|
66
|
+
Color(red: 0.75, green: 0.35, blue: 0.95), // Purple
|
|
67
|
+
Color(red: 1.0, green: 0.35, blue: 0.65), // Pink
|
|
68
|
+
Color(red: 0.0, green: 0.9, blue: 0.75), // Teal
|
|
69
|
+
Color(red: 0.0, green: 0.85, blue: 1.0) // Cyan
|
|
70
|
+
],
|
|
71
|
+
center: .center
|
|
72
|
+
),
|
|
73
|
+
lineWidth: max(1.8, circleDiameter * 0.028)
|
|
74
|
+
)
|
|
75
|
+
.shadow(
|
|
76
|
+
color: Color.cyan.opacity(showGlow ? 0.75 : 0.0),
|
|
77
|
+
radius: max(3.0, circleDiameter * 0.06)
|
|
78
|
+
)
|
|
79
|
+
.shadow(
|
|
80
|
+
color: Color(red: 0.7, green: 0.3, blue: 0.9).opacity(showGlow ? 0.45 : 0.0),
|
|
81
|
+
radius: max(6.0, circleDiameter * 0.1)
|
|
82
|
+
)
|
|
83
|
+
.frame(width: circleDiameter, height: circleDiameter)
|
|
84
|
+
|
|
85
|
+
// 3. Central Content Stack
|
|
86
|
+
VStack(spacing: circleDiameter * 0.04) {
|
|
87
|
+
// Sparkles Hero Glyph
|
|
88
|
+
Image(systemName: "sparkles")
|
|
89
|
+
.font(.system(size: sparklesSize, weight: .bold))
|
|
90
|
+
.foregroundStyle(
|
|
91
|
+
LinearGradient(
|
|
92
|
+
colors: [
|
|
93
|
+
Color.white,
|
|
94
|
+
Color(red: 0.2, green: 0.85, blue: 1.0),
|
|
95
|
+
Color(red: 0.65, green: 0.4, blue: 0.95)
|
|
96
|
+
],
|
|
97
|
+
startPoint: .topLeading,
|
|
98
|
+
endPoint: .bottomTrailing
|
|
99
|
+
)
|
|
100
|
+
)
|
|
101
|
+
.shadow(color: Color.cyan.opacity(0.8), radius: max(2.5, sparklesSize * 0.25))
|
|
102
|
+
.shadow(color: Color.purple.opacity(0.5), radius: max(5.0, sparklesSize * 0.4))
|
|
103
|
+
.offset(y: circleDiameter * 0.015)
|
|
104
|
+
|
|
105
|
+
// Bottom Pill Badge with "FlowPilot" (replacing token quantity)
|
|
106
|
+
HStack(spacing: circleDiameter * 0.025) {
|
|
107
|
+
if withBolt {
|
|
108
|
+
Image(systemName: "bolt.fill")
|
|
109
|
+
.font(.system(size: circleDiameter * 0.095, weight: .heavy))
|
|
110
|
+
.foregroundColor(Color(red: 0.2, green: 0.88, blue: 0.45))
|
|
111
|
+
.shadow(color: Color(red: 0.2, green: 0.88, blue: 0.45).opacity(0.8), radius: 2)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
Text(text)
|
|
115
|
+
.font(.system(
|
|
116
|
+
size: withBolt ? circleDiameter * 0.112 : circleDiameter * 0.125,
|
|
117
|
+
weight: .heavy,
|
|
118
|
+
design: .rounded
|
|
119
|
+
))
|
|
120
|
+
.foregroundColor(Color.white.opacity(0.98))
|
|
121
|
+
.shadow(color: Color.black.opacity(0.8), radius: 2, y: 1)
|
|
122
|
+
}
|
|
123
|
+
.padding(.horizontal, circleDiameter * 0.055)
|
|
124
|
+
.padding(.vertical, circleDiameter * 0.022)
|
|
125
|
+
.background(
|
|
126
|
+
Capsule()
|
|
127
|
+
.fill(Color(red: 0.04, green: 0.05, blue: 0.08).opacity(0.88))
|
|
128
|
+
)
|
|
129
|
+
.overlay(
|
|
130
|
+
Capsule()
|
|
131
|
+
.stroke(
|
|
132
|
+
LinearGradient(
|
|
133
|
+
colors: [
|
|
134
|
+
Color.white.opacity(0.35),
|
|
135
|
+
Color.cyan.opacity(0.35),
|
|
136
|
+
Color.white.opacity(0.12)
|
|
137
|
+
],
|
|
138
|
+
startPoint: .topLeading,
|
|
139
|
+
endPoint: .bottomTrailing
|
|
140
|
+
),
|
|
141
|
+
lineWidth: max(0.8, circleDiameter * 0.012)
|
|
142
|
+
)
|
|
143
|
+
)
|
|
144
|
+
.shadow(color: Color.black.opacity(0.6), radius: 4, y: 2)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// 4. Emerald Active Beacon Dot (Upper Right)
|
|
148
|
+
ZStack {
|
|
149
|
+
// Radiant beacon bloom
|
|
150
|
+
Circle()
|
|
151
|
+
.fill(Color(red: 0.2, green: 0.88, blue: 0.45).opacity(0.4))
|
|
152
|
+
.frame(width: beaconSize * 1.6, height: beaconSize * 1.6)
|
|
153
|
+
.blur(radius: beaconSize * 0.25)
|
|
154
|
+
|
|
155
|
+
// Solid beacon dot
|
|
156
|
+
Circle()
|
|
157
|
+
.fill(
|
|
158
|
+
RadialGradient(
|
|
159
|
+
colors: [
|
|
160
|
+
Color(red: 0.4, green: 1.0, blue: 0.65),
|
|
161
|
+
Color(red: 0.15, green: 0.82, blue: 0.4)
|
|
162
|
+
],
|
|
163
|
+
center: .center,
|
|
164
|
+
startRadius: 0,
|
|
165
|
+
endRadius: beaconSize * 0.5
|
|
166
|
+
)
|
|
167
|
+
)
|
|
168
|
+
.frame(width: beaconSize, height: beaconSize)
|
|
169
|
+
.overlay(
|
|
170
|
+
Circle()
|
|
171
|
+
.stroke(Color(red: 0.05, green: 0.06, blue: 0.09), lineWidth: max(1.2, beaconSize * 0.16))
|
|
172
|
+
)
|
|
173
|
+
.shadow(
|
|
174
|
+
color: Color(red: 0.2, green: 0.88, blue: 0.45).opacity(0.95),
|
|
175
|
+
radius: max(3.0, beaconSize * 0.35)
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
.offset(x: beaconOffset, y: -beaconOffset)
|
|
179
|
+
}
|
|
180
|
+
.frame(width: size, height: size)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
|
|
3
|
+
/// A modern, tactile switch toggle style tailored for dark glassmorphic overlays.
|
|
4
|
+
///
|
|
5
|
+
/// Features a vibrant luminous track when active, a clean translucent dark glass
|
|
6
|
+
/// track when inactive, smooth spring physics, subtle depth drop-shadows, and
|
|
7
|
+
/// clear visual contrast in macOS dark mode.
|
|
8
|
+
public struct SleekSwitchToggleStyle: ToggleStyle {
|
|
9
|
+
public var tint: Color
|
|
10
|
+
public var width: CGFloat
|
|
11
|
+
public var height: CGFloat
|
|
12
|
+
|
|
13
|
+
public init(tint: Color = .cyan, width: CGFloat = 34, height: CGFloat = 19) {
|
|
14
|
+
self.tint = tint
|
|
15
|
+
self.width = width
|
|
16
|
+
self.height = height
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public func makeBody(configuration: Configuration) -> some View {
|
|
20
|
+
SleekSwitchControl(configuration: configuration, tint: tint, width: width, height: height)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
private struct SleekSwitchControl: View {
|
|
25
|
+
let configuration: ToggleStyle.Configuration
|
|
26
|
+
let tint: Color
|
|
27
|
+
let width: CGFloat
|
|
28
|
+
let height: CGFloat
|
|
29
|
+
|
|
30
|
+
@Environment(\.isEnabled) private var isEnabled
|
|
31
|
+
@State private var isHovered = false
|
|
32
|
+
|
|
33
|
+
private var knobSize: CGFloat { height - 4 }
|
|
34
|
+
private var travelDistance: CGFloat { width - knobSize - 4 }
|
|
35
|
+
|
|
36
|
+
var body: some View {
|
|
37
|
+
Button {
|
|
38
|
+
withAnimation(.spring(response: 0.24, dampingFraction: 0.72)) {
|
|
39
|
+
configuration.isOn.toggle()
|
|
40
|
+
}
|
|
41
|
+
} label: {
|
|
42
|
+
ZStack(alignment: .leading) {
|
|
43
|
+
// Background Track
|
|
44
|
+
Capsule(style: .continuous)
|
|
45
|
+
.fill(
|
|
46
|
+
configuration.isOn
|
|
47
|
+
? LinearGradient(
|
|
48
|
+
colors: [tint, tint.opacity(0.82)],
|
|
49
|
+
startPoint: .topLeading,
|
|
50
|
+
endPoint: .bottomTrailing
|
|
51
|
+
)
|
|
52
|
+
: LinearGradient(
|
|
53
|
+
colors: [
|
|
54
|
+
Color.white.opacity(isHovered ? 0.16 : 0.11),
|
|
55
|
+
Color.white.opacity(isHovered ? 0.09 : 0.06)
|
|
56
|
+
],
|
|
57
|
+
startPoint: .top,
|
|
58
|
+
endPoint: .bottom
|
|
59
|
+
)
|
|
60
|
+
)
|
|
61
|
+
.overlay(
|
|
62
|
+
Capsule(style: .continuous)
|
|
63
|
+
.stroke(
|
|
64
|
+
configuration.isOn
|
|
65
|
+
? tint.opacity(0.55)
|
|
66
|
+
: Color.white.opacity(isHovered ? 0.18 : 0.12),
|
|
67
|
+
lineWidth: 0.8
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
// Active subtle ambient glow
|
|
72
|
+
if configuration.isOn {
|
|
73
|
+
Capsule(style: .continuous)
|
|
74
|
+
.stroke(tint.opacity(0.4), lineWidth: 1.5)
|
|
75
|
+
.blur(radius: 2)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Knob
|
|
79
|
+
Circle()
|
|
80
|
+
.fill(
|
|
81
|
+
configuration.isOn
|
|
82
|
+
? LinearGradient(
|
|
83
|
+
colors: [.white, Color(white: 0.94)],
|
|
84
|
+
startPoint: .top,
|
|
85
|
+
endPoint: .bottom
|
|
86
|
+
)
|
|
87
|
+
: LinearGradient(
|
|
88
|
+
colors: [
|
|
89
|
+
Color(white: isHovered ? 0.92 : 0.86),
|
|
90
|
+
Color(white: isHovered ? 0.82 : 0.76)
|
|
91
|
+
],
|
|
92
|
+
startPoint: .top,
|
|
93
|
+
endPoint: .bottom
|
|
94
|
+
)
|
|
95
|
+
)
|
|
96
|
+
.frame(width: knobSize, height: knobSize)
|
|
97
|
+
.shadow(
|
|
98
|
+
color: Color.black.opacity(configuration.isOn ? 0.35 : 0.28),
|
|
99
|
+
radius: 2,
|
|
100
|
+
x: configuration.isOn ? -0.5 : 0.5,
|
|
101
|
+
y: 1
|
|
102
|
+
)
|
|
103
|
+
.padding(.leading, 2)
|
|
104
|
+
.offset(x: configuration.isOn ? travelDistance : 0)
|
|
105
|
+
.scaleEffect(isHovered ? 1.04 : 1.0)
|
|
106
|
+
}
|
|
107
|
+
.frame(width: width, height: height)
|
|
108
|
+
.contentShape(Capsule(style: .continuous))
|
|
109
|
+
}
|
|
110
|
+
.buttonStyle(.plain)
|
|
111
|
+
.opacity(isEnabled ? 1.0 : 0.42)
|
|
112
|
+
.onHover { isHovered = $0 }
|
|
113
|
+
.accessibilityElement(children: .combine)
|
|
114
|
+
.accessibilityValue(configuration.isOn ? "On" : "Off")
|
|
115
|
+
.accessibilityAddTraits(.isButton)
|
|
116
|
+
}
|
|
117
|
+
}
|