From aab5c490dccd65be9e6ff9f983f831bded8653f1 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 14 Dec 2025 04:41:29 +0000 Subject: [PATCH] refactor(chat-ui): compact layout --- .../Sources/ClawdisChatUI/ChatComposer.swift | 69 ++++++++++++------- .../Sources/ClawdisChatUI/ChatView.swift | 50 +++++++------- 2 files changed, 66 insertions(+), 53 deletions(-) diff --git a/apps/shared/ClawdisKit/Sources/ClawdisChatUI/ChatComposer.swift b/apps/shared/ClawdisKit/Sources/ClawdisChatUI/ChatComposer.swift index e5bd874d6..a3dfc8cdf 100644 --- a/apps/shared/ClawdisKit/Sources/ClawdisChatUI/ChatComposer.swift +++ b/apps/shared/ClawdisKit/Sources/ClawdisChatUI/ChatComposer.swift @@ -17,7 +17,7 @@ struct ClawdisChatComposer: View { var body: some View { VStack(alignment: .leading, spacing: 8) { - HStack { + HStack(spacing: 10) { self.thinkingPicker Spacer() self.attachmentPicker @@ -29,24 +29,14 @@ struct ClawdisChatComposer: View { self.editor - HStack { - if let error = self.viewModel.errorText { - Text(error) - .font(.footnote) - .foregroundStyle(.red) - } - Spacer() - Button { - self.viewModel.send() - } label: { - Label(self.viewModel.isSending ? "Sending…" : "Send", systemImage: "arrow.up.circle.fill") - .font(.headline) - } - .buttonStyle(.borderedProminent) - .disabled(!self.viewModel.canSend) + if let error = self.viewModel.errorText, !error.isEmpty { + Text(error) + .font(.footnote) + .foregroundStyle(.red) + .lineLimit(2) } } - .padding(14) + .padding(12) .background( RoundedRectangle(cornerRadius: 16, style: .continuous) .fill(ClawdisChatTheme.card) @@ -67,7 +57,8 @@ struct ClawdisChatComposer: View { } .labelsHidden() .pickerStyle(.menu) - .frame(maxWidth: 200) + .controlSize(.small) + .frame(maxWidth: 140, alignment: .leading) } @ViewBuilder @@ -76,14 +67,18 @@ struct ClawdisChatComposer: View { Button { self.pickFilesMac() } label: { - Label("Add Image", systemImage: "paperclip") + Image(systemName: "paperclip") } + .help("Add Image") .buttonStyle(.bordered) + .controlSize(.small) #else PhotosPicker(selection: self.$pickerItems, maxSelectionCount: 8, matching: .images) { - Label("Add Image", systemImage: "paperclip") + Image(systemName: "paperclip") } + .help("Add Image") .buttonStyle(.bordered) + .controlSize(.small) .onChange(of: self.pickerItems) { _, newItems in Task { await self.loadPhotosPickerItems(newItems) } } @@ -135,7 +130,7 @@ struct ClawdisChatComposer: View { RoundedRectangle(cornerRadius: 12, style: .continuous) .fill(ClawdisChatTheme.card)) .overlay(self.editorOverlay) - .frame(maxHeight: 180) + .frame(maxHeight: 140) } private var editorOverlay: some View { @@ -143,17 +138,18 @@ struct ClawdisChatComposer: View { if self.viewModel.input.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { Text("Message Clawd…") .foregroundStyle(.tertiary) - .padding(.horizontal, 12) - .padding(.vertical, 10) + .padding(.horizontal, 10) + .padding(.vertical, 8) } #if os(macOS) ChatComposerTextView(text: self.$viewModel.input) { self.viewModel.send() } - .frame(minHeight: 54, maxHeight: 160) - .padding(.horizontal, 10) - .padding(.vertical, 8) + .frame(minHeight: 44, maxHeight: 120) + .padding(.horizontal, 8) + .padding(.vertical, 6) + .padding(.trailing, 44) #else TextEditor(text: self.$viewModel.input) .font(.system(size: 15)) @@ -162,6 +158,27 @@ struct ClawdisChatComposer: View { .padding(.vertical, 8) .focused(self.$isFocused) #endif + + VStack { + Spacer() + HStack { + Spacer() + Button { + self.viewModel.send() + } label: { + if self.viewModel.isSending { + ProgressView().controlSize(.small) + } else { + Image(systemName: "arrow.up") + .font(.system(size: 13, weight: .semibold)) + } + } + .buttonStyle(.borderedProminent) + .controlSize(.small) + .disabled(!self.viewModel.canSend) + .padding(8) + } + } } } diff --git a/apps/shared/ClawdisKit/Sources/ClawdisChatUI/ChatView.swift b/apps/shared/ClawdisKit/Sources/ClawdisChatUI/ChatView.swift index 8b07f009b..adfd93382 100644 --- a/apps/shared/ClawdisKit/Sources/ClawdisChatUI/ChatView.swift +++ b/apps/shared/ClawdisKit/Sources/ClawdisChatUI/ChatView.swift @@ -14,40 +14,18 @@ public struct ClawdisChatView: View { ClawdisChatTheme.surface .ignoresSafeArea() - VStack(spacing: 14) { - self.header + VStack(spacing: 10) { self.messageList ClawdisChatComposer(viewModel: self.viewModel) } - .padding(.horizontal, 18) - .padding(.vertical, 16) - .frame(maxWidth: 1040) + .padding(.horizontal, 12) + .padding(.vertical, 12) + .frame(maxWidth: .infinity) } .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) .onAppear { self.viewModel.load() } } - private var header: some View { - HStack { - VStack(alignment: .leading, spacing: 2) { - Text("Clawd Chat") - .font(.title2.weight(.semibold)) - Text("Session \(self.viewModel.sessionKey) · \(self.viewModel.healthOK ? "Connected" : "Connecting…")") - .font(.caption) - .foregroundStyle(.secondary) - } - - Spacer() - - Button { - self.viewModel.refresh() - } label: { - Label("Refresh", systemImage: "arrow.clockwise") - } - .buttonStyle(.bordered) - } - } - private var messageList: some View { ScrollViewReader { proxy in ScrollView { @@ -68,13 +46,31 @@ public struct ClawdisChatView: View { .frame(height: 1) .id(self.scrollerBottomID) } - .padding(.vertical, 10) + .padding(.top, 40) + .padding(.bottom, 10) .padding(.horizontal, 12) } .background( RoundedRectangle(cornerRadius: 16, style: .continuous) .fill(ClawdisChatTheme.card) .shadow(color: .black.opacity(0.05), radius: 12, y: 6)) + .overlay(alignment: .topLeading) { + HStack(spacing: 8) { + Circle() + .fill(self.viewModel.healthOK ? .green : .orange) + .frame(width: 7, height: 7) + Text(self.viewModel.sessionKey) + .font(.caption.weight(.semibold)) + Text(self.viewModel.healthOK ? "Connected" : "Connecting…") + .font(.caption) + .foregroundStyle(.secondary) + } + .padding(.horizontal, 10) + .padding(.vertical, 6) + .background(ClawdisChatTheme.subtleCard) + .clipShape(Capsule()) + .padding(10) + } .onChange(of: self.viewModel.messages.count) { _, _ in withAnimation(.snappy(duration: 0.22)) { proxy.scrollTo(self.scrollerBottomID, anchor: .bottom)