mirror of
https://github.com/wu736139669/hapi.git
synced 2026-10-10 19:42:21 +00:00
79 lines
3.9 KiB
Swift
79 lines
3.9 KiB
Swift
import Foundation
|
|
import HapiProtocol
|
|
import HapiUI
|
|
import SwiftUI
|
|
|
|
extension HapiTypography {
|
|
// Use the same resolved metrics as markdown, not the UIKit hosting cell's
|
|
// system font defaults. Baselines remain subheadline (15) / footnote (13).
|
|
var toolTitleFont: Font { .system(size: 15 * bodyScale, weight: boldText ? .semibold : .medium) }
|
|
var toolSubtitleFont: Font { .system(size: 13 * codeScale, weight: boldText ? .semibold : .regular) }
|
|
var toolIconWidth: CGFloat { 18 * codeScale }
|
|
var usesStackedToolLayout: Bool { bodySize > 24 }
|
|
}
|
|
|
|
/// Conversation-only labels. Keep the inspector's original tool/input presentation
|
|
/// intact: a useful activity summary is not a replacement for the recorded call.
|
|
func toolSummaryPresentation(_ tool: ChatToolCall, basePath: String?) -> ToolCardPresentation {
|
|
var canonical = tool
|
|
canonical.name = toolPresentationName(tool.name)
|
|
var presentation = toolCardPresentation(canonical, basePath: basePath)
|
|
let input = tool.input
|
|
|
|
func file(_ action: String, keys: [String], icon: String? = nil) -> ToolCardPresentation {
|
|
let target = chatInputString(input, keys).map(summaryBasename)
|
|
return ToolCardPresentation(
|
|
icon: icon ?? presentation.icon,
|
|
title: target.map { "\(action) · \($0)" } ?? action,
|
|
subtitle: nil
|
|
)
|
|
}
|
|
|
|
switch canonical.name {
|
|
case "Read", "NotebookRead":
|
|
return file(String(localized: "Read file"), keys: ["file_path", "path", "file", "notebook_path"])
|
|
case "Edit", "MultiEdit", "NotebookEdit":
|
|
return file(String(localized: "Edit file"), keys: ["file_path", "path", "notebook_path"])
|
|
case "Write":
|
|
return file(String(localized: "Write file"), keys: ["file_path", "path"])
|
|
case "LS":
|
|
return file(String(localized: "List files"), keys: ["path"])
|
|
case "CodexBash":
|
|
if let parsed = input?[chatKey: "parsed_cmd"]?.chatArray, parsed.count == 1,
|
|
parsed[0][chatKey: "type"]?.chatString == "read",
|
|
let path = parsed[0][chatKey: "name"]?.chatString {
|
|
return ToolCardPresentation(icon: "eye", title: "\(String(localized: "Read file")) · \(summaryBasename(path))", subtitle: nil)
|
|
}
|
|
case "view_image":
|
|
return file(String(localized: "View image"), keys: ["path", "file_path", "image_path"], icon: "photo")
|
|
case "mcp__hapi__display_image", "hapi__display_image", "hapi_display_image":
|
|
return file(String(localized: "Show image"), keys: ["path"], icon: "photo")
|
|
case "mcp__hapi__display_video", "hapi__display_video", "hapi_display_video":
|
|
return file(String(localized: "Show video"), keys: ["path"], icon: "film")
|
|
case "mcp__hapi__display_media", "hapi__display_media", "hapi_display_media":
|
|
return file(String(localized: "Share file"), keys: ["path"], icon: "doc")
|
|
case "exec":
|
|
return ToolCardPresentation(icon: "curlybraces", title: tool.description ?? String(localized: "Run script"), subtitle: nil)
|
|
case "wait":
|
|
return ToolCardPresentation(icon: "hourglass", title: String(localized: "Wait for result"), subtitle: nil)
|
|
case "write_stdin":
|
|
return ToolCardPresentation(icon: "terminal", title: String(localized: "Continue command"), subtitle: nil)
|
|
default:
|
|
break
|
|
}
|
|
if let subtitle = presentation.subtitle {
|
|
// Don't waste the preview on indentation, line breaks, or a long directory.
|
|
if let path = chatInputString(input, ["file_path", "path", "filePath", "file"]), subtitle == chatTruncate(path, 80) {
|
|
presentation.subtitle = summaryBasename(path)
|
|
} else {
|
|
presentation.subtitle = subtitle.prefix(240).split(whereSeparator: \.isWhitespace).joined(separator: " ")
|
|
}
|
|
if presentation.subtitle == presentation.title { presentation.subtitle = nil }
|
|
}
|
|
return presentation
|
|
}
|
|
|
|
private func summaryBasename(_ path: String) -> String {
|
|
path.split(separator: "/").last.map(String.init) ?? path
|
|
}
|