diff --git a/README.md b/README.md index 0cb35a1..12d0f5e 100644 --- a/README.md +++ b/README.md @@ -5,14 +5,15 @@ # YChat -YChat is a Kotlin Multiplatform (KMP) project that provides a simple API for integrating the powerful ChatGPT language model developed by OpenAI into mobile applications running on both iOS and Android. The goal of this project is to abstract all the API call logic from ChatGPT, allowing developers to easily leverage the capabilities of the language model in their mobile applications. +YChat is a Kotlin Multiplatform (KMP) project that provides a simple API for integrating the powerful ChatGPT language model developed by OpenAI into mobile applications running on multi platforms. The goal of this project is to abstract all the API call logic from ChatGPT, allowing developers to easily leverage the capabilities of the language model in their mobile applications. The repository contains the source code for the YChat library, along with examples and documentation for getting started with the library. The YChat library provides a consistent interface for interacting with ChatGPT, regardless of the platform, and makes it easy to generate human-like text based on a given prompt or context. -The library uses Kotlin Multiplatform to generate artifacts for both iOS and Android, allowing developers to write code once and use it on both platforms. The project is open source and actively maintained, with contributions from the community encouraged. Overall, YChat provides a convenient and powerful way for mobile developers to incorporate the advanced natural language processing capabilities of ChatGPT into their applications. +The library uses Kotlin Multiplatform to generate artifacts for both iOS, Android and JVM, allowing developers to write code once and use it on multiple platforms. The project is open source and actively maintained, with contributions from the community encouraged. Overall, YChat provides a convenient and powerful way for mobile developers to incorporate the advanced natural language processing capabilities of ChatGPT into their applications. +## ⚡️ Getting Started -## iOS setup +### iOS setup - Go to your project’s file settings and click "Add Package": @@ -24,7 +25,7 @@ The library uses Kotlin Multiplatform to generate artifacts for both iOS and And Once you have found the package click the "Add Package" button to add it to your project. Now you can start using the SDK in your iOS project! -See the code snippet below on how to initialize and use it: +See the code snippet below on how to initialize and use it one of the supported feature: ```swift var yChat: YChat { @@ -43,7 +44,7 @@ do { } ``` -## Android setup +### Android/JVM setup Add the following line to import the library via Gradle. First, make sure Maven Central has been added: @@ -55,13 +56,13 @@ repositories { } ``` -Then, simply import the dependency to your common source-set dependencies: +Then, simply import the dependency to your `build.gradle` dependencies: ```kotlin -implementation("co.yml:ychat:1.0.0") +implementation("co.yml:ychat:1.1.0") ``` -In the snippet below, you can see how to initialize the object and perform a first search: +Take a look at the Kotlin code snippet below for an example of how to initialize and use one of the supported features: ```kotlin @@ -82,11 +83,28 @@ try { } ``` +### Features + +- [Completions](guides/Features.md#completion) +- [ChatCompletions](guides/Features.md#chatcompletions) +- [ImageGenerations](guides/Features.md#imagegenerations) +- [Edits](guides/Features.md#edits) + +## ℹ️ Sample apps + +Take a look at our sample apps to learn how to use the SDK on different platforms: + +[Android Sample](https://github.com/yml-org/ychat/tree/main/sample/android) +
+[iOS Sample](https://github.com/yml-org/ychat/tree/main/sample/ios) +
+[JVM Sample](https://github.com/yml-org/ychat/tree/main/sample/jvm) + ## 🤝 Contributions Feel free to make a suggestion or if you find any error in this project, please open an issue. Make sure to read our [contribution guidelines](https://github.com/yml-org/ychat/blob/main/CONTRIBUTING.md) before. -## License +## 📄 License ``` Copyright 2023 YML diff --git a/YChat-1.0.0.zip b/YChat-1.0.0.zip deleted file mode 100644 index 762bb8d..0000000 Binary files a/YChat-1.0.0.zip and /dev/null differ diff --git a/YChat-1.1.0.zip b/YChat-1.1.0.zip new file mode 100644 index 0000000..01c898c Binary files /dev/null and b/YChat-1.1.0.zip differ diff --git a/YChat.xcframework/Info.plist b/YChat.xcframework/Info.plist index 92b884f..b1ef1ce 100644 --- a/YChat.xcframework/Info.plist +++ b/YChat.xcframework/Info.plist @@ -5,6 +5,8 @@ AvailableLibraries + DebugSymbolsPath + dSYMs LibraryIdentifier ios-arm64 LibraryPath @@ -17,6 +19,8 @@ ios + DebugSymbolsPath + dSYMs LibraryIdentifier ios-x86_64-simulator LibraryPath diff --git a/YChat.xcframework/ios-arm64/YChat.framework/Headers/YChat.h b/YChat.xcframework/ios-arm64/YChat.framework/Headers/YChat.h index eb33503..dd8fd13 100644 --- a/YChat.xcframework/ios-arm64/YChat.framework/Headers/YChat.h +++ b/YChat.xcframework/ios-arm64/YChat.framework/Headers/YChat.h @@ -6,9 +6,9 @@ #import #import -@class YChatKotlinThrowable, YChatYChatCompanion, YChatKotlinArray, YChatKotlinException, YChatKotlinRuntimeException, YChatKotlinIllegalStateException; +@class YChatKotlinThrowable, YChatYChatCompanion, YChatChatMessage, YChatKotlinArray, YChatKotlinException, YChatKotlinRuntimeException, YChatKotlinIllegalStateException; -@protocol YChatCompletion, YChatYChat, YChatYChatCallback, YChatKotlinIterator; +@protocol YChatChatCompletions, YChatCompletion, YChatEdits, YChatImageGenerations, YChatYChat, YChatYChatCallback, YChatKotlinIterator; NS_ASSUME_NONNULL_BEGIN #pragma clang diagnostic push @@ -147,7 +147,10 @@ __attribute__((swift_name("KotlinBoolean"))) __attribute__((swift_name("YChat"))) @protocol YChatYChat @required +- (id)chatCompletions __attribute__((swift_name("chatCompletions()"))); - (id)completion __attribute__((swift_name("completion()"))); +- (id)edits __attribute__((swift_name("edits()"))); +- (id)imageGenerations __attribute__((swift_name("imageGenerations()"))); @end __attribute__((swift_name("YChatCallback"))) @@ -172,6 +175,20 @@ __attribute__((swift_name("YChatCompanion"))) - (id)createApiKey:(NSString *)apiKey __attribute__((swift_name("create(apiKey:)"))); @end +__attribute__((objc_subclassing_restricted)) +__attribute__((swift_name("ChatMessage"))) +@interface YChatChatMessage : YChatBase +- (instancetype)initWithRole:(NSString *)role content:(NSString *)content __attribute__((swift_name("init(role:content:)"))) __attribute__((objc_designated_initializer)); +- (NSString *)component1 __attribute__((swift_name("component1()"))) __attribute__((deprecated("use corresponding property instead"))); +- (NSString *)component2 __attribute__((swift_name("component2()"))) __attribute__((deprecated("use corresponding property instead"))); +- (YChatChatMessage *)doCopyRole:(NSString *)role content:(NSString *)content __attribute__((swift_name("doCopy(role:content:)"))); +- (BOOL)isEqual:(id _Nullable)other __attribute__((swift_name("isEqual(_:)"))); +- (NSUInteger)hash __attribute__((swift_name("hash()"))); +- (NSString *)description __attribute__((swift_name("description()"))); +@property (readonly) NSString *content __attribute__((swift_name("content"))); +@property (readonly) NSString *role __attribute__((swift_name("role"))); +@end + __attribute__((swift_name("KotlinThrowable"))) @interface YChatKotlinThrowable : YChatBase - (instancetype)initWithMessage:(NSString * _Nullable)message __attribute__((swift_name("init(message:)"))) __attribute__((objc_designated_initializer)); @@ -209,6 +226,24 @@ __attribute__((swift_name("ChatGptException"))) @property YChatInt * _Nullable statusCode __attribute__((swift_name("statusCode"))); @end +__attribute__((swift_name("ChatCompletions"))) +@protocol YChatChatCompletions +@required +- (id)addMessageRole:(NSString *)role content:(NSString *)content __attribute__((swift_name("addMessage(role:content:)"))); + +/** + * @note This method converts instances of CancellationException, ChatGptException to errors. + * Other uncaught Kotlin exceptions are fatal. +*/ +- (void)executeContent:(NSString *)content completionHandler:(void (^)(NSArray * _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("execute(content:completionHandler:)"))); +- (void)executeContent:(NSString *)content callback:(id)callback __attribute__((swift_name("execute(content:callback:)"))); +- (id)setMaxResultsResults:(int32_t)results __attribute__((swift_name("setMaxResults(results:)"))); +- (id)setMaxTokensTokens:(int32_t)tokens __attribute__((swift_name("setMaxTokens(tokens:)"))); +- (id)setModelModel:(NSString *)model __attribute__((swift_name("setModel(model:)"))); +- (id)setTemperatureTemperature:(double)temperature __attribute__((swift_name("setTemperature(temperature:)"))); +- (id)setTopPTopP:(double)topP __attribute__((swift_name("setTopP(topP:)"))); +@end + __attribute__((swift_name("Completion"))) @protocol YChatCompletion @required @@ -227,6 +262,38 @@ __attribute__((swift_name("Completion"))) - (id)setTopPTopP:(double)topP __attribute__((swift_name("setTopP(topP:)"))); @end +__attribute__((swift_name("Edits"))) +@protocol YChatEdits +@required + +/** + * @note This method converts instances of CancellationException, ChatGptException to errors. + * Other uncaught Kotlin exceptions are fatal. +*/ +- (void)executeInstruction:(NSString *)instruction completionHandler:(void (^)(NSArray * _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("execute(instruction:completionHandler:)"))); +- (void)executeInstruction:(NSString *)instruction callback:(id)callback __attribute__((swift_name("execute(instruction:callback:)"))); +- (id)setInputInput:(NSString *)input __attribute__((swift_name("setInput(input:)"))); +- (id)setModelModel:(NSString *)model __attribute__((swift_name("setModel(model:)"))); +- (id)setResultsResults:(int32_t)results __attribute__((swift_name("setResults(results:)"))); +- (id)setTemperatureTemperature:(double)temperature __attribute__((swift_name("setTemperature(temperature:)"))); +- (id)setTopPTopP:(double)topP __attribute__((swift_name("setTopP(topP:)"))); +@end + +__attribute__((swift_name("ImageGenerations"))) +@protocol YChatImageGenerations +@required + +/** + * @note This method converts instances of CancellationException, ChatGptException to errors. + * Other uncaught Kotlin exceptions are fatal. +*/ +- (void)executePrompt:(NSString *)prompt completionHandler:(void (^)(NSArray * _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("execute(prompt:completionHandler:)"))); +- (void)executePrompt:(NSString *)prompt callback:(id)callback __attribute__((swift_name("execute(prompt:callback:)"))); +- (id)setResponseFormatResponseFormat:(NSString *)responseFormat __attribute__((swift_name("setResponseFormat(responseFormat:)"))); +- (id)setResultsResults:(int32_t)results __attribute__((swift_name("setResults(results:)"))); +- (id)setSizeSize:(NSString *)size __attribute__((swift_name("setSize(size:)"))); +@end + __attribute__((objc_subclassing_restricted)) __attribute__((swift_name("KotlinArray"))) @interface YChatKotlinArray : YChatBase diff --git a/YChat.xcframework/ios-arm64/YChat.framework/YChat b/YChat.xcframework/ios-arm64/YChat.framework/YChat index 7492a69..0992ffd 100755 Binary files a/YChat.xcframework/ios-arm64/YChat.framework/YChat and b/YChat.xcframework/ios-arm64/YChat.framework/YChat differ diff --git a/YChat.xcframework/ios-arm64/dSYMs/YChat.framework.dSYM/Contents/Info.plist b/YChat.xcframework/ios-arm64/dSYMs/YChat.framework.dSYM/Contents/Info.plist new file mode 100644 index 0000000..fa99c83 --- /dev/null +++ b/YChat.xcframework/ios-arm64/dSYMs/YChat.framework.dSYM/Contents/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleIdentifier + com.apple.xcode.dsym.co.yml.ychat.YChat + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + dSYM + CFBundleSignature + ???? + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/YChat.xcframework/ios-arm64/dSYMs/YChat.framework.dSYM/Contents/Resources/DWARF/YChat b/YChat.xcframework/ios-arm64/dSYMs/YChat.framework.dSYM/Contents/Resources/DWARF/YChat new file mode 100644 index 0000000..58b1875 Binary files /dev/null and b/YChat.xcframework/ios-arm64/dSYMs/YChat.framework.dSYM/Contents/Resources/DWARF/YChat differ diff --git a/YChat.xcframework/ios-x86_64-simulator/YChat.framework/Headers/YChat.h b/YChat.xcframework/ios-x86_64-simulator/YChat.framework/Headers/YChat.h index eb33503..dd8fd13 100644 --- a/YChat.xcframework/ios-x86_64-simulator/YChat.framework/Headers/YChat.h +++ b/YChat.xcframework/ios-x86_64-simulator/YChat.framework/Headers/YChat.h @@ -6,9 +6,9 @@ #import #import -@class YChatKotlinThrowable, YChatYChatCompanion, YChatKotlinArray, YChatKotlinException, YChatKotlinRuntimeException, YChatKotlinIllegalStateException; +@class YChatKotlinThrowable, YChatYChatCompanion, YChatChatMessage, YChatKotlinArray, YChatKotlinException, YChatKotlinRuntimeException, YChatKotlinIllegalStateException; -@protocol YChatCompletion, YChatYChat, YChatYChatCallback, YChatKotlinIterator; +@protocol YChatChatCompletions, YChatCompletion, YChatEdits, YChatImageGenerations, YChatYChat, YChatYChatCallback, YChatKotlinIterator; NS_ASSUME_NONNULL_BEGIN #pragma clang diagnostic push @@ -147,7 +147,10 @@ __attribute__((swift_name("KotlinBoolean"))) __attribute__((swift_name("YChat"))) @protocol YChatYChat @required +- (id)chatCompletions __attribute__((swift_name("chatCompletions()"))); - (id)completion __attribute__((swift_name("completion()"))); +- (id)edits __attribute__((swift_name("edits()"))); +- (id)imageGenerations __attribute__((swift_name("imageGenerations()"))); @end __attribute__((swift_name("YChatCallback"))) @@ -172,6 +175,20 @@ __attribute__((swift_name("YChatCompanion"))) - (id)createApiKey:(NSString *)apiKey __attribute__((swift_name("create(apiKey:)"))); @end +__attribute__((objc_subclassing_restricted)) +__attribute__((swift_name("ChatMessage"))) +@interface YChatChatMessage : YChatBase +- (instancetype)initWithRole:(NSString *)role content:(NSString *)content __attribute__((swift_name("init(role:content:)"))) __attribute__((objc_designated_initializer)); +- (NSString *)component1 __attribute__((swift_name("component1()"))) __attribute__((deprecated("use corresponding property instead"))); +- (NSString *)component2 __attribute__((swift_name("component2()"))) __attribute__((deprecated("use corresponding property instead"))); +- (YChatChatMessage *)doCopyRole:(NSString *)role content:(NSString *)content __attribute__((swift_name("doCopy(role:content:)"))); +- (BOOL)isEqual:(id _Nullable)other __attribute__((swift_name("isEqual(_:)"))); +- (NSUInteger)hash __attribute__((swift_name("hash()"))); +- (NSString *)description __attribute__((swift_name("description()"))); +@property (readonly) NSString *content __attribute__((swift_name("content"))); +@property (readonly) NSString *role __attribute__((swift_name("role"))); +@end + __attribute__((swift_name("KotlinThrowable"))) @interface YChatKotlinThrowable : YChatBase - (instancetype)initWithMessage:(NSString * _Nullable)message __attribute__((swift_name("init(message:)"))) __attribute__((objc_designated_initializer)); @@ -209,6 +226,24 @@ __attribute__((swift_name("ChatGptException"))) @property YChatInt * _Nullable statusCode __attribute__((swift_name("statusCode"))); @end +__attribute__((swift_name("ChatCompletions"))) +@protocol YChatChatCompletions +@required +- (id)addMessageRole:(NSString *)role content:(NSString *)content __attribute__((swift_name("addMessage(role:content:)"))); + +/** + * @note This method converts instances of CancellationException, ChatGptException to errors. + * Other uncaught Kotlin exceptions are fatal. +*/ +- (void)executeContent:(NSString *)content completionHandler:(void (^)(NSArray * _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("execute(content:completionHandler:)"))); +- (void)executeContent:(NSString *)content callback:(id)callback __attribute__((swift_name("execute(content:callback:)"))); +- (id)setMaxResultsResults:(int32_t)results __attribute__((swift_name("setMaxResults(results:)"))); +- (id)setMaxTokensTokens:(int32_t)tokens __attribute__((swift_name("setMaxTokens(tokens:)"))); +- (id)setModelModel:(NSString *)model __attribute__((swift_name("setModel(model:)"))); +- (id)setTemperatureTemperature:(double)temperature __attribute__((swift_name("setTemperature(temperature:)"))); +- (id)setTopPTopP:(double)topP __attribute__((swift_name("setTopP(topP:)"))); +@end + __attribute__((swift_name("Completion"))) @protocol YChatCompletion @required @@ -227,6 +262,38 @@ __attribute__((swift_name("Completion"))) - (id)setTopPTopP:(double)topP __attribute__((swift_name("setTopP(topP:)"))); @end +__attribute__((swift_name("Edits"))) +@protocol YChatEdits +@required + +/** + * @note This method converts instances of CancellationException, ChatGptException to errors. + * Other uncaught Kotlin exceptions are fatal. +*/ +- (void)executeInstruction:(NSString *)instruction completionHandler:(void (^)(NSArray * _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("execute(instruction:completionHandler:)"))); +- (void)executeInstruction:(NSString *)instruction callback:(id)callback __attribute__((swift_name("execute(instruction:callback:)"))); +- (id)setInputInput:(NSString *)input __attribute__((swift_name("setInput(input:)"))); +- (id)setModelModel:(NSString *)model __attribute__((swift_name("setModel(model:)"))); +- (id)setResultsResults:(int32_t)results __attribute__((swift_name("setResults(results:)"))); +- (id)setTemperatureTemperature:(double)temperature __attribute__((swift_name("setTemperature(temperature:)"))); +- (id)setTopPTopP:(double)topP __attribute__((swift_name("setTopP(topP:)"))); +@end + +__attribute__((swift_name("ImageGenerations"))) +@protocol YChatImageGenerations +@required + +/** + * @note This method converts instances of CancellationException, ChatGptException to errors. + * Other uncaught Kotlin exceptions are fatal. +*/ +- (void)executePrompt:(NSString *)prompt completionHandler:(void (^)(NSArray * _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("execute(prompt:completionHandler:)"))); +- (void)executePrompt:(NSString *)prompt callback:(id)callback __attribute__((swift_name("execute(prompt:callback:)"))); +- (id)setResponseFormatResponseFormat:(NSString *)responseFormat __attribute__((swift_name("setResponseFormat(responseFormat:)"))); +- (id)setResultsResults:(int32_t)results __attribute__((swift_name("setResults(results:)"))); +- (id)setSizeSize:(NSString *)size __attribute__((swift_name("setSize(size:)"))); +@end + __attribute__((objc_subclassing_restricted)) __attribute__((swift_name("KotlinArray"))) @interface YChatKotlinArray : YChatBase diff --git a/YChat.xcframework/ios-x86_64-simulator/YChat.framework/YChat b/YChat.xcframework/ios-x86_64-simulator/YChat.framework/YChat index 6d7f3ce..82e7d13 100755 Binary files a/YChat.xcframework/ios-x86_64-simulator/YChat.framework/YChat and b/YChat.xcframework/ios-x86_64-simulator/YChat.framework/YChat differ diff --git a/YChat.xcframework/ios-x86_64-simulator/dSYMs/YChat.framework.dSYM/Contents/Info.plist b/YChat.xcframework/ios-x86_64-simulator/dSYMs/YChat.framework.dSYM/Contents/Info.plist new file mode 100644 index 0000000..fa99c83 --- /dev/null +++ b/YChat.xcframework/ios-x86_64-simulator/dSYMs/YChat.framework.dSYM/Contents/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleIdentifier + com.apple.xcode.dsym.co.yml.ychat.YChat + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + dSYM + CFBundleSignature + ???? + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/YChat.xcframework/ios-x86_64-simulator/dSYMs/YChat.framework.dSYM/Contents/Resources/DWARF/YChat b/YChat.xcframework/ios-x86_64-simulator/dSYMs/YChat.framework.dSYM/Contents/Resources/DWARF/YChat new file mode 100644 index 0000000..70230b4 Binary files /dev/null and b/YChat.xcframework/ios-x86_64-simulator/dSYMs/YChat.framework.dSYM/Contents/Resources/DWARF/YChat differ diff --git a/gradle.properties b/gradle.properties index 27b9bf1..a17be81 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,7 +11,7 @@ kotlin.mpp.enableCInteropCommonization=true # Lib GROUP=co.yml POM_ARTIFACT_ID=ychat -VERSION_NAME=1.0.0 +VERSION_NAME=1.1.0 IOS_NAME=YChat # OSS diff --git a/guides/Features.md b/guides/Features.md new file mode 100644 index 0000000..d53a9c0 --- /dev/null +++ b/guides/Features.md @@ -0,0 +1,176 @@ +# Features + +- [Completion](#completion) +- [ChatCompletions](#chatcompletions) +- [ImageGenerations](#imagegenerations) +- [Edits](#edits) + +## Completion + +The completions api can be used for a wide variety of tasks. You input some text as a prompt, and the model will generate a text completion that attempts to match whatever context or pattern you gave it. For example, if you give the API the prompt, "As Descartes said, I think, therefore", it will return the completion " I am" with high probability. + +### Swift + +```swift +var yChat: YChat { + YChatCompanion.shared.create(apiKey: "your-api-key") +} + +do { + let result = try await chatGpt.completion() + .setInput(input: "Say this is a test.") + .setMaxTokens(tokens: 1024) + .set... // you can set more parameters + .execute() +} catch { + // catch any error that may occurs on api call. +} +``` + +### Kotlin + +```kotlin +val yChat by lazy { + YChat.create("your-api-key") +} + +try { + val result = yChat.completion() + .setInput("Say this is a test.") + .setMaxTokens(1024) + .set... // you can set more parameters + .execute() + +} catch (e: exception) { + // catch any error that may occurs on api call. +} +``` + +## ChatCompletions + +The chatCompletions api generates a list of chat completions for the given input message. It uses machine learning algorithms to generate responses that match the context or pattern provided in the input message. + +### Swift + +```swift +var yChat: YChat { + YChatCompanion.shared.create(apiKey: "your-api-key") +} + +do { + let result = try await chatGpt.chatCompletions() + .setMaxTokens(tokens: 1024) + .addMessage( + role: "assistant", + content: "You are a helpful assistant that only answers questions related to fitness" + ) + .set... // you can set more parameters + .execute(content: "What is the best exercise for building muscle?") +} catch { + // catch any error that may occurs on api call. +} +``` + +### Kotlin + +```kotlin +val yChat by lazy { + YChat.create("your-api-key") +} + +try { + val result = yChat.chatCompletions() + .setMaxTokens(1024) + .addMessage( + role = "assistant", + content = "You are a helpful assistant that only answers questions related to fitness" + ) + .set... // you can set more parameters + .execute("What is the best exercise for building muscle?") + +} catch (e: exception) { + // catch any error that may occurs on api call. +} +``` + +## ImageGenerations + +The image generations api is used to generate images based on a prompt. You input some text as a prompt, and the model will generate one or more images. + +### Swift + +```swift +var yChat: YChat { + YChatCompanion.shared.create(apiKey: "your-api-key") +} + +do { + let result = try await chatGpt.imageGenerations() + .setResults(results: 2) + .setSize(size: "1024x1024") + .set... // you can set more parameters + .execute(prompt: "ocean") +} catch { + // catch any error that may occurs on api call. +} +``` + +### Kotlin + +```kotlin +val yChat by lazy { + YChat.create("your-api-key") +} + +try { + val result = yChat.imageGenerations() + .setResults(2) + .setSize("1024x1024") + .set... // you can set more parameters + .execute("ocean") + +} catch (e: exception) { + // catch any error that may occurs on api call. +} +``` + +## Edits + +The edits api is used to edit prompts and re-generate. Given a prompt and an instruction, the model will return an edited version of the prompt. + +### Swift + +```swift +var yChat: YChat { + YChatCompanion.shared.create(apiKey: "your-api-key") +} + +do { + let result = try await chatGpt.edits() + .setInput(input: "What day of the wek is it?") + .setResults(result: 1) + .set... // you can set more parameters + .execute(instruction: "Fix the spelling mistakes") +} catch { + // catch any error that may occurs on api call. +} +``` + +### Kotlin + +```kotlin +val yChat by lazy { + YChat.create("your-api-key") +} + +try { + val result = yChat.edits() + .setInput("What day of the wek is it?") + .setResults(1) + .set... // you can set more parameters + .execute("Fix the spelling mistakes") + +} catch (e: exception) { + // catch any error that may occurs on api call. +} +``` \ No newline at end of file diff --git a/sample/jvm/README.md b/sample/jvm/README.md index fbe18d4..a1921c6 100644 --- a/sample/jvm/README.md +++ b/sample/jvm/README.md @@ -56,4 +56,19 @@ This endpoint generates images based on the provided prompt. ##### Example: -`GET http://localhost:8080/api/ychat/generations?prompt="ocean" \ No newline at end of file +`GET http://localhost:8080/api/ychat/generations?prompt="ocean" + +### Edits Endpoint + +This endpoint edits the prompt based on the provided instruction. + +##### Endpoint: http://localhost:[port_number]/api/ychat/edits + +##### Parameters: + +- `input`: The input text to use as a starting point for the edit. +- `instruction`: The instruction that tells the model how to edit the prompt. + +##### Example: + +`GET http://localhost:8080/api/ychat/edits?input=What day of the wek is it?&instruction=Fix the spelling mistakes \ No newline at end of file diff --git a/sample/jvm/src/main/java/co/yml/ychat/jvm/controller/YChatController.java b/sample/jvm/src/main/java/co/yml/ychat/jvm/controller/YChatController.java index 05a3290..824580b 100644 --- a/sample/jvm/src/main/java/co/yml/ychat/jvm/controller/YChatController.java +++ b/sample/jvm/src/main/java/co/yml/ychat/jvm/controller/YChatController.java @@ -40,6 +40,15 @@ public ResponseEntity imageGenerations( return ResponseEntity.ok(result); } + @GetMapping("edits") + public ResponseEntity edits( + @RequestParam(value = "input") String input, + @RequestParam(value = "instruction") String instruction + ) throws Exception { + String result = YChatService.getEditsAnswer(input, instruction); + return ResponseEntity.ok(result); + } + private static class Defaults { static final String COMPLETION_INPUT = "Say this is a test."; static final String CHAT_COMPLETION_INPUT = "Tell me one strength exercise"; diff --git a/sample/jvm/src/main/java/co/yml/ychat/jvm/services/YChatService.java b/sample/jvm/src/main/java/co/yml/ychat/jvm/services/YChatService.java index 6fa70fb..fe2f811 100644 --- a/sample/jvm/src/main/java/co/yml/ychat/jvm/services/YChatService.java +++ b/sample/jvm/src/main/java/co/yml/ychat/jvm/services/YChatService.java @@ -42,6 +42,14 @@ public String getImageGenerationsAnswer(String prompt) throws Exception { return future.get().get(0); } + public String getEditsAnswer(String input, String instruction) throws Exception { + final CompletableFuture> future = new CompletableFuture<>(); + ychat.edits() + .setInput(input) + .execute(instruction, new CompletionCallbackResult<>(future)); + return future.get().get(0); + } + private static class CompletionCallbackResult implements YChat.Callback { private final CompletableFuture future; diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/YChat.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/YChat.kt index c6e2286..fca0f34 100644 --- a/ychat/src/commonMain/kotlin/co/yml/ychat/YChat.kt +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/YChat.kt @@ -2,6 +2,7 @@ package co.yml.ychat import co.yml.ychat.entrypoint.features.ChatCompletions import co.yml.ychat.entrypoint.features.Completion +import co.yml.ychat.entrypoint.features.Edits import co.yml.ychat.entrypoint.features.ImageGenerations import co.yml.ychat.entrypoint.impl.YChatImpl import kotlin.jvm.JvmStatic @@ -86,13 +87,28 @@ interface YChat { * ``` * val result = YChat.create(apiKey).imageGenerations() * .setResults(2) - * .setSize(1024x1024) + * .setSize("1024x1024") * .set... - * .execute("/image ocean") + * .execute("ocean") * ``` */ fun imageGenerations(): ImageGenerations + /** + * The edits api is used to edit prompts and re-generate. Given a prompt and an instruction, + * the model will return an edited version of the prompt. + * + * You can configure the parameters of the edits before executing it. Example: + * ``` + * val result = YChat.create(apiKey).edits() + * .setInput("As Descartes said, I think, therefore") + * .setResults(1) + * .set... + * .execute("Fix spelling mistakes") + * ``` + */ + fun edits(): Edits + /** * Callback is an interface used for handling the results of an operation. * It provides two methods, `onSuccess` and `onError`, for handling the success diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/data/api/ChatGptApi.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/data/api/ChatGptApi.kt index a7e3a9f..4c2226f 100644 --- a/ychat/src/commonMain/kotlin/co/yml/ychat/data/api/ChatGptApi.kt +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/data/api/ChatGptApi.kt @@ -4,6 +4,8 @@ import co.yml.ychat.data.dto.ChatCompletionParamsDto import co.yml.ychat.data.dto.ChatCompletionsDto import co.yml.ychat.data.dto.CompletionDto import co.yml.ychat.data.dto.CompletionParamsDto +import co.yml.ychat.data.dto.EditsDto +import co.yml.ychat.data.dto.EditsParamsDto import co.yml.ychat.data.dto.ImageGenerationsDto import co.yml.ychat.data.dto.ImageGenerationsParamsDto import co.yml.ychat.data.infrastructure.ApiResult @@ -15,4 +17,6 @@ internal interface ChatGptApi { suspend fun chatCompletions(paramsDto: ChatCompletionParamsDto): ApiResult suspend fun imageGenerations(paramsDto: ImageGenerationsParamsDto): ApiResult + + suspend fun edits(paramsDto: EditsParamsDto): ApiResult } diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/data/api/impl/ChatGptApiImpl.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/data/api/impl/ChatGptApiImpl.kt index 791c8c2..44bb8b2 100644 --- a/ychat/src/commonMain/kotlin/co/yml/ychat/data/api/impl/ChatGptApiImpl.kt +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/data/api/impl/ChatGptApiImpl.kt @@ -5,6 +5,8 @@ import co.yml.ychat.data.dto.ChatCompletionParamsDto import co.yml.ychat.data.dto.ChatCompletionsDto import co.yml.ychat.data.dto.CompletionDto import co.yml.ychat.data.dto.CompletionParamsDto +import co.yml.ychat.data.dto.EditsDto +import co.yml.ychat.data.dto.EditsParamsDto import co.yml.ychat.data.dto.ImageGenerationsDto import co.yml.ychat.data.dto.ImageGenerationsParamsDto import co.yml.ychat.data.infrastructure.ApiExecutor @@ -36,4 +38,12 @@ internal class ChatGptApiImpl(private val apiExecutor: ApiExecutor) : ChatGptApi .setBody(paramsDto) .execute() } + + override suspend fun edits(paramsDto: EditsParamsDto): ApiResult { + return apiExecutor + .setEndpoint("v1/edits") + .setHttpMethod(HttpMethod.Post) + .setBody(paramsDto) + .execute() + } } diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/data/dto/EditsChoiceDto.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/data/dto/EditsChoiceDto.kt new file mode 100644 index 0000000..b72f7b7 --- /dev/null +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/data/dto/EditsChoiceDto.kt @@ -0,0 +1,12 @@ +package co.yml.ychat.data.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +internal data class EditsChoiceDto( + @SerialName("text") + val text: String, + @SerialName("index") + val index: Int +) diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/data/dto/EditsDto.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/data/dto/EditsDto.kt new file mode 100644 index 0000000..b3cf82c --- /dev/null +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/data/dto/EditsDto.kt @@ -0,0 +1,16 @@ +package co.yml.ychat.data.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +internal data class EditsDto( + @SerialName("object") + val objectType: String, + @SerialName("created") + val created: Long, + @SerialName("choices") + val choices: List, + @SerialName("usage") + val usage: UsageDto, +) diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/data/dto/EditsParamsDto.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/data/dto/EditsParamsDto.kt new file mode 100644 index 0000000..0364563 --- /dev/null +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/data/dto/EditsParamsDto.kt @@ -0,0 +1,20 @@ +package co.yml.ychat.data.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +internal data class EditsParamsDto( + @SerialName("model") + val model: String, + @SerialName("input") + val input: String, + @SerialName("instruction") + val instruction: String, + @SerialName("n") + val results: Int = 1, + @SerialName("temperature") + val temperature: Double, + @SerialName("top_p") + val topP: Double, +) diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/domain/model/ImageGeneratedDto.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/data/dto/ImageGeneratedDto.kt similarity index 91% rename from ychat/src/commonMain/kotlin/co/yml/ychat/domain/model/ImageGeneratedDto.kt rename to ychat/src/commonMain/kotlin/co/yml/ychat/data/dto/ImageGeneratedDto.kt index 055f950..11b4b72 100644 --- a/ychat/src/commonMain/kotlin/co/yml/ychat/domain/model/ImageGeneratedDto.kt +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/data/dto/ImageGeneratedDto.kt @@ -1,4 +1,4 @@ -package co.yml.ychat.domain.model +package co.yml.ychat.data.dto import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/data/dto/ImageGenerationsDto.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/data/dto/ImageGenerationsDto.kt index 7f2d2e2..1169378 100644 --- a/ychat/src/commonMain/kotlin/co/yml/ychat/data/dto/ImageGenerationsDto.kt +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/data/dto/ImageGenerationsDto.kt @@ -1,6 +1,5 @@ package co.yml.ychat.data.dto -import co.yml.ychat.domain.model.ImageGeneratedDto import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/di/module/LibraryModule.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/di/module/LibraryModule.kt index 7f1b5b7..f79e5a5 100644 --- a/ychat/src/commonMain/kotlin/co/yml/ychat/di/module/LibraryModule.kt +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/di/module/LibraryModule.kt @@ -7,12 +7,15 @@ import co.yml.ychat.data.storage.ChatLogStorage import co.yml.ychat.di.provider.NetworkProvider import co.yml.ychat.domain.usecases.ChatCompletionsUseCase import co.yml.ychat.domain.usecases.CompletionUseCase +import co.yml.ychat.domain.usecases.EditsUseCase import co.yml.ychat.domain.usecases.ImageGenerationsUseCase import co.yml.ychat.entrypoint.features.ChatCompletions import co.yml.ychat.entrypoint.features.Completion +import co.yml.ychat.entrypoint.features.Edits import co.yml.ychat.entrypoint.features.ImageGenerations import co.yml.ychat.entrypoint.impl.ChatCompletionsImpl import co.yml.ychat.entrypoint.impl.CompletionImpl +import co.yml.ychat.entrypoint.impl.EditsImpl import co.yml.ychat.entrypoint.impl.ImageGenerationsImpl import kotlinx.coroutines.Dispatchers import org.koin.core.module.Module @@ -27,12 +30,14 @@ internal class LibraryModule(private val apiKey: String) { factory { CompletionImpl(Dispatchers.Default, get()) } factory { ChatCompletionsImpl(Dispatchers.Default, get()) } factory { ImageGenerationsImpl(Dispatchers.Default, get()) } + factory { EditsImpl(Dispatchers.Default, get()) } } private val domainModule = module { factory { CompletionUseCase(get(), get()) } factory { ChatCompletionsUseCase(get()) } factory { ImageGenerationsUseCase(get()) } + factory { EditsUseCase(get()) } } private val dataModule = module { diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/domain/mapper/EditsMapper.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/domain/mapper/EditsMapper.kt new file mode 100644 index 0000000..86311b3 --- /dev/null +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/domain/mapper/EditsMapper.kt @@ -0,0 +1,20 @@ +package co.yml.ychat.domain.mapper + +import co.yml.ychat.data.dto.EditsDto +import co.yml.ychat.data.dto.EditsParamsDto +import co.yml.ychat.domain.model.EditsParams + +internal fun EditsDto.toEditsModel(): List { + return this.choices.map { it.text } +} + +internal fun EditsParams.toEditsParamsDto(): EditsParamsDto { + return EditsParamsDto( + model = this.model, + input = this.input, + instruction = this.instruction, + results = this.results, + temperature = this.temperature, + topP = this.topP + ) +} diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/domain/model/EditsParams.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/domain/model/EditsParams.kt new file mode 100644 index 0000000..7b9797b --- /dev/null +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/domain/model/EditsParams.kt @@ -0,0 +1,10 @@ +package co.yml.ychat.domain.model + +internal data class EditsParams( + var model: String = "text-davinci-edit-001", + var input: String = "", + var instruction: String = "", + var results: Int = 1, + var temperature: Double = 1.0, + var topP: Double = 1.0, +) diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/domain/usecases/EditsUseCase.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/domain/usecases/EditsUseCase.kt new file mode 100644 index 0000000..68e62de --- /dev/null +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/domain/usecases/EditsUseCase.kt @@ -0,0 +1,15 @@ +package co.yml.ychat.domain.usecases + +import co.yml.ychat.data.api.ChatGptApi +import co.yml.ychat.domain.mapper.toEditsModel +import co.yml.ychat.domain.mapper.toEditsParamsDto +import co.yml.ychat.domain.model.EditsParams + +internal data class EditsUseCase(private val chatGptApi: ChatGptApi) { + + suspend fun requestEdits(params: EditsParams): List { + val requestDto = params.toEditsParamsDto() + val response = chatGptApi.edits(requestDto) + return response.getBodyOrThrow().toEditsModel() + } +} diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/features/Edits.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/features/Edits.kt new file mode 100644 index 0000000..9be2857 --- /dev/null +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/features/Edits.kt @@ -0,0 +1,23 @@ +package co.yml.ychat.entrypoint.features + +import co.yml.ychat.YChat +import co.yml.ychat.data.exception.ChatGptException +import kotlin.coroutines.cancellation.CancellationException + +interface Edits { + + fun setInput(input: String): Edits + + fun setResults(results: Int): Edits + + fun setModel(model: String): Edits + + fun setTemperature(temperature: Double): Edits + + fun setTopP(topP: Double): Edits + + @Throws(CancellationException::class, ChatGptException::class) + suspend fun execute(instruction: String): List + + fun execute(instruction: String, callback: YChat.Callback>) +} diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/impl/EditsImpl.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/impl/EditsImpl.kt new file mode 100644 index 0000000..fd1f2ea --- /dev/null +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/impl/EditsImpl.kt @@ -0,0 +1,58 @@ +package co.yml.ychat.entrypoint.impl + +import co.yml.ychat.YChat +import co.yml.ychat.domain.model.EditsParams +import co.yml.ychat.domain.usecases.EditsUseCase +import co.yml.ychat.entrypoint.features.Edits +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch + +internal class EditsImpl( + private val dispatcher: CoroutineDispatcher, + private val editsUseCase: EditsUseCase +) : Edits { + + private val scope by lazy { CoroutineScope(SupervisorJob() + dispatcher) } + + private var params: EditsParams = EditsParams() + + override fun setInput(input: String): Edits { + params.input = input + return this + } + + override fun setResults(results: Int): Edits { + params.results = results + return this + } + + override fun setModel(model: String): Edits { + params.model = model + return this + } + + override fun setTemperature(temperature: Double): Edits { + params.temperature = temperature + return this + } + + override fun setTopP(topP: Double): Edits { + params.topP = topP + return this + } + + override suspend fun execute(instruction: String): List { + params.instruction = instruction + return editsUseCase.requestEdits(params) + } + + override fun execute(instruction: String, callback: YChat.Callback>) { + scope.launch { + runCatching { execute(instruction) } + .onSuccess { callback.onSuccess(it) } + .onFailure { callback.onError(it) } + } + } +} diff --git a/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/impl/YChatImpl.kt b/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/impl/YChatImpl.kt index c29f71e..512b185 100644 --- a/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/impl/YChatImpl.kt +++ b/ychat/src/commonMain/kotlin/co/yml/ychat/entrypoint/impl/YChatImpl.kt @@ -4,6 +4,7 @@ import co.yml.ychat.YChat import co.yml.ychat.di.module.LibraryModule import co.yml.ychat.entrypoint.features.ChatCompletions import co.yml.ychat.entrypoint.features.Completion +import co.yml.ychat.entrypoint.features.Edits import co.yml.ychat.entrypoint.features.ImageGenerations import org.koin.core.KoinApplication @@ -27,4 +28,8 @@ internal class YChatImpl(apiKey: String) : YChat { override fun imageGenerations(): ImageGenerations { return koinApp.koin.get() } + + override fun edits(): Edits { + return koinApp.koin.get() + } } diff --git a/ychat/src/commonTest/kotlin/co/yml/ychat/di/LibraryModuleTest.kt b/ychat/src/commonTest/kotlin/co/yml/ychat/di/LibraryModuleTest.kt index 214b693..8892d8f 100644 --- a/ychat/src/commonTest/kotlin/co/yml/ychat/di/LibraryModuleTest.kt +++ b/ychat/src/commonTest/kotlin/co/yml/ychat/di/LibraryModuleTest.kt @@ -9,6 +9,7 @@ import co.yml.ychat.domain.usecases.CompletionUseCase import co.yml.ychat.domain.usecases.ImageGenerationsUseCase import co.yml.ychat.entrypoint.features.ChatCompletions import co.yml.ychat.entrypoint.features.Completion +import co.yml.ychat.entrypoint.features.Edits import co.yml.ychat.entrypoint.features.ImageGenerations import io.ktor.client.HttpClient import kotlin.test.AfterTest @@ -43,5 +44,6 @@ class LibraryModuleTest : KoinTest { get() get() get() + get() } } diff --git a/ychat/src/commonTest/kotlin/co/yml/ychat/domain/mapper/EditsMapperTest.kt b/ychat/src/commonTest/kotlin/co/yml/ychat/domain/mapper/EditsMapperTest.kt new file mode 100644 index 0000000..01465ff --- /dev/null +++ b/ychat/src/commonTest/kotlin/co/yml/ychat/domain/mapper/EditsMapperTest.kt @@ -0,0 +1,34 @@ +package co.yml.ychat.domain.mapper + +import co.yml.ychat.data.dto.EditsChoiceDto +import co.yml.ychat.data.dto.EditsDto +import co.yml.ychat.data.dto.UsageDto +import co.yml.ychat.domain.model.EditsParams +import kotlin.test.Test +import kotlin.test.assertEquals + +class EditsMapperTest { + + @Test + fun `on convert EditsDto to EditsModel`() { + val listOfChoicesDto = listOf(EditsChoiceDto("text 1", 1), EditsChoiceDto("text 2", 2)) + val editsDto = EditsDto( + created = 12345, + objectType = "edit", + choices = listOfChoicesDto, + usage = UsageDto(1, 1, 1) + ) + assertEquals(listOfChoicesDto.map { it.text }, editsDto.toEditsModel()) + } + + @Test + fun `on convert EditsParams to EditsDto`() { + val editsParams = EditsParams(input = "this is a test") + assertEquals("text-davinci-edit-001", editsParams.toEditsParamsDto().model) + assertEquals("this is a test", editsParams.toEditsParamsDto().input) + assertEquals("", editsParams.toEditsParamsDto().instruction) + assertEquals(1, editsParams.toEditsParamsDto().results) + assertEquals(1.0, editsParams.toEditsParamsDto().temperature) + assertEquals(1.0, editsParams.toEditsParamsDto().topP) + } +} diff --git a/ychat/src/commonTest/kotlin/co/yml/ychat/domain/mapper/ImageGenerationsMapperTest.kt b/ychat/src/commonTest/kotlin/co/yml/ychat/domain/mapper/ImageGenerationsMapperTest.kt index 0ce66ff..7e1abb0 100644 --- a/ychat/src/commonTest/kotlin/co/yml/ychat/domain/mapper/ImageGenerationsMapperTest.kt +++ b/ychat/src/commonTest/kotlin/co/yml/ychat/domain/mapper/ImageGenerationsMapperTest.kt @@ -1,7 +1,7 @@ package co.yml.ychat.domain.mapper import co.yml.ychat.data.dto.ImageGenerationsDto -import co.yml.ychat.domain.model.ImageGeneratedDto +import co.yml.ychat.data.dto.ImageGeneratedDto import co.yml.ychat.domain.model.ImageGenerationsParams import kotlin.test.Test import kotlin.test.assertEquals diff --git a/ychat/src/commonTest/kotlin/co/yml/ychat/domain/model/EditsParamsTest.kt b/ychat/src/commonTest/kotlin/co/yml/ychat/domain/model/EditsParamsTest.kt new file mode 100644 index 0000000..d661d86 --- /dev/null +++ b/ychat/src/commonTest/kotlin/co/yml/ychat/domain/model/EditsParamsTest.kt @@ -0,0 +1,21 @@ +package co.yml.ychat.domain.model + +import kotlin.test.Test +import kotlin.test.assertEquals + +class EditsParamsTest { + + @Test + fun `on EditsParams verify default values`() { + // arrange + val params = EditsParams() + + // assert + assertEquals("text-davinci-edit-001", params.model) + assertEquals("", params.input) + assertEquals("", params.instruction) + assertEquals(1, params.results) + assertEquals(1.0, params.temperature) + assertEquals(1.0, params.topP) + } +} diff --git a/ychat/src/commonTest/kotlin/co/yml/ychat/domain/model/ImageGenerationsParamsTest.kt b/ychat/src/commonTest/kotlin/co/yml/ychat/domain/model/ImageGenerationsParamsTest.kt index 0c237cf..bf28863 100644 --- a/ychat/src/commonTest/kotlin/co/yml/ychat/domain/model/ImageGenerationsParamsTest.kt +++ b/ychat/src/commonTest/kotlin/co/yml/ychat/domain/model/ImageGenerationsParamsTest.kt @@ -6,7 +6,7 @@ import kotlin.test.assertEquals class ImageGenerationsParamsTest { @Test - fun `on ChatCompletionsParams verify default values`() { + fun `on ImageGenerationsParams verify default values`() { // arrange val params = ImageGenerationsParams() diff --git a/ychat/src/commonTest/kotlin/co/yml/ychat/domain/usecases/EditsUseCaseTest.kt b/ychat/src/commonTest/kotlin/co/yml/ychat/domain/usecases/EditsUseCaseTest.kt new file mode 100644 index 0000000..a447def --- /dev/null +++ b/ychat/src/commonTest/kotlin/co/yml/ychat/domain/usecases/EditsUseCaseTest.kt @@ -0,0 +1,68 @@ +package co.yml.ychat.domain.usecases + +import co.yml.ychat.data.api.ChatGptApi +import co.yml.ychat.data.dto.EditsChoiceDto +import co.yml.ychat.data.dto.EditsDto +import co.yml.ychat.data.dto.UsageDto +import co.yml.ychat.data.exception.ChatGptException +import co.yml.ychat.data.infrastructure.ApiResult +import co.yml.ychat.domain.model.EditsParams +import io.mockk.coEvery +import io.mockk.mockk +import kotlinx.coroutines.runBlocking +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals + +class EditsUseCaseTest { + + private lateinit var editsUseCase: EditsUseCase + + private val chatGptApiMock = mockk() + + @BeforeTest + fun setup() { + editsUseCase = EditsUseCase(chatGptApiMock) + } + + @Test + fun `on requestEdits when request succeed then should return formatted result`() { + // arrange + val prompt = "write text" + val editsDto = buildEditsDto(listOf("text 1", "text 2")) + val params = EditsParams(input = prompt, results = 2) + val apiResult = ApiResult(body = editsDto) + coEvery { chatGptApiMock.edits(any()) } returns apiResult + + // act + val result = runBlocking { editsUseCase.requestEdits(params) } + + // assert + assertEquals("text 2", result.last()) + } + + @Test + fun `on requestEdits when not request succeed then should throw an exception`() { + // arrange + val prompt = "text" + val params = EditsParams(input = prompt) + val apiResult = ApiResult(exception = ChatGptException()) + coEvery { chatGptApiMock.edits(any()) } returns apiResult + + // act + val result = + runCatching { runBlocking { editsUseCase.requestEdits(params) } } + + // assert + assertEquals(true, result.exceptionOrNull() is ChatGptException) + } + + private fun buildEditsDto(texts: List): EditsDto { + return EditsDto( + created = 12345, + objectType = "edits", + choices = listOf(EditsChoiceDto(text = texts[0], index = 1), EditsChoiceDto(text = texts[1], index = 2)), + usage = UsageDto(1, 1, 1) + ) + } +} diff --git a/ychat/src/commonTest/kotlin/co/yml/ychat/domain/usecases/ImageGenerationsUseCaseTest.kt b/ychat/src/commonTest/kotlin/co/yml/ychat/domain/usecases/ImageGenerationsUseCaseTest.kt index ad1ffe7..b9671fd 100644 --- a/ychat/src/commonTest/kotlin/co/yml/ychat/domain/usecases/ImageGenerationsUseCaseTest.kt +++ b/ychat/src/commonTest/kotlin/co/yml/ychat/domain/usecases/ImageGenerationsUseCaseTest.kt @@ -4,7 +4,7 @@ import co.yml.ychat.data.api.ChatGptApi import co.yml.ychat.data.dto.ImageGenerationsDto import co.yml.ychat.data.exception.ChatGptException import co.yml.ychat.data.infrastructure.ApiResult -import co.yml.ychat.domain.model.ImageGeneratedDto +import co.yml.ychat.data.dto.ImageGeneratedDto import co.yml.ychat.domain.model.ImageGenerationsParams import io.mockk.coEvery import io.mockk.mockk diff --git a/ychat/src/commonTest/kotlin/co/yml/ychat/entrypoint/YChatTest.kt b/ychat/src/commonTest/kotlin/co/yml/ychat/entrypoint/YChatTest.kt index 0b5be31..c8e195c 100644 --- a/ychat/src/commonTest/kotlin/co/yml/ychat/entrypoint/YChatTest.kt +++ b/ychat/src/commonTest/kotlin/co/yml/ychat/entrypoint/YChatTest.kt @@ -93,6 +93,28 @@ class YChatTest { assertEquals("https://testlink.com/image-test.jpg", result.first()) } + @Test + fun `on edits execute method should return result successfully`() { + // arrange + val expectedResult = "What day of the week is it?" + val imageGenerationsSuccessResult = MockStorage.editsSuccessResult(expectedResult) + mockHttpEngine(imageGenerationsSuccessResult) + + // act + val result = runBlocking { + yChat.edits() + .setResults(1) + .setTemperature(1.0) + .setModel("model-1") + .setTopP(1.0) + .setInput("What day of the wek is it?") + .execute("Fix the spelling mistakes") + } + + // assert + assertEquals(expectedResult, result.first()) + } + private fun mockHttpEngine(result: String) { val httpEngine = MockEngine { respond( diff --git a/ychat/src/commonTest/kotlin/infrastructure/MockStorage.kt b/ychat/src/commonTest/kotlin/infrastructure/MockStorage.kt index 95d843f..7e361c7 100644 --- a/ychat/src/commonTest/kotlin/infrastructure/MockStorage.kt +++ b/ychat/src/commonTest/kotlin/infrastructure/MockStorage.kt @@ -16,4 +16,8 @@ object MockStorage { fun imageGenerationsSuccessResult(text: String) = "{\"created\":1678805561,\"data\":[{\"url\":\"$text\"}]}" + + fun editsSuccessResult(text: String) = "{\"object\":\"edit\",\"created\":1679072839," + + "\"choices\":[{\"text\":\"$text\",\"index\":0}]," + + "\"usage\":{\"prompt_tokens\":25,\"completion_tokens\":28,\"total_tokens\":53}}" }