Skip to content

Commit f018fe3

Browse files
committed
fix: minimax reasoning in macos
Signed-off-by: xunzhuo <xunzhuo@vllm-semantic-router.ai>
1 parent e52f2ea commit f018fe3

8 files changed

Lines changed: 1654 additions & 272 deletions

File tree

apps/macos/Sources/AppModel.swift

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -160,13 +160,24 @@ struct ChatMessage: Identifiable, Equatable {
160160
case system
161161
}
162162

163+
enum InputModality: Equatable {
164+
case text
165+
case voice
166+
}
167+
163168
var id = UUID()
164169
var role: Role
165170
var text: String
166171
var date = Date()
167172
var attachments: [WakeAttachment] = []
168173
var toolEvents: [ToolUseEvent] = []
169174
var isStreaming = false
175+
var inputModality: InputModality = .text
176+
var voiceDuration: TimeInterval?
177+
178+
var isVoiceMessage: Bool {
179+
role == .user && inputModality == .voice
180+
}
170181
}
171182

172183
struct WakeAttachment: Identifiable, Equatable {
@@ -189,8 +200,13 @@ struct WakeQueuedPrompt: Identifiable, Equatable {
189200
var text: String
190201
var attachments: [WakeAttachment] = []
191202
var date = Date()
203+
var inputModality: ChatMessage.InputModality = .text
204+
var voiceDuration: TimeInterval?
192205

193206
var previewText: String {
207+
if inputModality == .voice {
208+
return "Voice message"
209+
}
194210
let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
195211
if !trimmed.isEmpty {
196212
return trimmed
@@ -1590,7 +1606,6 @@ final class ElephantAppModel: ObservableObject {
15901606
if status.contains("completed") || status.contains("succeeded") || status == "success" {
15911607
onboardingFinalizationStatus = text(.learningReady)
15921608
onboardingFinalizationComplete = true
1593-
onboardingStep = 17
15941609
scheduleOnboardingAutoCompletion()
15951610
return
15961611
}
@@ -1602,7 +1617,6 @@ final class ElephantAppModel: ObservableObject {
16021617
}
16031618
onboardingFinalizationStatus = text(.learningReady)
16041619
onboardingFinalizationComplete = true
1605-
onboardingStep = 17
16061620
scheduleOnboardingAutoCompletion()
16071621
}
16081622

@@ -2787,12 +2801,20 @@ final class ElephantAppModel: ObservableObject {
27872801
}
27882802

27892803
func sendWakeMessage() async {
2804+
await enqueueWakeMessage(inputModality: .text, voiceDuration: nil)
2805+
}
2806+
2807+
func sendVoiceWakeMessage(duration: TimeInterval?) async {
2808+
await enqueueWakeMessage(inputModality: .voice, voiceDuration: duration)
2809+
}
2810+
2811+
private func enqueueWakeMessage(inputModality: ChatMessage.InputModality, voiceDuration: TimeInterval?) async {
27902812
let text = wakeDraft.trimmingCharacters(in: .whitespacesAndNewlines)
27912813
let attachments = wakeAttachments
27922814
guard !text.isEmpty || !attachments.isEmpty else { return }
27932815
wakeDraft = ""
27942816
wakeAttachments = []
2795-
wakeQueue.append(WakeQueuedPrompt(text: text, attachments: attachments))
2817+
wakeQueue.append(WakeQueuedPrompt(text: text, attachments: attachments, inputModality: inputModality, voiceDuration: voiceDuration))
27962818
focusComposer()
27972819
await drainWakeQueueIfNeeded()
27982820
}
@@ -2928,12 +2950,22 @@ final class ElephantAppModel: ObservableObject {
29282950
}
29292951
while !wakeQueue.isEmpty {
29302952
let item = wakeQueue.removeFirst()
2931-
await runWakeMessage(item.text, attachments: item.attachments)
2953+
await runWakeMessage(
2954+
item.text,
2955+
attachments: item.attachments,
2956+
inputModality: item.inputModality,
2957+
voiceDuration: item.voiceDuration
2958+
)
29322959
}
29332960
}
29342961

2935-
private func runWakeMessage(_ text: String, attachments: [WakeAttachment]) async {
2936-
messages.append(ChatMessage(role: .user, text: text, attachments: attachments))
2962+
private func runWakeMessage(
2963+
_ text: String,
2964+
attachments: [WakeAttachment],
2965+
inputModality: ChatMessage.InputModality,
2966+
voiceDuration: TimeInterval?
2967+
) async {
2968+
messages.append(ChatMessage(role: .user, text: text, attachments: attachments, inputModality: inputModality, voiceDuration: voiceDuration))
29372969
chatScrollRevision += 1
29382970

29392971
let prompt = Self.wakePrompt(text: text, attachments: attachments)

apps/macos/Sources/DesignSystem.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,7 @@ struct ProviderLogoView: View {
357357
if normalized.contains("openclaw") { return "openclaw" }
358358
if normalized.contains("opencode") { return "opencode" }
359359
if normalized.contains("kimi") || normalized.contains("moonshot") { return "kimi" }
360+
if normalized.contains("minimax") { return "minimax" }
360361
if normalized == "pi" || normalized.contains("inflection") { return "pi" }
361362
if normalized.contains("codex") || normalized.contains("openai") { return "codex" }
362363
return normalized.isEmpty ? "agent" : normalized
@@ -392,6 +393,7 @@ struct ProviderLogoView: View {
392393
if normalizedProvider.contains("openclaw") { return "OpenClaw" }
393394
if normalizedProvider.contains("opencode") { return "OpenCode" }
394395
if normalizedProvider.contains("kimi") || normalizedProvider.contains("moonshot") { return "Kimi" }
396+
if normalizedProvider.contains("minimax") { return "MiniMax" }
395397
if normalizedProvider.contains("codex") || normalizedProvider.contains("openai") { return "Codex" }
396398
return providerID.isEmpty ? "Local agent" : providerID
397399
}
@@ -405,6 +407,7 @@ struct ProviderLogoView: View {
405407
if normalizedProvider.contains("openclaw") { return "hand.raised" }
406408
if normalizedProvider.contains("opencode") { return "chevron.left.forwardslash.chevron.right" }
407409
if normalizedProvider.contains("kimi") || normalizedProvider.contains("moonshot") { return "moon.stars" }
410+
if normalizedProvider.contains("minimax") { return "sparkles" }
408411
if normalizedProvider.contains("codex") || normalizedProvider.contains("openai") { return "terminal" }
409412
return "terminal"
410413
}
@@ -417,6 +420,7 @@ struct ProviderLogoView: View {
417420
if normalizedProvider.contains("hermes") { return ElephantTheme.orange }
418421
if normalizedProvider.contains("openclaw") { return ElephantTheme.ember }
419422
if normalizedProvider.contains("kimi") || normalizedProvider.contains("moonshot") { return ElephantTheme.green }
423+
if normalizedProvider.contains("minimax") { return ElephantTheme.green }
420424
return ElephantTheme.accent
421425
}
422426
}
8.46 KB
Loading

apps/macos/Sources/SpeechInputController.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import Speech
66
final class SpeechInputController: NSObject, ObservableObject {
77
@Published private(set) var isRecording = false
88
@Published private(set) var statusText = ""
9+
@Published private(set) var recordingStartedAt: Date?
10+
@Published private(set) var capturedDuration: TimeInterval = 0
11+
@Published private(set) var recognizedText = ""
912

1013
private let audioEngine = AVAudioEngine()
1114
private let recognizer = SFSpeechRecognizer(locale: Locale.current)
@@ -26,6 +29,9 @@ final class SpeechInputController: NSObject, ObservableObject {
2629
guard !isRecording else { return }
2730
baseText = text.trimmingCharacters(in: .whitespacesAndNewlines)
2831
self.onText = onText
32+
recognizedText = ""
33+
capturedDuration = 0
34+
recordingStartedAt = nil
2935
statusText = "Requesting microphone access..."
3036

3137
requestMicrophoneAccess { [weak self] allowed in
@@ -50,16 +56,28 @@ final class SpeechInputController: NSObject, ObservableObject {
5056

5157
func stop() {
5258
guard isRecording || audioEngine.isRunning else { return }
59+
updateCapturedDuration()
5360
audioEngine.stop()
5461
audioEngine.inputNode.removeTap(onBus: 0)
5562
recognitionRequest?.endAudio()
5663
recognitionTask?.cancel()
5764
recognitionRequest = nil
5865
recognitionTask = nil
5966
isRecording = false
67+
recordingStartedAt = nil
6068
statusText = "Voice input stopped."
6169
}
6270

71+
func resetCapture() {
72+
if isRecording || audioEngine.isRunning {
73+
stop()
74+
}
75+
recognizedText = ""
76+
capturedDuration = 0
77+
recordingStartedAt = nil
78+
statusText = ""
79+
}
80+
6381
private func requestMicrophoneAccess(_ completion: @escaping (Bool) -> Void) {
6482
switch AVCaptureDevice.authorizationStatus(for: .audio) {
6583
case .authorized:
@@ -105,12 +123,15 @@ final class SpeechInputController: NSObject, ObservableObject {
105123
}
106124

107125
isRecording = true
126+
recordingStartedAt = Date()
127+
capturedDuration = 0
108128
statusText = "Listening..."
109129
recognitionTask = recognizer.recognitionTask(with: recognitionRequest) { [weak self] result, error in
110130
Task { @MainActor [weak self] in
111131
guard let self else { return }
112132
if let result {
113133
let spoken = result.bestTranscription.formattedString.trimmingCharacters(in: .whitespacesAndNewlines)
134+
self.recognizedText = spoken
114135
let combined = [self.baseText, spoken].filter { !$0.isEmpty }.joined(separator: " ")
115136
self.onText?(combined)
116137
self.statusText = result.isFinal ? "Voice input captured." : "Listening..."
@@ -124,6 +145,7 @@ final class SpeechInputController: NSObject, ObservableObject {
124145
}
125146

126147
private func finishRecognition() {
148+
updateCapturedDuration()
127149
if audioEngine.isRunning {
128150
audioEngine.stop()
129151
audioEngine.inputNode.removeTap(onBus: 0)
@@ -132,5 +154,12 @@ final class SpeechInputController: NSObject, ObservableObject {
132154
recognitionRequest = nil
133155
recognitionTask = nil
134156
isRecording = false
157+
recordingStartedAt = nil
158+
}
159+
160+
private func updateCapturedDuration() {
161+
if let recordingStartedAt {
162+
capturedDuration = max(capturedDuration, Date().timeIntervalSince(recordingStartedAt))
163+
}
135164
}
136165
}

0 commit comments

Comments
 (0)