refactor(chat-ui): compact layout

main
Peter Steinberger 2025-12-14 04:41:29 +00:00
parent d54cc49d66
commit aab5c490dc
2 changed files with 66 additions and 53 deletions

View File

@ -17,7 +17,7 @@ struct ClawdisChatComposer: View {
var body: some View { var body: some View {
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
HStack { HStack(spacing: 10) {
self.thinkingPicker self.thinkingPicker
Spacer() Spacer()
self.attachmentPicker self.attachmentPicker
@ -29,24 +29,14 @@ struct ClawdisChatComposer: View {
self.editor self.editor
HStack { if let error = self.viewModel.errorText, !error.isEmpty {
if let error = self.viewModel.errorText { Text(error)
Text(error) .font(.footnote)
.font(.footnote) .foregroundStyle(.red)
.foregroundStyle(.red) .lineLimit(2)
}
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)
} }
} }
.padding(14) .padding(12)
.background( .background(
RoundedRectangle(cornerRadius: 16, style: .continuous) RoundedRectangle(cornerRadius: 16, style: .continuous)
.fill(ClawdisChatTheme.card) .fill(ClawdisChatTheme.card)
@ -67,7 +57,8 @@ struct ClawdisChatComposer: View {
} }
.labelsHidden() .labelsHidden()
.pickerStyle(.menu) .pickerStyle(.menu)
.frame(maxWidth: 200) .controlSize(.small)
.frame(maxWidth: 140, alignment: .leading)
} }
@ViewBuilder @ViewBuilder
@ -76,14 +67,18 @@ struct ClawdisChatComposer: View {
Button { Button {
self.pickFilesMac() self.pickFilesMac()
} label: { } label: {
Label("Add Image", systemImage: "paperclip") Image(systemName: "paperclip")
} }
.help("Add Image")
.buttonStyle(.bordered) .buttonStyle(.bordered)
.controlSize(.small)
#else #else
PhotosPicker(selection: self.$pickerItems, maxSelectionCount: 8, matching: .images) { PhotosPicker(selection: self.$pickerItems, maxSelectionCount: 8, matching: .images) {
Label("Add Image", systemImage: "paperclip") Image(systemName: "paperclip")
} }
.help("Add Image")
.buttonStyle(.bordered) .buttonStyle(.bordered)
.controlSize(.small)
.onChange(of: self.pickerItems) { _, newItems in .onChange(of: self.pickerItems) { _, newItems in
Task { await self.loadPhotosPickerItems(newItems) } Task { await self.loadPhotosPickerItems(newItems) }
} }
@ -135,7 +130,7 @@ struct ClawdisChatComposer: View {
RoundedRectangle(cornerRadius: 12, style: .continuous) RoundedRectangle(cornerRadius: 12, style: .continuous)
.fill(ClawdisChatTheme.card)) .fill(ClawdisChatTheme.card))
.overlay(self.editorOverlay) .overlay(self.editorOverlay)
.frame(maxHeight: 180) .frame(maxHeight: 140)
} }
private var editorOverlay: some View { private var editorOverlay: some View {
@ -143,17 +138,18 @@ struct ClawdisChatComposer: View {
if self.viewModel.input.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { if self.viewModel.input.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
Text("Message Clawd…") Text("Message Clawd…")
.foregroundStyle(.tertiary) .foregroundStyle(.tertiary)
.padding(.horizontal, 12) .padding(.horizontal, 10)
.padding(.vertical, 10) .padding(.vertical, 8)
} }
#if os(macOS) #if os(macOS)
ChatComposerTextView(text: self.$viewModel.input) { ChatComposerTextView(text: self.$viewModel.input) {
self.viewModel.send() self.viewModel.send()
} }
.frame(minHeight: 54, maxHeight: 160) .frame(minHeight: 44, maxHeight: 120)
.padding(.horizontal, 10) .padding(.horizontal, 8)
.padding(.vertical, 8) .padding(.vertical, 6)
.padding(.trailing, 44)
#else #else
TextEditor(text: self.$viewModel.input) TextEditor(text: self.$viewModel.input)
.font(.system(size: 15)) .font(.system(size: 15))
@ -162,6 +158,27 @@ struct ClawdisChatComposer: View {
.padding(.vertical, 8) .padding(.vertical, 8)
.focused(self.$isFocused) .focused(self.$isFocused)
#endif #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)
}
}
} }
} }

View File

@ -14,40 +14,18 @@ public struct ClawdisChatView: View {
ClawdisChatTheme.surface ClawdisChatTheme.surface
.ignoresSafeArea() .ignoresSafeArea()
VStack(spacing: 14) { VStack(spacing: 10) {
self.header
self.messageList self.messageList
ClawdisChatComposer(viewModel: self.viewModel) ClawdisChatComposer(viewModel: self.viewModel)
} }
.padding(.horizontal, 18) .padding(.horizontal, 12)
.padding(.vertical, 16) .padding(.vertical, 12)
.frame(maxWidth: 1040) .frame(maxWidth: .infinity)
} }
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
.onAppear { self.viewModel.load() } .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 { private var messageList: some View {
ScrollViewReader { proxy in ScrollViewReader { proxy in
ScrollView { ScrollView {
@ -68,13 +46,31 @@ public struct ClawdisChatView: View {
.frame(height: 1) .frame(height: 1)
.id(self.scrollerBottomID) .id(self.scrollerBottomID)
} }
.padding(.vertical, 10) .padding(.top, 40)
.padding(.bottom, 10)
.padding(.horizontal, 12) .padding(.horizontal, 12)
} }
.background( .background(
RoundedRectangle(cornerRadius: 16, style: .continuous) RoundedRectangle(cornerRadius: 16, style: .continuous)
.fill(ClawdisChatTheme.card) .fill(ClawdisChatTheme.card)
.shadow(color: .black.opacity(0.05), radius: 12, y: 6)) .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 .onChange(of: self.viewModel.messages.count) { _, _ in
withAnimation(.snappy(duration: 0.22)) { withAnimation(.snappy(duration: 0.22)) {
proxy.scrollTo(self.scrollerBottomID, anchor: .bottom) proxy.scrollTo(self.scrollerBottomID, anchor: .bottom)