import SwiftUI import AIFabric /// RAG proof page: routes corpusAsk through AIService (-> AIRouter -> MockAIProvider). Explicit backend /// pick sets explicitProvider; an explicit-unavailable result renders a visible, non-retrying error. struct AskView: View { @Environment(AppModel.self) private var model @State private var backend: BackendChoice = .auto var body: some View { @Bindable var model = model ScrollView { VStack(alignment: .leading, spacing: 14) { Picker("백엔드", selection: $backend) { ForEach(BackendChoice.allCases) { Text($0.label).tag($0) } } .pickerStyle(.segmented) HStack(spacing: 8) { TextField("코퍼스 전체에 질문", text: $model.askQuery) .textFieldStyle(.roundedBorder) .onSubmit { Task { await model.runAsk(backend: backend.provider) } } Button("질문") { Task { await model.runAsk(backend: backend.provider) } } .buttonStyle(.borderedProminent) } if let result = model.askResult { switch result { case .success(let response): AICompletionView(response: response) { docID in model.section = .documents Task { await model.openDocument(docID) } } if let meta = model.askMeta { HStack(spacing: 6) { Chip("완성도 \(meta.completeness)", Sage.muted) if let aspects = meta.coveredAspects { ForEach(aspects, id: \.self) { Chip($0, Sage.brand) } } } } case .failure(let err): ErrorBanner(text: message(for: err)) } } else { EmptyState(text: "질문을 입력하세요").frame(minHeight: 160) } } .padding(16) } .background(Sage.surface) } private func message(for error: AIServiceError) -> String { switch error { case .explicitUnavailable(let id): return "\(id.displayName) 백엔드를 쓸 수 없습니다 — 다른 백엔드로 자동 전환하지 않았습니다. 다른 백엔드를 고르세요." case .notConfigured(let id): return "\(id.displayName) 백엔드 미구성" case .noneAvailable: return "응답 가능한 백엔드가 없습니다." case .providerFailed(let s): return "응답 실패: \(s)" case .unknown(let s): return "오류: \(s)" } } } enum BackendChoice: String, CaseIterable, Identifiable { case auto, onDevice, localMLX, remoteDS var id: String { rawValue } var label: String { switch self { case .auto: return "자동" case .onDevice: return "온디바이스" case .localMLX: return "맥미니" case .remoteDS: return "원격 DS" } } var provider: AIProviderID? { switch self { case .auto: return nil case .onDevice: return .onDevice case .localMLX: return .localMLX case .remoteDS: return .remoteDS } } }