// // AiMessageItemView.swift // SodaLive // // Created by klaus on 9/2/25. // import SwiftUI import Kingfisher struct AiMessageBubbleShape: Shape { func path(in rect: CGRect) -> Path { let path = UIBezierPath() // 시작점 (왼쪽 상단, 4px 반지름) path.move(to: CGPoint(x: 4, y: 0)) // 상단 라인 (오른쪽 상단 16px 반지름까지) path.addLine(to: CGPoint(x: rect.width - 16, y: 0)) // 오른쪽 상단 모서리 (16px 반지름) path.addArc(withCenter: CGPoint(x: rect.width - 16, y: 16), radius: 16, startAngle: -CGFloat.pi / 2, endAngle: 0, clockwise: true) // 오른쪽 라인 (오른쪽 하단 16px 반지름까지) path.addLine(to: CGPoint(x: rect.width, y: rect.height - 16)) // 오른쪽 하단 모서리 (16px 반지름) path.addArc(withCenter: CGPoint(x: rect.width - 16, y: rect.height - 16), radius: 16, startAngle: 0, endAngle: CGFloat.pi / 2, clockwise: true) // 하단 라인 (왼쪽 하단 16px 반지름까지) path.addLine(to: CGPoint(x: 16, y: rect.height)) // 왼쪽 하단 모서리 (16px 반지름) path.addArc(withCenter: CGPoint(x: 16, y: rect.height - 16), radius: 16, startAngle: CGFloat.pi / 2, endAngle: CGFloat.pi, clockwise: true) // 왼쪽 라인 (왼쪽 상단 4px 반지름까지) path.addLine(to: CGPoint(x: 0, y: 4)) // 왼쪽 상단 모서리 (4px 반지름) path.addArc(withCenter: CGPoint(x: 4, y: 4), radius: 4, startAngle: CGFloat.pi, endAngle: -CGFloat.pi / 2, clockwise: true) path.close() return Path(path.cgPath) } } struct AiMessageItemView: View { let message: ServerChatMessage let characterName: String var body: some View { HStack(alignment: .bottom, spacing: 4) { // 메시지 영역 HStack(alignment: .top, spacing: 9) { // 프로필 이미지 KFImage(URL(string: message.profileImageUrl)) .placeholder { Image(systemName: "person.crop.circle") .resizable() .scaledToFit() } .resizable() .frame(width: 30, height: 30) .clipShape(Circle()) // 메시지 텍스트 영역 VStack(alignment: .leading, spacing: 4) { HStack(spacing: 4) { Text(characterName) .font(.custom(Font.preRegular.rawValue, size: 12)) .foregroundColor(.white) } // 메시지 버블 HStack(spacing: 10) { styledMessageText(message.message) .lineLimit(nil) .multilineTextAlignment(.leading) } .padding(.horizontal, 10) .padding(.vertical, 8) .background( Color.black.opacity(0.1) ) .clipShape(AiMessageBubbleShape()) } } // 시간 표시 VStack { Text(formatTime(from: message.createdAt)) .font(.custom(Font.preRegular.rawValue, size: 10)) .foregroundColor(.white) } Spacer() } .frame(maxWidth: .infinity, alignment: .leading) } private func formatTime(from timestamp: Int64) -> String { let date = Date(timeIntervalSince1970: TimeInterval(timestamp / 1000)) return date.convertDateFormat(dateFormat: "a h:mm") } private func styledMessageText(_ message: String) -> Text { var result = Text("") let components = message.components(separatedBy: "(") for (index, component) in components.enumerated() { if index == 0 { // 첫 번째 컴포넌트는 항상 일반 텍스트 if !component.isEmpty { result = result + Text(component) .font(.custom(Font.preRegular.rawValue, size: 16)) .foregroundColor(.white) } } else { // "(" 이후의 텍스트 처리 if let closeIndex = component.firstIndex(of: ")") { let beforeClose = String(component[..