|
@@ -3,46 +3,50 @@ import SwiftUI
|
|
|
struct ChatInputCard: View {
|
|
struct ChatInputCard: View {
|
|
|
@ObservedObject var viewModel: HomeViewModel
|
|
@ObservedObject var viewModel: HomeViewModel
|
|
|
|
|
|
|
|
- var body: some View {
|
|
|
|
|
- VStack(alignment: .leading, spacing: 0) {
|
|
|
|
|
- ZStack(alignment: .topLeading) {
|
|
|
|
|
- if viewModel.promptText.isEmpty {
|
|
|
|
|
- Text("How can I help you today?")
|
|
|
|
|
- .font(.system(size: 22, weight: .regular))
|
|
|
|
|
- .foregroundStyle(AppTheme.textPlaceholder)
|
|
|
|
|
- .padding(.top, 2)
|
|
|
|
|
- .allowsHitTesting(false)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- TextEditor(text: $viewModel.promptText)
|
|
|
|
|
- .font(.system(size: 22, weight: .regular))
|
|
|
|
|
- .foregroundStyle(AppTheme.textPrimary)
|
|
|
|
|
- .scrollContentBackground(.hidden)
|
|
|
|
|
- .background(Color.clear)
|
|
|
|
|
- .frame(minHeight: 88, maxHeight: 120)
|
|
|
|
|
- }
|
|
|
|
|
- .padding(.bottom, 16)
|
|
|
|
|
|
|
+ private let inputSectionHeight: CGFloat = AppTheme.chatInputHeight - AppTheme.chatInputToolbarHeight
|
|
|
|
|
+ private let actionButtonSize: CGFloat = 30
|
|
|
|
|
|
|
|
- HStack(spacing: 10) {
|
|
|
|
|
- CircleIconButton(symbol: "plus", accessibilityLabel: "Add attachment")
|
|
|
|
|
|
|
+ var body: some View {
|
|
|
|
|
+ VStack(spacing: 0) {
|
|
|
|
|
+ CenteredChatTextField(
|
|
|
|
|
+ text: $viewModel.promptText,
|
|
|
|
|
+ placeholder: "How can I help you",
|
|
|
|
|
+ fontSize: 16
|
|
|
|
|
+ )
|
|
|
|
|
+ .padding(.horizontal, 16)
|
|
|
|
|
+ .frame(height: inputSectionHeight)
|
|
|
|
|
|
|
|
- PillButton(title: "Web search", symbol: "globe")
|
|
|
|
|
|
|
+ HStack(spacing: 6) {
|
|
|
|
|
+ Button(action: {}) {
|
|
|
|
|
+ Image(systemName: "plus")
|
|
|
|
|
+ .font(.system(size: 17, weight: .regular))
|
|
|
|
|
+ .foregroundStyle(AppTheme.textSecondary)
|
|
|
|
|
+ .frame(width: actionButtonSize, height: actionButtonSize)
|
|
|
|
|
+ }
|
|
|
|
|
+ .buttonStyle(.plain)
|
|
|
|
|
+ .accessibilityLabel("Add attachment")
|
|
|
|
|
|
|
|
- Spacer(minLength: 8)
|
|
|
|
|
|
|
+ Spacer(minLength: 0)
|
|
|
|
|
|
|
|
modelMenu
|
|
modelMenu
|
|
|
|
|
|
|
|
- CircleIconButton(symbol: "mic", accessibilityLabel: "Voice input")
|
|
|
|
|
|
|
+ voiceButton
|
|
|
|
|
|
|
|
sendButton
|
|
sendButton
|
|
|
}
|
|
}
|
|
|
|
|
+ .padding(.horizontal, 12)
|
|
|
|
|
+ .frame(height: AppTheme.chatInputToolbarHeight)
|
|
|
}
|
|
}
|
|
|
- .padding(.horizontal, 22)
|
|
|
|
|
- .padding(.vertical, 20)
|
|
|
|
|
|
|
+ .frame(maxWidth: .infinity)
|
|
|
|
|
+ .frame(height: AppTheme.chatInputHeight)
|
|
|
.background(
|
|
.background(
|
|
|
- RoundedRectangle(cornerRadius: AppTheme.cornerRadiusLarge, style: .continuous)
|
|
|
|
|
- .fill(Color.white)
|
|
|
|
|
- .shadow(color: AppTheme.cardShadow, radius: 14, y: 4)
|
|
|
|
|
|
|
+ RoundedRectangle(cornerRadius: AppTheme.chatInputCornerRadius, style: .continuous)
|
|
|
|
|
+ .fill(AppTheme.chatInputBackground)
|
|
|
|
|
+ )
|
|
|
|
|
+ .clipShape(RoundedRectangle(cornerRadius: AppTheme.chatInputCornerRadius, style: .continuous))
|
|
|
|
|
+ .overlay(
|
|
|
|
|
+ RoundedRectangle(cornerRadius: AppTheme.chatInputCornerRadius, style: .continuous)
|
|
|
|
|
+ .stroke(AppTheme.chatInputBorder, lineWidth: 1)
|
|
|
)
|
|
)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -54,87 +58,46 @@ struct ChatInputCard: View {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
} label: {
|
|
} label: {
|
|
|
- HStack(spacing: 6) {
|
|
|
|
|
|
|
+ HStack(spacing: 4) {
|
|
|
Text(viewModel.selectedModel)
|
|
Text(viewModel.selectedModel)
|
|
|
.font(.system(size: 13, weight: .medium))
|
|
.font(.system(size: 13, weight: .medium))
|
|
|
.foregroundStyle(AppTheme.textPrimary)
|
|
.foregroundStyle(AppTheme.textPrimary)
|
|
|
Image(systemName: "chevron.down")
|
|
Image(systemName: "chevron.down")
|
|
|
- .font(.system(size: 10, weight: .semibold))
|
|
|
|
|
|
|
+ .font(.system(size: 9, weight: .semibold))
|
|
|
.foregroundStyle(AppTheme.textSecondary)
|
|
.foregroundStyle(AppTheme.textSecondary)
|
|
|
}
|
|
}
|
|
|
- .padding(.horizontal, 12)
|
|
|
|
|
- .padding(.vertical, 8)
|
|
|
|
|
|
|
+ .padding(.horizontal, 10)
|
|
|
|
|
+ .padding(.vertical, 6)
|
|
|
.background(
|
|
.background(
|
|
|
- RoundedRectangle(cornerRadius: 10, style: .continuous)
|
|
|
|
|
- .fill(AppTheme.controlBackground)
|
|
|
|
|
|
|
+ RoundedRectangle(cornerRadius: 8, style: .continuous)
|
|
|
|
|
+ .fill(Color.white)
|
|
|
)
|
|
)
|
|
|
}
|
|
}
|
|
|
.menuStyle(.borderlessButton)
|
|
.menuStyle(.borderlessButton)
|
|
|
.fixedSize()
|
|
.fixedSize()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private var sendButton: some View {
|
|
|
|
|
|
|
+ private var voiceButton: some View {
|
|
|
Button(action: {}) {
|
|
Button(action: {}) {
|
|
|
- HStack(spacing: 7) {
|
|
|
|
|
- Image(systemName: "paperplane.fill")
|
|
|
|
|
- .font(.system(size: 12, weight: .bold))
|
|
|
|
|
- Text("Send")
|
|
|
|
|
- .font(.system(size: 14, weight: .bold))
|
|
|
|
|
- }
|
|
|
|
|
- .foregroundStyle(.white)
|
|
|
|
|
- .padding(.horizontal, 18)
|
|
|
|
|
- .padding(.vertical, 10)
|
|
|
|
|
- .background(
|
|
|
|
|
- RoundedRectangle(cornerRadius: 12, style: .continuous)
|
|
|
|
|
- .fill(AppTheme.accent)
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ Image(systemName: "waveform")
|
|
|
|
|
+ .font(.system(size: 12, weight: .medium))
|
|
|
|
|
+ .foregroundStyle(AppTheme.textSecondary)
|
|
|
|
|
+ .frame(width: actionButtonSize, height: actionButtonSize)
|
|
|
|
|
+ .background(Circle().fill(Color.white))
|
|
|
}
|
|
}
|
|
|
.buttonStyle(.plain)
|
|
.buttonStyle(.plain)
|
|
|
- .accessibilityLabel("Send message")
|
|
|
|
|
|
|
+ .accessibilityLabel("Voice input")
|
|
|
}
|
|
}
|
|
|
-}
|
|
|
|
|
|
|
|
|
|
-private struct CircleIconButton: View {
|
|
|
|
|
- let symbol: String
|
|
|
|
|
- let accessibilityLabel: String
|
|
|
|
|
-
|
|
|
|
|
- var body: some View {
|
|
|
|
|
- Button(action: {}) {
|
|
|
|
|
- Image(systemName: symbol)
|
|
|
|
|
- .font(.system(size: 13, weight: .semibold))
|
|
|
|
|
- .foregroundStyle(AppTheme.accent)
|
|
|
|
|
- .frame(width: 34, height: 34)
|
|
|
|
|
- .background(
|
|
|
|
|
- Circle()
|
|
|
|
|
- .fill(AppTheme.controlBackground)
|
|
|
|
|
- )
|
|
|
|
|
- }
|
|
|
|
|
- .buttonStyle(.plain)
|
|
|
|
|
- .accessibilityLabel(accessibilityLabel)
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-private struct PillButton: View {
|
|
|
|
|
- let title: String
|
|
|
|
|
- let symbol: String
|
|
|
|
|
-
|
|
|
|
|
- var body: some View {
|
|
|
|
|
|
|
+ private var sendButton: some View {
|
|
|
Button(action: {}) {
|
|
Button(action: {}) {
|
|
|
- HStack(spacing: 6) {
|
|
|
|
|
- Image(systemName: symbol)
|
|
|
|
|
- .font(.system(size: 11, weight: .semibold))
|
|
|
|
|
- Text(title)
|
|
|
|
|
- .font(.system(size: 13, weight: .medium))
|
|
|
|
|
- }
|
|
|
|
|
- .foregroundStyle(AppTheme.textPrimary)
|
|
|
|
|
- .padding(.horizontal, 12)
|
|
|
|
|
- .padding(.vertical, 8)
|
|
|
|
|
- .background(
|
|
|
|
|
- RoundedRectangle(cornerRadius: 10, style: .continuous)
|
|
|
|
|
- .fill(AppTheme.controlBackground)
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ Image(systemName: "arrow.up")
|
|
|
|
|
+ .font(.system(size: 15, weight: .bold))
|
|
|
|
|
+ .foregroundStyle(.white)
|
|
|
|
|
+ .frame(width: actionButtonSize, height: actionButtonSize)
|
|
|
|
|
+ .background(Circle().fill(AppTheme.accent))
|
|
|
}
|
|
}
|
|
|
.buttonStyle(.plain)
|
|
.buttonStyle(.plain)
|
|
|
- .accessibilityLabel(title)
|
|
|
|
|
|
|
+ .accessibilityLabel("Send message")
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|