@@ -2,6 +2,8 @@ import AppKit
22import MarkdownUI
33import SwiftUI
44
5+ private let r : Double = 8
6+
57struct ChatPanel : View {
68 let chat : ChatProvider
79 @Namespace var inputAreaNamespace
@@ -12,13 +14,11 @@ struct ChatPanel: View {
1214 ChatPanelToolbar ( chat: chat)
1315 Divider ( )
1416 ChatPanelMessages (
15- chat: chat,
16- inputAreaNamespace: inputAreaNamespace
17+ chat: chat
1718 )
1819 Divider ( )
1920 ChatPanelInputArea (
2021 chat: chat,
21- inputAreaNamespace: inputAreaNamespace,
2222 typedMessage: $typedMessage
2323 )
2424 }
@@ -60,8 +60,6 @@ struct ChatPanelToolbar: View {
6060
6161struct ChatPanelMessages : View {
6262 @ObservedObject var chat : ChatProvider
63- var inputAreaNamespace : Namespace . ID
64- @Environment ( \. colorScheme) var colorScheme
6563 @AppStorage ( \. disableLazyVStack) var disableLazyVStack
6664
6765 @ViewBuilder
@@ -79,132 +77,151 @@ struct ChatPanelMessages: View {
7977
8078 var body : some View {
8179 CustomScrollView {
82- vstack {
83- let r = 8 as Double
84-
85- Spacer ( )
86-
87- if chat. isReceivingMessage {
88- Button ( action: {
89- chat. stop ( )
90- } ) {
91- HStack ( spacing: 4 ) {
92- Image ( systemName: " stop.fill " )
93- Text ( " Stop Responding " )
94- }
95- . rotationEffect ( Angle ( degrees: 180 ) )
96- . padding ( 8 )
97- . background (
98- . regularMaterial,
99- in: RoundedRectangle ( cornerRadius: r, style: . continuous)
100- )
101- . overlay {
102- RoundedRectangle ( cornerRadius: r, style: . continuous)
103- . stroke ( Color ( nsColor: . separatorColor) , lineWidth: 1 )
104- }
80+ VStack ( spacing: 0 ) {
81+ vstack {
82+ Spacer ( )
83+
84+ if chat. isReceivingMessage {
85+ StopRespondingButton ( chat: chat)
10586 }
106- . buttonStyle ( . borderless)
107- . scaleEffect ( x: - 1 , y: 1 , anchor: . center)
108- }
10987
110- if chat. history. isEmpty {
111- Text ( " New Chat " )
112- . frame ( maxWidth: . infinity, alignment: . center)
113- . padding ( )
114- . rotationEffect ( Angle ( degrees: 180 ) )
115- . scaleEffect ( x: - 1 , y: 1 , anchor: . center)
116- . foregroundStyle ( . secondary)
117- }
118-
119- ForEach ( chat. history. reversed ( ) , id: \. id) { message in
120- let text = message. text. isEmpty && !message. isUser ? " ... " : message
121- . text
122-
123- if message. isUser {
124- Markdown ( text)
125- . textSelection ( . enabled)
126- . markdownTheme ( . gitHub. text {
127- BackgroundColor ( Color . clear)
128- } )
129- . markdownCodeSyntaxHighlighter (
130- ChatCodeSyntaxHighlighter ( brightMode: colorScheme != . dark)
131- )
132- . frame ( alignment: . trailing)
88+ if chat. history. isEmpty {
89+ Text ( " New Chat " )
90+ . frame ( maxWidth: . infinity, alignment: . center)
13391 . padding ( )
134- . background {
135- RoundedCorners ( tl: r, tr: r, bl: r, br: 0 )
136- . fill ( Color . userChatContentBackground)
137- }
138- . overlay {
139- RoundedCorners ( tl: r, tr: r, bl: r, br: 0 )
140- . stroke ( Color ( nsColor: . separatorColor) , lineWidth: 1 )
141- }
142- . padding ( . leading)
143- . padding ( . trailing, 8 )
144- . rotationEffect ( Angle ( degrees: 180 ) )
145- . scaleEffect ( x: - 1 , y: 1 , anchor: . center)
146- . shadow ( color: . black. opacity ( 0.1 ) , radius: 2 )
147- . frame ( maxWidth: . infinity, alignment: . trailing)
148- . contextMenu {
149- Button ( " Copy " ) {
150- NSPasteboard . general. clearContents ( )
151- NSPasteboard . general. setString ( text, forType: . string)
152- }
153- }
154- } else {
155- HStack ( alignment: . bottom, spacing: 2 ) {
156- Markdown ( text)
157- . textSelection ( . enabled)
158- . markdownTheme ( . gitHub. text {
159- BackgroundColor ( Color . clear)
160- } )
161- . markdownCodeSyntaxHighlighter (
162- ChatCodeSyntaxHighlighter ( brightMode: colorScheme != . dark)
163- )
164- . frame ( alignment: . leading)
165- . padding ( )
166- . background {
167- RoundedCorners ( tl: r, tr: r, bl: 0 , br: r)
168- . fill ( Color . contentBackground)
169- }
170- . overlay {
171- RoundedCorners ( tl: r, tr: r, bl: 0 , br: r)
172- . stroke ( Color ( nsColor: . separatorColor) , lineWidth: 1 )
173- }
174- . padding ( . leading, 8 )
175- . rotationEffect ( Angle ( degrees: 180 ) )
176- . scaleEffect ( x: - 1 , y: 1 , anchor: . center)
177- . shadow ( color: . black. opacity ( 0.1 ) , radius: 2 )
178- . contextMenu {
179- Button ( " Copy " ) {
180- NSPasteboard . general. clearContents ( )
181- NSPasteboard . general. setString ( text, forType: . string)
182- }
183- }
184-
185- CopyButton {
186- NSPasteboard . general. clearContents ( )
187- NSPasteboard . general. setString ( text, forType: . string)
188- }
189- . rotationEffect ( Angle ( degrees: 180 ) )
190- . scaleEffect ( x: - 1 , y: 1 , anchor: . center)
92+ . scaleEffect ( x: - 1 , y: - 1 , anchor: . center)
93+ . foregroundStyle ( . secondary)
94+ }
95+
96+ ForEach ( chat. history. reversed ( ) , id: \. id) { message in
97+ let text = message. text. isEmpty && !message. isUser ? " ... " : message
98+ . text
99+
100+ if message. isUser {
101+ UserMessage ( text: text)
102+ } else {
103+ BotMessage ( text: text)
191104 }
192- . frame ( maxWidth: . infinity, alignment: . leading)
193- . padding ( . trailing, 2 )
194105 }
106+
107+ Spacer ( )
195108 }
109+ }
110+ }
111+ . scaleEffect ( x: - 1 , y: - 1 , anchor: . center)
112+ }
113+ }
114+
115+ private struct StopRespondingButton : View {
116+ let chat : ChatProvider
196117
197- Spacer ( )
118+ var body : some View {
119+ Button ( action: {
120+ chat. stop ( )
121+ } ) {
122+ HStack ( spacing: 4 ) {
123+ Image ( systemName: " stop.fill " )
124+ Text ( " Stop Responding " )
198125 }
126+ . padding ( 8 )
127+ . background (
128+ . regularMaterial,
129+ in: RoundedRectangle ( cornerRadius: r, style: . continuous)
130+ )
131+ . overlay {
132+ RoundedRectangle ( cornerRadius: r, style: . continuous)
133+ . stroke ( Color ( nsColor: . separatorColor) , lineWidth: 1 )
134+ }
135+ }
136+ . buttonStyle ( . borderless)
137+ . scaleEffect ( x: - 1 , y: - 1 , anchor: . center)
138+ }
139+ }
140+
141+ private struct UserMessage : View {
142+ let text : String
143+ @Environment ( \. colorScheme) var colorScheme
144+
145+ var body : some View {
146+ Markdown ( text)
147+ . textSelection ( . enabled)
148+ . markdownTheme ( . gitHub. text {
149+ BackgroundColor ( Color . clear)
150+ } )
151+ . markdownCodeSyntaxHighlighter (
152+ ChatCodeSyntaxHighlighter ( brightMode: colorScheme != . dark)
153+ )
154+ . frame ( alignment: . leading)
155+ . padding ( )
156+ . background {
157+ RoundedCorners ( tl: r, tr: r, bl: r, br: 0 )
158+ . fill ( Color . userChatContentBackground)
159+ }
160+ . overlay {
161+ RoundedCorners ( tl: r, tr: r, bl: r, br: 0 )
162+ . stroke ( Color ( nsColor: . separatorColor) , lineWidth: 1 )
163+ }
164+ . padding ( . leading)
165+ . padding ( . trailing, 8 )
166+ . scaleEffect ( x: - 1 , y: - 1 , anchor: . center)
167+ . shadow ( color: . black. opacity ( 0.1 ) , radius: 2 )
168+ . frame ( maxWidth: . infinity, alignment: . leading)
169+ . contextMenu {
170+ Button ( " Copy " ) {
171+ NSPasteboard . general. clearContents ( )
172+ NSPasteboard . general. setString ( text, forType: . string)
173+ }
174+ }
175+ }
176+ }
177+
178+ private struct BotMessage : View {
179+ let text : String
180+ @Environment ( \. colorScheme) var colorScheme
181+
182+ var body : some View {
183+ HStack ( alignment: . bottom, spacing: 2 ) {
184+ CopyButton {
185+ NSPasteboard . general. clearContents ( )
186+ NSPasteboard . general. setString ( text, forType: . string)
187+ }
188+ . scaleEffect ( x: - 1 , y: - 1 , anchor: . center)
189+
190+ Markdown ( text)
191+ . textSelection ( . enabled)
192+ . markdownTheme ( . gitHub. text {
193+ BackgroundColor ( Color . clear)
194+ } )
195+ . markdownCodeSyntaxHighlighter (
196+ ChatCodeSyntaxHighlighter ( brightMode: colorScheme != . dark)
197+ )
198+ . frame ( alignment: . trailing)
199+ . padding ( )
200+ . background {
201+ RoundedCorners ( tl: r, tr: r, bl: 0 , br: r)
202+ . fill ( Color . contentBackground)
203+ }
204+ . overlay {
205+ RoundedCorners ( tl: r, tr: r, bl: 0 , br: r)
206+ . stroke ( Color ( nsColor: . separatorColor) , lineWidth: 1 )
207+ }
208+ . padding ( . leading, 8 )
209+ . scaleEffect ( x: - 1 , y: - 1 , anchor: . center)
210+ . shadow ( color: . black. opacity ( 0.1 ) , radius: 2 )
211+ . contextMenu {
212+ Button ( " Copy " ) {
213+ NSPasteboard . general. clearContents ( )
214+ NSPasteboard . general. setString ( text, forType: . string)
215+ }
216+ }
199217 }
200- . rotationEffect ( Angle ( degrees : 180 ) )
201- . scaleEffect ( x : - 1 , y : 1 , anchor : . center )
218+ . frame ( maxWidth : . infinity , alignment : . trailing )
219+ . padding ( . trailing , 2 )
202220 }
203221}
204222
205223struct ChatPanelInputArea : View {
206224 @ObservedObject var chat : ChatProvider
207- var inputAreaNamespace : Namespace . ID
208225 @Binding var typedMessage : String
209226 @FocusState var isInputAreaFocused : Bool
210227
0 commit comments