fix: polish macos web chat composer
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -9,6 +9,8 @@ coverage
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
**/.DS_Store
|
**/.DS_Store
|
||||||
ui/src/ui/__screenshots__/
|
ui/src/ui/__screenshots__/
|
||||||
|
ui/playwright-report/
|
||||||
|
ui/test-results/
|
||||||
|
|
||||||
# Bun build artifacts
|
# Bun build artifacts
|
||||||
*.bun-build
|
*.bun-build
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
- Android Chat UI: use `onPrimary` for user bubble text to preserve contrast (thanks @Syhids).
|
- Android Chat UI: use `onPrimary` for user bubble text to preserve contrast (thanks @Syhids).
|
||||||
- Control UI: sync sidebar navigation with the URL for deep-linking, and auto-scroll chat to the latest message.
|
- Control UI: sync sidebar navigation with the URL for deep-linking, and auto-scroll chat to the latest message.
|
||||||
- Control UI: disable Web Chat + Talk when no iOS/Android node is connected; refreshed Web Chat styling and keyboard send.
|
- Control UI: disable Web Chat + Talk when no iOS/Android node is connected; refreshed Web Chat styling and keyboard send.
|
||||||
- macOS Web Chat: improve empty/error states, focus message field on open, and keep pill/send inside the input field.
|
- macOS Web Chat: improve empty/error states, focus message field on open, keep pill/send inside the input field, and make the composer pill edge-to-edge with square top corners.
|
||||||
- macOS: bundle Control UI assets into the app relay so the packaged app can serve them (thanks @mbelinky).
|
- macOS: bundle Control UI assets into the app relay so the packaged app can serve them (thanks @mbelinky).
|
||||||
- Talk Mode: wait for chat history to surface the assistant reply before starting TTS (macOS/iOS/Android).
|
- Talk Mode: wait for chat history to surface the assistant reply before starting TTS (macOS/iOS/Android).
|
||||||
- iOS Talk Mode: fix chat completion wait to time out even if no events arrive (prevents “Thinking…” hangs).
|
- iOS Talk Mode: fix chat completion wait to time out even if no events arrive (prevents “Thinking…” hangs).
|
||||||
|
|||||||
@@ -37,20 +37,44 @@ struct ClawdisChatComposer: View {
|
|||||||
self.editor
|
self.editor
|
||||||
}
|
}
|
||||||
.padding(self.composerPadding)
|
.padding(self.composerPadding)
|
||||||
.background(
|
.background {
|
||||||
RoundedRectangle(cornerRadius: 18, style: .continuous)
|
let cornerRadius: CGFloat = 18
|
||||||
|
|
||||||
|
#if os(macOS)
|
||||||
|
if self.style == .standard {
|
||||||
|
let shape = UnevenRoundedRectangle(
|
||||||
|
cornerRadii: RectangleCornerRadii(
|
||||||
|
topLeading: 0,
|
||||||
|
bottomLeading: cornerRadius,
|
||||||
|
bottomTrailing: cornerRadius,
|
||||||
|
topTrailing: 0),
|
||||||
|
style: .continuous)
|
||||||
|
shape
|
||||||
|
.fill(ClawdisChatTheme.composerBackground)
|
||||||
|
.overlay(shape.strokeBorder(ClawdisChatTheme.composerBorder, lineWidth: 1))
|
||||||
|
.shadow(color: .black.opacity(0.12), radius: 12, y: 6)
|
||||||
|
} else {
|
||||||
|
let shape = RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)
|
||||||
|
shape
|
||||||
|
.fill(ClawdisChatTheme.composerBackground)
|
||||||
|
.overlay(shape.strokeBorder(ClawdisChatTheme.composerBorder, lineWidth: 1))
|
||||||
|
.shadow(color: .black.opacity(0.12), radius: 12, y: 6)
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
let shape = RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)
|
||||||
|
shape
|
||||||
.fill(ClawdisChatTheme.composerBackground)
|
.fill(ClawdisChatTheme.composerBackground)
|
||||||
.overlay(
|
.overlay(shape.strokeBorder(ClawdisChatTheme.composerBorder, lineWidth: 1))
|
||||||
RoundedRectangle(cornerRadius: 18, style: .continuous)
|
.shadow(color: .black.opacity(0.12), radius: 12, y: 6)
|
||||||
.strokeBorder(ClawdisChatTheme.composerBorder, lineWidth: 1))
|
#endif
|
||||||
.shadow(color: .black.opacity(0.12), radius: 12, y: 6))
|
}
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
.onDrop(of: [.fileURL], isTargeted: nil) { providers in
|
.onDrop(of: [.fileURL], isTargeted: nil) { providers in
|
||||||
self.handleDrop(providers)
|
self.handleDrop(providers)
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
self.shouldFocusTextView = true
|
self.shouldFocusTextView = true
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ public struct ClawdisChatView: View {
|
|||||||
static let outerPaddingHorizontal: CGFloat = 6
|
static let outerPaddingHorizontal: CGFloat = 6
|
||||||
static let outerPaddingVertical: CGFloat = 0
|
static let outerPaddingVertical: CGFloat = 0
|
||||||
static let composerPaddingHorizontal: CGFloat = 0
|
static let composerPaddingHorizontal: CGFloat = 0
|
||||||
static let stackSpacing: CGFloat = 6
|
static let stackSpacing: CGFloat = 0
|
||||||
static let messageSpacing: CGFloat = 6
|
static let messageSpacing: CGFloat = 6
|
||||||
static let messageListPaddingTop: CGFloat = 0
|
static let messageListPaddingTop: CGFloat = 0
|
||||||
static let messageListPaddingBottom: CGFloat = 4
|
static let messageListPaddingBottom: CGFloat = 4
|
||||||
@@ -79,13 +79,33 @@ public struct ClawdisChatView: View {
|
|||||||
private var messageList: some View {
|
private var messageList: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
LazyVStack(spacing: Layout.messageSpacing) {
|
#if os(macOS)
|
||||||
self.messageListRows
|
VStack(spacing: 0) {
|
||||||
|
LazyVStack(spacing: Layout.messageSpacing) {
|
||||||
|
self.messageListRows
|
||||||
|
}
|
||||||
|
|
||||||
|
Color.clear
|
||||||
|
.frame(height: 0)
|
||||||
|
.id(self.scrollerBottomID)
|
||||||
}
|
}
|
||||||
// Use scroll targets for stable auto-scroll without ScrollViewReader relayout glitches.
|
// Use scroll targets for stable auto-scroll without ScrollViewReader relayout glitches.
|
||||||
.scrollTargetLayout()
|
.scrollTargetLayout()
|
||||||
.padding(.top, Layout.messageListPaddingTop)
|
.padding(.top, Layout.messageListPaddingTop)
|
||||||
.padding(.horizontal, Layout.messageListPaddingHorizontal)
|
.padding(.horizontal, Layout.messageListPaddingHorizontal)
|
||||||
|
#else
|
||||||
|
LazyVStack(spacing: Layout.messageSpacing) {
|
||||||
|
self.messageListRows
|
||||||
|
|
||||||
|
Color.clear
|
||||||
|
.frame(height: Layout.messageListPaddingBottom + 1)
|
||||||
|
.id(self.scrollerBottomID)
|
||||||
|
}
|
||||||
|
// Use scroll targets for stable auto-scroll without ScrollViewReader relayout glitches.
|
||||||
|
.scrollTargetLayout()
|
||||||
|
.padding(.top, Layout.messageListPaddingTop)
|
||||||
|
.padding(.horizontal, Layout.messageListPaddingHorizontal)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
// Keep the scroll pinned to the bottom for new messages.
|
// Keep the scroll pinned to the bottom for new messages.
|
||||||
.scrollPosition(id: self.$scrollPosition, anchor: .bottom)
|
.scrollPosition(id: self.$scrollPosition, anchor: .bottom)
|
||||||
@@ -147,10 +167,6 @@ public struct ClawdisChatView: View {
|
|||||||
ChatStreamingAssistantBubble(text: text)
|
ChatStreamingAssistantBubble(text: text)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
}
|
}
|
||||||
|
|
||||||
Color.clear
|
|
||||||
.frame(height: Layout.messageListPaddingBottom + 1)
|
|
||||||
.id(self.scrollerBottomID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private var visibleMessages: [ClawdisChatMessage] {
|
private var visibleMessages: [ClawdisChatMessage] {
|
||||||
|
|||||||
Reference in New Issue
Block a user