diff --git a/ChatBot/ChatBot.xcodeproj/project.pbxproj b/ChatBot/ChatBot.xcodeproj/project.pbxproj index b33a3b48..408f97f6 100644 --- a/ChatBot/ChatBot.xcodeproj/project.pbxproj +++ b/ChatBot/ChatBot.xcodeproj/project.pbxproj @@ -9,19 +9,25 @@ /* Begin PBXBuildFile section */ B4B3E2BD2B42D1BB00818B3C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4B3E2BC2B42D1BB00818B3C /* AppDelegate.swift */; }; B4B3E2BF2B42D1BB00818B3C /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4B3E2BE2B42D1BB00818B3C /* SceneDelegate.swift */; }; - B4B3E2C12B42D1BB00818B3C /* ChatGPTViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4B3E2C02B42D1BB00818B3C /* ChatGPTViewController.swift */; }; - B4B3E2C42B42D1BB00818B3C /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B4B3E2C22B42D1BB00818B3C /* Main.storyboard */; }; B4B3E2C62B42D1BC00818B3C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B4B3E2C52B42D1BC00818B3C /* Assets.xcassets */; }; B4B3E2C92B42D1BC00818B3C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B4B3E2C72B42D1BC00818B3C /* LaunchScreen.storyboard */; }; C764C3A02BBFC8BD007AA7B8 /* Mapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C764C39F2BBFC8BD007AA7B8 /* Mapper.swift */; }; C764C3A32BBFC8EE007AA7B8 /* DataDecodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C764C3A22BBFC8EE007AA7B8 /* DataDecodable.swift */; }; C764C3A52BBFC8F6007AA7B8 /* DataDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = C764C3A42BBFC8F6007AA7B8 /* DataDecoder.swift */; }; C764C3A72BBFC90E007AA7B8 /* Mappable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C764C3A62BBFC90E007AA7B8 /* Mappable.swift */; }; + C77BCB6E2BC9042300DC220B /* ChatRoomModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C77BCB6D2BC9042300DC220B /* ChatRoomModel.swift */; }; + C77BCB722BC904AF00DC220B /* ChatBubbleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C77BCB712BC904AF00DC220B /* ChatBubbleView.swift */; }; + C77BCB752BC904D600DC220B /* ChatRoomCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C77BCB742BC904D600DC220B /* ChatRoomCell.swift */; }; + C77BCB772BC904E500DC220B /* ChatRoomCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C77BCB762BC904E500DC220B /* ChatRoomCellViewModel.swift */; }; + C77BCB7A2BC90C0900DC220B /* DEBUG-Keys.plist in Resources */ = {isa = PBXBuildFile; fileRef = C77BCB782BC90C0900DC220B /* DEBUG-Keys.plist */; }; C79F5A432BB5851D009E3AB4 /* GPTRequestDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = C79F5A422BB5851D009E3AB4 /* GPTRequestDTO.swift */; }; C7DE45392BBC196D0087C6C3 /* Requestable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7DE45382BBC196D0087C6C3 /* Requestable.swift */; }; C7DE453B2BBC19820087C6C3 /* NetworkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7DE453A2BBC19820087C6C3 /* NetworkManager.swift */; }; - C7DE45442BBC25A40087C6C3 /* DEBUG-Keys.plist in Resources */ = {isa = PBXBuildFile; fileRef = C7DE45432BBC25A40087C6C3 /* DEBUG-Keys.plist */; }; C7DE45472BBC26360087C6C3 /* KeyEnviromentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7DE45462BBC26360087C6C3 /* KeyEnviromentHandler.swift */; }; + F332A89E2BC9065C009DB954 /* ChatRoomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F332A89D2BC9065C009DB954 /* ChatRoomView.swift */; }; + F332A8A02BC90742009DB954 /* ActivityIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F332A89F2BC90742009DB954 /* ActivityIndicatorView.swift */; }; + F332A8A32BC9085E009DB954 /* ChatRoomViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F332A8A22BC9085E009DB954 /* ChatRoomViewModel.swift */; }; + F332A8A52BC908FC009DB954 /* ChatRoomViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F332A8A42BC908FC009DB954 /* ChatRoomViewController.swift */; }; F39BA7EB2BB573B10058257C /* GPTResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = F39BA7EA2BB573B10058257C /* GPTResponseDTO.swift */; }; F39BA7EE2BB5740D0058257C /* GPTMessageDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = F39BA7ED2BB5740D0058257C /* GPTMessageDTO.swift */; }; F39BA7F02BB5744E0058257C /* GPTBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = F39BA7EF2BB5744E0058257C /* GPTBody.swift */; }; @@ -40,8 +46,6 @@ B4B3E2B92B42D1BB00818B3C /* ChatBot.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ChatBot.app; sourceTree = BUILT_PRODUCTS_DIR; }; B4B3E2BC2B42D1BB00818B3C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; B4B3E2BE2B42D1BB00818B3C /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; - B4B3E2C02B42D1BB00818B3C /* ChatGPTViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatGPTViewController.swift; sourceTree = ""; }; - B4B3E2C32B42D1BB00818B3C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; B4B3E2C52B42D1BC00818B3C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; B4B3E2C82B42D1BC00818B3C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; B4B3E2CA2B42D1BC00818B3C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -49,11 +53,19 @@ C764C3A22BBFC8EE007AA7B8 /* DataDecodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataDecodable.swift; sourceTree = ""; }; C764C3A42BBFC8F6007AA7B8 /* DataDecoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataDecoder.swift; sourceTree = ""; }; C764C3A62BBFC90E007AA7B8 /* Mappable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mappable.swift; sourceTree = ""; }; + C77BCB6D2BC9042300DC220B /* ChatRoomModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatRoomModel.swift; sourceTree = ""; }; + C77BCB712BC904AF00DC220B /* ChatBubbleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatBubbleView.swift; sourceTree = ""; }; + C77BCB742BC904D600DC220B /* ChatRoomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatRoomCell.swift; sourceTree = ""; }; + C77BCB762BC904E500DC220B /* ChatRoomCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatRoomCellViewModel.swift; sourceTree = ""; }; + C77BCB782BC90C0900DC220B /* DEBUG-Keys.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "DEBUG-Keys.plist"; sourceTree = ""; }; C79F5A422BB5851D009E3AB4 /* GPTRequestDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GPTRequestDTO.swift; sourceTree = ""; }; C7DE45382BBC196D0087C6C3 /* Requestable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Requestable.swift; sourceTree = ""; }; C7DE453A2BBC19820087C6C3 /* NetworkManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkManager.swift; sourceTree = ""; }; - C7DE45432BBC25A40087C6C3 /* DEBUG-Keys.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "DEBUG-Keys.plist"; sourceTree = ""; }; C7DE45462BBC26360087C6C3 /* KeyEnviromentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyEnviromentHandler.swift; sourceTree = ""; }; + F332A89D2BC9065C009DB954 /* ChatRoomView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatRoomView.swift; sourceTree = ""; }; + F332A89F2BC90742009DB954 /* ActivityIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityIndicatorView.swift; sourceTree = ""; }; + F332A8A22BC9085E009DB954 /* ChatRoomViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatRoomViewModel.swift; sourceTree = ""; }; + F332A8A42BC908FC009DB954 /* ChatRoomViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatRoomViewController.swift; sourceTree = ""; }; F39BA7EA2BB573B10058257C /* GPTResponseDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GPTResponseDTO.swift; sourceTree = ""; }; F39BA7ED2BB5740D0058257C /* GPTMessageDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GPTMessageDTO.swift; sourceTree = ""; }; F39BA7EF2BB5744E0058257C /* GPTBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GPTBody.swift; sourceTree = ""; }; @@ -99,9 +111,11 @@ isa = PBXGroup; children = ( C7DE45492BBC26820087C6C3 /* App */, - C7DE45482BBC26720087C6C3 /* Controller */, - C7DE45362BBC189C0087C6C3 /* Model */, - B4B3E2C22B42D1BB00818B3C /* Main.storyboard */, + F332A8A12BC90745009DB954 /* Shared */, + C77BCB6B2BC903F400DC220B /* Presentation */, + C77BCB6A2BC903D800DC220B /* Domain */, + C7DE45362BBC189C0087C6C3 /* Data */, + C77BCB792BC90C0900DC220B /* Private */, B4B3E2C52B42D1BC00818B3C /* Assets.xcassets */, B4B3E2C72B42D1BC00818B3C /* LaunchScreen.storyboard */, B4B3E2CA2B42D1BC00818B3C /* Info.plist */, @@ -127,6 +141,60 @@ path = Decoder; sourceTree = ""; }; + C77BCB6A2BC903D800DC220B /* Domain */ = { + isa = PBXGroup; + children = ( + C77BCB6C2BC9041300DC220B /* Entity */, + C764C39E2BBFC8A7007AA7B8 /* Mapper */, + ); + path = Domain; + sourceTree = ""; + }; + C77BCB6B2BC903F400DC220B /* Presentation */ = { + isa = PBXGroup; + children = ( + C77BCB6F2BC9049000DC220B /* ChatRoom */, + ); + path = Presentation; + sourceTree = ""; + }; + C77BCB6C2BC9041300DC220B /* Entity */ = { + isa = PBXGroup; + children = ( + C77BCB6D2BC9042300DC220B /* ChatRoomModel.swift */, + ); + path = Entity; + sourceTree = ""; + }; + C77BCB6F2BC9049000DC220B /* ChatRoom */ = { + isa = PBXGroup; + children = ( + F332A89D2BC9065C009DB954 /* ChatRoomView.swift */, + F332A8A22BC9085E009DB954 /* ChatRoomViewModel.swift */, + F332A8A42BC908FC009DB954 /* ChatRoomViewController.swift */, + C77BCB702BC9049600DC220B /* Cell */, + ); + path = ChatRoom; + sourceTree = ""; + }; + C77BCB702BC9049600DC220B /* Cell */ = { + isa = PBXGroup; + children = ( + C77BCB712BC904AF00DC220B /* ChatBubbleView.swift */, + C77BCB742BC904D600DC220B /* ChatRoomCell.swift */, + C77BCB762BC904E500DC220B /* ChatRoomCellViewModel.swift */, + ); + path = Cell; + sourceTree = ""; + }; + C77BCB792BC90C0900DC220B /* Private */ = { + isa = PBXGroup; + children = ( + C77BCB782BC90C0900DC220B /* DEBUG-Keys.plist */, + ); + path = Private; + sourceTree = ""; + }; C7DE45332BBC18820087C6C3 /* NetworkService */ = { isa = PBXGroup; children = ( @@ -137,18 +205,16 @@ path = NetworkService; sourceTree = ""; }; - C7DE45362BBC189C0087C6C3 /* Model */ = { + C7DE45362BBC189C0087C6C3 /* Data */ = { isa = PBXGroup; children = ( - C764C39E2BBFC8A7007AA7B8 /* Mapper */, C764C3A12BBFC8C7007AA7B8 /* Decoder */, F39BA7EC2BB573BE0058257C /* DTO */, C7DE45332BBC18820087C6C3 /* NetworkService */, F39BA7F62BB576380058257C /* Error */, C7DE45452BBC25FC0087C6C3 /* Constant */, - C7DE45422BBC25880087C6C3 /* Private */, ); - path = Model; + path = Data; sourceTree = ""; }; C7DE45372BBC194E0087C6C3 /* NetworkManger */ = { @@ -160,14 +226,6 @@ path = NetworkManger; sourceTree = ""; }; - C7DE45422BBC25880087C6C3 /* Private */ = { - isa = PBXGroup; - children = ( - C7DE45432BBC25A40087C6C3 /* DEBUG-Keys.plist */, - ); - path = Private; - sourceTree = ""; - }; C7DE45452BBC25FC0087C6C3 /* Constant */ = { isa = PBXGroup; children = ( @@ -176,21 +234,21 @@ path = Constant; sourceTree = ""; }; - C7DE45482BBC26720087C6C3 /* Controller */ = { + C7DE45492BBC26820087C6C3 /* App */ = { isa = PBXGroup; children = ( - B4B3E2C02B42D1BB00818B3C /* ChatGPTViewController.swift */, + B4B3E2BC2B42D1BB00818B3C /* AppDelegate.swift */, + B4B3E2BE2B42D1BB00818B3C /* SceneDelegate.swift */, ); - path = Controller; + path = App; sourceTree = ""; }; - C7DE45492BBC26820087C6C3 /* App */ = { + F332A8A12BC90745009DB954 /* Shared */ = { isa = PBXGroup; children = ( - B4B3E2BC2B42D1BB00818B3C /* AppDelegate.swift */, - B4B3E2BE2B42D1BB00818B3C /* SceneDelegate.swift */, + F332A89F2BC90742009DB954 /* ActivityIndicatorView.swift */, ); - path = App; + path = Shared; sourceTree = ""; }; F39BA7EC2BB573BE0058257C /* DTO */ = { @@ -293,9 +351,8 @@ buildActionMask = 2147483647; files = ( B4B3E2C92B42D1BC00818B3C /* LaunchScreen.storyboard in Resources */, - C7DE45442BBC25A40087C6C3 /* DEBUG-Keys.plist in Resources */, + C77BCB7A2BC90C0900DC220B /* DEBUG-Keys.plist in Resources */, B4B3E2C62B42D1BC00818B3C /* Assets.xcassets in Resources */, - B4B3E2C42B42D1BB00818B3C /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -308,15 +365,17 @@ files = ( C7DE453B2BBC19820087C6C3 /* NetworkManager.swift in Sources */, F39BA7EB2BB573B10058257C /* GPTResponseDTO.swift in Sources */, + C77BCB752BC904D600DC220B /* ChatRoomCell.swift in Sources */, C7DE45472BBC26360087C6C3 /* KeyEnviromentHandler.swift in Sources */, F3D2DF992BBC078000BEECC0 /* RequstInfomation.swift in Sources */, + C77BCB722BC904AF00DC220B /* ChatBubbleView.swift in Sources */, C764C3A02BBFC8BD007AA7B8 /* Mapper.swift in Sources */, F3D2DF8A2BBBFA9400BEECC0 /* APIHostType.swift in Sources */, C764C3A32BBFC8EE007AA7B8 /* DataDecodable.swift in Sources */, - B4B3E2C12B42D1BB00818B3C /* ChatGPTViewController.swift in Sources */, F3D2DF972BBC075D00BEECC0 /* RequestProvider.swift in Sources */, F39BA7EE2BB5740D0058257C /* GPTMessageDTO.swift in Sources */, C79F5A432BB5851D009E3AB4 /* GPTRequestDTO.swift in Sources */, + F332A8A32BC9085E009DB954 /* ChatRoomViewModel.swift in Sources */, F39BA7F02BB5744E0058257C /* GPTBody.swift in Sources */, C764C3A52BBFC8F6007AA7B8 /* DataDecoder.swift in Sources */, F3D2DF922BBBFB2E00BEECC0 /* URLInformation.swift in Sources */, @@ -325,8 +384,13 @@ F3D2DF8C2BBBFAA900BEECC0 /* SchemeType.swift in Sources */, F3D2DF902BBBFB1100BEECC0 /* EndpointProvider.swift in Sources */, C7DE45392BBC196D0087C6C3 /* Requestable.swift in Sources */, + F332A8A52BC908FC009DB954 /* ChatRoomViewController.swift in Sources */, + C77BCB772BC904E500DC220B /* ChatRoomCellViewModel.swift in Sources */, B4B3E2BF2B42D1BB00818B3C /* SceneDelegate.swift in Sources */, + C77BCB6E2BC9042300DC220B /* ChatRoomModel.swift in Sources */, F3D2DF8E2BBBFAEF00BEECC0 /* EndpointProvidable.swift in Sources */, + F332A8A02BC90742009DB954 /* ActivityIndicatorView.swift in Sources */, + F332A89E2BC9065C009DB954 /* ChatRoomView.swift in Sources */, F3D2DF952BBC074600BEECC0 /* RequstProvidable.swift in Sources */, C764C3A72BBFC90E007AA7B8 /* Mappable.swift in Sources */, ); @@ -335,14 +399,6 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ - B4B3E2C22B42D1BB00818B3C /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - B4B3E2C32B42D1BB00818B3C /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; B4B3E2C72B42D1BC00818B3C /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( @@ -485,7 +541,6 @@ INFOPLIST_FILE = ChatBot/Info.plist; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; - INFOPLIST_KEY_UIMainStoryboardFile = Main; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -514,7 +569,6 @@ INFOPLIST_FILE = ChatBot/Info.plist; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; - INFOPLIST_KEY_UIMainStoryboardFile = Main; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 15.0; diff --git a/ChatBot/ChatBot/App/SceneDelegate.swift b/ChatBot/ChatBot/App/SceneDelegate.swift index b0fdb6dd..fe3cd9c5 100644 --- a/ChatBot/ChatBot/App/SceneDelegate.swift +++ b/ChatBot/ChatBot/App/SceneDelegate.swift @@ -4,7 +4,12 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { - guard let _ = (scene as? UIWindowScene) else { return } + + guard let windowScene = (scene as? UIWindowScene) else { return } + + window = UIWindow(windowScene: windowScene) + window?.rootViewController = ChatRoomViewController() + window?.makeKeyAndVisible() } func sceneDidDisconnect(_ scene: UIScene) { } diff --git a/ChatBot/ChatBot/Assets.xcassets/NormalPink.colorset/Contents.json b/ChatBot/ChatBot/Assets.xcassets/NormalPink.colorset/Contents.json new file mode 100644 index 00000000..6be9ba9d --- /dev/null +++ b/ChatBot/ChatBot/Assets.xcassets/NormalPink.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0.541", + "green" : "0.475", + "red" : "0.918" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0.541", + "green" : "0.475", + "red" : "0.918" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ChatBot/ChatBot/Assets.xcassets/SunshineYellow.colorset/Contents.json b/ChatBot/ChatBot/Assets.xcassets/SunshineYellow.colorset/Contents.json new file mode 100644 index 00000000..77dfee06 --- /dev/null +++ b/ChatBot/ChatBot/Assets.xcassets/SunshineYellow.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0.714", + "green" : "0.925", + "red" : "1.000" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0.714", + "green" : "0.925", + "red" : "1.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ChatBot/ChatBot/Base.lproj/Main.storyboard b/ChatBot/ChatBot/Base.lproj/Main.storyboard index 62c43e86..bec707b6 100644 --- a/ChatBot/ChatBot/Base.lproj/Main.storyboard +++ b/ChatBot/ChatBot/Base.lproj/Main.storyboard @@ -1,18 +1,20 @@ + + - + - + - + @@ -20,6 +22,7 @@ + diff --git a/ChatBot/ChatBot/Controller/ChatGPTViewController.swift b/ChatBot/ChatBot/Controller/ChatGPTViewController.swift deleted file mode 100644 index 9f538e9e..00000000 --- a/ChatBot/ChatBot/Controller/ChatGPTViewController.swift +++ /dev/null @@ -1,32 +0,0 @@ -import UIKit -import Combine - -final class ChatGPTViewController: UIViewController { - private var cancellables = Set() - private let mapper: Mappable = Mapper(dataDecoder: DataDecoder(networkManager: NetworkManager())) - private let messages = [ - Message(role: .system, content: "안녕 너는 나와 아무 주제에 대해서 이야기할텐데, 나의 이름을 대화 내내 기억해야해, 답변은 30글자 내로 해줘", toolCalls: nil), - Message(role: .user, content: "안녕 나의 이름은 Harry야, Jin 그리고 July와 함께 챗봇을 만드는 중이야! 마지막 프로젝트인데 어렵고 넘모 힘들다~ 웅우엉웅웡", - toolCalls: nil) - ] - - override func viewDidLoad() { - super.viewDidLoad() - printUserQuestion(with: messages[1].content) - printChatGPTAnswer(with: messages) - } - - private func printUserQuestion(with question: String) { - print("Harry: \(question)") - } - - private func printChatGPTAnswer(with messages: [Message]) { - mapper.mapChatGPTContent(with: messages) - .sink { completion in - print(completion) - } receiveValue: { value in - print(value) - } - .store(in: &cancellables) - } -} diff --git a/ChatBot/ChatBot/Model/Constant/KeyEnviromentHandler.swift b/ChatBot/ChatBot/Data/Constant/KeyEnviromentHandler.swift similarity index 100% rename from ChatBot/ChatBot/Model/Constant/KeyEnviromentHandler.swift rename to ChatBot/ChatBot/Data/Constant/KeyEnviromentHandler.swift diff --git a/ChatBot/ChatBot/Model/DTO/GPTBody.swift b/ChatBot/ChatBot/Data/DTO/GPTBody.swift similarity index 100% rename from ChatBot/ChatBot/Model/DTO/GPTBody.swift rename to ChatBot/ChatBot/Data/DTO/GPTBody.swift diff --git a/ChatBot/ChatBot/Model/DTO/GPTMessageDTO.swift b/ChatBot/ChatBot/Data/DTO/GPTMessageDTO.swift similarity index 100% rename from ChatBot/ChatBot/Model/DTO/GPTMessageDTO.swift rename to ChatBot/ChatBot/Data/DTO/GPTMessageDTO.swift diff --git a/ChatBot/ChatBot/Model/DTO/GPTRequestDTO.swift b/ChatBot/ChatBot/Data/DTO/GPTRequestDTO.swift similarity index 100% rename from ChatBot/ChatBot/Model/DTO/GPTRequestDTO.swift rename to ChatBot/ChatBot/Data/DTO/GPTRequestDTO.swift diff --git a/ChatBot/ChatBot/Model/DTO/GPTResponseDTO.swift b/ChatBot/ChatBot/Data/DTO/GPTResponseDTO.swift similarity index 100% rename from ChatBot/ChatBot/Model/DTO/GPTResponseDTO.swift rename to ChatBot/ChatBot/Data/DTO/GPTResponseDTO.swift diff --git a/ChatBot/ChatBot/Model/Decoder/DataDecodable.swift b/ChatBot/ChatBot/Data/Decoder/DataDecodable.swift similarity index 100% rename from ChatBot/ChatBot/Model/Decoder/DataDecodable.swift rename to ChatBot/ChatBot/Data/Decoder/DataDecodable.swift diff --git a/ChatBot/ChatBot/Model/Decoder/DataDecoder.swift b/ChatBot/ChatBot/Data/Decoder/DataDecoder.swift similarity index 100% rename from ChatBot/ChatBot/Model/Decoder/DataDecoder.swift rename to ChatBot/ChatBot/Data/Decoder/DataDecoder.swift diff --git a/ChatBot/ChatBot/Model/Error/NetworkError.swift b/ChatBot/ChatBot/Data/Error/NetworkError.swift similarity index 100% rename from ChatBot/ChatBot/Model/Error/NetworkError.swift rename to ChatBot/ChatBot/Data/Error/NetworkError.swift diff --git a/ChatBot/ChatBot/Model/NetworkService/NetworkManger/NetworkManager.swift b/ChatBot/ChatBot/Data/NetworkService/NetworkManger/NetworkManager.swift similarity index 100% rename from ChatBot/ChatBot/Model/NetworkService/NetworkManger/NetworkManager.swift rename to ChatBot/ChatBot/Data/NetworkService/NetworkManger/NetworkManager.swift diff --git a/ChatBot/ChatBot/Model/NetworkService/NetworkManger/Requestable.swift b/ChatBot/ChatBot/Data/NetworkService/NetworkManger/Requestable.swift similarity index 100% rename from ChatBot/ChatBot/Model/NetworkService/NetworkManger/Requestable.swift rename to ChatBot/ChatBot/Data/NetworkService/NetworkManger/Requestable.swift diff --git a/ChatBot/ChatBot/Model/NetworkService/URL/APIHostType.swift b/ChatBot/ChatBot/Data/NetworkService/URL/APIHostType.swift similarity index 100% rename from ChatBot/ChatBot/Model/NetworkService/URL/APIHostType.swift rename to ChatBot/ChatBot/Data/NetworkService/URL/APIHostType.swift diff --git a/ChatBot/ChatBot/Model/NetworkService/URL/EndpointProvidable.swift b/ChatBot/ChatBot/Data/NetworkService/URL/EndpointProvidable.swift similarity index 100% rename from ChatBot/ChatBot/Model/NetworkService/URL/EndpointProvidable.swift rename to ChatBot/ChatBot/Data/NetworkService/URL/EndpointProvidable.swift diff --git a/ChatBot/ChatBot/Model/NetworkService/URL/EndpointProvider.swift b/ChatBot/ChatBot/Data/NetworkService/URL/EndpointProvider.swift similarity index 100% rename from ChatBot/ChatBot/Model/NetworkService/URL/EndpointProvider.swift rename to ChatBot/ChatBot/Data/NetworkService/URL/EndpointProvider.swift diff --git a/ChatBot/ChatBot/Model/NetworkService/URL/SchemeType.swift b/ChatBot/ChatBot/Data/NetworkService/URL/SchemeType.swift similarity index 100% rename from ChatBot/ChatBot/Model/NetworkService/URL/SchemeType.swift rename to ChatBot/ChatBot/Data/NetworkService/URL/SchemeType.swift diff --git a/ChatBot/ChatBot/Model/NetworkService/URL/URLInformation.swift b/ChatBot/ChatBot/Data/NetworkService/URL/URLInformation.swift similarity index 100% rename from ChatBot/ChatBot/Model/NetworkService/URL/URLInformation.swift rename to ChatBot/ChatBot/Data/NetworkService/URL/URLInformation.swift diff --git a/ChatBot/ChatBot/Model/NetworkService/URLRequst/RequestProvider.swift b/ChatBot/ChatBot/Data/NetworkService/URLRequst/RequestProvider.swift similarity index 100% rename from ChatBot/ChatBot/Model/NetworkService/URLRequst/RequestProvider.swift rename to ChatBot/ChatBot/Data/NetworkService/URLRequst/RequestProvider.swift diff --git a/ChatBot/ChatBot/Model/NetworkService/URLRequst/RequstInfomation.swift b/ChatBot/ChatBot/Data/NetworkService/URLRequst/RequstInfomation.swift similarity index 100% rename from ChatBot/ChatBot/Model/NetworkService/URLRequst/RequstInfomation.swift rename to ChatBot/ChatBot/Data/NetworkService/URLRequst/RequstInfomation.swift diff --git a/ChatBot/ChatBot/Model/NetworkService/URLRequst/RequstProvidable.swift b/ChatBot/ChatBot/Data/NetworkService/URLRequst/RequstProvidable.swift similarity index 100% rename from ChatBot/ChatBot/Model/NetworkService/URLRequst/RequstProvidable.swift rename to ChatBot/ChatBot/Data/NetworkService/URLRequst/RequstProvidable.swift diff --git a/ChatBot/ChatBot/Domain/Entity/ChatRoomModel.swift b/ChatBot/ChatBot/Domain/Entity/ChatRoomModel.swift new file mode 100644 index 00000000..d3c6b1ea --- /dev/null +++ b/ChatBot/ChatBot/Domain/Entity/ChatRoomModel.swift @@ -0,0 +1,11 @@ +import Foundation + +enum Section { + case chat +} + +struct ChatRoomModel: Hashable { + let id: UUID = UUID() + let content: String + let role: Role +} diff --git a/ChatBot/ChatBot/Model/Mapper/Mappable.swift b/ChatBot/ChatBot/Domain/Mapper/Mappable.swift similarity index 80% rename from ChatBot/ChatBot/Model/Mapper/Mappable.swift rename to ChatBot/ChatBot/Domain/Mapper/Mappable.swift index 34e968d4..eb2a2de0 100644 --- a/ChatBot/ChatBot/Model/Mapper/Mappable.swift +++ b/ChatBot/ChatBot/Domain/Mapper/Mappable.swift @@ -2,5 +2,5 @@ import Foundation import Combine protocol Mappable { - func mapChatGPTContent(with messages: [Message]) -> AnyPublisher + func mapChatGPTContent(with messages: [Message]) -> AnyPublisher<[ChatRoomModel], Error> } diff --git a/ChatBot/ChatBot/Model/Mapper/Mapper.swift b/ChatBot/ChatBot/Domain/Mapper/Mapper.swift similarity index 72% rename from ChatBot/ChatBot/Model/Mapper/Mapper.swift rename to ChatBot/ChatBot/Domain/Mapper/Mapper.swift index 36a18162..82b5fe51 100644 --- a/ChatBot/ChatBot/Model/Mapper/Mapper.swift +++ b/ChatBot/ChatBot/Domain/Mapper/Mapper.swift @@ -8,11 +8,12 @@ final class Mapper: Mappable { self.dataDecoder = dataDecoder } - func mapChatGPTContent(with messages: [Message]) -> AnyPublisher { + func mapChatGPTContent(with messages: [Message]) -> AnyPublisher<[ChatRoomModel], Error> { return dataDecoder.decodedChatGPTCompletionData(with: messages, type: GPTResponseDTO.self) .receive(on: RunLoop.main) .map { result in - return result.choices[0].message.content + print(result) + return [ChatRoomModel(content: result.choices[0].message.content, role: result.choices[0].message.role)] } .eraseToAnyPublisher() } diff --git a/ChatBot/ChatBot/Info.plist b/ChatBot/ChatBot/Info.plist index dd3c9afd..0eb786dc 100644 --- a/ChatBot/ChatBot/Info.plist +++ b/ChatBot/ChatBot/Info.plist @@ -15,8 +15,6 @@ Default Configuration UISceneDelegateClassName $(PRODUCT_MODULE_NAME).SceneDelegate - UISceneStoryboardFile - Main diff --git a/ChatBot/ChatBot/Presentation/ChatRoom/Cell/ChatBubbleView.swift b/ChatBot/ChatBot/Presentation/ChatRoom/Cell/ChatBubbleView.swift new file mode 100644 index 00000000..79954bf7 --- /dev/null +++ b/ChatBot/ChatBot/Presentation/ChatRoom/Cell/ChatBubbleView.swift @@ -0,0 +1,80 @@ +import UIKit + +final class ChatBubbleView: UIView { + + let bubbleLayer = CAShapeLayer() + + let chatLabel: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.numberOfLines = 0 + return label + }() + + var outcoming = Role.user + + override init(frame: CGRect) { + super.init(frame: frame) + commonInit() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + commonInit() + } + + private func commonInit() -> Void { + layer.addSublayer(bubbleLayer) + addSubview(chatLabel) + + NSLayoutConstraint.activate([ + chatLabel.topAnchor.constraint(equalTo: self.topAnchor, constant: 12.0), + chatLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -12.0), + chatLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 12.0), + chatLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -12.0), + ]) + } + + override func layoutSubviews() { + super.layoutSubviews() + + let width = bounds.size.width + let height = bounds.size.height + + let bezierPath = UIBezierPath() + + if outcoming == .user { + bezierPath.move(to: CGPoint(x: width - 22, y: height)) + bezierPath.addLine(to: CGPoint(x: 17, y: height)) + bezierPath.addCurve(to: CGPoint(x: 0, y: height - 17), controlPoint1: CGPoint(x: 7.61, y: height), controlPoint2: CGPoint(x: 0, y: height - 7.61)) + bezierPath.addLine(to: CGPoint(x: 0, y: 17)) + bezierPath.addCurve(to: CGPoint(x: 17, y: 0), controlPoint1: CGPoint(x: 0, y: 7.61), controlPoint2: CGPoint(x: 7.61, y: 0)) + bezierPath.addLine(to: CGPoint(x: width - 21, y: 0)) + bezierPath.addCurve(to: CGPoint(x: width - 4, y: 17), controlPoint1: CGPoint(x: width - 11.61, y: 0), controlPoint2: CGPoint(x: width - 4, y: 7.61)) + bezierPath.addLine(to: CGPoint(x: width - 4, y: height - 11)) + bezierPath.addCurve(to: CGPoint(x: width, y: height), controlPoint1: CGPoint(x: width - 4, y: height - 1), controlPoint2: CGPoint(x: width, y: height)) + bezierPath.addLine(to: CGPoint(x: width + 0.05, y: height - 0.01)) + bezierPath.addCurve(to: CGPoint(x: width - 11.04, y: height - 4.04), controlPoint1: CGPoint(x: width - 4.07, y: height + 0.43), controlPoint2: CGPoint(x: width - 8.16, y: height - 1.06)) + bezierPath.addCurve(to: CGPoint(x: width - 22, y: height), controlPoint1: CGPoint(x: width - 16, y: height), controlPoint2: CGPoint(x: width - 19, y: height)) + bezierPath.close() + } else { + bezierPath.move(to: CGPoint(x: 22, y: height)) + bezierPath.addLine(to: CGPoint(x: width - 17, y: height)) + bezierPath.addCurve(to: CGPoint(x: width, y: height - 17), controlPoint1: CGPoint(x: width - 7.61, y: height), controlPoint2: CGPoint(x: width, y: height - 7.61)) + bezierPath.addLine(to: CGPoint(x: width, y: 17)) + bezierPath.addCurve(to: CGPoint(x: width - 17, y: 0), controlPoint1: CGPoint(x: width, y: 7.61), controlPoint2: CGPoint(x: width - 7.61, y: 0)) + bezierPath.addLine(to: CGPoint(x: 21, y: 0)) + bezierPath.addCurve(to: CGPoint(x: 4, y: 17), controlPoint1: CGPoint(x: 11.61, y: 0), controlPoint2: CGPoint(x: 4, y: 7.61)) + bezierPath.addLine(to: CGPoint(x: 4, y: height - 11)) + bezierPath.addCurve(to: CGPoint(x: 0, y: height), controlPoint1: CGPoint(x: 4, y: height - 1), controlPoint2: CGPoint(x: 0, y: height)) + bezierPath.addLine(to: CGPoint(x: -0.05, y: height - 0.01)) + bezierPath.addCurve(to: CGPoint(x: 11.04, y: height - 4.04), controlPoint1: CGPoint(x: 4.07, y: height + 0.43), controlPoint2: CGPoint(x: 8.16, y: height - 1.06)) + bezierPath.addCurve(to: CGPoint(x: 22, y: height), controlPoint1: CGPoint(x: 16, y: height), controlPoint2: CGPoint(x: 19, y: height)) + bezierPath.close() + } + + bubbleLayer.fillColor = (outcoming == .user) ? UIColor(named: "SunshineYellow")?.cgColor : UIColor(white: 0.90, alpha: 1.0).cgColor + + bubbleLayer.path = bezierPath.cgPath + } +} diff --git a/ChatBot/ChatBot/Presentation/ChatRoom/Cell/ChatRoomCell.swift b/ChatBot/ChatBot/Presentation/ChatRoom/Cell/ChatRoomCell.swift new file mode 100644 index 00000000..195b7f33 --- /dev/null +++ b/ChatBot/ChatBot/Presentation/ChatRoom/Cell/ChatRoomCell.swift @@ -0,0 +1,51 @@ +import UIKit + +final class ChatRoomCell: UICollectionViewListCell { + static let identifier = "ChatRoomCell" + + var viewModel: ChatRoomCellViewModel! { + didSet { setupViewModel() } + } + + var bubbleView = ChatBubbleView() + var leadingOrTrailingConstraint = NSLayoutConstraint() + + override init(frame: CGRect) { + super.init(frame: .zero) + commonInit() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func prepareForReuse() { + super.prepareForReuse() + commonInit() + } + + private func commonInit() { + bubbleView.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(bubbleView) + + NSLayoutConstraint.activate([ + bubbleView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 12.0), + bubbleView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -12.0), + bubbleView.widthAnchor.constraint(lessThanOrEqualTo: contentView.widthAnchor, multiplier: 0.66) + ]) + } + + private func setupViewModel() { + + bubbleView.chatLabel.text = viewModel.contentMessage + bubbleView.outcoming = viewModel.contentRole + + leadingOrTrailingConstraint.isActive = false + if viewModel.contentRole == .user { + leadingOrTrailingConstraint = bubbleView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -12.0) + } else { + leadingOrTrailingConstraint = bubbleView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 12.0) + } + leadingOrTrailingConstraint.isActive = true + } +} diff --git a/ChatBot/ChatBot/Presentation/ChatRoom/Cell/ChatRoomCellViewModel.swift b/ChatBot/ChatBot/Presentation/ChatRoom/Cell/ChatRoomCellViewModel.swift new file mode 100644 index 00000000..404a6de4 --- /dev/null +++ b/ChatBot/ChatBot/Presentation/ChatRoom/Cell/ChatRoomCellViewModel.swift @@ -0,0 +1,20 @@ +import Foundation +import Combine + +final class ChatRoomCellViewModel { + @Published var contentMessage: String = "" + @Published var contentRole: Role = .user + + private let message: ChatRoomModel + + init(message: ChatRoomModel) { + self.message = message + + setupBinding() + } + + private func setupBinding() { + contentMessage = message.content + contentRole = message.role + } +} diff --git a/ChatBot/ChatBot/Presentation/ChatRoom/ChatRoomView.swift b/ChatBot/ChatBot/Presentation/ChatRoom/ChatRoomView.swift new file mode 100644 index 00000000..3c51ec3b --- /dev/null +++ b/ChatBot/ChatBot/Presentation/ChatRoom/ChatRoomView.swift @@ -0,0 +1,107 @@ +import UIKit + +final class ChatRoomView: UIView { + lazy var collectionView = UICollectionView(frame: .zero, collectionViewLayout: createLayout()) + lazy var chattingTextView = UITextView() + lazy var activityIndicationView = ActivityIndicatorView(style: .medium) + lazy var sendButton: UIButton = { + let button = UIButton() + let configuration = UIImage.SymbolConfiguration(pointSize: 30) + let image = UIImage(systemName: "arrow.up.square.fill", withConfiguration: configuration) + + button.setImage(image, for: .normal) + button.tintColor = UIColor(named: "SunshineYellow") + + NSLayoutConstraint.activate([ + button.widthAnchor.constraint(equalToConstant: 40), + button.heightAnchor.constraint(equalToConstant: 40) + ]) + return button + }() + + private lazy var textStackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .horizontal + stackView.distribution = .fill + stackView.alignment = .fill + return stackView + }() + + init() { + super.init(frame: .zero) + addSubviews() + setupConstraints() + setupViews() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func addSubviews() { + textStackView.addArrangedSubview(chattingTextView) + textStackView.addArrangedSubview(sendButton) + + let subviews = [collectionView, textStackView, activityIndicationView] + + subviews.forEach { + addSubview($0) + $0.translatesAutoresizingMaskIntoConstraints = false + } + } + + func startLoading() { + collectionView.isUserInteractionEnabled = false + collectionView.keyboardDismissMode = .interactive + chattingTextView.isUserInteractionEnabled = false + activityIndicationView.isHidden = false + activityIndicationView.startAnimating() + } + + func finishLoading() { + collectionView.isUserInteractionEnabled = true + chattingTextView.isUserInteractionEnabled = true + activityIndicationView.stopAnimating() + } + + private func setupConstraints() { + + NSLayoutConstraint.activate([ + collectionView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 4), + collectionView.leadingAnchor.constraint(equalTo: leadingAnchor), + collectionView.trailingAnchor.constraint(equalTo: trailingAnchor), + collectionView.bottomAnchor.constraint(equalTo: textStackView.topAnchor, constant: -10), + + textStackView.heightAnchor.constraint(equalToConstant: 40), + textStackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 25), + textStackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -25), + textStackView.bottomAnchor.constraint(equalTo: keyboardLayoutGuide.topAnchor, constant: -10), + + activityIndicationView.centerXAnchor.constraint(equalTo: centerXAnchor), + activityIndicationView.centerYAnchor.constraint(equalTo: centerYAnchor), + activityIndicationView.heightAnchor.constraint(equalToConstant: 50), + activityIndicationView.widthAnchor.constraint(equalToConstant: 50) + ]) + } + + private func setupViews() { + collectionView.backgroundColor = .systemBackground + chattingTextView.font = .preferredFont(forTextStyle: .body) + chattingTextView.layer.cornerRadius = 10 + chattingTextView.autocorrectionType = .no + chattingTextView.backgroundColor = .systemBackground + } + + private func createLayout() -> UICollectionViewLayout { + let size = NSCollectionLayoutSize( + widthDimension: .fractionalWidth(1), + heightDimension: .estimated(40)) + let item = NSCollectionLayoutItem(layoutSize: size) + let group = NSCollectionLayoutGroup.horizontal(layoutSize: size, subitem: item, count: 1) + + let section = NSCollectionLayoutSection(group: group) + section.contentInsets = NSDirectionalEdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 5) + section.interGroupSpacing = 5 + return UICollectionViewCompositionalLayout(section: section) + } +} diff --git a/ChatBot/ChatBot/Presentation/ChatRoom/ChatRoomViewController.swift b/ChatBot/ChatBot/Presentation/ChatRoom/ChatRoomViewController.swift new file mode 100644 index 00000000..f7134a34 --- /dev/null +++ b/ChatBot/ChatBot/Presentation/ChatRoom/ChatRoomViewController.swift @@ -0,0 +1,123 @@ +import UIKit +import Combine + +final class ChatRoomViewController: UIViewController { + private typealias DataSource = UICollectionViewDiffableDataSource + private typealias Snapshot = NSDiffableDataSourceSnapshot + + private lazy var contentView = ChatRoomView() + private let viewModel: ChatRoomViewModel + private var bindings = Set() + + private var dataSource: DataSource! + private var history: String = "" + private var count: Int = 0 + + init(viewModel: ChatRoomViewModel = ChatRoomViewModel()) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + view = contentView + } + + override func viewDidLoad() { + view.backgroundColor = UIColor(named: "NormalPink") + configureButton() + setupTableView() + configureDataSource() + setupBindings() + } + + private func setupTableView() { + contentView.collectionView.register(ChatRoomCell.self, forCellWithReuseIdentifier: ChatRoomCell.identifier) + contentView.collectionView.allowsSelection = false + } + + private func configureButton() { + contentView.sendButton.addTarget(self, action: #selector(questionTest), for: .touchUpInside) + } + + @objc private func questionTest() { + guard let query = contentView.chattingTextView.text else { return } + history += "UserQuestion\(count): \(query)" + " " + print("질문:\(query)") + viewModel.askQuestion(query: query, history: history) + contentView.chattingTextView.text = "" + } + + private func setupBindings() { + func bindViewModelToView() { + viewModel.$comments + .receive(on: RunLoop.main) + .sink(receiveValue: { [weak self] answer in + guard let self = self else { return } + if !answer.isEmpty { + print("호출 두번되는 문제가 있음") + history += "GPTAnswer\(count): \(answer[0].content)" + " " + self.count += 1 + self.updateSections() + } + }) + .store(in: &bindings) + + let stateValueHandler: (ChatRoomModelState) -> Void = { [weak self] state in + switch state { + case .loading: + self?.contentView.startLoading() + case .finishedLoading: + self?.contentView.finishLoading() + case .error(let error): + self?.contentView.finishLoading() + self?.showError(error) + } + } + + viewModel.$state + .receive(on: RunLoop.main) + .sink(receiveValue: stateValueHandler) + .store(in: &bindings) + } + + bindViewModelToView() + } + + private func showError(_ error: Error) { + let alertController = UIAlertController(title: "error", message: error.localizedDescription, preferredStyle: .alert) + let alertAction = UIAlertAction(title: "OK", style: .default) { [unowned self] _ in + self.dismiss(animated: true, completion: nil) + } + alertController.addAction(alertAction) + present(alertController, animated: true, completion: nil) + } + + private func updateSections() { + var snapshot = Snapshot() + snapshot.appendSections([.chat]) + snapshot.appendItems(viewModel.comments) + dataSource.apply(snapshot, animatingDifferences: true) + guard let newItem = viewModel.comments.last, + let index = dataSource.indexPath(for: newItem) else { return } + contentView.collectionView.scrollToItem(at: index, at: .bottom, animated: true) + } +} + +// MARK: - UICollectionViewDataSource +extension ChatRoomViewController { + private func configureDataSource() { + dataSource = DataSource( + collectionView: contentView.collectionView, + cellProvider: { (collectionView, indexPath, message) -> UICollectionViewCell? in + let cell = collectionView.dequeueReusableCell( + withReuseIdentifier: ChatRoomCell.identifier, + for: indexPath) as? ChatRoomCell + cell?.viewModel = ChatRoomCellViewModel(message: message) + return cell + }) + } +} diff --git a/ChatBot/ChatBot/Presentation/ChatRoom/ChatRoomViewModel.swift b/ChatBot/ChatBot/Presentation/ChatRoom/ChatRoomViewModel.swift new file mode 100644 index 00000000..cdc0ca87 --- /dev/null +++ b/ChatBot/ChatBot/Presentation/ChatRoom/ChatRoomViewModel.swift @@ -0,0 +1,60 @@ +import Foundation +import Combine + +enum ChatRoomModelError: Error, Equatable { + case chattingFetch +} + +enum ChatRoomModelState: Equatable { + case loading + case finishedLoading + case error(ChatRoomModelError) +} + +final class ChatRoomViewModel { + @Published private(set) var comments: [ChatRoomModel] = [] + @Published private(set) var state: ChatRoomModelState = .finishedLoading + private var requestMessage: [Message] = [Message(role: .system, content: + "질문은 유저와 GPT가 나눈 대화 이력이 들어있습니다. 항상 마지막 질문이 지금 물어보는 것이니 이전 기록을 잘 참고해서 자연스럽게 대화를 이어가주세요. 마치 오랜 친구처럼요. 항상 마지막 가장 큰 번호의 UserQuestion질문에만 답해주세요.", toolCalls: nil)] + + private let mapper: Mappable + private var bindings = Set() + + init(mapper: Mappable = Mapper(dataDecoder: DataDecoder(networkManager: NetworkManager()))) { + self.mapper = mapper + } + + func askQuestion(query: String, history: String) { + fetchData(with: query, of: history) + } +} + +extension ChatRoomViewModel { + private func fetchData(with question: String?, of history: String?) { + state = .loading + guard let question = question, let history = history else { return } + + self.comments.append(contentsOf: [ChatRoomModel(content: question, role: .user)]) + + let questionCompletionHandler: (Subscribers.Completion) -> Void = { [weak self] + completion in + switch completion { + case .failure: + self?.state = .error(.chattingFetch) + case .finished: + self?.state = .finishedLoading + } + } + + let questionValueHander: ([ChatRoomModel]) -> Void = { [weak self] messages in + self?.comments.append(contentsOf: messages) + } + + requestMessage.append(Message(role: .user, content: history, toolCalls: nil)) + + print("요청 메시지: \(requestMessage)") + mapper.mapChatGPTContent(with: requestMessage) + .sink(receiveCompletion: questionCompletionHandler, receiveValue: questionValueHander) + .store(in: &bindings) + } +} diff --git a/ChatBot/ChatBot/Shared/ActivityIndicatorView.swift b/ChatBot/ChatBot/Shared/ActivityIndicatorView.swift new file mode 100644 index 00000000..3420467f --- /dev/null +++ b/ChatBot/ChatBot/Shared/ActivityIndicatorView.swift @@ -0,0 +1,20 @@ +import UIKit + +final class ActivityIndicatorView: UIActivityIndicatorView { + override init(style: UIActivityIndicatorView.Style) { + super.init(style: style) + + setUp() + } + + required init(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setUp() { + color = .white + backgroundColor = .darkGray + layer.cornerRadius = 5.0 + hidesWhenStopped = true + } +}