VoiceInput/Sources/UI/AgentProgressTracker.swift
JOJO 76741877a8 feat: 朗读功能、弹窗优化、Node.js 打包、Prompt 更新
- Assist/Agent 模式新增朗读开关 + NSSpeechSynthesizer 语音合成
- 朗读完成后弹窗延迟5秒带动画关闭(不再写死15/20秒)
- 打字模式下不自动关闭弹窗
- 二次输入时取消旧朗读/旧延迟关闭,避免竞争问题
- Agent 进度计时器第二次输入时正确归零
- DMG 打包自动清空 models.json API Key
- Node.js 运行时打包进 Agent/bin/,用户无需安装
- Agent system prompt 更新为 VoiceInput 身份 + 语音容错说明
- AGENTS.md 全面更新,补全 Agent 模式文档
2026-04-29 18:22:14 +08:00

107 lines
3.1 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import SwiftUI
// MARK: - Agent
final class AgentProgressTracker: ObservableObject {
struct ToolEntry: Identifiable {
let id = UUID()
let tool: String
let display: String
var resultLines: [String] = []
}
@Published var mode: String = "idle" // idle / thinking / running / done / error
@Published var elapsedSeconds: Int = 0
@Published var toolEntries: [ToolEntry] = []
@Published var finalContent: String = ""
@Published var errorMessage: String = ""
@Published var streamingContent: String = "" //
@Published var isTextInputMode: Bool = false //
private var timer: Timer?
private var startTime: Date = Date()
private var toolPhaseActive = false //
func start() {
startTime = Date()
elapsedSeconds = 0
mode = "thinking"
toolEntries = []
finalContent = ""
errorMessage = ""
streamingContent = ""
toolPhaseActive = false
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
guard let self = self, self.mode != "done", self.mode != "error" else { return }
self.elapsedSeconds = Int(Date().timeIntervalSince(self.startTime))
}
}
/// thinking
func resetForInput() {
mode = "idle"
elapsedSeconds = 0
toolEntries = []
finalContent = ""
errorMessage = ""
streamingContent = ""
toolPhaseActive = false
timer?.invalidate()
timer = nil
}
func appendChunk(_ chunk: String) {
if toolPhaseActive {
streamingContent = chunk
toolPhaseActive = false
} else {
streamingContent += chunk
}
}
func addTool(_ tool: String, display: String) {
mode = "running"
toolPhaseActive = true //
toolEntries.append(ToolEntry(tool: tool, display: display))
}
func updateToolResult(_ tool: String, resultLines: [String]) {
if let idx = toolEntries.lastIndex(where: { $0.tool == tool && $0.resultLines.isEmpty }) {
toolEntries[idx].resultLines = resultLines
}
}
func finish(content: String) {
mode = "done"
finalContent = content
timer?.invalidate()
timer = nil
}
func fail(_ message: String) {
mode = "error"
errorMessage = message
timer?.invalidate()
timer = nil
}
var formattedTime: String {
let m = elapsedSeconds / 60
let s = elapsedSeconds % 60
return String(format: "%02d:%02d", m, s)
}
var summary: String {
if mode == "done" {
return "工作完成 · \(formattedTime)"
} else if mode == "error" {
return "错误: \(errorMessage)"
} else if mode == "idle" {
return "准备就绪"
} else {
return "工作中 \(formattedTime)"
}
}
}