From a2e68a2e8d2f86dbe03c52ba4f9a99dbbcf137fa Mon Sep 17 00:00:00 2001 From: Leon Nissen <50104433+LeonNissen@users.noreply.github.com> Date: Wed, 11 Dec 2024 17:23:20 -0800 Subject: [PATCH] Remove LLM Functionality from Package (#24) # Remove LLM Functionality from Package ## :recycle: Current situation & Problem Remove the LLM summary and interpretation logic from this package, as it uses application-specific functionality that does not belong within the package ## :pencil: Code of Conduct & Contributing Guidelines By submitting creating this pull request, you agree to follow our [Code of Conduct](https://github.com/StanfordSpezi/.github/blob/main/CODE_OF_CONDUCT.md) and [Contributing Guidelines](https://github.com/StanfordSpezi/.github/blob/main/CONTRIBUTING.md): - [x] I agree to follow the [Code of Conduct](https://github.com/StanfordSpezi/.github/blob/main/CODE_OF_CONDUCT.md) and [Contributing Guidelines](https://github.com/StanfordSpezi/.github/blob/main/CONTRIBUTING.md). --------- Co-authored-by: Leon Nissen <> --- Package.swift | 28 +- .../Extensions/FHIRResource+Flattener.swift | 335 +++++ .../FHIRStore+HealthKit.swift | 1 + .../FHIRGetResourceLLMFunction.swift | 119 -- .../FHIRMultipleResourceInterpreter.swift | 153 --- .../MultipleResourcesChatView.swift | 112 -- .../FHIRResourceInterpreter.swift | 83 -- .../FHIRInterpretationModule.swift | 103 -- .../FHIRProcessor/FHIRResourceProcessor.swift | 78 -- .../FHIRResourceProcessorError.swift | 26 - .../FHIRSummary/FHIRResourceSummary.swift | 108 -- .../FHIRSummary/FHIRResourceSummaryView.swift | 86 -- .../SpeziFHIRLLM/Helpers/Binding+Negate.swift | 20 - .../Helpers/FHIRResource+Identifier.swift | 27 - .../Helpers/FHIRStore+Interpretation.swift | 152 --- .../Resources/Localizable.xcstrings | 1074 ----------------- .../Resources/Localizable.xcstrings.license | 5 - .../SpeziFHIRLLM/Settings/FHIRPrompt.swift | 69 -- .../Settings/FHIRPromptSettingsView.swift | 56 - .../Views/FHIRResourcesView.swift | 145 --- .../Views/InspectResourceView.swift | 141 --- Tests/UITests/TestApp/ContentView.swift | 22 - Tests/UITests/TestApp/ExampleModule.swift | 36 - Tests/UITests/TestApp/PromptSettings.swift | 48 - Tests/UITests/TestApp/TestAppDelegate.swift | 8 +- .../FHIRMockDataStorageProviderTests.swift | 25 - .../UITests/UITests.xcodeproj/project.pbxproj | 15 - .../xcshareddata/swiftpm/Package.resolved | 79 +- 28 files changed, 345 insertions(+), 2809 deletions(-) create mode 100644 Sources/SpeziFHIR/Extensions/FHIRResource+Flattener.swift delete mode 100644 Sources/SpeziFHIRLLM/FHIRInterpretation/MultipleResources/FHIRGetResourceLLMFunction.swift delete mode 100644 Sources/SpeziFHIRLLM/FHIRInterpretation/MultipleResources/FHIRMultipleResourceInterpreter.swift delete mode 100644 Sources/SpeziFHIRLLM/FHIRInterpretation/MultipleResources/MultipleResourcesChatView.swift delete mode 100644 Sources/SpeziFHIRLLM/FHIRInterpretation/SingleResource/FHIRResourceInterpreter.swift delete mode 100644 Sources/SpeziFHIRLLM/FHIRInterpretationModule.swift delete mode 100644 Sources/SpeziFHIRLLM/FHIRProcessor/FHIRResourceProcessor.swift delete mode 100644 Sources/SpeziFHIRLLM/FHIRProcessor/FHIRResourceProcessorError.swift delete mode 100644 Sources/SpeziFHIRLLM/FHIRSummary/FHIRResourceSummary.swift delete mode 100644 Sources/SpeziFHIRLLM/FHIRSummary/FHIRResourceSummaryView.swift delete mode 100644 Sources/SpeziFHIRLLM/Helpers/Binding+Negate.swift delete mode 100644 Sources/SpeziFHIRLLM/Helpers/FHIRResource+Identifier.swift delete mode 100644 Sources/SpeziFHIRLLM/Helpers/FHIRStore+Interpretation.swift delete mode 100644 Sources/SpeziFHIRLLM/Resources/Localizable.xcstrings delete mode 100644 Sources/SpeziFHIRLLM/Resources/Localizable.xcstrings.license delete mode 100644 Sources/SpeziFHIRLLM/Settings/FHIRPrompt.swift delete mode 100644 Sources/SpeziFHIRLLM/Settings/FHIRPromptSettingsView.swift delete mode 100644 Sources/SpeziFHIRLLM/Views/FHIRResourcesView.swift delete mode 100644 Sources/SpeziFHIRLLM/Views/InspectResourceView.swift delete mode 100644 Tests/UITests/TestApp/ExampleModule.swift delete mode 100644 Tests/UITests/TestApp/PromptSettings.swift diff --git a/Package.swift b/Package.swift index 371ee32..3ebc577 100644 --- a/Package.swift +++ b/Package.swift @@ -21,18 +21,13 @@ let package = Package( products: [ .library(name: "SpeziFHIR", targets: ["SpeziFHIR"]), .library(name: "SpeziFHIRHealthKit", targets: ["SpeziFHIRHealthKit"]), - .library(name: "SpeziFHIRLLM", targets: ["SpeziFHIRLLM"]), .library(name: "SpeziFHIRMockPatients", targets: ["SpeziFHIRMockPatients"]) ], dependencies: [ .package(url: "https://github.com/apple/FHIRModels", .upToNextMinor(from: "0.6.0")), .package(url: "https://github.com/StanfordBDHG/HealthKitOnFHIR", .upToNextMinor(from: "0.2.11")), .package(url: "https://github.com/StanfordSpezi/Spezi", from: "1.8.0"), - .package(url: "https://github.com/StanfordSpezi/SpeziHealthKit", .upToNextMinor(from: "0.6.0")), - .package(url: "https://github.com/StanfordSpezi/SpeziLLM", .upToNextMinor(from: "0.8.4")), - .package(url: "https://github.com/StanfordSpezi/SpeziStorage", from: "1.2.1"), - .package(url: "https://github.com/StanfordSpezi/SpeziChat", .upToNextMinor(from: "0.2.1")), - .package(url: "https://github.com/StanfordSpezi/SpeziSpeech", from: "1.1.0") + .package(url: "https://github.com/StanfordSpezi/SpeziHealthKit", .upToNextMinor(from: "0.6.0")) ] + swiftLintPackage(), targets: [ .target( @@ -54,23 +49,6 @@ let package = Package( ], plugins: [] + swiftLintPlugin() ), - .target( - name: "SpeziFHIRLLM", - dependencies: [ - .target(name: "SpeziFHIR"), - .product(name: "Spezi", package: "Spezi"), - .product(name: "ModelsR4", package: "FHIRModels"), - .product(name: "SpeziLLM", package: "SpeziLLM"), - .product(name: "SpeziLLMOpenAI", package: "SpeziLLM"), - .product(name: "SpeziLocalStorage", package: "SpeziStorage"), - .product(name: "SpeziChat", package: "SpeziChat"), - .product(name: "SpeziSpeechSynthesizer", package: "SpeziSpeech") - ], - resources: [ - .process("Resources") - ], - plugins: [] + swiftLintPlugin() - ), .target( name: "SpeziFHIRMockPatients", dependencies: [ @@ -85,7 +63,9 @@ let package = Package( .testTarget( name: "SpeziFHIRTests", dependencies: [ - .target(name: "SpeziFHIR") + .target(name: "SpeziFHIR"), + .product(name: "HealthKitOnFHIR", package: "HealthKitOnFHIR"), + .product(name: "SpeziHealthKit", package: "SpeziHealthKit") ], plugins: [] + swiftLintPlugin() ) diff --git a/Sources/SpeziFHIR/Extensions/FHIRResource+Flattener.swift b/Sources/SpeziFHIR/Extensions/FHIRResource+Flattener.swift new file mode 100644 index 0000000..5c56729 --- /dev/null +++ b/Sources/SpeziFHIR/Extensions/FHIRResource+Flattener.swift @@ -0,0 +1,335 @@ +// +// This source file is part of the Stanford Spezi open source project +// +// SPDX-FileCopyrightText: 2024 Stanford University and the project authors (see CONTRIBUTORS.md) +// +// SPDX-License-Identifier: MIT +// + +import Foundation +import ModelsDSTU2 +import ModelsR4 + + +extension FHIRResource { + /// A computed property that generates an extended description of a FHIR resource. + /// - Returns: A brief description of the resource, or an empty string if no description is available. + public var resourceDescription: String { + switch versionedResource { + case let .r4(resource): + switch resource { + case let patient as ModelsR4.Patient: + return constructPatientDescription(patient) + case let observation as ModelsR4.Observation: + return constructeObservationDescription(observation) + default: + return "" + } + default: + return "" + } + } + + /// A computed property that generates a short description of a FHIR resource. + /// - Returns: A brief description of the resource, or an empty string if no description is available. + public var shortResourceDescription: String { + switch versionedResource { + case let .r4(resource): + switch resource { + case let patient as ModelsR4.Patient: + return constructShortPatientDescription(patient) + case let observation as ModelsR4.Observation: + return constructeShortObservationDescription(observation) + default: + return "" + } + default: + return "" + } + } + + + private func constructShortPatientDescription(_ patient: ModelsR4.Patient) -> String { + """ + Patient name is \(patientNameToString(patient)), gender is \(patientGenderToString(patient)). Born: \(patientBirthDateToString(patient)). Main communication language is \(patientLanguageToString(patient)). + """ + } + + private func constructeShortObservationDescription(_ observation: ModelsR4.Observation) -> String { + """ + This \(splitCamel(resourceType)) is \(observationCodeToString(observation)), value: \(observationValueToString(observation)) at \(observationEffectiveDateTimeToString(observation)). + """ + } + + + private func constructPatientDescription(_ patient: ModelsR4.Patient) -> String { + """ + The name for this patient is \(patientNameToString(patient)). + The gender for this patient is \(patientGenderToString(patient)). + The birth date for this patient is \(patientBirthDateToString(patient)). + The communication language for this patient is \(patientLanguageToString(patient)). + """ + } + + private func constructeObservationDescription(_ observation: ModelsR4.Observation) -> String { + """ + The type of information in this entry is \(splitCamel(resourceType)). + The status for this \(splitCamel(resourceType)) is \(observationStatusToString(observation)). + The category of this obervarion is \(observationCategoryToString(observation)). + The code for this observation is \(observationCodeToString(observation)). + The observation was effectove date time on \(observationEffectiveDateTimeToString(observation)). + The value \(observationTypeToString(observation)) for this observation is \(observationValueToString(observation)). + """ + } + + private func patientNameToString(_ patient: ModelsR4.Patient) -> String { + guard let name = patient.name?.first?.text else { + return "N/A" + } + return name.value?.string ?? "N/A" + } + + private func patientGenderToString(_ patient: ModelsR4.Patient) -> String { + guard let gender = patient.gender?.value?.rawValue else { + return "N/A" + } + return gender + } + + private func patientBirthDateToString(_ patient: ModelsR4.Patient) -> String { + guard let birthDate = patient.birthDate?.valueDescription else { + return "N/A" + } + + if let brithDate = try? patient.birthDate?.value?.asNSDate(), + let years = Calendar.current.dateComponents([.year], from: brithDate, to: .now).year { + return birthDate + " (\(years) years old)" + } + + return birthDate + } + + private func patientLanguageToString(_ patient: ModelsR4.Patient) -> String { + guard let language = patient.communication?.first?.language.coding?.first?.code else { + return "N/A" + } + return language.valueDescription + } + + private func observationStatusToString(_ observation: ModelsR4.Observation) -> String { + guard let status = observation.status.value?.rawValue else { + return "N/A" + } + return status + } + + private func observationCategoryToString(_ observation: ModelsR4.Observation) -> String { + guard let category = observation.category else { + return "N/A" + } + let joinedCoding = category.compactMap(\.coding).joined() + let joinedCodingDisplay = joinedCoding.compactMap { $0.display?.value?.string }.joined(separator: ", ") + return joinedCodingDisplay + } + + private func observationCodeToString(_ observation: ModelsR4.Observation) -> String { + guard let coding = observation.code.coding else { + return "N/A" + } + return coding.compactMap { $0.display?.value?.string }.joined(separator: ", ") + } + + private func observationEffectiveDateTimeToString(_ observation: ModelsR4.Observation) -> String { + guard let effectiveDateTime = observation.effective else { + return "N/A" + } + switch effectiveDateTime { + case let .dateTime(value): + return value.valueDescription + case let .instant(value): + return value.valueDescription + case let .period(value): + return value.valueDescription + case let .timing(value): + return value.valueDescription + } + } + + private func observationTypeToString(_ observation: ModelsR4.Observation) -> String { + guard let value = observation.value else { + return "N/A" + } + return value.typeName + } + + private func observationValueToString(_ observation: ModelsR4.Observation) -> String { + switch observation.value { + case let .boolean(boolean): + return boolean.valueDescription + case let .dateTime(dateTime): + return dateTime.valueDescription + case let .string(string): + return string.valueDescription + case let .quantity(quantity): + return quantity.valueDescription + case let .integer(integer): + return integer.valueDesciption + case let .period(period): + return period.valueDescription + case let .time(time): + return time.valueDescription + case let .range(range): + return range.valueDescription + default: + return "" + } + } + + fileprivate func splitCamel(_ text: String) -> String { + var newText = text.trimmingCharacters(in: .whitespacesAndNewlines) + + newText = newText.replacingOccurrences( + of: #"([a-z])([A-Z])"#, + with: "$1 $2", + options: .regularExpression + ) + + newText = newText.replacingOccurrences( + of: #"([A-Z]+)([A-Z][a-z])"#, + with: "$1 $2", + options: .regularExpression + ) + + return newText.lowercased().trimmingCharacters(in: .whitespacesAndNewlines) + } +} + + +extension ModelsR4.Observation.ValueX { + var typeName: String { + switch self { + case .boolean: "boolean" + case .codeableConcept: "codeable concept" + case .dateTime: "date time" + case .integer: "number" + case .period: "period" + case .quantity: "quantity" + case .range: "range" + case .ratio: "ratio" + case .sampledData: "sampled data" + case .string: "string" + case .time: "time" + } + } +} + +extension ModelsR4.FHIRPrimitive where PrimitiveType == ModelsR4.FHIRBool { + var valueDescription: String { + guard let value = self.value?.bool else { + return "N/A" + } + return "\(value)" + } +} + +extension ModelsR4.FHIRPrimitive where PrimitiveType == ModelsR4.FHIRDate { + var valueDescription: String { + guard let value = try? self.value?.asNSDate() else { + return "N/A" + } + return "\(value.formatted(.dateTime))" + } +} + +extension ModelsR4.FHIRPrimitive where PrimitiveType == ModelsR4.FHIRDecimal { + var valueDescription: String { + guard let value = self.value?.decimal else { + return "N/A" + } + return NSDecimalNumber(decimal: value).stringValue + } +} + +extension ModelsR4.FHIRPrimitive where PrimitiveType == ModelsR4.FHIRString { + var valueDescription: String { + guard let value = self.value?.string else { + return "N/A" + } + return value + } +} + +extension ModelsR4.FHIRPrimitive where PrimitiveType == ModelsR4.FHIRInteger { + var valueDesciption: String { + guard let value = self.value?.integer else { + return "N/A" + } + return String(value) + } +} + +extension ModelsR4.FHIRPrimitive where PrimitiveType == ModelsR4.DateTime { + var valueDescription: String { + guard let value = try? self.value?.asNSDate() else { + return "N/A" + } + return "\(value.formatted(.dateTime))" + } +} + +extension ModelsR4.FHIRPrimitive where PrimitiveType == ModelsR4.FHIRTime { + var valueDescription: String { + guard let value = self.value else { + return "N/A" + } + return value.description + } +} + +extension ModelsR4.FHIRPrimitive where PrimitiveType == ModelsR4.Instant { + var valueDescription: String { + guard let value = try? self.value?.asNSDate() else { + return "N/A" + } + return "\(value.formatted(.dateTime))" + } +} + +extension ModelsR4.Period { + var valueDescription: String { + guard let valueStart = try? self.start?.value?.asNSDate(), + let valueEnd = try? self.end?.value?.asNSDate() else { + return "N/A" + } + return "\(valueStart.formatted(.dateTime)) - \(valueEnd.formatted(.dateTime))" + } +} + +extension ModelsR4.Range { + var valueDescription: String { + guard let valueLow = self.low?.value?.value, + let valueHigh = self.high?.value?.value else { + return "N/A" + } + return "\(valueLow) - \(valueHigh)" + } +} + +extension ModelsR4.Quantity { + var valueDescription: String { + guard let value = self.value?.value?.decimal, + let unit = self.unit?.value else { + return "N/A" + } + return "\(value) \(unit)" + } +} + +extension ModelsR4.Timing { + var valueDescription: String { + guard let value = self.event?.compactMap({ $0.valueDescription }).joined() else { + return "N/A" + } + return value + } +} diff --git a/Sources/SpeziFHIRHealthKit/FHIRStore+HealthKit.swift b/Sources/SpeziFHIRHealthKit/FHIRStore+HealthKit.swift index 2a18df2..d599058 100644 --- a/Sources/SpeziFHIRHealthKit/FHIRStore+HealthKit.swift +++ b/Sources/SpeziFHIRHealthKit/FHIRStore+HealthKit.swift @@ -51,6 +51,7 @@ extension FHIRStore { let decoder = JSONDecoder() let resourceProxy = try decoder.decode(ModelsDSTU2.ResourceProxy.self, from: fhirResource.data) + return FHIRResource( versionedResource: .dstu2(resourceProxy.get()), displayName: clinicalResource.displayName diff --git a/Sources/SpeziFHIRLLM/FHIRInterpretation/MultipleResources/FHIRGetResourceLLMFunction.swift b/Sources/SpeziFHIRLLM/FHIRInterpretation/MultipleResources/FHIRGetResourceLLMFunction.swift deleted file mode 100644 index a166a8c..0000000 --- a/Sources/SpeziFHIRLLM/FHIRInterpretation/MultipleResources/FHIRGetResourceLLMFunction.swift +++ /dev/null @@ -1,119 +0,0 @@ -// -// This source file is part of the Stanford Spezi project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import os -import SpeziFHIR -import SpeziLLMOpenAI - - -struct FHIRGetResourceLLMFunction: LLMFunction { - static let logger = Logger(subsystem: "edu.stanford.spezi.fhir", category: "SpeziFHIRLLM") - - static let name = "get_resources" - static let description = String(localized: "FUNCTION_DESCRIPTION") - - private let fhirStore: FHIRStore - private let resourceSummary: FHIRResourceSummary - - - @Parameter var resources: [String] - - - init( - fhirStore: FHIRStore, - resourceSummary: FHIRResourceSummary, - resourceCountLimit: Int, - allowedResourcesFunctionCallIdentifiers: Set? = nil // swiftlint:disable:this discouraged_optional_collection - ) { - self.fhirStore = fhirStore - self.resourceSummary = resourceSummary - - // Only take newest values of the health records - var allResourcesFunctionCallIdentifiers = Set(fhirStore.allResourcesFunctionCallIdentifier.suffix(resourceCountLimit)) - - // If identifiers are restricted, filter for only allowed function call identifiers of health records. - if let allowedResourcesFunctionCallIdentifiers { - allResourcesFunctionCallIdentifiers.formIntersection(allowedResourcesFunctionCallIdentifiers) - } - - _resources = Parameter( - description: String(localized: "PARAMETER_DESCRIPTION"), - enum: Array(allResourcesFunctionCallIdentifiers) - ) - } - - - private static func filterFittingResources(_ fittingResources: [FHIRResource]) -> [FHIRResource] { - Self.logger.debug("Overall fitting Resources: \(fittingResources.count)") - - var fittingResources = fittingResources - - if fittingResources.count > 64 { - fittingResources = fittingResources.lazy.sorted(by: { $0.date ?? .distantPast < $1.date ?? .distantPast }).suffix(64) - Self.logger.debug( - """ - Reduced to the following 64 resources: \(fittingResources.map { $0.functionCallIdentifier }.joined(separator: ",")) - """ - ) - } - - return fittingResources - } - - - func execute() async throws -> String? { - var functionOutput: [String] = [] - - try await withThrowingTaskGroup(of: [String].self) { outerGroup in - // Iterate over all requested resources by the LLM - for requestedResource in resources { - outerGroup.addTask { @Sendable [fhirStore, resourceSummary] in - // Fetch relevant FHIR resources matching the resources requested by the LLM - var fittingResources = fhirStore.llmRelevantResources.filter { $0.functionCallIdentifier.contains(requestedResource) } - - // Stores output of nested task group summarizing fitting resources - var nestedFunctionOutputResults = [String]() - - guard !fittingResources.isEmpty else { - nestedFunctionOutputResults.append( - String( - localized: "The medical record does not include any FHIR resources for the search term \(requestedResource)." - ) - ) - return [] - } - - // Filter out fitting resources (if greater than 64 entries) - fittingResources = Self.filterFittingResources(fittingResources) - try await withThrowingTaskGroup(of: String.self) { innerGroup in - // Iterate over fitting resources and summarizing them - for resource in fittingResources { - innerGroup.addTask { @Sendable [resourceSummary] in - let summary = try await resourceSummary.summarize(resource: resource) - Self.logger.debug("Summary of appended FHIR resource \(requestedResource): \(summary.description)") - return String(localized: "This is the summary of the requested \(requestedResource):\n\n\(summary.description)") - } - } - - for try await nestedResult in innerGroup { - nestedFunctionOutputResults.append(nestedResult) - } - } - - return nestedFunctionOutputResults - } - } - - for try await result in outerGroup { - functionOutput.append(contentsOf: result) - } - } - - return functionOutput.joined(separator: "\n\n") - } -} diff --git a/Sources/SpeziFHIRLLM/FHIRInterpretation/MultipleResources/FHIRMultipleResourceInterpreter.swift b/Sources/SpeziFHIRLLM/FHIRInterpretation/MultipleResources/FHIRMultipleResourceInterpreter.swift deleted file mode 100644 index 7ba7971..0000000 --- a/Sources/SpeziFHIRLLM/FHIRInterpretation/MultipleResources/FHIRMultipleResourceInterpreter.swift +++ /dev/null @@ -1,153 +0,0 @@ -// -// This source file is part of the Stanford Spezi project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import os -import Spezi -import SpeziChat -import SpeziFHIR -import SpeziLLM -import SpeziLLMOpenAI -import SpeziLocalStorage -import SpeziViews -import SwiftUI - - -private enum FHIRMultipleResourceInterpreterConstants { - static let context = "FHIRMultipleResourceInterpreter.context" -} - - -/// Used to interpret multiple FHIR resources via a chat-based interface with an LLM. -@Observable -public class FHIRMultipleResourceInterpreter { - static let logger = Logger(subsystem: "edu.stanford.spezi.fhir", category: "SpeziFHIRLLM") - - private let localStorage: LocalStorage - private let llmRunner: LLMRunner - private var llmSchema: any LLMSchema - private let fhirStore: FHIRStore - - var llm: (any LLMSession)? - - - required init( - localStorage: LocalStorage, - llmRunner: LLMRunner, - llmSchema: any LLMSchema, - fhirStore: FHIRStore - ) { - self.localStorage = localStorage - self.llmRunner = llmRunner - self.llmSchema = llmSchema - self.fhirStore = fhirStore - } - - - @MainActor - func resetChat() { - llm = llmRunner(with: llmSchema) - llm?.context.append(systemMessage: FHIRPrompt.interpretMultipleResources.prompt) - if let patient = fhirStore.patient { - llm?.context.append(systemMessage: patient.jsonDescription) - } - } - - @MainActor - func prepareLLM() async { - guard llm == nil else { - return - } - - let llm = llmRunner(with: llmSchema) - // Read initial conversation from storage - if let storedContext: LLMContext = try? localStorage.read(storageKey: FHIRMultipleResourceInterpreterConstants.context) { - llm.context = storedContext - } else { - llm.context.append(systemMessage: FHIRPrompt.interpretMultipleResources.prompt) - if let patient = fhirStore.patient { - llm.context.append(systemMessage: patient.jsonDescription) - } - } - - self.llm = llm - } - - @MainActor - func queryLLM() { - guard let llm, - llm.context.last?.role == .user || !(llm.context.contains(where: { $0.role == .assistant() }) ) else { - return - } - - Task { - Self.logger.debug("The Multiple Resource Interpreter has access to \(self.fhirStore.llmRelevantResources.count) resources.") - - guard let stream = try? await llm.generate() else { - return - } - - for try await token in stream { - llm.context.append(assistantOutput: token) - } - - // Store conversation to storage - try localStorage.store(llm.context, storageKey: FHIRMultipleResourceInterpreterConstants.context) - } - } - - /// Change the `LLMSchema` used by the ``FHIRMultipleResourceInterpreter``. - @MainActor - public func changeLLMSchema( - openAIModel model: LLMOpenAIModelType, - resourceCountLimit: Int, - resourceSummary: FHIRResourceSummary, - allowedResourcesFunctionCallIdentifiers: Set? = nil // swiftlint:disable:this discouraged_optional_collection - ) { - self.llmSchema = LLMOpenAISchema( - parameters: .init( - modelType: model, - systemPrompts: [] // No system prompt as this will be determined later by the resource interpreter - ) - ) { - // FHIR interpretation function - FHIRGetResourceLLMFunction( - fhirStore: self.fhirStore, - resourceSummary: resourceSummary, - resourceCountLimit: resourceCountLimit, - allowedResourcesFunctionCallIdentifiers: allowedResourcesFunctionCallIdentifiers - ) - } - self.llm = nil - - Task { - await prepareLLM() - } - } -} - - -extension FHIRPrompt { - /// Prompt used to interpret multiple FHIR resources - /// - /// This prompt is used by the ``FHIRMultipleResourceInterpreter``. - public static let interpretMultipleResources: FHIRPrompt = { - FHIRPrompt( - storageKey: "prompt.interpretMultipleResources", - localizedDescription: String( - localized: "Interpretation Prompt", - bundle: .module, - comment: "Title of the multiple resources interpretation prompt." - ), - defaultPrompt: String( - localized: "Multiple Resource Interpretation Prompt Content", - bundle: .module, - comment: "Content of the multiple resources interpretation prompt." - ) - ) - }() -} diff --git a/Sources/SpeziFHIRLLM/FHIRInterpretation/MultipleResources/MultipleResourcesChatView.swift b/Sources/SpeziFHIRLLM/FHIRInterpretation/MultipleResources/MultipleResourcesChatView.swift deleted file mode 100644 index 33db7c6..0000000 --- a/Sources/SpeziFHIRLLM/FHIRInterpretation/MultipleResources/MultipleResourcesChatView.swift +++ /dev/null @@ -1,112 +0,0 @@ -// -// This source file is part of the Stanford Spezi project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import SpeziChat -import SpeziFHIR -import SpeziLLM -import SpeziLLMOpenAI -import SpeziSpeechSynthesizer -import SpeziViews -import SwiftUI - - -public struct MultipleResourcesChatView: View { - @Environment(FHIRMultipleResourceInterpreter.self) private var multipleResourceInterpreter - @Environment(\.dismiss) private var dismiss - - @Binding private var textToSpeech: Bool - private let navigationTitle: Text - - - public var body: some View { - @Bindable var multipleResourceInterpreter = multipleResourceInterpreter - NavigationStack { - Group { - if let llm = multipleResourceInterpreter.llm { - let contextBinding = Binding { llm.context.chat } set: { llm.context.chat = $0 } - - ChatView( - contextBinding, - disableInput: llm.state.representation == .processing - ) - .speak(llm.context.chat, muted: !textToSpeech) - .speechToolbarButton(muted: !$textToSpeech) - .viewStateAlert(state: llm.state) - .onChange(of: llm.context, initial: true) { _, _ in - if llm.state != .generating { - multipleResourceInterpreter.queryLLM() - } - } - } else { - ProgressView() - } - } - .navigationTitle(navigationTitle) - .toolbar { - toolbar - } - .task { - await multipleResourceInterpreter.prepareLLM() - } - } - .interactiveDismissDisabled() - } - - - @MainActor @ToolbarContentBuilder private var toolbar: some ToolbarContent { - ToolbarItem(placement: .cancellationAction) { - if multipleResourceInterpreter.llm?.state.representation == .processing { - ProgressView() - } else { - Button("Close") { - dismiss() - } - } - } - ToolbarItem(placement: .primaryAction) { - Button( - action: { - multipleResourceInterpreter.resetChat() - }, - label: { - Image(systemName: "trash") - .accessibilityLabel(Text("Reset Chat")) - } - ) - .disabled(multipleResourceInterpreter.llm?.state.representation == .processing) - } - } - - - /// Creates a ``MultipleResourcesChatView`` displaying a Spezi `Chat` with all available FHIR resources via a Spezi LLM.. - /// - /// - Parameters: - /// - navigationTitle: The localized title displayed for purposes of navigation. - /// - textToSpeech: Indicates if the output of the LLM is converted to speech and outputted to the user. - public init( - navigationTitle: LocalizedStringResource, - textToSpeech: Binding - ) { - self.navigationTitle = Text(navigationTitle) - self._textToSpeech = textToSpeech - } - - /// Creates a ``MultipleResourcesChatView`` displaying a Spezi `Chat` with all available FHIR resources via a Spezi LLM.. - /// - /// - Parameters: - /// - navigationTitle: The title displayed for purposes of navigation. - /// - textToSpeech: Indicates if the output of the LLM is converted to speech and outputted to the user. - @_disfavoredOverload - public init( - navigationTitle: Title, - textToSpeech: Binding - ) { - self.navigationTitle = Text(verbatim: String(navigationTitle)) - self._textToSpeech = textToSpeech - } -} diff --git a/Sources/SpeziFHIRLLM/FHIRInterpretation/SingleResource/FHIRResourceInterpreter.swift b/Sources/SpeziFHIRLLM/FHIRInterpretation/SingleResource/FHIRResourceInterpreter.swift deleted file mode 100644 index ede18a5..0000000 --- a/Sources/SpeziFHIRLLM/FHIRInterpretation/SingleResource/FHIRResourceInterpreter.swift +++ /dev/null @@ -1,83 +0,0 @@ -// -// This source file is part of the Stanford Spezi open source project -// -// SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md) -// -// SPDX-License-Identifier: MIT -// - -import Foundation -import SpeziFHIR -import SpeziLLM -import SpeziLocalStorage - - -/// Responsible for interpreting FHIR resources. -@Observable -public final class FHIRResourceInterpreter: Sendable { - private let resourceProcessor: FHIRResourceProcessor - - - /// - Parameters: - /// - localStorage: Local storage module that needs to be passed to the ``FHIRResourceInterpreter`` to allow it to cache interpretations. - /// - openAIModel: OpenAI module that needs to be passed to the ``FHIRResourceInterpreter`` to allow it to retrieve interpretations. - public init(localStorage: LocalStorage, llmRunner: LLMRunner, llmSchema: any LLMSchema) { - self.resourceProcessor = FHIRResourceProcessor( - localStorage: localStorage, - llmRunner: llmRunner, - llmSchema: llmSchema, - storageKey: "FHIRResourceInterpreter.Interpretations", - prompt: FHIRPrompt.interpretation - ) - } - - - /// Interprets a given FHIR resource. Returns a human-readable interpretation. - /// - /// - Parameters: - /// - resource: The `FHIRResource` to be interpreted. - /// - forceReload: A boolean value that indicates whether to reload and reprocess the resource. - /// - Returns: An asynchronous `String` representing the interpretation of the resource. - @discardableResult - public func interpret(resource: FHIRResource, forceReload: Bool = false) async throws -> String { - try await resourceProcessor.process(resource: resource, forceReload: forceReload) - } - - /// Retrieve the cached interpretation of a given FHIR resource. Returns a human-readable interpretation or `nil` if it is not present. - /// - /// - Parameter resource: The resource where the cached interpretation should be loaded from. - /// - Returns: The cached interpretation. Returns `nil` if the resource is not present. - public func cachedInterpretation(forResource resource: FHIRResource) -> String? { - resourceProcessor.results[resource.id] - } - - /// Adjust the LLM schema used by the ``FHIRResourceInterpreter``. - /// - /// - Parameters: - /// - schema: The to-be-used `LLMSchema`. - public func changeLLMSchema(to schema: Schema) { - self.resourceProcessor.llmSchema = schema - } -} - - -extension FHIRPrompt { - /// Prompt used to interpret FHIR resources - /// - /// This prompt is used by the ``FHIRResourceInterpreter``. - public static let interpretation: FHIRPrompt = { - FHIRPrompt( - storageKey: "prompt.interpretation", - localizedDescription: String( - localized: "Interpretation Prompt", - bundle: .module, - comment: "Title of the interpretation prompt." - ), - defaultPrompt: String( - localized: "Interpretation Prompt Content", - bundle: .module, - comment: "Content of the interpretation prompt." - ) - ) - }() -} diff --git a/Sources/SpeziFHIRLLM/FHIRInterpretationModule.swift b/Sources/SpeziFHIRLLM/FHIRInterpretationModule.swift deleted file mode 100644 index 3ed60b3..0000000 --- a/Sources/SpeziFHIRLLM/FHIRInterpretationModule.swift +++ /dev/null @@ -1,103 +0,0 @@ -// -// This source file is part of the Stanford Spezi project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import Spezi -import SpeziFHIR -import SpeziLLM -import SpeziLLMOpenAI -import SpeziLocalStorage -import SwiftUI - - -public class FHIRInterpretationModule: Module, DefaultInitializable { - public enum Defaults { - public static var llmSchema: LLMOpenAISchema { - .init( - parameters: .init( - modelType: .gpt4_turbo, - systemPrompts: [] // No system prompt as this will be determined later by the resource interpreter - ) - ) - } - } - - - @Dependency(LocalStorage.self) private var localStorage - @Dependency(LLMRunner.self) private var llmRunner - @Dependency(FHIRStore.self) private var fhirStore - - @Model private var resourceSummary: FHIRResourceSummary - @Model private var resourceInterpreter: FHIRResourceInterpreter - @Model private var multipleResourceInterpreter: FHIRMultipleResourceInterpreter - - let summaryLLMSchema: any LLMSchema - let interpretationLLMSchema: any LLMSchema - let openAIModelType: LLMOpenAIModelType - let resourceCountLimit: Int - let allowedResourcesFunctionCallIdentifiers: Set? // swiftlint:disable:this discouraged_optional_collection - - - /// - Warning: Ensure that passed LLM schema's don't contain a system prompt! This will be configured by the ``FHIRInterpretationModule``. - public init( - summaryLLMSchema: SummaryLLM = Defaults.llmSchema, - interpretationLLMSchema: InterpretationLLM = Defaults.llmSchema, // swiftlint:disable:this function_default_parameter_at_end - multipleResourceInterpretationOpenAIModel: LLMOpenAIModelType, // swiftlint:disable:this identifier_name - resourceCountLimit: Int = 250, - allowedResourcesFunctionCallIdentifiers: Set? = nil // swiftlint:disable:this discouraged_optional_collection - ) { - self.summaryLLMSchema = summaryLLMSchema - self.interpretationLLMSchema = interpretationLLMSchema - self.openAIModelType = multipleResourceInterpretationOpenAIModel - self.resourceCountLimit = resourceCountLimit - self.allowedResourcesFunctionCallIdentifiers = allowedResourcesFunctionCallIdentifiers - } - - - public required convenience init() { - self.init( - summaryLLMSchema: Defaults.llmSchema, - interpretationLLMSchema: Defaults.llmSchema, - multipleResourceInterpretationOpenAIModel: .gpt4_turbo - ) - } - - - public func configure() { - resourceSummary = FHIRResourceSummary( - localStorage: localStorage, - llmRunner: llmRunner, - llmSchema: summaryLLMSchema - ) - - resourceInterpreter = FHIRResourceInterpreter( - localStorage: localStorage, - llmRunner: llmRunner, - llmSchema: interpretationLLMSchema - ) - - multipleResourceInterpreter = FHIRMultipleResourceInterpreter( - localStorage: localStorage, - llmRunner: llmRunner, - llmSchema: LLMOpenAISchema( - parameters: .init( - modelType: openAIModelType, - systemPrompts: [] // No system prompt as this will be determined later by the resource interpreter - ) - ) { - // FHIR interpretation function - FHIRGetResourceLLMFunction( - fhirStore: self.fhirStore, - resourceSummary: self.resourceSummary, - resourceCountLimit: self.resourceCountLimit, - allowedResourcesFunctionCallIdentifiers: self.allowedResourcesFunctionCallIdentifiers - ) - }, - fhirStore: fhirStore - ) - } -} diff --git a/Sources/SpeziFHIRLLM/FHIRProcessor/FHIRResourceProcessor.swift b/Sources/SpeziFHIRLLM/FHIRProcessor/FHIRResourceProcessor.swift deleted file mode 100644 index 446e8e9..0000000 --- a/Sources/SpeziFHIRLLM/FHIRProcessor/FHIRResourceProcessor.swift +++ /dev/null @@ -1,78 +0,0 @@ -// -// This source file is part of the Stanford Spezi open source project -// -// SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md) -// -// SPDX-License-Identifier: MIT -// - -import Foundation -import SpeziChat -import SpeziFHIR -import SpeziLLM -import SpeziLLMOpenAI -import SpeziLocalStorage - - -// Unchecked `Sendable` conformance is fine as storage is guarded by `NSLock`. -final class FHIRResourceProcessor: @unchecked Sendable { - typealias Results = [FHIRResource.ID: Content] - - - private let localStorage: LocalStorage - private let llmRunner: LLMRunner - private let storageKey: String - private let prompt: FHIRPrompt - private let lock = NSLock() - var llmSchema: any LLMSchema - - - var results: Results = [:] { - didSet { - do { - try localStorage.store(results, storageKey: storageKey) - } catch { - print(error) - } - } - } - - - init( - localStorage: LocalStorage, - llmRunner: LLMRunner, - llmSchema: any LLMSchema, - storageKey: String, - prompt: FHIRPrompt - ) { - self.localStorage = localStorage - self.llmRunner = llmRunner - self.llmSchema = llmSchema - self.storageKey = storageKey - self.prompt = prompt - self.results = (try? localStorage.read(storageKey: storageKey)) ?? [:] - } - - - @discardableResult - func process(resource: FHIRResource, forceReload: Bool = false) async throws -> Content { - if let result = results[resource.id], !result.description.isEmpty, !forceReload { - return result - } - - let chatStreamResult: String = try await llmRunner.oneShot( - with: llmSchema, - context: .init(systemMessages: [prompt.prompt(withFHIRResource: resource.jsonDescription)]) - ) - - guard let content = Content(chatStreamResult) else { - throw FHIRResourceProcessorError.notParsableAsAString - } - - lock.withLock { - results[resource.id] = content - } - - return content - } -} diff --git a/Sources/SpeziFHIRLLM/FHIRProcessor/FHIRResourceProcessorError.swift b/Sources/SpeziFHIRLLM/FHIRProcessor/FHIRResourceProcessorError.swift deleted file mode 100644 index d8df61e..0000000 --- a/Sources/SpeziFHIRLLM/FHIRProcessor/FHIRResourceProcessorError.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// This source file is part of the Stanford Spezi open source project -// -// SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md) -// -// SPDX-License-Identifier: MIT -// - -import Foundation - - -enum FHIRResourceProcessorError: LocalizedError { - case notParsableAsAString - - - var errorDescription: String? { - switch self { - case .notParsableAsAString: - String( - localized: "Unable to parse result of the LLM prompt.", - bundle: .module, - comment: "Error thrown if the result can not be parsed in the underlying type." - ) - } - } -} diff --git a/Sources/SpeziFHIRLLM/FHIRSummary/FHIRResourceSummary.swift b/Sources/SpeziFHIRLLM/FHIRSummary/FHIRResourceSummary.swift deleted file mode 100644 index b048424..0000000 --- a/Sources/SpeziFHIRLLM/FHIRSummary/FHIRResourceSummary.swift +++ /dev/null @@ -1,108 +0,0 @@ -// -// This source file is part of the Stanford Spezi open source project -// -// SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md) -// -// SPDX-License-Identifier: MIT -// - -import Foundation -import SpeziFHIR -import SpeziLLM -import SpeziLocalStorage - - -/// Responsible for summarizing FHIR resources. -@Observable -public final class FHIRResourceSummary: Sendable { - /// Summary of a FHIR resource emitted by the ``FHIRResourceSummary``. - public struct Summary: Codable, LosslessStringConvertible, Sendable { - /// Title of the FHIR resource, should be shorter than 4 words. - public let title: String - /// Summary of the FHIR resource, should be a single line of text. - public let summary: String - - - public var description: String { - title + "\n" + summary - } - - - public init?(_ description: String) { - let components = description.split(separator: "\n") - guard components.count == 2, let title = components.first, let summary = components.last else { - return nil - } - - self.title = String(title) - self.summary = String(summary) - } - } - - - private let resourceProcessor: FHIRResourceProcessor - - - /// - Parameters: - /// - localStorage: Local storage module that needs to be passed to the ``FHIRResourceSummary`` to allow it to cache summaries. - /// - openAIModel: OpenAI module that needs to be passed to the ``FHIRResourceSummary`` to allow it to retrieve summaries. - public init(localStorage: LocalStorage, llmRunner: LLMRunner, llmSchema: any LLMSchema) { - self.resourceProcessor = FHIRResourceProcessor( - localStorage: localStorage, - llmRunner: llmRunner, - llmSchema: llmSchema, - storageKey: "FHIRResourceSummary.Summaries", - prompt: FHIRPrompt.summary - ) - } - - - /// Summarizes a given FHIR resource. Returns a human-readable summary. - /// - /// - Parameters: - /// - resource: The `FHIRResource` to be summarized. - /// - forceReload: A boolean value that indicates whether to reload and reprocess the resource. - /// - Returns: An asynchronous `String` representing the summarization of the resource. - @discardableResult - public func summarize(resource: FHIRResource, forceReload: Bool = false) async throws -> Summary { - try await resourceProcessor.process(resource: resource, forceReload: forceReload) - } - - /// Retrieve the cached summary of a given FHIR resource. Returns a human-readable summary or `nil` if it is not present. - /// - /// - Parameter resource: The resource where the cached summary should be loaded from. - /// - Returns: The cached summary. Returns `nil` if the resource is not present. - public func cachedSummary(forResource resource: FHIRResource) -> Summary? { - resourceProcessor.results[resource.id] - } - - /// Adjust the LLM schema used by the ``FHIRResourceSummary``. - /// - /// - Parameters: - /// - schema: The to-be-used `LLMSchema`. - public func changeLLMSchema(to schema: Schema) { - self.resourceProcessor.llmSchema = schema - } -} - - -extension FHIRPrompt { - /// Prompt used to summarize FHIR resources - /// - /// This prompt is used by the ``FHIRResourceSummary``. - public static let summary: FHIRPrompt = { - FHIRPrompt( - storageKey: "prompt.summary", - localizedDescription: String( - localized: "Summary Prompt", - bundle: .module, - comment: "Title of the summary prompt." - ), - defaultPrompt: String( - localized: "Summary Prompt Content", - bundle: .module, - comment: "Content of the summary prompt." - ) - ) - }() -} diff --git a/Sources/SpeziFHIRLLM/FHIRSummary/FHIRResourceSummaryView.swift b/Sources/SpeziFHIRLLM/FHIRSummary/FHIRResourceSummaryView.swift deleted file mode 100644 index 332904d..0000000 --- a/Sources/SpeziFHIRLLM/FHIRSummary/FHIRResourceSummaryView.swift +++ /dev/null @@ -1,86 +0,0 @@ -// -// This source file is part of the Stanford Spezi open source project -// -// SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md) -// -// SPDX-License-Identifier: MIT -// - -import SpeziFHIR -import SpeziViews -import SwiftUI - - -/// Displays a FHIR resource, a summary if loaded, and provides a mechanism to load a summary using a context menu. -public struct FHIRResourceSummaryView: View { - @Environment(FHIRResourceSummary.self) private var fhirResourceSummary - - @State private var viewState: ViewState = .idle - private let resource: FHIRResource - - - public var body: some View { - Group { - if let summary = fhirResourceSummary.cachedSummary(forResource: resource) { - VStack(alignment: .leading, spacing: 0) { - Text(summary.title) - if let date = resource.date { - Text(date, style: .date) - .font(.caption2) - .foregroundStyle(.secondary) - .padding(.top, 2) - } - Text(summary.summary) - .font(.caption) - .padding(.top, 4) - } - .multilineTextAlignment(.leading) - } else { - VStack(alignment: .leading, spacing: 0) { - Text(resource.displayName) - if let date = resource.date { - Text(date, style: .date) - .font(.caption2) - .foregroundStyle(.secondary) - .padding(.top, 2) - } - if viewState == .processing { - ProgressView() - .progressViewStyle(.circular) - .padding(.vertical, 6) - .padding(.top, 4) - } - } - .contextMenu { - contextMenu - } - } - } - .viewStateAlert(state: $viewState) - } - - - @ViewBuilder private var contextMenu: some View { - Button(String(localized: "Create Resource Summary", bundle: .module)) { - Task { - viewState = .processing - do { - try await fhirResourceSummary.summarize(resource: resource) - viewState = .idle - } catch { - viewState = .error( - AnyLocalizedError( - error: error, - defaultErrorDescription: String(localized: "Could not create FHIR Summary", bundle: .module) - ) - ) - } - } - } - } - - - public init(resource: FHIRResource) { - self.resource = resource - } -} diff --git a/Sources/SpeziFHIRLLM/Helpers/Binding+Negate.swift b/Sources/SpeziFHIRLLM/Helpers/Binding+Negate.swift deleted file mode 100644 index 2708aa6..0000000 --- a/Sources/SpeziFHIRLLM/Helpers/Binding+Negate.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// This source file is part of the Stanford Spezi project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import SwiftUI - - -extension Binding where Value == Bool { - /// Negates a `Binding`. - public prefix static func ! (value: Binding) -> Binding { - Binding( - get: { !value.wrappedValue }, - set: { value.wrappedValue = !$0 } - ) - } -} diff --git a/Sources/SpeziFHIRLLM/Helpers/FHIRResource+Identifier.swift b/Sources/SpeziFHIRLLM/Helpers/FHIRResource+Identifier.swift deleted file mode 100644 index 6ff2278..0000000 --- a/Sources/SpeziFHIRLLM/Helpers/FHIRResource+Identifier.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// This source file is part of the Stanford Spezi project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import Foundation -import SpeziFHIR - - -extension FHIRResource { - private static let dateFormatter: DateFormatter = { - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "MM-dd-yyyy" - return dateFormatter - }() - - - var functionCallIdentifier: String { - resourceType.filter { !$0.isWhitespace } - + displayName.filter { !$0.isWhitespace } - + "-" - + (date.map { FHIRResource.dateFormatter.string(from: $0) } ?? "") - } -} diff --git a/Sources/SpeziFHIRLLM/Helpers/FHIRStore+Interpretation.swift b/Sources/SpeziFHIRLLM/Helpers/FHIRStore+Interpretation.swift deleted file mode 100644 index ea248a1..0000000 --- a/Sources/SpeziFHIRLLM/Helpers/FHIRStore+Interpretation.swift +++ /dev/null @@ -1,152 +0,0 @@ -// -// This source file is part of the Stanford Spezi project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import ModelsR4 -import SpeziFHIR -import SwiftUI - - -extension FHIRStore { - /// All relevant `FHIRResource`s for the LLM interpretation. - public var llmRelevantResources: [FHIRResource] { - allergyIntolerances - + llmConditions - + encounters.uniqueDisplayNames - + immunizations - + llmMedications - + observations.uniqueDisplayNames - + procedures.uniqueDisplayNames - } - - /// All `FHIRResource`s. - public var allResources: [FHIRResource] { - allergyIntolerances - + conditions - + diagnostics - + encounters - + immunizations - + medications - + observations - + otherResources - + procedures - } - - /// The patient `FHIRResource` - public var patient: FHIRResource? { - otherResources - .first { resource in - guard case let .r4(resource) = resource.versionedResource, - resource is ModelsR4.Patient else { - return false - } - - return true - } - } - - private var llmConditions: [FHIRResource] { - conditions - .filter { resource in - guard case let .r4(resource) = resource.versionedResource, - let condition = resource as? ModelsR4.Condition else { - return false - } - - return condition.clinicalStatus?.coding?.contains { coding in - guard coding.system?.value?.url == URL(string: "http://terminology.hl7.org/CodeSystem/condition-clinical"), - coding.code?.value?.string == "active" else { - return false - } - - return true - } ?? false - } - } - - private var llmMedications: [FHIRResource] { - func medicationRequest(resource: FHIRResource) -> MedicationRequest? { - guard case let .r4(resource) = resource.versionedResource, - let medicationRequest = resource as? ModelsR4.MedicationRequest else { - return nil - } - - return medicationRequest - } - - let outpatientMedications = medications - .filter { medication in - guard let medicationRequest = medicationRequest(resource: medication), - medicationRequest.category? - .contains(where: { codableconcept in - codableconcept.text?.value?.string.lowercased() == "outpatient" - }) - ?? false else { - return false - } - - return true - } - .uniqueDisplayNames - - let activeMedications = medications - .filter { medication in - guard let medicationRequest = medicationRequest(resource: medication), - medicationRequest.status == .active else { - return false - } - - return true - } - .uniqueDisplayNames - - return outpatientMedications + activeMedications - } - - /// Get the function call identifiers of all available health resources in the `FHIRStore`. - /// - /// - Tip: We use an array as the order indicates the sorting, oldest resources come first, newest one last - public var allResourcesFunctionCallIdentifier: [String] { - let relevantResources: [FHIRResource] = llmRelevantResources - .lazy - .filter { - $0.date != nil - } - .sorted { - $0.date ?? .distantPast < $1.date ?? .distantPast - } - - return Array(Set(relevantResources.removingDuplicates().map { $0.functionCallIdentifier })) - } -} - - -extension Array where Element == FHIRResource { - fileprivate var uniqueDisplayNames: [FHIRResource] { - let reducedEncounters = Dictionary( - map { ($0.displayName, $0) }, - uniquingKeysWith: { first, second in - if first.date ?? .distantFuture < second.date ?? .distantPast { - return second - } else { - return first - } - } - ) - - return Array(reducedEncounters.values) - } - - fileprivate func removingDuplicates() -> [FHIRResource] { - var seen = Set() - return filter { seen.insert($0).inserted } - } - - fileprivate func dateSuffix(maxLength: Int) -> [FHIRResource] { - self.lazy.sorted(by: { $0.date ?? .distantPast < $1.date ?? .distantPast }).suffix(maxLength) - } -} diff --git a/Sources/SpeziFHIRLLM/Resources/Localizable.xcstrings b/Sources/SpeziFHIRLLM/Resources/Localizable.xcstrings deleted file mode 100644 index 8dd7b7d..0000000 --- a/Sources/SpeziFHIRLLM/Resources/Localizable.xcstrings +++ /dev/null @@ -1,1074 +0,0 @@ -{ - "sourceLanguage" : "en", - "strings" : { - "Close" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Schließen" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Cerrar" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Fermer" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "关闭" - } - } - } - }, - "Conditions" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bedingungen" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Condiciones" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Conditions" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "条件" - } - } - } - }, - "Could not create FHIR Summary" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "FHIR-Zusammenfassung konnte nicht erstellt werden" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Could not create FHIR Summary" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "No se ha podido crear el resumen FHIR" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Impossible de créer un résumé FHIR" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "无法创建 FHIR 摘要" - } - } - } - }, - "Create Resource Summary" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ressourcenzusammenfassung erstellen" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Create Resource Summary" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Crear resumen de recursos" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Créer un résumé des ressources" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "创建资源摘要" - } - } - } - }, - "Customize the %@." : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Customize the %@." - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Customize the %@." - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Personaliza el %@." - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Personnalisez le %@." - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "自定义 %@。" - } - } - } - }, - "Diagnostics" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Diagnostik" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Diagnóstico" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Diagnostics" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "诊断" - } - } - } - }, - "Encounters" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Begegnungen" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Encuentros" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Rencontres" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "邂逅" - } - } - } - }, - "FHIR_RESOURCES_EMPTY_SEARCH_MESSAGE" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Keine Treffer gefunden" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "No matches found" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "No se han encontrado coincidencias" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Aucune correspondance trouvée" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "未找到匹配项" - } - } - } - }, - "FHIR_RESOURCES_INTERPRETATION_BUTTON" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Lade Interpretation" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Load Interpretation" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Interpretación de la carga" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Interprétation de la charge" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "负载释义" - } - } - } - }, - "FHIR_RESOURCES_INTERPRETATION_LOADING" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ergebnis laden ..." - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Loading result ..." - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Cargando resultado ..." - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Chargement du résultat ..." - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "加载结果..." - } - } - } - }, - "FHIR_RESOURCES_INTERPRETATION_RESOURCE" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "FHIR-Ressource" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "FHIR Resource" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Recurso FHIR" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "FHIR Resource" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "FHIR 资源" - } - } - } - }, - "FHIR_RESOURCES_INTERPRETATION_SECTION" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Interpretation" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Interpretation" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Interpretación" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Interprétation" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "口译" - } - } - } - }, - "FHIR_RESOURCES_SUMMARY_BUTTON" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Zusammenfassung der Ressourcen" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Load Resource Summary" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Resumen de recursos de carga" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Load Resource Summary" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "负荷资源汇总" - } - } - } - }, - "FHIR_RESOURCES_SUMMARY_SECTION" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Zusammenfassung" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Summary" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Resumen" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Résumé" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "摘要" - } - } - } - }, - "FUNCTION_DESCRIPTION" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Rufen Sie diese Funktion auf, um die relevanten FHIR-Gesundheitsdatentitel basierend auf der Frage des Benutzers zu ermitteln. Die Titel müssen den genauen Namen haben, der in der anfänglichen Aufforderung definiert wurde. \n\nÜbergeben Sie einen oder mehrere Titel, auf die Sie Zugriff benötigen, in der Eigenschaft \"resources\" als Array von Strings.\n\nVersuchen Sie immer, die geringste Anzahl von Ressourcentiteln auszugeben, die an das Modell gesendet werden, um ein Überschreiten des Token-Limits zu vermeiden." - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Call this function to determine the relevant FHIR health record titles based on the user's question. The titles must have the exact name defined in the initial prompt. \n\nPass in one or more titles that you need access within the \"resources\" property as an array of Strings.\n\nAlways try to output the least amount of resource titles to be sent to the model to prevent exceeding the token limit." - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Llame a esta función para determinar los títulos de registros sanitarios FHIR relevantes en función de la pregunta del usuario. Los títulos deben tener el nombre exacto definido en la pregunta inicial. \n\nPase uno o más títulos a los que necesite acceder dentro de la propiedad \"resources\" como un array de Strings.\n\nIntente siempre enviar la menor cantidad de títulos de recursos al modelo para evitar exceder el límite de tokens." - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Appeler cette fonction pour déterminer les titres d'enregistrements de santé FHIR pertinents en fonction de la question de l'utilisateur. Les titres doivent porter le nom exact défini dans l'invite initiale. \n\nTransmettez un ou plusieurs titres auxquels vous devez accéder dans la propriété \"resources\" sous la forme d'un tableau de chaînes de caractères.\n\nEssayez toujours de produire le moins de titres de ressources possible à envoyer au modèle pour éviter de dépasser la limite de jetons." - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "调用此函数可根据用户的问题确定相关的 FHIR 健康记录标题。标题必须与初始提示中定义的名称完全一致。\n\n以字符串数组的形式在 \"resources \"属性中输入一个或多个需要访问的标题。\n\n始终尽量少输出要发送到模型的资源标题,以防止超出标记限制。" - } - } - } - }, - "Immunizations" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Impfungen" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Vacunas" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Vaccinations" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "免疫接种" - } - } - } - }, - "Interpretation Prompt" : { - "comment" : "Title of the interpretation prompt.\nTitle of the multiple resources interpretation prompt.", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Interpretation Aufforderung" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Interpretation Prompt" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Interpretación" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Invitation à l'interprétation" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "口译提示" - } - } - } - }, - "Interpretation Prompt Content" : { - "comment" : "Content of the interpretation prompt.", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ihre Aufgabe ist es, die folgende FHIR-Ressource aus der Krankenakte des Benutzers zu interpretieren. Sie sollten den Titel und die Zusammenfassung im folgenden Gebietsschema angeben: {{LOCALE}}.\n\nInterpretieren Sie die Ressource, indem Sie die für die Gesundheit des Benutzers relevanten Daten erklären.\nErläutern Sie den relevanten medizinischen Kontext in einer Sprache, die für einen Benutzer, der kein medizinisches Fachpersonal ist, verständlich ist.\nSie sollten sachliche und präzise Informationen in einer kompakten Zusammenfassung in kurzen Antworten liefern.\n\nGeben Sie dem Nutzer sofort eine Interpretation zurück und beginnen Sie das Gespräch.\nStellen Sie sich nicht gleich zu Beginn vor, sondern beginnen Sie mit Ihrer Interpretation.\n\nDie folgende JSON-Darstellung definiert die FHIR-Ressource, für die Sie eine Interpretation liefern sollten:\n\n{{FHIR_RESOURCE}}" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Your task is to interpret the following FHIR resource from the user's clinical record. You should provide the title and summary in the following locale: {{LOCALE}}.\n\nInterpret the resource by explaining its data relevant to the user's health.\nExplain the relevant medical context in a language understandable by a user who is not a medical professional.\nYou should provide factual and precise information in a compact summary in short responses.\n\nImmediately return an interpretation to the user, starting the conversation.\nDo not introduce yourself at the beginning, and start with your interpretation.\n\nThe following JSON representation defines the FHIR resource that you should provide an interpretation for:\n\n{{FHIR_RESOURCE}}" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Su tarea consiste en interpretar el siguiente recurso FHIR de la historia clínica del usuario. Debe proporcionar el título y el resumen en la siguiente configuración regional: {{LOCALE}}.\n\nInterprete el recurso explicando sus datos relevantes para la salud del usuario.\nExplique el contexto médico pertinente en un lenguaje comprensible para un usuario que no sea profesional de la medicina.\nDebe proporcionar información objetiva y precisa en un resumen compacto en respuestas breves.\n\nDevuelve inmediatamente una interpretación al usuario, iniciando la conversación.\nNo se presente al principio y comience con su interpretación.\n\nLa siguiente representación JSON define el recurso FHIR para el que debe proporcionar una interpretación:\n\n{{FHIR_RESOURCE}}" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Your task is to interpret the following FHIR resource from the user's clinical record. You should provide the title and summary in the following locale: {{LOCALE}}.\n\nInterpret the resource by explaining its data relevant to the user's health.\nExplain the relevant medical context in a language understandable by a user who is not a medical professional.\nYou should provide factual and precise information in a compact summary in short responses.\n\nImmediately return an interpretation to the user, starting the conversation.\nDo not introduce yourself at the beginning, and start with your interpretation.\n\nThe following JSON representation defines the FHIR resource that you should provide an interpretation for:\n\n{{FHIR_RESOURCE}}" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "您的任务是解释用户临床记录中的以下 FHIR 资源。您应使用以下语言提供标题和摘要: {{LOCALE}}。\n\n通过解释与用户健康相关的数据来解释该资源。\n用非医学专业人士也能理解的语言解释相关的医学背景。\n您应在简短的回复中以紧凑的摘要提供事实和准确的信息。\n\n立即向用户提供解释,开始对话。\n不要一开始就介绍自己,而是从您的解释开始。\n\n以下 JSON 表示法定义了您应提供解释的 FHIR 资源:\n\n{{FHIR_RESOURCE}}" - } - } - } - }, - "Medications" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Medikamente" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Medicamentos" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Médicaments" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "药物" - } - } - } - }, - "Multiple Resource Interpretation Prompt Content" : { - "comment" : "Content of the multiple resources interpretation prompt.", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sie sind eine Anwendung, die die Aufgabe hat, FHIR-Ressourcen aus den Krankenakten des Benutzers zu interpretieren.\n\nVerwenden Sie während des gesamten Gesprächs mit dem Benutzer die Funktion \"get_resources\", um die FHIR-Gesundheitsressourcen zu erhalten, die für die ordnungsgemäße Beantwortung der Frage des Benutzers erforderlich sind. Wenn der Benutzer beispielsweise nach seinen Allergien fragt, müssen Sie die Funktion \"get_resources\" verwenden, um die FHIR-Ressourcentitel für Allergiedatensätze auszugeben, damit Sie diese dann zur Beantwortung der Frage verwenden können. Ziel ist es, die Frage des Benutzers bestmöglich zu beantworten und dabei die mit \"get_resources\" erhaltenen FHIR-Ressourcen zu berücksichtigen. Lassen Sie alle technischen Details wie JSON, FHIR-Ressourcen und andere Implementierungsdetails der zugrunde liegenden Datenressourcen weg.\nFordern Sie nur relevante Ressourcen an und konzentrieren Sie sich auf aktuelle Ressourcen. Versuchen Sie, die Anzahl der angeforderten Ressourcen auf ein vernünftiges Maß zu reduzieren.\nWenn der Benutzer z. B. nach seinen Medikamenten fragt, wäre es empfehlenswert, alle FHIR-Ressourcentypen MedicationRequest anzufordern.\n\nInterpretieren Sie die Ressourcen, indem Sie die für die Gesundheit des Benutzers relevanten Daten erklären. Erläutern Sie den relevanten medizinischen Kontext in einer Sprache, die für einen Benutzer, der kein Mediziner ist, verständlich ist, idealerweise auf dem Niveau der fünften Klasse. Sie sollten sachliche und präzise Informationen in einer kompakten Zusammenfassung in kurzen Antworten bereitstellen.\n\nDie Patientenressource wird als anfänglicher Kontext bereitgestellt. Stellen Sie sicher, dass Sie alle sensiblen Nummern wie SSN, Passnummer und Telefonnummer weglassen. \nStellen Sie sich nicht zu Beginn vor und geben Sie sofort eine Zusammenfassung des Benutzers auf der Grundlage der FHIR-Patientenressourcen zurück. Fordern Sie für Ihre erste Nachricht keine FHIR-Ressourcen über die Funktion \"get_resources\" an. Die erste Zusammenfassung sollte kurz und einfach sein; bleiben Sie auf einem hohen Niveau. Beenden Sie sie mit einer Frage an den Benutzer, ob er noch Fragen hat. Achten Sie darauf, dass Ihre Antwort in derselben Sprache verfasst ist, in der der Benutzer Ihnen schreibt, und nicht in der Sprache, die in der Patientenressource angegeben ist. Die Zeitform sollte das Präsens sein." - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "You are an application that is tasked with interpreting FHIR resources from the user's clinical records.\n\nThroughout the conversation with the user, use the \"get_resources\" function to obtain the FHIR health resources necessary to answer the user's question properly. For example, if the user asks about their allergies, you must use the \"get_resources\" function to output the FHIR resource titles for allergy records so you can then use them to answer the question. The end goal is to answer the user's question in the best way possible while taking the FHIR resources obtained using \"get_resources\" into consideration. Leave out any technical details like JSON, FHIR resources, and other implementation details of the underlying data resources.\nOnly request relevant resources and focus on recent resources. Try to reduce the number of requested resources to a reasonable scope.\nE.g. if the user asks about their Medication it would be recommended to request all MedicationRequest FHIR resource types.\n\nInterpret the resources by explaining the data relevant to the user's health. Explain the relevant medical context in a language understandable by a user who is not a medical professional, ideally at a 5th-grade reading level. You should provide factual and precise information in a compact summary in short responses.\n\nThe patient resource is provided as the initial context. Ensure to leave out any sensitive numbers like SSN, passport number, and telephone number. \nDo not introduce yourself at the beginning and immediately return a summary of the user based on the FHIR patient resources. No not request any FHIR resources using the \"get_resources\" function for your first message. The initial summary should be short and simple; stay at a high level. End with a question asking the user if they have any questions. Make sure your response is in the same language the user writes to you in, not the one stated in the patient resource. The tense should be present." - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Usted es una aplicación encargada de interpretar los recursos FHIR de la historia clínica del usuario.\n\nA lo largo de la conversación con el usuario, utilice la función \"get_resources\" para obtener los recursos sanitarios FHIR necesarios para responder correctamente a la pregunta del usuario. Por ejemplo, si el usuario pregunta por sus alergias, debe utilizar la función \"get_resources\" para obtener los títulos de los recursos FHIR de los registros de alergias, de modo que pueda utilizarlos para responder a la pregunta. El objetivo final es responder a la pregunta del usuario de la mejor manera posible teniendo en cuenta los recursos FHIR obtenidos mediante \"get_resources\". Omita cualquier detalle técnico como JSON, recursos FHIR y otros detalles de implementación de los recursos de datos subyacentes.\nSolicite únicamente recursos relevantes y céntrese en los recursos recientes. Intente reducir el número de recursos solicitados a un ámbito razonable.\nPor ejemplo, si el usuario pregunta por su medicación, se recomienda solicitar todos los tipos de recursos FHIR MedicationRequest.\n\nInterpretar los recursos explicando los datos relevantes para la salud del usuario. Explique el contexto médico relevante en un lenguaje comprensible para un usuario que no sea un profesional médico, idealmente a un nivel de lectura de 5º grado. Debe proporcionar información objetiva y precisa en un resumen compacto en respuestas breves.\n\nEl recurso del paciente se proporciona como contexto inicial. Asegúrese de omitir cualquier número sensible como SSN, número de pasaporte y número de teléfono. \nNo se presente al principio y devuelva inmediatamente un resumen del usuario basado en los recursos de paciente FHIR. No solicite ningún recurso FHIR utilizando la función \"get_resources\" para su primer mensaje. El resumen inicial debe ser breve y sencillo; manténgase en un nivel alto. Termine con una pregunta al usuario si tiene alguna duda. Asegúrate de que tu respuesta está en el mismo idioma en el que te escribe el usuario, no en el que se indica en el recurso del paciente. El tiempo verbal debe ser presente." - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Vous êtes une application chargée d'interpréter les ressources FHIR à partir des dossiers cliniques de l'utilisateur.\n\nTout au long de la conversation avec l'utilisateur, utilisez la fonction \"get_resources\" pour obtenir les ressources de santé FHIR nécessaires pour répondre correctement à la question de l'utilisateur. Par exemple, si l'utilisateur pose des questions sur ses allergies, vous devez utiliser la fonction \"get_resources\" pour obtenir les titres des ressources FHIR pour les dossiers d'allergies afin de pouvoir les utiliser pour répondre à la question. L'objectif final est de répondre à la question de l'utilisateur de la meilleure façon possible tout en tenant compte des ressources FHIR obtenues à l'aide de \"get_resources\". Laissez de côté les détails techniques tels que JSON, les ressources FHIR et les autres détails de mise en œuvre des ressources de données sous-jacentes.\nNe demandez que les ressources pertinentes et concentrez-vous sur les ressources récentes. Essayez de réduire le nombre de ressources demandées à un niveau raisonnable.\nPar exemple, si l'utilisateur demande des informations sur ses médicaments, il est recommandé de demander tous les types de ressources FHIR MedicationRequest.\n\nInterpréter les ressources en expliquant les données pertinentes pour la santé de l'utilisateur. Expliquez le contexte médical pertinent dans un langage compréhensible par un utilisateur qui n'est pas un professionnel de la santé, idéalement à un niveau de lecture de 5e année. Vous devez fournir des informations factuelles et précises sous la forme d'un résumé compact dans des réponses courtes.\n\nLa ressource du patient est fournie comme contexte initial. Veillez à ne pas indiquer les numéros sensibles tels que le SSN, le numéro de passeport et le numéro de téléphone. \nNe vous présentez pas au début et ne renvoyez pas immédiatement un résumé de l'utilisateur basé sur les ressources patient FHIR. Ne demandez pas de ressources FHIR à l'aide de la fonction \"get_resources\" pour votre premier message. Le résumé initial doit être court et simple ; restez à un niveau élevé. Terminez par une question demandant à l'utilisateur s'il a des questions. Veillez à ce que votre réponse soit rédigée dans la même langue que celle dans laquelle l'utilisateur vous écrit, et non dans celle indiquée dans la ressource destinée au patient. Le temps doit être au présent." - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "您是一个应用程序,负责解释用户临床记录中的 FHIR 资源。\n\n在与用户的整个对话过程中,使用 \"get_resources \"函数获取必要的 FHIR 健康资源,以便正确回答用户的问题。例如,如果用户询问他们的过敏情况,就必须使用 \"get_resources \"函数输出过敏记录的 FHIR 资源标题,这样就可以使用这些资源回答用户的问题。最终目标是在考虑使用 \"get_resources \"获取的 FHIR 资源的同时,以最佳方式回答用户的问题。请忽略任何技术细节,如 JSON、FHIR 资源和底层数据资源的其他实现细节。\n只请求相关资源,并关注最新资源。尽量将请求的资源数量减少到合理范围。\n例如,如果用户询问其用药情况,建议请求所有 MedicationRequest FHIR 资源类型。\n\n通过解释与用户健康相关的数据来解释资源。用非专业医疗人员也能理解的语言解释相关医疗背景,最好能达到五年级的阅读水平。您应在简短的回复中以紧凑的摘要提供事实和准确的信息。\n\n患者资源作为初始上下文提供。确保省略任何敏感数字,如 SSN、护照号码和电话号码。\n不要在一开始就介绍自己,并立即根据 FHIR 患者资源返回用户摘要。不要在第一条信息中使用 \"get_resources \"函数请求任何 FHIR 资源。初始摘要应简短;保持在较高水平。最后询问用户是否有任何问题。确保您的回复使用与用户写信给您时相同的语言,而不是患者资源中所述的语言。时态应为现在时。" - } - } - } - }, - "Observations" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Beobachtungen" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Observaciones" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Observations" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "意见" - } - } - } - }, - "Other Resources" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Andere Ressourcen" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Otros recursos" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Autres ressources" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "其他资源" - } - } - } - }, - "PARAMETER_DESCRIPTION" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ein Array aller FHIR-Gesundheitsdatentitel mit dem exakt gleichen Namen wie in der Liste angegeben, die für die Beantwortung der Fragen des Benutzers in Frage kommen. Es ist möglich, dass mehrere Titel auf die Frage des Benutzers zutreffen (z. B. für mehrere Medikamente). Sie können auch einen größeren Satz von FHIR-Ressourcen anfordern, indem Sie z. B. nur den Ressourcentyp angeben, was jedoch möglicherweise nicht alle relevanten Ressourcen umfasst, um eine Überschreitung des Token-Limits zu vermeiden.\n\nDie FHIR-Ressourcenbezeichner setzen sich aus drei Elementen zusammen:\n1. Der FHIR-Ressourcentyp, z.B. MedicationRequest, Condition, und weitere.\n2. Der Titel der FHIR-Ressource\n3. Das mit der FHIR-Ressource verbundene Datum.\n\nVerwenden Sie diese Informationen, um die bestmögliche FHIR-Ressource für jede Frage zu bestimmen. Versuchen Sie, alle erforderlichen Titel abzufragen, damit Sie die Frage umfassend beantworten können. \n\nWenn der Benutzer beispielsweise nach seiner Medikation fragt, wäre es empfehlenswert, alle FHIR-Ressourcentypen \"MedicationRequest\" sowie andere verwandte FHIR-Ressourcen anzufordern.\n\nEine Frage kann auf einer vorherigen Frage aufbauen und muss nicht explizit sein. Wenn ein Benutzer z. B. \"prescribe\" (verschreiben) sagt, ist dies mit \"medication\" (Medikamente) verbunden." - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "An array of all the FHIR health record titles with the exact same name as given in the list that are applicable to answer the user's questions. It is possible that multiple titles apply to the users's question (e.g for multiple medications). You can also request a larger set of FHIR resources by, e.g., just stating the resource type but this might not include all relevant resources to avoid exceeding the token limit.\n\nThe FHIR resource identifiers are composed of three elements:\n1. The FHIR resource type, e.g., MedicationRequest, Condition, and more.\n2. The title of the FHIR resource\n3. The date associated with the FHIR resource.\n\nUse these informations to determine the best possible FHIR resource for each question. Try to request all the required titles to allow yourself to fully answer the question in a comprehensive manner. \n\nFor example, if the user asks about their Medication it would be recommended to request all MedicationRequest FHIR resource types as well as other related FHIR resources.\n\nA question can build upon a previous question and does not need to be explicit. e.g. if a user says prescribe, this is associated with medication." - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Una matriz de todos los títulos de historiales médicos FHIR con el mismo nombre exacto que el indicado en la lista que son aplicables para responder a las preguntas del usuario. Es posible que se apliquen varios títulos a la pregunta del usuario (por ejemplo, para varios medicamentos). También puede solicitar un conjunto más amplio de recursos FHIR, por ejemplo, indicando sólo el tipo de recurso, pero es posible que no incluya todos los recursos relevantes para evitar superar el límite de tokens.\n\nLos identificadores de recursos FHIR se componen de tres elementos:\n1. 1. El tipo de recurso FHIR, por ejemplo, MedicationRequest, Condition, etc.\n2. El título del recurso FHIR.\n3. La fecha asociada al recurso FHIR.\n\nUtilice esta información para determinar el mejor recurso FHIR posible para cada pregunta. Intente solicitar todos los títulos necesarios para poder responder a la pregunta de forma completa. \n\nPor ejemplo, si el usuario pregunta sobre su medicación, sería recomendable solicitar todos los tipos de recursos FHIR MedicationRequest, así como otros recursos FHIR relacionados.\n\nUna pregunta puede basarse en una pregunta anterior y no necesita ser explícita. Por ejemplo, si un usuario dice prescribir, esto se asocia con la medicación." - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Un tableau de tous les titres d'enregistrements de santé FHIR ayant exactement le même nom que celui donné dans la liste et qui sont applicables pour répondre aux questions de l'utilisateur. Il est possible que plusieurs titres s'appliquent à la question de l'utilisateur (par exemple pour plusieurs médicaments). Vous pouvez également demander un ensemble plus large de ressources FHIR, par exemple en indiquant simplement le type de ressource, mais il se peut que cela n'inclue pas toutes les ressources pertinentes pour éviter de dépasser la limite de jetons.\n\nLes identifiants de ressources FHIR sont composés de trois éléments :\n1. Le type de ressource FHIR, par exemple MedicationRequest, Condition, etc.\n2. Le titre de la ressource FHIR\n3. La date associée à la ressource FHIR.\n\nUtilisez ces informations pour déterminer la meilleure ressource FHIR possible pour chaque question. Essayez de demander tous les titres requis pour vous permettre de répondre à la question de manière complète. \n\nPar exemple, si l'utilisateur pose une question sur ses médicaments, il est recommandé de demander tous les types de ressources FHIR MedicationRequest ainsi que d'autres ressources FHIR connexes.\n\nUne question peut s'appuyer sur une question précédente et n'a pas besoin d'être explicite. Par exemple, si un utilisateur dit \"prescrire\", cela est associé aux médicaments." - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "一个数组,包含与列表中给出的名称完全相同且适用于回答用户问题的所有 FHIR 健康记录标题。可能有多个标题适用于用户的问题(如多种药物)。您也可以请求更多的 FHIR 资源,例如只说明资源类型,但这可能不包括所有相关资源,以避免超出令牌限制。\n\nFHIR 资源标识符由三个元素组成:\n1. FHIR 资源类型,如 MedicationRequest、Condition 等。\n2. FHIR 资源的标题\n3. 与 FHIR 资源相关的日期。\n\n使用这些信息为每个问题确定最佳的 FHIR 资源。尽量要求提供所有必要的标题,以便全面回答问题。\n\n例如,如果用户询问他们的用药情况,建议请求所有 MedicationRequest FHIR 资源类型以及其他相关的 FHIR 资源。\n\n例如,如果用户说 \"处方\",这就与用药相关联。" - } - } - } - }, - "Place %@ at the position in the prompt where the FHIR resource should be inserted. Optionally place %@ where you would like to insert the current locale." : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Setzen Sie %1$@ an die Stelle in der Eingabeaufforderung, an der die FHIR-Ressource eingefügt werden soll. Optional können Sie %2$@ an der Stelle einfügen, an der Sie das aktuelle Gebietsschema einfügen möchten." - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Place %1$@ at the position in the prompt where the FHIR resource should be inserted. Optionally place %2$@ where you would like to insert the current locale." - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Coloque %1$@ en la posición de la solicitud en la que debe insertarse el recurso FHIR. Opcionalmente, coloque %2$@ donde desee insertar la configuración regional actual." - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Placez %1$@ à l'endroit de l'invite où la ressource FHIR doit être insérée. Placez éventuellement %2$@ à l'endroit où vous souhaitez insérer la locale actuelle." - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "将 %1$@ 插入提示符中应插入 FHIR 资源的位置。可选择将 %2$@ 放在要插入当前语言的位置。" - } - } - } - }, - "Procedures" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Verfahren" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Procedimientos" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Procédures" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "程序" - } - } - } - }, - "Reset Chat" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Chat zurücksetzen" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Reiniciar chat" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Réinitialiser le chat" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "重置聊天" - } - } - } - }, - "Save Prompt" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Prompt speichern" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Save Prompt" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Guardar aviso" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sauvegarder l'invite" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "保存提示" - } - } - } - }, - "Summary Prompt" : { - "comment" : "Title of the summary prompt.", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Zusammenfassungs Prompt" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Summary Prompt" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Resumen" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Résumé de l'énoncé" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "摘要提示" - } - } - } - }, - "Summary Prompt Content" : { - "comment" : "Content of the summary prompt.", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ihre Aufgabe ist es, einen Titel und eine kompakte Zusammenfassung für eine FHIR-Ressource aus der klinischen Akte des Benutzers zu erstellen. Sie sollten den Titel und die Zusammenfassung in dem folgenden Gebietsschema bereitstellen: {{LOCALE}}.\n\nIhre Antwort sollte aus zwei Zeilen bestehen, ohne Überschriften, Markdown-Formatierung oder eine andere Struktur, die über zwei Zeilen hinausgeht. Geben Sie den Inhalt direkt und ohne zusätzliche Struktur oder eine Einleitung an. Ein anderes Computerprogramm wird die Ausgabe parsen.\n\n1. Zeile: Eine 1-5-Wort-Zusammenfassung der FHIR-Ressource, die die Ressource sofort identifiziert und die wesentlichen Informationen auf einen Blick liefert. Verwenden Sie KEINEN vollständigen Satz, sondern eine Formatierung, die typischerweise für Titel in Computersystemen verwendet wird.\n\n2. Zeile: Geben Sie eine kurze Zusammenfassung der Ressource in einem Satz mit weniger als 100 Wörtern. Achten Sie darauf, dass sich die Zusammenfassung nur auf die wesentlichen Informationen konzentriert, die ein Patient benötigen würde, und z. B. die Person, die ein Medikament verschrieben hat, oder ähnliche Metadaten ausschließt. Es muss sichergestellt werden, dass alle klinisch relevanten Daten enthalten sind, damit wir die Zusammenfassung in zusätzlichen Aufforderungen verwenden können, bei denen die Verwendung des vollständigen JSON nicht möglich ist.\n\nDie folgende JSON-Darstellung definiert die FHIR-Ressource, für die Sie einen Titel und eine Zusammenfassung angeben sollten:\n\n{{FHIR_RESOURCE}}" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Your task is to private a title and compact summary for an FHIR resource from the user's clinical record. You should provide the title and summary in the following locale: {{LOCALE}}.\n\nYour response should contain two lines without headings, markdown formatting, or any other structure beyond two lines. Directly provide the content without any additional structure or an introduction. Another computer program will parse the output.\n\n1. Line: A 1-5 Word summary of the FHIR resource that immediately identifies the resource and provides the essential information at a glance. Do NOT use a complete sentence; instead, use a formatting typically used for titles in computer systems.\n\n2. Line: Provide a short one-sentence summary of the resource in less than 100 words. Ensure that the summary only focuses on the essential information that a patient would need and, e.g., excludes the person who prescribed a medication or similar metadata. Ensure that all clinically relevant data is included, which allows us to use the summary in additional prompts where using the complete JSON might not be feasible.\n\nThe following JSON representation defines the FHIR resource that you should provide a title and summary for:\n\n{{FHIR_RESOURCE}}" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Su tarea consiste en privado un título y un resumen compacto para un recurso FHIR de la historia clínica del usuario. Debe proporcionar el título y el resumen en la siguiente configuración regional: {{LOCALE}}.\n\nSu respuesta debe contener dos líneas sin encabezados, formato markdown o cualquier otra estructura más allá de dos líneas. Proporcione directamente el contenido sin ninguna estructura adicional ni introducción. Otro programa informático analizará la salida.\n\n1. Línea: Un resumen de 1-5 palabras del recurso FHIR que identifique inmediatamente el recurso y proporcione la información esencial de un vistazo. NO utilice una frase completa; en su lugar, utilice un formato típicamente utilizado para títulos en sistemas informáticos.\n\n2. Línea: Proporcione un breve resumen de una sola frase del recurso en menos de 100 palabras. Asegúrese de que el resumen sólo se centra en la información esencial que necesitaría un paciente y, por ejemplo, excluya la persona que prescribió un medicamento o metadatos similares. Garantizar que se incluyen todos los datos clínicamente relevantes, lo que nos permite utilizar el resumen en indicaciones adicionales en las que utilizar el JSON completo podría no ser factible.\n\nLa siguiente representación JSON define el recurso FHIR para el que debe proporcionar un título y un resumen:\n\n{{FHIR_RESOURCE}}" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Votre tâche consiste à fournir un titre et un résumé compact pour une ressource FHIR à partir du dossier clinique de l'utilisateur. Vous devez fournir le titre et le résumé dans la locale suivante : {{LOCALE}}.\n\nVotre réponse doit contenir deux lignes sans titres, formatage markdown ou toute autre structure au-delà de deux lignes. Fournissez directement le contenu sans structure supplémentaire ni introduction. Un autre programme informatique analysera le résultat.\n\n1. Ligne : Un résumé de 1 à 5 mots de la ressource FHIR qui identifie immédiatement la ressource et fournit les informations essentielles en un coup d'œil. N'utilisez PAS de phrase complète, mais plutôt un formatage généralement utilisé pour les titres dans les systèmes informatiques.\n\n2. Ligne : Fournir un résumé d'une phrase de la ressource en moins de 100 mots. Veillez à ce que le résumé se concentre uniquement sur les informations essentielles dont un patient aurait besoin et excluez, par exemple, la personne qui a prescrit un médicament ou d'autres métadonnées similaires. Veiller à ce que toutes les données cliniques pertinentes soient incluses, ce qui nous permet d'utiliser le résumé dans des invites supplémentaires lorsqu'il n'est pas possible d'utiliser le JSON complet.\n\nLa représentation JSON suivante définit la ressource FHIR pour laquelle vous devez fournir un titre et un résumé :\n\n{{FHIR_RESOURCE}}" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "您的任务是为用户临床记录中的 FHIR 资源创建标题和摘要。您应使用以下语言提供标题和摘要: {{LOCALE}}。\n\n您的回复应包含两行内容,不含标题、markdown 格式或超出两行的任何其他结构。直接提供内容,无需任何附加结构或引言。另一个计算机程序将解析输出。\n\n1. 行: 1-5 个字的 FHIR 资源摘要,可立即识别资源并提供一目了然的基本信息。请勿使用完整的句子,而应使用计算机系统中通常用于标题的格式。\n\n2. 行: 提供少于 100 字的简短单句资源摘要。确保摘要只关注患者需要的基本信息,例如,不包括开药人或类似元数据。确保包含所有临床相关数据,以便在无法使用完整 JSON 的情况下在附加提示中使用摘要。\n\n以下 JSON 表示法定义了您应提供标题和摘要的 FHIR 资源:\n\n{{FHIR_RESOURCE}}" - } - } - } - }, - "The medical record does not include any FHIR resources for the search term %@." : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Die Krankenakte enthält keine FHIR-Ressourcen für den Suchbegriff %@." - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The medical record does not include any FHIR resources for the search term %@." - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "La historia clínica no incluye ningún recurso FHIR para el término de búsqueda %@." - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Le dossier médical ne contient aucune ressource FHIR pour le terme de recherche %@." - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "病历中不包含搜索词 %@ 的任何 FHIR 资源。" - } - } - } - }, - "This is the summary of the requested %@:\n\n%@" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Dies ist die Zusammenfassung der angeforderten %1$@:\n\n%2$@" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "This is the summary of the requested %1$@:\n\n%2$@" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Este es el resumen del %1$@ solicitado:\n\n%2$@" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Voici le résumé de la demande %1$@ :\n\n%2$@" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "这是请求 %1$@ 的摘要:\n\n%2$@" - } - } - } - }, - "Total Number of Resources: %lld" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Gesamtzahl der Ressourcen: %lld" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Total Number of Resources: %lld" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Número total de recursos: %lld" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nombre total de ressources : %lld" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "资源总数: %lld" - } - } - } - }, - "Unable to parse result of the LLM prompt." : { - "comment" : "Error thrown if the result can not be parsed in the underlying type.", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Das Ergebnis der LLM-Eingabeaufforderung kann nicht analysiert werden." - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Unable to parse result of the LLM prompt." - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "No se ha podido analizar el resultado de la consulta LLM." - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Impossible d'analyser le résultat de l'invite LLM." - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "无法解析 LLM 提示结果。" - } - } - } - } - }, - "version" : "1.0" -} \ No newline at end of file diff --git a/Sources/SpeziFHIRLLM/Resources/Localizable.xcstrings.license b/Sources/SpeziFHIRLLM/Resources/Localizable.xcstrings.license deleted file mode 100644 index 9bfad3b..0000000 --- a/Sources/SpeziFHIRLLM/Resources/Localizable.xcstrings.license +++ /dev/null @@ -1,5 +0,0 @@ -This source file is part of the Stanford Spezi open-source project - -SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md) - -SPDX-License-Identifier: MIT diff --git a/Sources/SpeziFHIRLLM/Settings/FHIRPrompt.swift b/Sources/SpeziFHIRLLM/Settings/FHIRPrompt.swift deleted file mode 100644 index cc5ae1a..0000000 --- a/Sources/SpeziFHIRLLM/Settings/FHIRPrompt.swift +++ /dev/null @@ -1,69 +0,0 @@ -// -// This source file is part of the Stanford Spezi project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import Foundation - - -/// Handle dynamic, localized LLM prompts for FHIR resources. -public struct FHIRPrompt: Hashable, Sendable { - /// Placeholder for FHIR resource in prompts. - public static let fhirResourcePlaceholder = "{{FHIR_RESOURCE}}" - /// Placeholder for the current locale in a prompt - public static let localePlaceholder = "{{LOCALE}}" - - /// The key used for storing and retrieving the prompt. - public let storageKey: String - /// A human-readable description of the prompt, localized as needed. - public let localizedDescription: String - /// The default prompt text to be used if no custom prompt is set. - public let defaultPrompt: String - - /// The current prompt, either from UserDefaults or the default, appended with a localized message that adapts to the user's language settings. - public var prompt: String { - UserDefaults.standard.string(forKey: storageKey) ?? defaultPrompt - } - - - /// - Parameters: - /// - storageKey: The key used for storing and retrieving the prompt. - /// - localizedDescription: A human-readable description of the prompt, localized as needed. - /// - defaultPrompt: The default prompt text to be used if no custom prompt is set. - public init( - storageKey: String, - localizedDescription: String, - defaultPrompt: String - ) { - self.storageKey = storageKey - self.localizedDescription = localizedDescription - self.defaultPrompt = defaultPrompt - } - - - /// Saves a new version of the prompt. - /// - Parameter prompt: The new prompt. - public func save(prompt: String) { - UserDefaults.standard.set(prompt, forKey: storageKey) - } - - public func hash(into hasher: inout Hasher) { - hasher.combine(storageKey) - } - - /// Creates a prompt based in the variable input. - /// - /// Use ``FHIRPrompt/fhirResourcePlaceholder`` and ``FHIRPrompt/localePlaceholder`` to define the elements that should be replaced. - /// - Parameters: - /// - resource: The resource that should be inserted in the prompt. - /// - locale: The current locale that should be inserted in the prompt. - /// - Returns: The constructed prompt. - public func prompt(withFHIRResource resource: String, locale: String = Locale.preferredLanguages[0]) -> String { - prompt - .replacingOccurrences(of: FHIRPrompt.fhirResourcePlaceholder, with: resource) - .replacingOccurrences(of: FHIRPrompt.localePlaceholder, with: locale) - } -} diff --git a/Sources/SpeziFHIRLLM/Settings/FHIRPromptSettingsView.swift b/Sources/SpeziFHIRLLM/Settings/FHIRPromptSettingsView.swift deleted file mode 100644 index 36c4953..0000000 --- a/Sources/SpeziFHIRLLM/Settings/FHIRPromptSettingsView.swift +++ /dev/null @@ -1,56 +0,0 @@ -// -// This source file is part of the Stanford Spezi project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import SwiftUI - - -/// Customize LLM ``FHIRPrompt``s. -/// -/// Allows users to edit and save a prompt associated with a specific ``FHIRPrompt`` type, including where to insert FHIR resources dynamically in the prompt. -public struct FHIRPromptSettingsView: View { - private let promptType: FHIRPrompt - private let onSave: () -> Void - @State private var prompt: String = "" - - - public var body: some View { - VStack(spacing: 16) { - Text("Customize the \(promptType.localizedDescription.lowercased()).") - .multilineTextAlignment(.leading) - TextEditor(text: $prompt) - .fontDesign(.monospaced) - Text("Place \(FHIRPrompt.fhirResourcePlaceholder) at the position in the prompt where the FHIR resource should be inserted. Optionally place \(FHIRPrompt.localePlaceholder) where you would like to insert the current locale.") - .multilineTextAlignment(.leading) - .font(.caption) - Button( - action: { - promptType.save(prompt: prompt) - onSave() - }, - label: { - Text("Save Prompt") - .frame(maxWidth: .infinity, minHeight: 40) - } - ) - .buttonStyle(.borderedProminent) - } - .padding() - .navigationTitle(promptType.localizedDescription) - } - - - /// Initializes a new `PromptSettingsView` with the specified ``FHIRPrompt`` and a save action. - /// - Parameters: - /// - promptType: The ``FHIRPrompt`` instance whose settings are being modified. It holds the information about the specific prompt being edited. - /// - onSave: A closure to be called when the user saves the prompt. This allows for custom actions, like dismissing the view. - public init(promptType: FHIRPrompt, onSave: @escaping () -> Void) { - self.promptType = promptType - self.onSave = onSave - self._prompt = State(initialValue: promptType.prompt) - } -} diff --git a/Sources/SpeziFHIRLLM/Views/FHIRResourcesView.swift b/Sources/SpeziFHIRLLM/Views/FHIRResourcesView.swift deleted file mode 100644 index 41593bb..0000000 --- a/Sources/SpeziFHIRLLM/Views/FHIRResourcesView.swift +++ /dev/null @@ -1,145 +0,0 @@ -// -// This source file is part of the Stanford Spezi project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import SpeziFHIR -import SwiftUI - - -/// Displays a `List` of all available FHIR resources. -/// -/// The ``FHIRResourcesView`` displays a SwiftUI `List` of all available resources in the `SpeziFHIR` [`FHIRStore`](https://swiftpackageindex.com/stanfordspezi/spezifhir/documentation/spezifhir/fhirstore). -/// The FHIR resources are displayed in sections, for example conditions, medications etc. -/// In order to simply locating a concrete FHIR resource, the ``FHIRResourcesView`` provides a search bar on top of the `List`. -/// -/// The ``FHIRResourcesView`` contains an optional content as well as action `View` that are located on top of the resource `List` and can be configured via ``FHIRResourcesView/init(navigationTitle:contentView:actionView:)``. -/// The content and action `View`s are placed within the Swift `List` as a `Section`, enabling proper visual integration with the remainder of the `List`. -/// -/// - Warning: Ensure that the `SpeziFHIR` [`FHIRStore`](https://swiftpackageindex.com/stanfordspezi/spezifhir/documentation/spezifhir/fhirstore) is properly set up and accessible within the SwiftUI `Environment`. -/// -/// ### Usage -/// -/// The example below showcases a minimal example of using the ``FHIRResourcesView``. -/// -/// ```swift -/// struct ResourcesView: View { -/// var body: some View { -/// FHIRResourcesView(navigationTitle: "...") { -/// Button("Some Action") { -/// // Action to perform -/// // ... -/// } -/// } -/// } -/// } -/// ``` -public struct FHIRResourcesView: View { - @Environment(FHIRStore.self) private var fhirStore - @State private var searchText = "" - - private let navigationTitle: Text - private let contentView: ContentView - private let actionView: ActionView - - - public var body: some View { - List { - Section { - contentView - } - - if searchText.isEmpty { - Section { - actionView - } - } - - if fhirStore.allResources.filterByDisplayName(with: searchText).isEmpty { - Text("FHIR_RESOURCES_EMPTY_SEARCH_MESSAGE", bundle: .module) - } else { - resourcesSection - } - - Section { } footer: { - Text("Total Number of Resources: \(fhirStore.allResources.count)", bundle: .module) - } - } - .searchable(text: $searchText) - .navigationDestination(for: FHIRResource.self) { resource in - InspectResourceView(resource: resource) - } - .navigationTitle(navigationTitle) - } - - @ViewBuilder private var resourcesSection: some View { - section(for: \.conditions, sectionName: String(localized: "Conditions")) - section(for: \.diagnostics, sectionName: String(localized: "Diagnostics")) - section(for: \.encounters, sectionName: String(localized: "Encounters")) - section(for: \.immunizations, sectionName: String(localized: "Immunizations")) - section(for: \.medications, sectionName: String(localized: "Medications")) - section(for: \.observations, sectionName: String(localized: "Observations")) - section(for: \.procedures, sectionName: String(localized: "Procedures")) - section(for: \.otherResources, sectionName: String(localized: "Other Resources")) - } - - - /// Creates a ``FHIRResourcesView`` displaying a `List` of all available FHIR resources. - /// - /// - Parameters: - /// - navigationTitle: The localized title displayed for purposes of navigation. - /// - contentView: A custom content `View` that is displayed as the first `Section` of the `List`. - /// - actionView: A custom action `View` that is displayed as the second `Section` of the `List`. Only shown if no search `String` is present. - public init( - navigationTitle: LocalizedStringResource, - @ViewBuilder contentView: () -> ContentView = { EmptyView() }, - @ViewBuilder _ actionView: () -> ActionView = { EmptyView() } - ) { - self.navigationTitle = Text(navigationTitle) - self.contentView = contentView() - self.actionView = actionView() - } - - /// Creates a ``FHIRResourcesView`` displaying a `List` of all available FHIR resources. - /// - /// - Parameters: - /// - navigationTitle: The title displayed for purposes of navigation. - /// - contentView: A custom content `View` that is displayed as the first `Section` of the `List`. - /// - actionView: A custom action `View` that is displayed as the second `Section` of the `List`. Only shown if no search `String` is present. - @_disfavoredOverload - public init( - navigationTitle: Title, - @ViewBuilder contentView: () -> ContentView = { EmptyView() }, - @ViewBuilder _ actionView: () -> ActionView = { EmptyView() } - ) { - self.navigationTitle = Text(verbatim: String(navigationTitle)) - self.contentView = contentView() - self.actionView = actionView() - } - - - private func section(for keyPath: KeyPath, sectionName: String) -> some View { - var resources = fhirStore[keyPath: keyPath] - - if !searchText.isEmpty { - resources = resources.filterByDisplayName(with: searchText) - } - - guard !resources.isEmpty else { - return AnyView(EmptyView()) - } - - return AnyView( - Section(sectionName) { - ForEach(resources) { resource in - NavigationLink(value: resource) { - FHIRResourceSummaryView(resource: resource) - } - } - } - ) - } -} diff --git a/Sources/SpeziFHIRLLM/Views/InspectResourceView.swift b/Sources/SpeziFHIRLLM/Views/InspectResourceView.swift deleted file mode 100644 index 1f6e286..0000000 --- a/Sources/SpeziFHIRLLM/Views/InspectResourceView.swift +++ /dev/null @@ -1,141 +0,0 @@ -// -// This source file is part of the Stanford Spezi project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import SpeziFHIR -import SpeziLLM -import SpeziViews -import SwiftUI - -struct InspectResourceView: View { - @Environment(FHIRResourceInterpreter.self) var fhirResourceInterpreter - @Environment(FHIRResourceSummary.self) var fhirResourceSummary - - @State var interpreting: ViewState = .idle - @State var loadingSummary: ViewState = .idle - - var resource: FHIRResource - - - var body: some View { - List { - summarySection - interpretationSection - resourceSection - } - .navigationTitle(resource.displayName) - .viewStateAlert(state: $interpreting) - .viewStateAlert(state: $loadingSummary) - .task { - #if !TEST - interpret() - #endif - } - } - - @ViewBuilder private var summarySection: some View { - Section("FHIR_RESOURCES_SUMMARY_SECTION".localized(.module).localizedString()) { - if loadingSummary == .processing { - HStack { - Spacer() - ProgressView() - Spacer() - } - } else if let summary = fhirResourceSummary.cachedSummary(forResource: resource) { - VStack { - HStack(spacing: 0) { - Text(summary.title) - .font(.title2) - .multilineTextAlignment(.leading) - .bold() - Spacer() - } - HStack(spacing: 0) { - Text(summary.summary) - .multilineTextAlignment(.leading) - .contextMenu { - Button("FHIR_RESOURCES_SUMMARY_BUTTON".localized(.module).localizedString()) { - loadSummary(forceReload: true) - } - } - Spacer() - } - } - } else { - Button("FHIR_RESOURCES_SUMMARY_BUTTON".localized(.module).localizedString()) { - loadSummary() - } - } - } - } - - @ViewBuilder private var interpretationSection: some View { - Section("FHIR_RESOURCES_INTERPRETATION_SECTION".localized(.module).localizedString()) { - if let interpretation = fhirResourceInterpreter.cachedInterpretation(forResource: resource), !interpretation.isEmpty { - Text(interpretation) - .multilineTextAlignment(.leading) - .contextMenu { - Button("FHIR_RESOURCES_INTERPRETATION_BUTTON".localized(.module).localizedString()) { - interpret(forceReload: true) - } - } - } else if interpreting == .processing { - VStack(alignment: .center) { - Text("FHIR_RESOURCES_INTERPRETATION_LOADING", bundle: .module) - .frame(maxWidth: .infinity) - ProgressView() - .progressViewStyle(.circular) - } - } else { - VStack(alignment: .center) { - Button("FHIR_RESOURCES_INTERPRETATION_BUTTON".localized(.module).localizedString()) { - interpret() - } - } - } - } - } - - @ViewBuilder private var resourceSection: some View { - Section("FHIR_RESOURCES_INTERPRETATION_RESOURCE".localized(.module).localizedString()) { - LazyText(verbatim: resource.jsonDescription) - .fontDesign(.monospaced) - .lineLimit(1) - .font(.caption2) - } - } - - private func loadSummary(forceReload: Bool = false) { - loadingSummary = .processing - - Task { - do { - try await fhirResourceSummary.summarize(resource: resource, forceReload: forceReload) - loadingSummary = .idle - } catch let error as LLMError { - loadingSummary = .error(error) - } catch { - loadingSummary = .error(LLMDefaultError.unknown(error)) - } - } - } - - private func interpret(forceReload: Bool = false) { - interpreting = .processing - - Task { - do { - try await fhirResourceInterpreter.interpret(resource: resource, forceReload: forceReload) - interpreting = .idle - } catch let error as LLMError { - interpreting = .error(error) - } catch { - interpreting = .error(LLMDefaultError.unknown(error)) - } - } - } -} diff --git a/Tests/UITests/TestApp/ContentView.swift b/Tests/UITests/TestApp/ContentView.swift index 97f8c92..09895cb 100644 --- a/Tests/UITests/TestApp/ContentView.swift +++ b/Tests/UITests/TestApp/ContentView.swift @@ -13,7 +13,6 @@ import SwiftUI struct ContentView: View { @Environment(FHIRStore.self) var fhirStore @State var presentPatientSelection = false - @State var presentPromptSettings = false var body: some View { @@ -37,30 +36,9 @@ struct ContentView: View { .sheet(isPresented: $presentPatientSelection) { MockPatientSelection(presentPatientSelection: $presentPatientSelection) } - .sheet(isPresented: $presentPromptSettings) { - PromptSettings(presentPromptSettings: $presentPromptSettings) - } - .toolbar { - ToolbarItem { - presentPromptSettingsButton - } - } } } - - @ViewBuilder private var presentPromptSettingsButton: some View { - Button( - action: { - presentPromptSettings.toggle() - }, - label: { - Image(systemName: "gear") - .accessibilityLabel(Text("Settings")) - } - ) - } - @ViewBuilder private var presentPatientSelectionButton: some View { Button( action: { diff --git a/Tests/UITests/TestApp/ExampleModule.swift b/Tests/UITests/TestApp/ExampleModule.swift deleted file mode 100644 index 632107d..0000000 --- a/Tests/UITests/TestApp/ExampleModule.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// This source file is part of the Stanford Spezi open-source project -// -// SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md) -// -// SPDX-License-Identifier: MIT -// - -import Spezi -import SpeziFHIRLLM -import SpeziLLM -import SpeziLocalStorage - - -class ExampleModule: Module, @unchecked Sendable { - @Dependency private var localStorage: LocalStorage - @Dependency private var llmRunner: LLMRunner - - @Model private var resourceSummary: FHIRResourceSummary - @Model private var resourceInterpreter: FHIRResourceInterpreter - - let llmSchema = LLMMockSchema() - - func configure() { - resourceSummary = FHIRResourceSummary( - localStorage: localStorage, - llmRunner: llmRunner, - llmSchema: llmSchema - ) - resourceInterpreter = FHIRResourceInterpreter( - localStorage: localStorage, - llmRunner: llmRunner, - llmSchema: llmSchema - ) - } -} diff --git a/Tests/UITests/TestApp/PromptSettings.swift b/Tests/UITests/TestApp/PromptSettings.swift deleted file mode 100644 index 5cfbab0..0000000 --- a/Tests/UITests/TestApp/PromptSettings.swift +++ /dev/null @@ -1,48 +0,0 @@ -// -// This source file is part of the Stanford Spezi open-source project -// -// SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md) -// -// SPDX-License-Identifier: MIT -// - -import SpeziFHIRLLM -import SwiftUI - - -struct PromptSettings: View { - @Binding var presentPromptSettings: Bool - @State var prompt: FHIRPrompt? - - - var body: some View { - NavigationStack { - List { - NavigationLink(value: FHIRPrompt.summary) { - Text(FHIRPrompt.summary.localizedDescription) - } - NavigationLink(value: FHIRPrompt.interpretation) { - Text(FHIRPrompt.interpretation.localizedDescription) - } - } - .navigationDestination(for: FHIRPrompt.self) { prompt in - FHIRPromptSettingsView(promptType: prompt) { - print("Saved \(prompt.localizedDescription)") - } - } - .toolbar { - ToolbarItem { - Button( - action: { - presentPromptSettings.toggle() - }, - label: { - Text("Dismiss") - } - ) - } - } - .navigationTitle("Prompt Settings") - } - } -} diff --git a/Tests/UITests/TestApp/TestAppDelegate.swift b/Tests/UITests/TestApp/TestAppDelegate.swift index 96eca3e..b3d3d8b 100644 --- a/Tests/UITests/TestApp/TestAppDelegate.swift +++ b/Tests/UITests/TestApp/TestAppDelegate.swift @@ -8,17 +8,11 @@ import Spezi import SpeziFHIR -import SpeziLLM import SwiftUI class TestAppDelegate: SpeziAppDelegate { override var configuration: Configuration { - Configuration(standard: FHIR()) { - LLMRunner { - LLMMockPlatform() - } - ExampleModule() - } + Configuration(standard: FHIR()) { } } } diff --git a/Tests/UITests/TestAppUITests/FHIRMockDataStorageProviderTests.swift b/Tests/UITests/TestAppUITests/FHIRMockDataStorageProviderTests.swift index b964e87..6a0eb23 100644 --- a/Tests/UITests/TestAppUITests/FHIRMockDataStorageProviderTests.swift +++ b/Tests/UITests/TestAppUITests/FHIRMockDataStorageProviderTests.swift @@ -29,8 +29,6 @@ final class SpeziFHIRTests: XCTestCase { XCTAssert(app.buttons["Jamison785 Denesik803"].waitForExistence(timeout: 20)) app.buttons["Jamison785 Denesik803"].tap() - sleep(3) - app.navigationBars["Select Mock Patient"].buttons["Dismiss"].tap() XCTAssert(app.staticTexts["Allergy Intolerances: 0"].waitForExistence(timeout: 2)) @@ -48,8 +46,6 @@ final class SpeziFHIRTests: XCTestCase { XCTAssert(app.buttons["Maye976 Dickinson688"].waitForExistence(timeout: 20)) app.buttons["Maye976 Dickinson688"].tap() - sleep(3) - app.navigationBars["Select Mock Patient"].buttons["Dismiss"].tap() XCTAssert(app.staticTexts["Allergy Intolerances: 0"].waitForExistence(timeout: 2)) @@ -62,25 +58,4 @@ final class SpeziFHIRTests: XCTestCase { XCTAssert(app.staticTexts["Other Resources: 322"].waitForExistence(timeout: 2)) XCTAssert(app.staticTexts["Procedures: 225"].waitForExistence(timeout: 2)) } - - - func testPromptSettings() throws { - let app = XCUIApplication() - app.launch() - - XCTAssert(app.buttons["Settings"].waitForExistence(timeout: 2)) - app.buttons["Settings"].tap() - - XCTAssert(app.buttons["Summary Prompt"].waitForExistence(timeout: 2)) - app.buttons["Summary Prompt"].tap() - - XCTAssert(app.buttons["Save Prompt"].waitForExistence(timeout: 2)) - app.buttons["Save Prompt"].tap() - app.navigationBars["Summary Prompt"].buttons["Prompt Settings"].tap() - - XCTAssert(app.buttons["Interpretation Prompt"].waitForExistence(timeout: 2)) - app.buttons["Interpretation Prompt"].tap() - app.navigationBars["Interpretation Prompt"].buttons["Prompt Settings"].tap() - app.navigationBars["Prompt Settings"].buttons["Dismiss"].tap() - } } diff --git a/Tests/UITests/UITests.xcodeproj/project.pbxproj b/Tests/UITests/UITests.xcodeproj/project.pbxproj index 7bdee5e..0a5207b 100644 --- a/Tests/UITests/UITests.xcodeproj/project.pbxproj +++ b/Tests/UITests/UITests.xcodeproj/project.pbxproj @@ -8,9 +8,7 @@ /* Begin PBXBuildFile section */ 2F34D14E2B0CF1F1009300C1 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F34D14D2B0CF1F1009300C1 /* ContentView.swift */; }; - 2F34D1502B0CF42F009300C1 /* PromptSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F34D14F2B0CF42F009300C1 /* PromptSettings.swift */; }; 2F34D1522B0CF59A009300C1 /* MockPatientSelection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F34D1512B0CF59A009300C1 /* MockPatientSelection.swift */; }; - 2F34D1542B0D833F009300C1 /* ExampleModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F34D1532B0D833F009300C1 /* ExampleModule.swift */; }; 2F36AD33299DB72400B1077C /* FHIRMockDataStorageProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F36AD32299DB72400B1077C /* FHIRMockDataStorageProviderTests.swift */; }; 2F6D139A28F5F386007C25D6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2F6D139928F5F386007C25D6 /* Assets.xcassets */; }; 2FA7382C290ADFAA007ACEB9 /* TestApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA7382B290ADFAA007ACEB9 /* TestApp.swift */; }; @@ -18,7 +16,6 @@ 2FBD9AFB2B01E4A800237A04 /* SpeziFHIRHealthKit in Frameworks */ = {isa = PBXBuildFile; productRef = 2FBD9AFA2B01E4A800237A04 /* SpeziFHIRHealthKit */; }; 2FD021DD299E0F2900E5B91B /* TestAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FD021DC299E0F2900E5B91B /* TestAppDelegate.swift */; }; 2FF6813F2A849F77002897C6 /* SpeziFHIR in Frameworks */ = {isa = PBXBuildFile; productRef = 2FF6813E2A849F77002897C6 /* SpeziFHIR */; }; - 97FFAD502B8C6CEA00F64722 /* SpeziFHIRLLM in Frameworks */ = {isa = PBXBuildFile; productRef = 97FFAD4F2B8C6CEA00F64722 /* SpeziFHIRLLM */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -33,9 +30,7 @@ /* Begin PBXFileReference section */ 2F34D14D2B0CF1F1009300C1 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; - 2F34D14F2B0CF42F009300C1 /* PromptSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PromptSettings.swift; sourceTree = ""; }; 2F34D1512B0CF59A009300C1 /* MockPatientSelection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockPatientSelection.swift; sourceTree = ""; }; - 2F34D1532B0D833F009300C1 /* ExampleModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleModule.swift; sourceTree = ""; }; 2F36AD32299DB72400B1077C /* FHIRMockDataStorageProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FHIRMockDataStorageProviderTests.swift; sourceTree = ""; }; 2F6D139228F5F384007C25D6 /* TestApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TestApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 2F6D139928F5F386007C25D6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -52,7 +47,6 @@ buildActionMask = 2147483647; files = ( 2FBD9AFB2B01E4A800237A04 /* SpeziFHIRHealthKit in Frameworks */, - 97FFAD502B8C6CEA00F64722 /* SpeziFHIRLLM in Frameworks */, 2FF6813F2A849F77002897C6 /* SpeziFHIR in Frameworks */, 2FBD9AF92B01E4A000237A04 /* SpeziFHIRMockPatients in Frameworks */, ); @@ -94,10 +88,8 @@ children = ( 2FA7382B290ADFAA007ACEB9 /* TestApp.swift */, 2FD021DC299E0F2900E5B91B /* TestAppDelegate.swift */, - 2F34D1532B0D833F009300C1 /* ExampleModule.swift */, 2F34D14D2B0CF1F1009300C1 /* ContentView.swift */, 2F34D1512B0CF59A009300C1 /* MockPatientSelection.swift */, - 2F34D14F2B0CF42F009300C1 /* PromptSettings.swift */, 2F6D139928F5F386007C25D6 /* Assets.xcassets */, ); path = TestApp; @@ -139,7 +131,6 @@ 2FF6813E2A849F77002897C6 /* SpeziFHIR */, 2FBD9AF82B01E4A000237A04 /* SpeziFHIRMockPatients */, 2FBD9AFA2B01E4A800237A04 /* SpeziFHIRHealthKit */, - 97FFAD4F2B8C6CEA00F64722 /* SpeziFHIRLLM */, ); productName = Example; productReference = 2F6D139228F5F384007C25D6 /* TestApp.app */; @@ -251,8 +242,6 @@ files = ( 2FA7382C290ADFAA007ACEB9 /* TestApp.swift in Sources */, 2FD021DD299E0F2900E5B91B /* TestAppDelegate.swift in Sources */, - 2F34D1542B0D833F009300C1 /* ExampleModule.swift in Sources */, - 2F34D1502B0CF42F009300C1 /* PromptSettings.swift in Sources */, 2F34D1522B0CF59A009300C1 /* MockPatientSelection.swift in Sources */, 2F34D14E2B0CF1F1009300C1 /* ContentView.swift in Sources */, ); @@ -546,10 +535,6 @@ isa = XCSwiftPackageProductDependency; productName = SpeziFHIR; }; - 97FFAD4F2B8C6CEA00F64722 /* SpeziFHIRLLM */ = { - isa = XCSwiftPackageProductDependency; - productName = SpeziFHIRLLM; - }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 2F6D138A28F5F384007C25D6 /* Project object */; diff --git a/Tests/UITests/UITests.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Tests/UITests/UITests.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index a6c9466..1ff7f8f 100644 --- a/Tests/UITests/UITests.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Tests/UITests/UITests.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,4 +1,5 @@ { + "originHash" : "9b34b6536014597deb1ec711cbd71207d38c977060bbd264fb4930995e63e38e", "pins" : [ { "identity" : "fhirmodels", @@ -14,26 +15,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/StanfordBDHG/HealthKitOnFHIR", "state" : { - "revision" : "87a9257e6fa37407f3437e4a0bf21dd09a4ea7c5", - "version" : "0.2.11" - } - }, - { - "identity" : "llama.cpp", - "kind" : "remoteSourceControl", - "location" : "https://github.com/StanfordBDHG/llama.cpp", - "state" : { - "revision" : "6839853a321778906e210a33ee2c6aec52f34c97", - "version" : "0.3.3" - } - }, - { - "identity" : "openai", - "kind" : "remoteSourceControl", - "location" : "https://github.com/StanfordBDHG/OpenAI", - "state" : { - "revision" : "1ad95dd531d7c854a3f98f588b0eb68fa83e8a8c", - "version" : "0.2.9" + "revision" : "a1a71254a75c6a3b18a0b356fd8f76bc489a3030", + "version" : "0.2.12" } }, { @@ -45,15 +28,6 @@ "version" : "1.8.0" } }, - { - "identity" : "spezichat", - "kind" : "remoteSourceControl", - "location" : "https://github.com/StanfordSpezi/SpeziChat.git", - "state" : { - "revision" : "940ffbec504849968305d9f956344a4f35b6cd48", - "version" : "0.2.1" - } - }, { "identity" : "spezifoundation", "kind" : "remoteSourceControl", @@ -72,51 +46,6 @@ "version" : "0.6.0" } }, - { - "identity" : "spezillm", - "kind" : "remoteSourceControl", - "location" : "https://github.com/StanfordSpezi/SpeziLLM.git", - "state" : { - "revision" : "6633d8a78eece57d18a9876197e5806c01516343", - "version" : "0.8.4" - } - }, - { - "identity" : "spezionboarding", - "kind" : "remoteSourceControl", - "location" : "https://github.com/StanfordSpezi/SpeziOnboarding", - "state" : { - "revision" : "8d6dda3501720a1952573439b21a503cbecd9e0f", - "version" : "1.2.0" - } - }, - { - "identity" : "spezispeech", - "kind" : "remoteSourceControl", - "location" : "https://github.com/StanfordSpezi/SpeziSpeech", - "state" : { - "revision" : "0b79f72fc8b0606e09787926446dfafb541669d3", - "version" : "1.2.0" - } - }, - { - "identity" : "spezistorage", - "kind" : "remoteSourceControl", - "location" : "https://github.com/StanfordSpezi/SpeziStorage", - "state" : { - "revision" : "0f4a54430e51f82d29da63a7ce5f61bad7dfb9cd", - "version" : "1.2.1" - } - }, - { - "identity" : "speziviews", - "kind" : "remoteSourceControl", - "location" : "https://github.com/StanfordSpezi/SpeziViews", - "state" : { - "revision" : "ff61e6594677572df051b96905cc2a7a12cffd10", - "version" : "1.5.0" - } - }, { "identity" : "swift-atomics", "kind" : "remoteSourceControl", @@ -145,5 +74,5 @@ } } ], - "version" : 2 + "version" : 3 }