Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 30 additions & 4 deletions GimiFeedback/GimiFeedback.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
D306028E2E377EC500908D1D /* FeedbackWriteViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D306028C2E377EC500908D1D /* FeedbackWriteViewModel.swift */; };
D30602962E3782A200908D1D /* KcELECTRA-base-v2022.mlpackage in Sources */ = {isa = PBXBuildFile; fileRef = D30602952E3782A200908D1D /* KcELECTRA-base-v2022.mlpackage */; };
D36C43842E38A029006A027E /* TokenizerREADME.md in Resources */ = {isa = PBXBuildFile; fileRef = D36C43832E38A025006A027E /* TokenizerREADME.md */; };
EC25CC332E5975880087EC50 /* FirebaseAppCheck in Frameworks */ = {isa = PBXBuildFile; productRef = EC25CC322E5975880087EC50 /* FirebaseAppCheck */; };
EC371D842E55E9AA008E4AB7 /* GeminiManger.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC371D832E55E9AA008E4AB7 /* GeminiManger.swift */; };
EC371DC92E560D76008E4AB7 /* FirebaseAI in Frameworks */ = {isa = PBXBuildFile; productRef = EC371DC82E560D76008E4AB7 /* FirebaseAI */; };
EC6A4C842E31284F0087DB5C /* ChannelDetail+DescriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC6A4C832E31284F0087DB5C /* ChannelDetail+DescriptionView.swift */; };
EC6A4C862E3129300087DB5C /* ChannelDetail+EmptyFeedbackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC6A4C852E3129300087DB5C /* ChannelDetail+EmptyFeedbackView.swift */; };
EC6A4C882E312A3B0087DB5C /* ChannelDetail+FeedbackListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC6A4C872E312A3B0087DB5C /* ChannelDetail+FeedbackListView.swift */; };
Expand Down Expand Up @@ -64,7 +67,6 @@
EE41FEDC2E2250DF0002E595 /* KakaoSDKAuth in Frameworks */ = {isa = PBXBuildFile; productRef = EE41FEDB2E2250DF0002E595 /* KakaoSDKAuth */; };
EE41FEDE2E2250DF0002E595 /* KakaoSDKCommon in Frameworks */ = {isa = PBXBuildFile; productRef = EE41FEDD2E2250DF0002E595 /* KakaoSDKCommon */; };
EE41FEE02E2250DF0002E595 /* KakaoSDKUser in Frameworks */ = {isa = PBXBuildFile; productRef = EE41FEDF2E2250DF0002E595 /* KakaoSDKUser */; };
EE41FEE52E2256280002E595 /* Config.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = EE41FEE42E2256280002E595 /* Config.xcconfig */; };
EE41FEE72E22572C0002E595 /* Bundle+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE41FEE62E2257260002E595 /* Bundle+Extension.swift */; };
EE41FEE92E2259350002E595 /* UserViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE41FEE82E2259310002E595 /* UserViewModel.swift */; };
EE41FEEB2E22599C0002E595 /* KakaoAuthManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE41FEEA2E2259980002E595 /* KakaoAuthManager.swift */; };
Expand Down Expand Up @@ -132,6 +134,7 @@
D306028C2E377EC500908D1D /* FeedbackWriteViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackWriteViewModel.swift; sourceTree = "<group>"; };
D30602952E3782A200908D1D /* KcELECTRA-base-v2022.mlpackage */ = {isa = PBXFileReference; lastKnownFileType = folder.mlpackage; path = "KcELECTRA-base-v2022.mlpackage"; sourceTree = "<group>"; };
D36C43832E38A025006A027E /* TokenizerREADME.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = TokenizerREADME.md; sourceTree = "<group>"; };
EC371D832E55E9AA008E4AB7 /* GeminiManger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeminiManger.swift; sourceTree = "<group>"; };
EC6A4C832E31284F0087DB5C /* ChannelDetail+DescriptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ChannelDetail+DescriptionView.swift"; sourceTree = "<group>"; };
EC6A4C852E3129300087DB5C /* ChannelDetail+EmptyFeedbackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ChannelDetail+EmptyFeedbackView.swift"; sourceTree = "<group>"; };
EC6A4C872E312A3B0087DB5C /* ChannelDetail+FeedbackListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ChannelDetail+FeedbackListView.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -238,10 +241,12 @@
EEDBB1BE2E2913CE00F16407 /* FirebaseFunctions in Frameworks */,
D306027D2E377E8400908D1D /* Transformers in Frameworks */,
EE41FEDE2E2250DF0002E595 /* KakaoSDKCommon in Frameworks */,
EC25CC332E5975880087EC50 /* FirebaseAppCheck in Frameworks */,
EE3755462E1E5B8D009AD262 /* FirebaseAuth in Frameworks */,
ECC980B12E290DC4004A483B /* KakaoSDKTemplate in Frameworks */,
EE3755482E1E5B8D009AD262 /* FirebaseFirestore in Frameworks */,
EE41FEE02E2250DF0002E595 /* KakaoSDKUser in Frameworks */,
EC371DC92E560D76008E4AB7 /* FirebaseAI in Frameworks */,
EE41FEDC2E2250DF0002E595 /* KakaoSDKAuth in Frameworks */,
ECC980AF2E290DC4004A483B /* KakaoSDKShare in Frameworks */,
);
Expand Down Expand Up @@ -300,6 +305,15 @@
path = MLWeights;
sourceTree = "<group>";
};
EC371D822E55E99C008E4AB7 /* LLM */ = {
isa = PBXGroup;
children = (
EC94C0F22E3746130020E59D /* GPTManger.swift */,
EC371D832E55E9AA008E4AB7 /* GeminiManger.swift */,
);
path = LLM;
sourceTree = "<group>";
};
EC6A4C822E31274B0087DB5C /* SubViews */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -594,7 +608,6 @@
EE856A4B2E1CD3AA006820B9 /* FirestoreManager.swift */,
EE41FEEC2E225A1C0002E595 /* FirebaseAuthManager.swift */,
EEDBB1BB2E28DEB900F16407 /* FCMManager.swift */,
EC94C0F22E3746130020E59D /* GPTManger.swift */,
ECC980AB2E28E91E004A483B /* DeepLinkManager.swift */,
ECC980B42E294835004A483B /* KakaoShareManager.swift */,
EE41FEEA2E2259980002E595 /* KakaoAuthManager.swift */,
Expand Down Expand Up @@ -667,6 +680,7 @@
EEFEBF592E4B7EFB001D824D /* AI */ = {
isa = PBXGroup;
children = (
EC371D822E55E99C008E4AB7 /* LLM */,
D30602832E377E9700908D1D /* MLModel */,
D30602892E377EA100908D1D /* MLWeights */,
);
Expand Down Expand Up @@ -701,6 +715,8 @@
EEDBB1B72E28D45700F16407 /* FirebaseMessaging */,
EEDBB1BD2E2913CE00F16407 /* FirebaseFunctions */,
D306027C2E377E8400908D1D /* Transformers */,
EC371DC82E560D76008E4AB7 /* FirebaseAI */,
EC25CC322E5975880087EC50 /* FirebaseAppCheck */,
);
productName = A10;
productReference = EE856A262E1CD093006820B9 /* GimiFeedback.app */;
Expand All @@ -714,7 +730,7 @@
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1630;
LastUpgradeCheck = 1630;
LastUpgradeCheck = 1640;
TargetAttributes = {
EE856A252E1CD093006820B9 = {
CreatedOnToolsVersion = 16.3;
Expand Down Expand Up @@ -751,7 +767,6 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
EE41FEE52E2256280002E595 /* Config.xcconfig in Resources */,
EE465EE92E26205D0096109A /* GoogleService-Info.plist in Resources */,
EE3754AF2E1D7B36009AD262 /* .swiftlint.yml in Resources */,
D36C43842E38A029006A027E /* TokenizerREADME.md in Resources */,
Expand Down Expand Up @@ -838,6 +853,7 @@
EC6A4CA52E350B720087DB5C /* Spicy+Extension.swift in Sources */,
EE37553D2E1E10EA009AD262 /* FeedbackContentType.swift in Sources */,
EE37554A2E1E5BBD009AD262 /* AppDelegate.swift in Sources */,
EC371D842E55E9AA008E4AB7 /* GeminiManger.swift in Sources */,
D30602962E3782A200908D1D /* KcELECTRA-base-v2022.mlpackage in Sources */,
EE3755412E1E1105009AD262 /* FeedbackChannel.swift in Sources */,
EE37553F2E1E10F9009AD262 /* FeedbackContent.swift in Sources */,
Expand Down Expand Up @@ -1145,6 +1161,16 @@
package = D306027B2E377E8400908D1D /* XCRemoteSwiftPackageReference "swift-transformers" */;
productName = Transformers;
};
EC25CC322E5975880087EC50 /* FirebaseAppCheck */ = {
isa = XCSwiftPackageProductDependency;
package = EE3755442E1E5B8D009AD262 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */;
productName = FirebaseAppCheck;
};
EC371DC82E560D76008E4AB7 /* FirebaseAI */ = {
isa = XCSwiftPackageProductDependency;
package = EE3755442E1E5B8D009AD262 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */;
productName = FirebaseAI;
};
ECC980AE2E290DC4004A483B /* KakaoSDKShare */ = {
isa = XCSwiftPackageProductDependency;
package = EE41FEDA2E2250DF0002E595 /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1630"
LastUpgradeVersion = "1640"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
52 changes: 52 additions & 0 deletions GimiFeedback/GimiFeedback/AI/LLM/GeminiManger.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import Foundation
import FirebaseAI

final class GeminiManger {
static let shared = GeminiManger()

private let model: GenerativeModel?

init() {
let firebaseService = FirebaseAI.firebaseAI(backend: .googleAI())
model = firebaseService.generativeModel(
modelName: "gemini-2.5-flash",
systemInstruction: ModelContent(role: "system", parts: systemPrompt))
}
Comment on lines +5 to +14

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이렇게 init으로 만들면 shared로 결국 싱글톤이 아니고 어디서든 생성이 가능한거 아닌가요?!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private을 붙혔어야 했는데, 빠트렸네요


let systemPrompt = """
너는 어떤 피드백이든 무조건 부드럽고 배려 있는 말로 순화하는 전문가야.
너의 유일한 임무는 사용자 피드백을 공감적인 말투로 바꾸는 것이며, 이외의 추가적인 대화나 설명은 절대 하지 마.

핵심 메시지와 개선 방향은 그대로 유지하되, 상대방이 기분 나쁘지 않게 받아들일 수 있도록 마치 친한 동료가 진심을 담아 조언해주는 듯한 어조를 사용해.
긍정적이거나 이미 부드러운 피드백이더라도, 더 따뜻하고 공감적인 표현으로 다듬어서 결과물만 출력해.

아래는 예시야:

<예시>
<입력 예시>회의 시간에 집중 좀 해. 계속 딴짓해서 짜증나.</입력 예시>
<출력 예시>회의에 더 집중해주면 좋겠어요. 가끔 딴짓하는 모습이 보여서 회의 흐름이 조금 끊기는 느낌이 있었어요.</출력 예시>
</예시>

<예시>
<입력 예시>일을 너무 느리게 해. 같이 일하기 힘들어.</입력 예시>
<출력 예시>업무 속도가 조금 더 빨라지면 협업이 훨씬 원활할 것 같아요. 지금도 충분히 잘하고 있지만, 약간만 더 신경 써주면 좋겠어요.</출력 예시>
</예시>

<예시>
<입력 예시>말이 너무 많고 눈치가 없어서 분위기 망치고 있어.</입력 예시>
<출력 예시>이야기를 적극적으로 해주는 건 좋지만, 상황에 맞게 조절해주면 더 좋은 분위기가 될 수 있을 것 같아요.</출력 예시>
</예시>

위와 같이, 표현은 순화하지만 피드백의 본질은 흐리지 않도록 해줘.
"""

}

extension GeminiManger {
func generate(inputText: String) async throws -> String {
guard let model else { return "" }

let response = try await model.generateContent(inputText)
return response.text ?? ""
}
}
21 changes: 21 additions & 0 deletions GimiFeedback/GimiFeedback/App/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import SwiftUI
import FirebaseCore
import UserNotifications
import FirebaseMessaging
import FirebaseAppCheck

final class AppDelegate: NSObject, UIApplicationDelegate {
var saveUserInfo: [AnyHashable: Any]?
Expand All @@ -17,6 +18,10 @@ final class AppDelegate: NSObject, UIApplicationDelegate {
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {

let providerFactory = YourAppCheckProviderFactory()
AppCheck.setAppCheckProviderFactory(providerFactory)

Comment on lines +21 to +24

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이부분은 혹시 어떤 내용일까요?!

@seungchan2022 seungchan2022 Aug 26, 2025

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

승인되지 않은 클라이언트가 백엔드 리소스에 액세스하는 것을 방지하여 앱 백엔드를 악용으로부터 보호해 주기 위해 App Check를 추가해준 것 입니다.

아래 공식문서 첨부 하겠습니다.
App Check
App Attest

FirebaseApp.configure()
setUIAppearance()

Expand Down Expand Up @@ -134,3 +139,19 @@ extension AppDelegate {
)
}
}

final class YourAppCheckProviderFactory: NSObject, AppCheckProviderFactory {
func createProvider(with app: FirebaseApp) -> AppCheckProvider? {
// 시뮬레이터 환경에서는 AppCheckDebugProvider를 사용하도록 설정합니다.
#if targetEnvironment(simulator)
return AppCheckDebugProvider(app: app)
#else
// 실제 기기에서는 OS 버전에 따라 올바른 프로바이더를 선택합니다.
if #available(iOS 14.0, *) {
return AppAttestProvider(app: app)
} else {
return DeviceCheckProvider(app: app)
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저희 어짜피 17이상 버전이라 14 아래는 안해줘도 되지 않나요?!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수정하겠습니다!!

#endif
}
}
2 changes: 2 additions & 0 deletions GimiFeedback/GimiFeedback/App/GimiFeedback.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.devicecheck.appattest-environment</key>
<string>production</string>
</dict>
</plist>
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ extension FeedbackDetailViewModel {
private func transFeedbackContent(content: FeedbackContent) {
Task {
isTransLoading = true
let response = try await GPTManger.shared.sendChatCompletion(inputText: content.content)
let response = try await GeminiManger.shared.generate(inputText: content.content)

guard let index = feedbackItem.content.firstIndex(where: { $0.id == content.id }) else { return }

Expand Down
Loading