f512d94c74
git-subtree-dir: clients/ds-app git-subtree-mainline:a24e3e6f22git-subtree-split:5206cf3b0c
60 lines
2.5 KiB
Swift
60 lines
2.5 KiB
Swift
import Foundation
|
|
import AIFabric
|
|
|
|
/// Renderable failure (the UI never sees a raw AIRoutingError — that would break the
|
|
/// "visible error, not silent fallback" contract).
|
|
public enum AIServiceError: Error, Sendable {
|
|
case explicitUnavailable(AIProviderID)
|
|
case notConfigured(AIProviderID)
|
|
case noneAvailable(AITask)
|
|
case providerFailed(String)
|
|
case unknown(String)
|
|
}
|
|
|
|
public enum AIResult: Sendable {
|
|
case success(AICompletionResponse)
|
|
case failure(AIServiceError)
|
|
}
|
|
|
|
/// The single app-facing facade over the S2 fabric. Views call intent methods; AIService is the ONLY
|
|
/// place AICompletionRequest is built. An actor (not @Observable): routing is async work that should
|
|
/// serialize and not hop to MainActor.
|
|
public actor AIService {
|
|
private let router: AIRouter
|
|
|
|
public init(router: AIRouter) { self.router = router }
|
|
|
|
private func run(_ request: AICompletionRequest) async -> AIResult {
|
|
do {
|
|
return .success(try await router.route(request))
|
|
} catch let e as AIRoutingError {
|
|
switch e {
|
|
case .explicitProviderUnavailable(let id): return .failure(.explicitUnavailable(id))
|
|
case .providerNotConfigured(let id): return .failure(.notConfigured(id))
|
|
case .noProviderAvailable(let t): return .failure(.noneAvailable(t))
|
|
}
|
|
} catch let e as AIProviderError {
|
|
return .failure(.providerFailed("\(e)"))
|
|
} catch {
|
|
return .failure(.unknown("\(error)"))
|
|
}
|
|
}
|
|
|
|
// Intent methods — UI gesture -> AITask. (vision is deferred: the frozen request is text-only.)
|
|
public func summarize(text: String) async -> AIResult {
|
|
await run(AICompletionRequest(task: .quickSummarize, prompt: "다음 문서를 요약", context: text))
|
|
}
|
|
public func memoAssist(content: String) async -> AIResult {
|
|
await run(AICompletionRequest(task: .memoAssist, prompt: "제목과 태그 제안", context: content))
|
|
}
|
|
public func askSelection(selection: String, question: String) async -> AIResult {
|
|
await run(AICompletionRequest(task: .askSelection, prompt: question, context: selection))
|
|
}
|
|
public func corpusAsk(question: String, explicit: AIProviderID? = nil) async -> AIResult {
|
|
await run(AICompletionRequest(task: .corpusAsk, prompt: question, context: nil, explicitProvider: explicit))
|
|
}
|
|
public func classify(documentText: String) async -> AIResult {
|
|
await run(AICompletionRequest(task: .classify, prompt: "도메인/카테고리 분류 제안", context: documentText))
|
|
}
|
|
}
|