// Copyright © 2024 Ryan Booker. All rights reserved.
import ArgumentParser
import Domain
import Foundation
import Toolbox
public struct Summarize: AsyncParsableCommand {
public static let configuration = CommandConfiguration(
commandName: "summarize",
abstract: "Summarize something.",
usage: "kg summarize \"Some text or a url to summarize.\""
)
@Argument(help: "Some text or a url to summarize")
public var query: Either<URL, String>
@Option(help: Engine.help)
public var engine: Engine?
@Option(help: SummaryType.help)
public var summaryType: SummaryType?
var client = Client.liveValue
enum CodingKeys: CodingKey {
case query, engine, summaryType
}
public init() {
// Intentionally left empty
}
public mutating func run() async throws {
do {
let response = try await Client.liveValue.run(query: query, engine: engine, summaryType: summaryType)
print(response)
} catch {
throw ValidationError(error.localizedDescription)
}
}
}
// MARK: - Processing
extension Summarize {
struct Client {
var run: @Sendable (
_ query: Either<URL, String>,
_ engine: Engine?,
_ summaryType: SummaryType?
) async throws -> Response<Summarization>
@inlinable
func run(
query: Either<URL, String>,
engine: Engine?,
summaryType: SummaryType?
) async throws -> Response<Summarization> {
try await run(query, engine, summaryType)
}
}
}
extension Summarize.Client {
static let liveValue = Self(
run: { query, engine, summaryType in
var body: [String: Any] = query.reduce(first: { ["url": $0.absoluteString] }, second: { ["text": $0] })
if let engine {
body["engine"] = engine.rawValue
}
if let summaryType {
body["summary_type"] = summaryType.rawValue
}
var request = try Endpoint.summarize.authorizedRequest
request.httpBody = try JSONSerialization.data(withJSONObject: body)
let (data, response) = try await URLSession.shared.data(for: request)
do {
return try JSONDecoder().decode(Response.self, from: data)
} catch {
#if DEBUG
print(response)
print(error)
print("-----")
#endif
throw error
}
}
)
}
// MARK: - Response
extension Summarize {
public enum Engine: String, CaseIterable, CustomStringConvertible, ExpressibleByArgument {
case cecil
case agnes
case daphne
case muriel
public var description: String {
switch self {
case .cecil:
"Friendly, descriptive, fast summary (default)"
case .agnes:
"Formal, technical, analytical summary"
case .daphne:
"Informal, creative, friendly summary"
case .muriel:
"Best-in-class summary using our enterprise-grade model"
}
}
public static var help: ArgumentHelp? {
.init(
allCases.map { "- \($0.rawValue): \($0.description)" }.joined(separator: "\n") + "\n"
)
}
public var defaultValueDescription: String {
Self.cecil.rawValue
}
}
public enum SummaryType: String, CaseIterable, CustomStringConvertible, ExpressibleByArgument {
case summary
case takeaway
public var description: String {
switch self {
case .summary:
"Paragraph(s) of summary prose (default)"
case .takeaway:
"Bulleted list of key points"
}
}
public static var help: ArgumentHelp? {
.init(
allCases.map { "- \($0.rawValue): \($0.description)" }.joined(separator: "\n") + "\n"
)
}
public var defaultValueDescription: String {
Self.summary.rawValue
}
}
public struct Summarization: CustomStringConvertible, Decodable {
public var output: String
public var tokens: Int
public var description: String {
"""
Summary:
\(output)
"""
}
}
}