更新阅读器功能与示例
This commit is contained in:
@@ -0,0 +1,448 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
/// UTF-16 offsets are shared by PDF text runs, EPUB search and speech progress.
|
||||
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 }
|
||||
|
||||
public func intersects(_ other: RDAITextRange) -> Bool {
|
||||
location < other.upperBound && other.location < upperBound
|
||||
}
|
||||
}
|
||||
|
||||
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 = min(max(x, 0), 1)
|
||||
self.y = min(max(y, 0), 1)
|
||||
self.width = min(max(width, 0), 1)
|
||||
self.height = min(max(height, 0), 1)
|
||||
}
|
||||
}
|
||||
|
||||
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 init(pageIndex: Int, rects: [RDAINormalizedRect], textSource: TextSource, readingOrder: Int? = nil) {
|
||||
self.pageIndex = max(0, pageIndex)
|
||||
self.rects = rects
|
||||
self.textSource = textSource
|
||||
self.readingOrder = readingOrder
|
||||
}
|
||||
}
|
||||
|
||||
public struct RDAIEPUBAnchor: Codable, Hashable, Sendable {
|
||||
public var href: String
|
||||
public var cfi: String?
|
||||
public var rangeCFI: String?
|
||||
public var progression: Double?
|
||||
|
||||
public init(href: String, cfi: String? = nil, rangeCFI: String? = nil, progression: Double? = nil) {
|
||||
self.href = href
|
||||
self.cfi = cfi
|
||||
self.rangeCFI = rangeCFI
|
||||
self.progression = progression.map { min(max($0, 0), 1) }
|
||||
}
|
||||
}
|
||||
|
||||
public enum RDAIAnchor: Hashable, Sendable {
|
||||
case pdf(RDAIPDFAnchor)
|
||||
case epub(RDAIEPUBAnchor)
|
||||
}
|
||||
|
||||
extension RDAIAnchor: Codable {
|
||||
private enum CodingKeys: String, CodingKey { case type, pdf, epub }
|
||||
private enum Kind: String, Codable { case pdf, epub }
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
switch try container.decode(Kind.self, forKey: .type) {
|
||||
case .pdf:
|
||||
self = .pdf(try container.decode(RDAIPDFAnchor.self, forKey: .pdf))
|
||||
case .epub:
|
||||
self = .epub(try container.decode(RDAIEPUBAnchor.self, forKey: .epub))
|
||||
}
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
switch self {
|
||||
case .pdf(let anchor):
|
||||
try container.encode(Kind.pdf, forKey: .type)
|
||||
try container.encode(anchor, forKey: .pdf)
|
||||
case .epub(let anchor):
|
||||
try container.encode(Kind.epub, forKey: .type)
|
||||
try container.encode(anchor, forKey: .epub)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 init(
|
||||
documentIdentifier: RDAIDocumentIdentifier,
|
||||
resourceIdentifier: RDAIResourceIdentifier,
|
||||
textRange: RDAITextRange,
|
||||
anchor: RDAIAnchor,
|
||||
sourceHash: String
|
||||
) {
|
||||
self.documentIdentifier = documentIdentifier
|
||||
self.resourceIdentifier = resourceIdentifier
|
||||
self.textRange = textRange
|
||||
self.anchor = anchor
|
||||
self.sourceHash = sourceHash
|
||||
}
|
||||
}
|
||||
|
||||
public struct RDAICitation: Codable, Hashable, Sendable, Identifiable {
|
||||
public let id: String
|
||||
public let passageIdentifier: String
|
||||
public let quote: String
|
||||
public let locator: RDAILocator
|
||||
|
||||
public init(id: String = UUID().uuidString, passageIdentifier: String, quote: String, locator: RDAILocator) {
|
||||
self.id = id
|
||||
self.passageIdentifier = passageIdentifier
|
||||
self.quote = quote
|
||||
self.locator = locator
|
||||
}
|
||||
}
|
||||
|
||||
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 init(identifier: RDAIDocumentIdentifier, title: String, format: RDAIDocumentFormat, contentRevision: String) {
|
||||
self.identifier = identifier
|
||||
self.title = title
|
||||
self.format = format
|
||||
self.contentRevision = contentRevision
|
||||
}
|
||||
}
|
||||
|
||||
public struct RDAIResourceDescriptor: Codable, Equatable, Sendable {
|
||||
public let identifier: RDAIResourceIdentifier
|
||||
public let title: String?
|
||||
public let order: Int
|
||||
public let estimatedUTF16Length: Int?
|
||||
|
||||
public init(identifier: RDAIResourceIdentifier, title: String? = nil, order: Int, estimatedUTF16Length: Int? = nil) {
|
||||
self.identifier = identifier
|
||||
self.title = title
|
||||
self.order = max(0, order)
|
||||
self.estimatedUTF16Length = estimatedUTF16Length
|
||||
}
|
||||
}
|
||||
|
||||
public struct RDAILocatorRun: Sendable {
|
||||
public let textRange: RDAITextRange
|
||||
public let anchor: RDAIAnchor
|
||||
|
||||
public init(textRange: RDAITextRange, anchor: RDAIAnchor) {
|
||||
self.textRange = textRange
|
||||
self.anchor = anchor
|
||||
}
|
||||
}
|
||||
|
||||
public struct RDAIResourceSnapshot: Sendable {
|
||||
public let descriptor: RDAIResourceDescriptor
|
||||
public let sourceText: String
|
||||
public let sourceHash: String
|
||||
public let locatorRuns: [RDAILocatorRun]
|
||||
|
||||
public init(descriptor: RDAIResourceDescriptor, sourceText: String, sourceHash: String, locatorRuns: [RDAILocatorRun]) {
|
||||
self.descriptor = descriptor
|
||||
self.sourceText = sourceText
|
||||
self.sourceHash = sourceHash
|
||||
self.locatorRuns = locatorRuns
|
||||
}
|
||||
}
|
||||
|
||||
@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()
|
||||
}
|
||||
|
||||
public struct RDAIReadScope: Codable, Equatable, Sendable {
|
||||
public let upperBound: RDAILocator?
|
||||
public let includesWholeDocument: Bool
|
||||
|
||||
public init(upperBound: RDAILocator? = nil, includesWholeDocument: Bool = false) {
|
||||
self.upperBound = upperBound
|
||||
self.includesWholeDocument = includesWholeDocument
|
||||
}
|
||||
|
||||
public static let wholeDocument = RDAIReadScope(includesWholeDocument: true)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
public protocol RDAIReadScopeProviding: AnyObject {
|
||||
func aiCurrentReadScope() -> RDAIReadScope
|
||||
}
|
||||
|
||||
public enum RDAIPassageKind: String, Codable, Sendable {
|
||||
case paragraph
|
||||
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 init(
|
||||
id: String,
|
||||
documentIdentifier: RDAIDocumentIdentifier,
|
||||
resourceIdentifier: RDAIResourceIdentifier,
|
||||
text: String,
|
||||
languageCode: String?,
|
||||
kind: RDAIPassageKind = .paragraph,
|
||||
locator: RDAILocator,
|
||||
contentHash: String,
|
||||
order: Int
|
||||
) {
|
||||
self.id = id
|
||||
self.documentIdentifier = documentIdentifier
|
||||
self.resourceIdentifier = resourceIdentifier
|
||||
self.text = text
|
||||
self.languageCode = languageCode
|
||||
self.kind = kind
|
||||
self.locator = locator
|
||||
self.contentHash = contentHash
|
||||
self.order = order
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
public init(id: String, normalizedName: String, surfaceText: String, kind: RDAIEntityKind, confidence: Double, locator: RDAILocator) {
|
||||
self.id = id
|
||||
self.normalizedName = normalizedName
|
||||
self.surfaceText = surfaceText
|
||||
self.kind = kind
|
||||
self.confidence = min(max(confidence, 0), 1)
|
||||
self.locator = locator
|
||||
}
|
||||
}
|
||||
|
||||
public struct RDAIAnalysisResult: Sendable {
|
||||
public let passages: [RDAIPassage]
|
||||
public let entityMentions: [RDAIEntityMention]
|
||||
|
||||
public init(passages: [RDAIPassage], entityMentions: [RDAIEntityMention]) {
|
||||
self.passages = passages
|
||||
self.entityMentions = entityMentions
|
||||
}
|
||||
}
|
||||
|
||||
public protocol RDAIAnalyzing: Sendable {
|
||||
func analyze(document: RDAIDocumentDescriptor, snapshot: RDAIResourceSnapshot) async -> RDAIAnalysisResult
|
||||
}
|
||||
|
||||
public protocol RDAISemanticScoring: Sendable {
|
||||
func score(query: String, text: String, languageCode: String?) async -> Double?
|
||||
}
|
||||
|
||||
public enum RDAIError: Error, Equatable, Sendable {
|
||||
case disabled
|
||||
case invalidDocument
|
||||
case resourceUnavailable(RDAIResourceIdentifier)
|
||||
case staleCitation
|
||||
case indexingFailed(String)
|
||||
case modelUnavailable(RDAIUnavailableReason)
|
||||
case insufficientEvidence
|
||||
case storageFailure(String)
|
||||
case cancelled
|
||||
}
|
||||
|
||||
public enum RDAICapability: String, Codable, Sendable {
|
||||
case languageAnalysis, entityExtraction, lexicalSearch, semanticSearch
|
||||
case summarization, questionAnswering, characterRelationships
|
||||
}
|
||||
|
||||
public enum RDAIUnavailableReason: Equatable, Sendable {
|
||||
case operatingSystemUnsupported, deviceNotEligible, appleIntelligenceNotEnabled
|
||||
case modelNotReady, languageUnsupported(String?), embeddingAssetsUnavailable
|
||||
case providerNotInstalled, 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
|
||||
}
|
||||
|
||||
public enum RDAISummaryLength: String, Codable, Sendable { case brief, standard, detailed }
|
||||
public enum RDAIAnswerStatus: String, Codable, Sendable { case answered, insufficientEvidence, unsupportedLanguage, unavailable }
|
||||
public enum RDAIRelationshipStatus: String, Codable, Sendable { case confirmed, possible, conflicting }
|
||||
|
||||
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
|
||||
|
||||
public init(providerIdentifier: String, modelVersion: String? = nil, promptIdentifier: String, promptVersion: Int, generatedAt: Date = .init(), scopeHash: String) {
|
||||
self.providerIdentifier = providerIdentifier
|
||||
self.modelVersion = modelVersion
|
||||
self.promptIdentifier = promptIdentifier
|
||||
self.promptVersion = promptVersion
|
||||
self.generatedAt = generatedAt
|
||||
self.scopeHash = scopeHash
|
||||
}
|
||||
}
|
||||
|
||||
public struct RDAISourcedStatement: Codable, Sendable, Identifiable {
|
||||
public let id: String
|
||||
public let text: String
|
||||
public let citationIdentifiers: [String]
|
||||
public init(id: String = UUID().uuidString, text: String, citationIdentifiers: [String]) { self.id = id; self.text = text; self.citationIdentifiers = citationIdentifiers }
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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 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]
|
||||
}
|
||||
|
||||
public enum RDAIGenerationTask: Sendable { case summary(RDAISummaryLength), answer, characters, relationships }
|
||||
|
||||
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 init(task: RDAIGenerationTask, userText: String? = nil, passages: [RDAIPassage], locale: Locale = .current, scope: RDAIReadScope) { self.task = task; self.userText = userText; self.passages = passages; self.locale = locale; self.scope = scope }
|
||||
}
|
||||
|
||||
public enum RDAIGeneratedArtifact: Sendable {
|
||||
case summary(overview: String, statements: [RDAISourcedStatement])
|
||||
case answer(text: String, statements: [RDAISourcedStatement])
|
||||
case characters([RDAICharacter])
|
||||
case relationships([RDAIRelationship])
|
||||
}
|
||||
|
||||
public protocol RDAIGenerativeProvider: RDAICapabilityProviding {
|
||||
var identifier: String { get }
|
||||
func generate(_ request: RDAIGenerationRequest) async throws -> RDAIGeneratedArtifact
|
||||
}
|
||||
|
||||
public enum RDAISpoilerPolicy: String, Codable, Sendable { case readContentOnly, wholeDocument }
|
||||
public enum RDAIDiagnosticsLevel: String, Codable, Sendable { case disabled, metadataOnly }
|
||||
|
||||
public struct RDAIReaderConfiguration: Sendable {
|
||||
public var isAIEnabled: Bool
|
||||
public var isLocalOnly: Bool
|
||||
public var spoilerPolicy: RDAISpoilerPolicy
|
||||
public var maximumRetrievedPassages: Int
|
||||
public var storesGeneratedArtifacts: Bool
|
||||
public var diagnosticsLevel: RDAIDiagnosticsLevel
|
||||
public init(isAIEnabled: Bool = true, isLocalOnly: Bool = true, spoilerPolicy: RDAISpoilerPolicy = .readContentOnly, maximumRetrievedPassages: Int = 4, storesGeneratedArtifacts: Bool = true, diagnosticsLevel: RDAIDiagnosticsLevel = .metadataOnly) { self.isAIEnabled = isAIEnabled; self.isLocalOnly = isLocalOnly; self.spoilerPolicy = spoilerPolicy; self.maximumRetrievedPassages = max(1, maximumRetrievedPassages); self.storesGeneratedArtifacts = storesGeneratedArtifacts; self.diagnosticsLevel = diagnosticsLevel }
|
||||
public static let `default` = RDAIReaderConfiguration()
|
||||
}
|
||||
Reference in New Issue
Block a user