Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
18 changes: 18 additions & 0 deletions AIProject/iCo/Domain/Model/Chat/ChatBotFAQ.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// ChatBotFAQ.swift
// iCo
//
// Created by 강대훈 on 10/1/25.
//

import Foundation

enum ChatBotFAQ: String, CaseIterable, Identifiable {
var id: String { self.rawValue }

case whatIsBlockchain = "블록체인이 뭐예요?"
case bitcoinVsEthereum = "비트코인과 이더리움은 뭐가 달라요?"
case howPriceDetermined = "코인 시세는 어떻게 결정돼요?"
case exchangeVsWallet = "거래소랑 지갑은 뭐가 달라요?"
case whatIsKimchiPremium = "김치 프리미엄(김프)이 뭔가요?"
}
80 changes: 80 additions & 0 deletions AIProject/iCo/Features/ChatBot/View/ChatBotFAQView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//
// FAQView 2.swift
// iCo
//
// Created by 강대훈 on 10/1/25.
//


import SwiftUI

struct ChatBotFAQView: View {
@Environment(\.colorScheme) var colorScheme

@ObservedObject var viewModel: ChatBotViewModel

var body: some View {
HStack {
VStack {
Image("logo")
.renderingMode(.template)
.resizable()
.scaledToFit()
.frame(width: 34)
.foregroundColor(colorScheme == .light ? .aiCoAccent : .aiCoLabel)
.opacity(0.8)
.padding(12)
.overlay {
Circle()
.strokeBorder(.accentGradient, lineWidth: 0.5)
}
.background {
Circle()
.fill(.aiCoBackgroundBlue)
}
Spacer()
}

Group {
VStack(spacing: 15) {
HStack {
Text("안녕하세요, 아이코 챗봇입니다.\n궁금하신 내용을 선택해주세요.")
.font(.system(size: 15))
Spacer()
}

ForEach(ChatBotFAQ.allCases) { faq in
Button {
Task { await viewModel.sendMessage(message: faq.rawValue) }
} label: {
Text(faq.rawValue)
}
.frame(maxWidth: .infinity)
.padding(.vertical, 10)
.font(.system(size: 14))
.background(.aiCoBackgroundAccent)
.clipShape(RoundedRectangle(cornerRadius: 15))
.overlay(RoundedRectangle(cornerRadius: 15).strokeBorder(.accentGradient, lineWidth: 0.5))
.disabled(viewModel.isStreaming)
}
}
}
.foregroundStyle(.aiCoLabel)
.font(.system(size: 14))
.lineSpacing(6)
.padding(.vertical, 15)
.padding(.horizontal, 18)
.background {
UnevenRoundedRectangle(bottomLeadingRadius: 16, bottomTrailingRadius: 16, topTrailingRadius: 16)
.fill(.aiCoBackgroundBlue)
}
.overlay {
UnevenRoundedRectangle(bottomLeadingRadius: 16, bottomTrailingRadius: 16, topTrailingRadius: 16)
.strokeBorder(.accentGradient, lineWidth: 0.5)
}
.frame(maxWidth: 300, alignment: .leading)

Spacer()
}
}
}
15 changes: 11 additions & 4 deletions AIProject/iCo/Features/ChatBot/View/ChatBotView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ struct ChatBotView: View {
@Environment(\.colorScheme) var colorScheme

@StateObject private var viewModel = ChatBotViewModel()

@State private var searchText: String = ""

@State private var isPortrait: Bool = false
@State private var isPad: Bool = false

Expand All @@ -27,6 +26,8 @@ struct ChatBotView: View {
VStack(spacing: 0) {
ChatScrollView(viewModel: viewModel) {
LazyVStack(spacing: 20) {
ChatBotFAQView(viewModel: viewModel)

ForEach(viewModel.messages) { message in
Group {
if message.isUser {
Expand Down Expand Up @@ -55,11 +56,17 @@ struct ChatBotView: View {
GeometryReader { proxy in
Color.clear
.onAppear {
isPortrait = !UIDevice.current.orientation.isPortrait
if let orientation = UIApplication.shared.connectedScenes.compactMap({ $0 as? UIWindowScene }).first?.interfaceOrientation {
isPortrait = orientation.isPortrait
}

isPad = hSizeClass == .regular && vSizeClass == .regular
}
.onChange(of: proxy.size) {
isPortrait = !UIDevice.current.orientation.isPortrait
if let orientation = UIApplication.shared.connectedScenes.compactMap({ $0 as? UIWindowScene }).first?.interfaceOrientation {
isPortrait = orientation.isPortrait
}

isPad = hSizeClass == .regular && vSizeClass == .regular
}
}
Expand Down
4 changes: 2 additions & 2 deletions AIProject/iCo/Features/ChatBot/View/ChatInputView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import SwiftUI
/// 챗봇 입력창에 해당하는 View입니다.
struct ChatInputView: View {
@ObservedObject var viewModel: ChatBotViewModel

@FocusState private var isFocused: Bool

var body: some View {
Expand All @@ -22,7 +22,7 @@ struct ChatInputView: View {
.focused($isFocused)

Button {
Task { await viewModel.sendMessage() }
Task { await viewModel.sendMessage(message: viewModel.searchText) }
} label: {
Image(systemName: "arrow.up")
.padding(10)
Expand Down
2 changes: 1 addition & 1 deletion AIProject/iCo/Features/ChatBot/View/ChatScrollView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ struct ChatScrollView<Content: View>: View {
)
.onPreferenceChange(ChatOffSetPreferenceKey.self) { value in
scrollOffSet = value.y
reachToBottom = contentHeight <= (value.y + viewportHeight + 50) // 50: 스크롤 바닥 근처인지를 확인하는 최소 임계값입니다.
reachToBottom = contentHeight <= (value.y + viewportHeight + 150) // 150: 스크롤 바닥 근처인지를 확인하는 최소 임계값입니다.
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,7 @@ final class ChatBotViewModel: ObservableObject {
///
/// 이 메소드는 메인 쓰레드에서 실행됩니다.
@MainActor
func sendMessage() async {
let message = searchText

func sendMessage(message: String) async {
searchText = ""
isStreaming = true

Expand Down