Files
PhoneClaw/UI/ChatModels.swift
T
Kellyvv 0f146d4e8f refactor: flatten structure + remove dead code
- Move PhoneClaw/ contents to project root (no more nesting)
- Delete PhoneClawMac/ (nested SPM package, duplicated iOS code)
- Delete LLM/LocalLLMService.swift (dead MediaPipe implementation)
- Delete llm_inference_engine.h + PhoneClaw-Bridging-Header.h (unused)
- Delete Sources/ (empty dir), phoneclaw.code-workspace, TECH_PATH_ANALYSIS.md, project.yml
- Fix project.pbxproj: update INFOPLIST_FILE, CODE_SIGN_ENTITLEMENTS paths
- Fix project.pbxproj: remove SWIFT_OBJC_BRIDGING_HEADER setting
- Fix project.pbxproj: remove LocalLLMService.swift file reference
- Fix project.pbxproj: PhoneClaw group now uses name= instead of path=
2026-04-06 01:33:10 +08:00

102 lines
3.3 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: - 显示模型(跨平台共享)
/// 单个 Skill 卡片数据
struct SkillCard: Identifiable, Equatable {
let id: UUID
var skillName: String
var skillStatus: String? // "identified", "loaded", "executing", "done"
var toolName: String? // 正在执行的具体 Tool 名(如 "device-info")
}
/// AI 回复块:多张 skill 卡片 + 思考动画 + 回复文本
struct ResponseBlock: Identifiable, Equatable {
let id: UUID
var skills: [SkillCard] = []
var responseText: String?
var isThinking: Bool
}
/// 聊天列表的统一显示项
enum DisplayItem: Identifiable {
case user(ChatMessage)
case response(ResponseBlock)
var id: UUID {
switch self {
case .user(let msg): return msg.id
case .response(let block): return block.id
}
}
}
// MARK: - Messages → DisplayItems 转换(跨平台共享)
/// 用于纯思考占位的稳定 ID
private let thinkingPlaceholderID = UUID()
func buildDisplayItems(from messages: [ChatMessage], isProcessing: Bool) -> [DisplayItem] {
var items: [DisplayItem] = []
var block: ResponseBlock? = nil
func flush() {
if let b = block { items.append(.response(b)); block = nil }
}
for msg in messages {
switch msg.role {
case .user:
flush()
items.append(.user(msg))
case .system:
if let name = msg.skillName {
if block == nil { block = ResponseBlock(id: msg.id, isThinking: false) }
let content = msg.content
// 查找已有的同名卡片 → 更新状态;否则新建
if let idx = block?.skills.firstIndex(where: { $0.skillName == name }) {
if content.hasPrefix("executing:") {
block?.skills[idx].skillStatus = "executing"
block?.skills[idx].toolName = String(content.dropFirst("executing:".count))
} else {
block?.skills[idx].skillStatus = content
}
} else {
var card = SkillCard(id: msg.id, skillName: name)
if content.hasPrefix("executing:") {
card.skillStatus = "executing"
card.toolName = String(content.dropFirst("executing:".count))
} else {
card.skillStatus = content
}
block?.skills.append(card)
}
}
case .skillResult:
break
case .assistant:
if block == nil { block = ResponseBlock(id: msg.id, isThinking: false) }
if msg.content != "▍" && !msg.content.isEmpty {
block?.responseText = msg.content
}
}
}
if var b = block, !b.skills.isEmpty || b.responseText != nil {
if isProcessing && !b.skills.isEmpty && b.responseText == nil {
b.isThinking = true
}
items.append(.response(b))
}
if isProcessing {
let hasAI = block.map { !$0.skills.isEmpty || $0.responseText != nil } ?? false
if !hasAI {
items.append(.response(ResponseBlock(id: thinkingPlaceholderID, isThinking: true)))
}
}
return items
}