ソースを参照

Restyle the chat input toolbar with a light-theme model selector and action buttons.

Introduce AIModel and a shared model menu so the right-aligned toolbar matches the reference layout with premium model indicators.

Co-authored-by: Cursor <cursoragent@cursor.com>
AhtashamShahzad1 3 週間 前
コミット
d763dc1578

+ 13 - 0
clone _of_clarus_ai_chat_bot/Models/AIModel.swift

@@ -0,0 +1,13 @@
+import Foundation
+
+struct AIModel: Identifiable, Hashable {
+    let id: String
+    let name: String
+    let isPremium: Bool
+
+    init(name: String, isPremium: Bool = false) {
+        self.id = name
+        self.name = name
+        self.isPremium = isPremium
+    }
+}

+ 6 - 2
clone _of_clarus_ai_chat_bot/Utilities/AppTheme.swift

@@ -27,7 +27,11 @@ enum AppTheme {
 
     static let chatInputCornerRadius: CGFloat = 22
     static let chatInputBorder = Color(red: 0.90, green: 0.90, blue: 0.90)
-    static let chatInputBackground = Color.white
+    static let chatInputBackground = Color(red: 0.97, green: 0.97, blue: 0.98)
     static let chatInputHeight: CGFloat = 84
-    static let chatInputToolbarHeight: CGFloat = 36
+    static let chatInputToolbarHeight: CGFloat = 40
+    static let chatToolbarPillBackground = Color(red: 0.92, green: 0.92, blue: 0.94)
+    static let chatActionButtonBorder = Color(red: 0.84, green: 0.84, blue: 0.87)
+    static let chatSendButtonBackground = Color(red: 0.14, green: 0.14, blue: 0.16)
+    static let chatHeaderDivider = Color(red: 0.92, green: 0.92, blue: 0.94)
 }

+ 11 - 2
clone _of_clarus_ai_chat_bot/ViewModels/HomeViewModel.swift

@@ -4,7 +4,7 @@ import Foundation
 @MainActor
 final class HomeViewModel: ObservableObject {
     @Published var promptText = ""
-    @Published var selectedModel = "Claude-Haiku"
+    @Published var selectedModel = AIModel(name: "Claude-Haiku")
 
     let navigationItems: [SidebarItem] = [
         SidebarItem(title: "History", symbol: "clock"),
@@ -18,5 +18,14 @@ final class HomeViewModel: ObservableObject {
         QuickAction(title: "More Stuff", symbol: "cup.and.saucer")
     ]
 
-    let availableModels = ["Claude-Haiku", "GPT-4o", "Claude-Sonnet"]
+    let availableModels: [AIModel] = [
+        AIModel(name: "Auto"),
+        AIModel(name: "Sonnet 4.6"),
+        AIModel(name: "Sonnet 4.5"),
+        AIModel(name: "Opus 4.8", isPremium: true),
+        AIModel(name: "Opus 4.6", isPremium: true),
+        AIModel(name: "Haiku 4.5"),
+        AIModel(name: "Haiku 3", isPremium: true),
+        AIModel(name: "Claude-Haiku")
+    ]
 }

+ 19 - 35
clone _of_clarus_ai_chat_bot/Views/ChatInputCard.swift

@@ -4,7 +4,7 @@ struct ChatInputCard: View {
     @ObservedObject var viewModel: HomeViewModel
 
     private let inputSectionHeight: CGFloat = AppTheme.chatInputHeight - AppTheme.chatInputToolbarHeight
-    private let actionButtonSize: CGFloat = 30
+    private let actionButtonSize: CGFloat = 32
 
     var body: some View {
         VStack(spacing: 0) {
@@ -16,7 +16,7 @@ struct ChatInputCard: View {
             .padding(.horizontal, 16)
             .frame(height: inputSectionHeight)
 
-            HStack(spacing: 6) {
+            HStack(spacing: 8) {
                 Button(action: {}) {
                     Image(systemName: "plus")
                         .font(.system(size: 17, weight: .regular))
@@ -28,13 +28,14 @@ struct ChatInputCard: View {
 
                 Spacer(minLength: 0)
 
-                modelMenu
+                ModelSelectorMenu(viewModel: viewModel)
 
                 voiceButton
 
                 sendButton
             }
             .padding(.horizontal, 12)
+            .padding(.bottom, 8)
             .frame(height: AppTheme.chatInputToolbarHeight)
         }
         .frame(maxWidth: .infinity)
@@ -50,40 +51,20 @@ struct ChatInputCard: View {
         )
     }
 
-    private var modelMenu: some View {
-        Menu {
-            ForEach(viewModel.availableModels, id: \.self) { model in
-                Button(model) {
-                    viewModel.selectedModel = model
-                }
-            }
-        } label: {
-            HStack(spacing: 4) {
-                Text(viewModel.selectedModel)
-                    .font(.system(size: 13, weight: .medium))
-                    .foregroundStyle(AppTheme.textPrimary)
-                Image(systemName: "chevron.down")
-                    .font(.system(size: 9, weight: .semibold))
-                    .foregroundStyle(AppTheme.textSecondary)
-            }
-            .padding(.horizontal, 10)
-            .padding(.vertical, 6)
-            .background(
-                RoundedRectangle(cornerRadius: 8, style: .continuous)
-                    .fill(Color.white)
-            )
-        }
-        .menuStyle(.borderlessButton)
-        .fixedSize()
-    }
-
     private var voiceButton: some View {
         Button(action: {}) {
             Image(systemName: "waveform")
-                .font(.system(size: 12, weight: .medium))
-                .foregroundStyle(AppTheme.textSecondary)
+                .font(.system(size: 13, weight: .medium))
+                .foregroundStyle(AppTheme.textPrimary.opacity(0.72))
                 .frame(width: actionButtonSize, height: actionButtonSize)
-                .background(Circle().fill(Color.white))
+                .background(
+                    Circle()
+                        .fill(Color.white)
+                )
+                .overlay(
+                    Circle()
+                        .stroke(AppTheme.chatActionButtonBorder, lineWidth: 1)
+                )
         }
         .buttonStyle(.plain)
         .accessibilityLabel("Voice input")
@@ -92,10 +73,13 @@ struct ChatInputCard: View {
     private var sendButton: some View {
         Button(action: {}) {
             Image(systemName: "arrow.up")
-                .font(.system(size: 15, weight: .bold))
+                .font(.system(size: 14, weight: .bold))
                 .foregroundStyle(.white)
                 .frame(width: actionButtonSize, height: actionButtonSize)
-                .background(Circle().fill(AppTheme.accent))
+                .background(
+                    Circle()
+                        .fill(AppTheme.chatSendButtonBackground)
+                )
         }
         .buttonStyle(.plain)
         .accessibilityLabel("Send message")

+ 44 - 0
clone _of_clarus_ai_chat_bot/Views/ModelSelectorMenu.swift

@@ -0,0 +1,44 @@
+import SwiftUI
+
+struct ModelSelectorMenu: View {
+    @ObservedObject var viewModel: HomeViewModel
+
+    var body: some View {
+        Menu {
+            ForEach(viewModel.availableModels) { model in
+                Button {
+                    viewModel.selectedModel = model
+                } label: {
+                    if model.isPremium {
+                        Label(model.name, systemImage: "crown.fill")
+                    } else {
+                        Text(model.name)
+                    }
+                }
+            }
+        } label: {
+            labelContent
+        }
+        .menuStyle(.borderlessButton)
+        .fixedSize()
+        .accessibilityLabel("Selected model, \(viewModel.selectedModel.name)")
+    }
+
+    private var labelContent: some View {
+        HStack(spacing: 5) {
+            Text(viewModel.selectedModel.name)
+                .font(.system(size: 13, weight: .semibold))
+                .foregroundStyle(AppTheme.textPrimary)
+
+            Image(systemName: "chevron.down")
+                .font(.system(size: 9, weight: .bold))
+                .foregroundStyle(AppTheme.textSecondary)
+        }
+        .padding(.horizontal, 12)
+        .padding(.vertical, 7)
+        .background(
+            RoundedRectangle(cornerRadius: 10, style: .continuous)
+                .fill(AppTheme.chatToolbarPillBackground)
+        )
+    }
+}