112 lines
4.4 KiB
Swift
112 lines
4.4 KiB
Swift
import AgentKit
|
|
import SwiftUI
|
|
import UniformTypeIdentifiers
|
|
|
|
public struct SkillPackageEditor: View {
|
|
@Binding private var package: AgentSkillPackage?
|
|
private let readResource: (URL) throws -> Data
|
|
@State private var importing = false
|
|
@State private var exporting = false
|
|
@State private var exportData = Data()
|
|
@State private var exportName = "resource"
|
|
@State private var editing = false
|
|
@State private var path = ""
|
|
@State private var text = ""
|
|
@State private var error: String?
|
|
public init(package: Binding<AgentSkillPackage?>, readResource: @escaping (URL) throws -> Data) {
|
|
_package = package
|
|
self.readResource = readResource
|
|
}
|
|
public var body: some View {
|
|
Section("Supporting Files") {
|
|
ForEach(package?.files.keys.sorted() ?? [], id: \.self) { path in
|
|
HStack {
|
|
Button(path) {
|
|
self.path = path
|
|
text = package?.files[path].flatMap { String(data: $0, encoding: .utf8) } ?? ""
|
|
editing = true
|
|
}.disabled(package?.files[path].flatMap { String(data: $0, encoding: .utf8) } == nil)
|
|
Spacer()
|
|
Menu {
|
|
Button("Export") {
|
|
exportData = package?.files[path] ?? Data()
|
|
exportName = URL(filePath: path).lastPathComponent
|
|
exporting = true
|
|
}
|
|
Button("Delete", role: .destructive) {
|
|
var value = package
|
|
value?.files.removeValue(forKey: path)
|
|
package = value
|
|
}
|
|
} label: {
|
|
Image(systemName: "ellipsis.circle")
|
|
}
|
|
}
|
|
}
|
|
#if os(macOS)
|
|
HStack(spacing: 8) {
|
|
addFileButtons
|
|
Spacer()
|
|
}
|
|
.buttonStyle(.bordered)
|
|
.controlSize(.small)
|
|
#else
|
|
addFileButtons
|
|
#endif
|
|
if let error { Text(error).font(.caption).foregroundStyle(.red) }
|
|
}
|
|
.fileImporter(isPresented: $importing, allowedContentTypes: [.data]) { result in
|
|
do {
|
|
let url = try result.get()
|
|
let path = "assets/" + url.lastPathComponent
|
|
guard package?.files[path] == nil else { throw AgentSkillImportError.notReadable }
|
|
try save(try readResource(url), at: path)
|
|
} catch { self.error = error.localizedDescription }
|
|
}
|
|
.fileExporter(
|
|
isPresented: $exporting, document: KnowledgeFileDocument(data: exportData),
|
|
contentType: .data, defaultFilename: exportName
|
|
) { result in
|
|
if case .failure(let error) = result { self.error = error.localizedDescription }
|
|
}
|
|
.sheet(isPresented: $editing) {
|
|
NavigationStack {
|
|
Form {
|
|
TextField("Relative Path", text: $path)
|
|
TextEditor(text: $text).font(.body.monospaced()).frame(minHeight: 240)
|
|
if let error { Text(error).foregroundStyle(.red) }
|
|
}
|
|
.navigationTitle("Supporting File")
|
|
.toolbar {
|
|
ToolbarItem(placement: .cancellationAction) { Button("Cancel") { editing = false } }
|
|
ToolbarItem(placement: .confirmationAction) {
|
|
Button("Save") {
|
|
do {
|
|
try save(Data(text.utf8), at: path)
|
|
editing = false
|
|
} catch { self.error = error.localizedDescription }
|
|
}
|
|
}
|
|
}
|
|
}.frame(minWidth: 320, minHeight: 360)
|
|
}
|
|
}
|
|
@ViewBuilder
|
|
private var addFileButtons: some View {
|
|
Button("Add Text File") {
|
|
path = "references/notes.md"
|
|
text = ""
|
|
editing = true
|
|
}
|
|
Button("Import Resource") { importing = true }
|
|
}
|
|
|
|
private func save(_ data: Data, at path: String) throws {
|
|
var updated = package ?? AgentSkillPackage()
|
|
updated.files[path] = data
|
|
try updated.validate()
|
|
package = updated
|
|
error = nil
|
|
}
|
|
}
|