Files
ReadViewSDK/Doc/RDAIReaderView/RDAIReaderView-API.md
T
2026-07-27 21:43:13 +08:00

605 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# RDAIReaderView 公共 API 设计
**文档状态:** Proposal 0.1
**最后更新:** 2026-07-25
**目标版本:** RDAIReaderView 1.0
## 1. API 设计原则
- Core 最低支持 iOS 15,不直接依赖 UIKit、PDFKit、DTCoreText 或 FoundationModels。
- 公共模型优先使用值类型,并遵循 `Codable``Sendable``Equatable`
- 文本范围统一使用 UTF-16 偏移,与现有 PDF、EPUB、NSString 和 TTS 范围保持一致。
- Reader Adapter 负责格式转换,Core 不识别 PDF 页视图或 EPUB 排版对象。
- Foundation Models 通过 Provider 协议接入,不能泄漏到基础 API。
- 公开 API 在 1.0 后遵循语义化版本;新增字段必须有解码默认值。
本文中的 Swift 定义是实现合同,允许在不改变语义的前提下调整文件组织和内部实现。
## 2. 标识符与基础范围
```swift
import Foundation
public struct RDAIDocumentIdentifier: RawRepresentable, Codable, Hashable, Sendable {
public let rawValue: String
public init(rawValue: String) {
self.rawValue = rawValue
}
}
public struct RDAIResourceIdentifier: RawRepresentable, Codable, Hashable, Sendable {
public let rawValue: String
public init(rawValue: String) {
self.rawValue = rawValue
}
}
public struct RDAITextRange: Codable, Hashable, Sendable {
public var location: Int
public var length: Int
public init(location: Int, length: Int) {
self.location = max(0, location)
self.length = max(0, length)
}
public var upperBound: Int { location + length }
}
```
约束:
- `RDAIDocumentIdentifier` 必须与宿主书籍 ID 一致并保持稳定。
- `RDAIResourceIdentifier` 在 PDF 中使用页索引字符串,在 EPUB 中使用规范化 `href`
- 所有文本范围均针对资源原始文本,不针对规范化搜索文本。
## 3. 定位与引用
### 3.1 归一化矩形
Core 使用自定义矩形,避免公共存储格式依赖 UIKit:
```swift
public struct RDAINormalizedRect: Codable, Hashable, Sendable {
public var x: Double
public var y: Double
public var width: Double
public var height: Double
public init(x: Double, y: Double, width: Double, height: Double) {
self.x = x
self.y = y
self.width = width
self.height = height
}
}
```
所有值应限制在 `0...1`。Reader Adapter 负责与 `CGRect` 转换。
### 3.2 格式 Anchor
```swift
public struct RDAIPDFAnchor: Codable, Hashable, Sendable {
public enum TextSource: String, Codable, Sendable {
case native
case ocr
}
public var pageIndex: Int
public var rects: [RDAINormalizedRect]
public var textSource: TextSource
public var readingOrder: Int?
}
public struct RDAIEPUBAnchor: Codable, Hashable, Sendable {
public var href: String
public var cfi: String?
public var rangeCFI: String?
public var progression: Double?
}
public enum RDAIAnchor: Codable, Hashable, Sendable {
case pdf(RDAIPDFAnchor)
case epub(RDAIEPUBAnchor)
}
```
`RDAIAnchor` 必须实现显式 Codable discriminator,例如 `type: "pdf"`,未知类型解码为明确错误,不能误当成其他格式。
### 3.3 通用定位
```swift
public struct RDAILocator: Codable, Hashable, Sendable {
public var documentIdentifier: RDAIDocumentIdentifier
public var resourceIdentifier: RDAIResourceIdentifier
public var textRange: RDAITextRange
public var anchor: RDAIAnchor
public var sourceHash: String
}
public struct RDAICitation: Codable, Hashable, Sendable, Identifiable {
public let id: String
public let passageIdentifier: String
public let quote: String
public let locator: RDAILocator
}
```
引用恢复流程:
1. 校验文档和资源存在。
2. 校验 `sourceHash`
3. 优先按格式 Anchor 恢复。
4. Anchor 失败时使用文本范围和 quote 搜索。
5. 仍失败则返回 `staleCitation`,不得跳转到近似但未验证的位置。
## 4. 文档与资源
```swift
public enum RDAIDocumentFormat: String, Codable, Sendable {
case pdf
case epub
}
public struct RDAIDocumentDescriptor: Codable, Equatable, Sendable {
public let identifier: RDAIDocumentIdentifier
public let title: String
public let format: RDAIDocumentFormat
public let contentRevision: String
}
public struct RDAIResourceDescriptor: Codable, Equatable, Sendable {
public let identifier: RDAIResourceIdentifier
public let title: String?
public let order: Int
public let estimatedUTF16Length: Int?
}
public struct RDAIResourceSnapshot: Sendable {
public let descriptor: RDAIResourceDescriptor
public let sourceText: String
public let sourceHash: String
public let locatorRuns: [RDAILocatorRun]
}
public struct RDAILocatorRun: Sendable {
public let textRange: RDAITextRange
public let anchor: RDAIAnchor
}
```
`contentRevision` 由宿主提供;若宿主没有版本号,Adapter 使用资源哈希汇总生成。
## 5. 内容提供协议
```swift
@MainActor
public protocol RDAIContentProvider: AnyObject {
func aiDocumentDescriptor() -> RDAIDocumentDescriptor
func aiResources() async throws -> [RDAIResourceDescriptor]
func aiResourceSnapshot(
for identifier: RDAIResourceIdentifier
) async throws -> RDAIResourceSnapshot
func aiNavigate(to locator: RDAILocator, animated: Bool) async throws
func aiShowCitationHighlight(_ citation: RDAICitation) async throws
func aiClearCitationHighlight()
}
```
协议标记为 `@MainActor`,因为现有 Reader Controller 和页面缓存均由主线程管理。实现必须只在主线程获取快照引用和 UI 状态;OCR、分块、分析和数据库写入移交后台 actor。
可选读取范围协议:
```swift
public struct RDAIReadScope: Codable, Equatable, Sendable {
public let upperBound: RDAILocator?
public let includesWholeDocument: Bool
}
@MainActor
public protocol RDAIReadScopeProviding: AnyObject {
func aiCurrentReadScope() -> RDAIReadScope
}
```
未实现时,默认只允许当前资源及之前的资源,不能默认整本书。
## 6. Passage 与分析结果
```swift
public enum RDAIPassageKind: String, Codable, Sendable {
case title
case paragraph
case list
case table
case code
case footnote
case unknown
}
public struct RDAIPassage: Codable, Equatable, Sendable, Identifiable {
public let id: String
public let documentIdentifier: RDAIDocumentIdentifier
public let resourceIdentifier: RDAIResourceIdentifier
public let text: String
public let languageCode: String?
public let kind: RDAIPassageKind
public let locator: RDAILocator
public let contentHash: String
public let order: Int
}
public enum RDAIEntityKind: String, Codable, Sendable {
case person
case place
case organization
case other
}
public struct RDAIEntityMention: Codable, Equatable, Sendable, Identifiable {
public let id: String
public let normalizedName: String
public let surfaceText: String
public let kind: RDAIEntityKind
public let confidence: Double
public let locator: RDAILocator
}
```
Natural Language 的标签不是最终人物事实,只是 `RDAIEntityMention` 候选。
## 7. 索引 API
```swift
public enum RDAIIndexState: Equatable, Sendable {
case notStarted
case indexing(completedResources: Int, totalResources: Int)
case paused
case ready
case failed(RDAIError)
}
public struct RDAIIndexOptions: Sendable {
public var scope: RDAIReadScope
public var priorityResource: RDAIResourceIdentifier?
public var allowsEmbeddingAssetDownload: Bool
}
public protocol RDAIIndexing: AnyObject, Sendable {
func prepareIndex(options: RDAIIndexOptions) async throws
func pauseIndexing() async
func resumeIndexing() async
func indexState() async -> RDAIIndexState
func stateUpdates() async -> AsyncStream<RDAIIndexState>
func removeIndex() async throws
}
```
要求:
- `prepareIndex` 幂等。
- 重复调用只能扩大范围或提高优先级,不能创建重复 Job。
- `removeIndex` 删除索引、实体和生成缓存,但不删除原书或用户笔记。
## 8. 能力与可用性
```swift
public enum RDAICapability: String, Codable, Sendable {
case languageAnalysis
case entityExtraction
case lexicalSearch
case semanticSearch
case summarization
case questionAnswering
case characterRelationships
}
public enum RDAIUnavailableReason: Equatable, Sendable {
case operatingSystemUnsupported
case deviceNotEligible
case appleIntelligenceNotEnabled
case modelNotReady
case languageUnsupported(String?)
case embeddingAssetsUnavailable
case providerNotInstalled
case unknown(String)
}
public enum RDAICapabilityAvailability: Equatable, Sendable {
case available
case degraded(reason: RDAIUnavailableReason)
case unavailable(reason: RDAIUnavailableReason)
}
public protocol RDAICapabilityProviding: Sendable {
func availability(
for capability: RDAICapability,
locale: Locale?
) async -> RDAICapabilityAvailability
}
```
UI 只能根据枚举状态展示文案,不能匹配本地化 Error 字符串。
## 9. 检索 API
```swift
public struct RDAIRetrievalOptions: Sendable {
public var maximumResults: Int
public var scope: RDAIReadScope
public var minimumScore: Double
}
public struct RDAIRetrievalMatch: Sendable, Identifiable {
public let id: String
public let passage: RDAIPassage
public let score: Double
public let lexicalScore: Double?
public let semanticScore: Double?
}
public protocol RDAIRetrieving: Sendable {
func retrieve(
query: String,
options: RDAIRetrievalOptions
) async throws -> [RDAIRetrievalMatch]
}
```
检索分数只用于同一索引版本内排序,不承诺跨版本数值稳定。
## 10. 生成结果
### 10.1 摘要
```swift
public enum RDAISummaryLength: String, Codable, Sendable {
case brief
case standard
case detailed
}
public struct RDAISummary: Codable, Sendable {
public let title: String
public let overview: String
public let keyPoints: [RDAISourcedStatement]
public let citations: [RDAICitation]
public let metadata: RDAIGenerationMetadata
}
```
### 10.2 问答
```swift
public enum RDAIAnswerStatus: String, Codable, Sendable {
case answered
case insufficientEvidence
case unsupportedLanguage
case unavailable
}
public struct RDAIAnswer: Codable, Sendable {
public let status: RDAIAnswerStatus
public let text: String
public let statements: [RDAISourcedStatement]
public let citations: [RDAICitation]
public let metadata: RDAIGenerationMetadata
}
public struct RDAISourcedStatement: Codable, Sendable, Identifiable {
public let id: String
public let text: String
public let citationIdentifiers: [String]
}
```
### 10.3 人物关系
```swift
public enum RDAIRelationshipStatus: String, Codable, Sendable {
case confirmed
case possible
case conflicting
}
public struct RDAICharacter: Codable, Sendable, Identifiable {
public let id: String
public let displayName: String
public let aliases: [String]
public let description: String
public let firstAppearance: RDAICitation?
public let evidence: [RDAICitation]
}
public struct RDAIRelationship: Codable, Sendable, Identifiable {
public let id: String
public let sourceCharacterIdentifier: String
public let targetCharacterIdentifier: String
public let label: String
public let status: RDAIRelationshipStatus
public let evidence: [RDAICitation]
}
```
### 10.4 生成元数据
```swift
public struct RDAIGenerationMetadata: Codable, Sendable {
public let providerIdentifier: String
public let modelVersion: String?
public let promptIdentifier: String
public let promptVersion: Int
public let generatedAt: Date
public let scopeHash: String
}
```
元数据用于缓存失效和问题追踪,不向普通用户展示内部 Prompt。
## 11. 高层服务
```swift
public protocol RDAIReaderServicing: AnyObject, Sendable {
func prepare(options: RDAIIndexOptions) async throws
func summarize(
scope: RDAIReadScope,
length: RDAISummaryLength
) async throws -> RDAISummary
func answer(
question: String,
scope: RDAIReadScope
) async throws -> RDAIAnswer
func characters(
scope: RDAIReadScope
) async throws -> [RDAICharacter]
func relationships(
scope: RDAIReadScope
) async throws -> [RDAIRelationship]
func removeAllAIData() async throws
}
```
建议具体实现为 actor。若用户开始新的同类请求,UI 层负责决定取消旧请求或并行;同一 Foundation Models session 不允许并行请求。
## 12. Provider 协议
```swift
public struct RDAIGenerationRequest: Sendable {
public let task: RDAIGenerationTask
public let userText: String?
public let passages: [RDAIPassage]
public let locale: Locale
public let scope: RDAIReadScope
}
public enum RDAIGenerationTask: Sendable {
case summary(RDAISummaryLength)
case answer
case characters
case relationships
}
public protocol RDAIGenerativeProvider: Sendable {
var identifier: String { get }
func availability(locale: Locale) async -> RDAICapabilityAvailability
func generate(_ request: RDAIGenerationRequest) async throws -> RDAIGeneratedArtifact
}
```
`RDAIGeneratedArtifact` 是 Core 内部或受控公共枚举,用于把 Provider 输出转换为第 10 节模型。Provider 不能直接保存结果或操作 Reader UI。
## 13. 错误模型
```swift
public enum RDAIError: Error, Equatable, Sendable {
case invalidDocument
case resourceUnavailable(RDAIResourceIdentifier)
case staleCitation
case indexingFailed(code: String)
case modelUnavailable(RDAIUnavailableReason)
case unsupportedLanguage(String?)
case contextLimitExceeded
case invalidGeneratedStructure
case invalidCitation
case insufficientEvidence
case cancelled
case storageFailure(code: String)
}
```
公共错误不携带原文或数据库底层错误字符串。内部错误映射为稳定 code,并通过本地诊断系统保存脱敏详情。
## 14. PDF Adapter
建议公开:
```swift
public extension RDPDFReaderViewController {
func makeAIContentProvider() -> RDPDFAIContentProvider
func makeAIReaderService(
configuration: RDAIReaderConfiguration = .default
) throws -> RDAIReaderServicing
}
```
映射要求:
- 页面资源 ID 为十进制页索引。
- 文本顺序使用 `RDPDFReaderTextRun.readingOrder`
- `normalizedRects` 转换为 `RDAINormalizedRect`
- 原生文本标记为 `.native`Vision OCR 标记为 `.ocr`
- AI 高亮复用或泛化现有 speech highlight,不同时维护两个相互覆盖的临时层。
## 15. EPUB Adapter
建议公开:
```swift
public extension RDEPUBReaderController {
func makeAIContentProvider() -> RDEPUBAIContentProvider
func makeAIReaderService(
configuration: RDAIReaderConfiguration = .default
) throws -> RDAIReaderServicing
}
```
映射要求:
- 资源 ID 使用 ResourceResolver 规范化后的 `href`
- Passage 范围从章节 attributed content 的原始字符串计算。
- 使用现有 index table 生成 `cfi``rangeCFI`
- 导航复用 `go(to:)`/位置恢复流程。
- 固定版式或无法提取文本的章节返回明确 unavailable,不制造空 Passage。
## 16. TTS 集成
AI 层不依赖 RDSpeechReaderView。宿主可以把摘要或回答转换为临时 `RDSpeechContentProvider`
后续可增加桥接 Pod
```ruby
pod 'RDSpeechReaderView/AI'
```
桥接只负责朗读 AI 结果;Natural Language 的分句与语言识别实现应抽取为共享内部组件,避免同一文本产生不同范围。
## 17. 配置
```swift
public struct RDAIReaderConfiguration: Sendable {
public var spoilerPolicy: RDAISpoilerPolicy
public var maximumRetrievedPassages: Int
public var allowsEmbeddingAssetDownload: Bool
public var storesGeneratedArtifacts: Bool
public var diagnosticsLevel: RDAIDiagnosticsLevel
public static let `default`: RDAIReaderConfiguration
}
```
默认值:
- `spoilerPolicy = .readContentOnly`
- `maximumRetrievedPassages = 4`
- `allowsEmbeddingAssetDownload = false`
- `storesGeneratedArtifacts = true`
- `diagnosticsLevel = .metadataOnly`
## 18. API 演进规则
- 1.0 前可以调整命名,但每次调整同步更新五份设计文档。
- 1.0 后删除或改变语义需要主版本升级。
- Codable 枚举新增 case 时必须实现向后兼容策略。
- 数据库 Schema 版本与 SDK 版本独立。
- Prompt 版本与 SDK 版本独立。
- Reader Adapter 可以增加格式能力,但不能改变 Core Locator 的 UTF-16 语义。