@@ -9,26 +9,208 @@ private let r: Double = 8
99struct ChatWindowView : View {
1010 let store : StoreOf < ChatPanelFeature >
1111
12+ struct OverallState : Equatable {
13+ var isPanelDisplayed : Bool
14+ var colorScheme : ColorScheme
15+ var selectedTabId : String ?
16+ }
17+
1218 var body : some View {
13- WithViewStore ( store, observe: { $0 } ) { viewStore in
14- Group {
15- if let chat = viewStore. chat {
16- ChatPanel ( chat: chat)
17- . background {
18- Button ( action: {
19- viewStore. send ( . hideButtonClicked)
20- } ) {
21- EmptyView ( )
22- }
23- . keyboardShortcut ( " M " , modifiers: [ . command] )
24- }
19+ WithViewStore (
20+ store,
21+ observe: {
22+ OverallState (
23+ isPanelDisplayed: $0. isPanelDisplayed,
24+ colorScheme: $0. colorScheme,
25+ selectedTabId: $0. chatTapGroup. selectedTabId
26+ )
27+ }
28+ ) { viewStore in
29+ VStack ( spacing: 0 ) {
30+ ChatTabBar ( store: store)
31+ . frame ( height: 32 )
32+
33+ Divider ( )
34+
35+ ChatTabContainer ( store: store)
36+ . frame ( maxWidth: . infinity, maxHeight: . infinity)
37+ }
38+ . background {
39+ Button ( action: {
40+ viewStore. send ( . hideButtonClicked)
41+ } ) {
42+ EmptyView ( )
2543 }
44+ . opacity ( 0 )
45+ . keyboardShortcut ( " M " , modifiers: [ . command] )
2646 }
47+ . background ( . regularMaterial)
2748 . xcodeStyleFrame ( )
28- . opacity ( viewStore. isPanelDisplayed ? 1 : 0 )
49+ . opacity ( viewStore. state . isPanelDisplayed ? 1 : 0 )
2950 . frame ( minWidth: Style . panelWidth, minHeight: Style . panelHeight)
30- . preferredColorScheme ( viewStore. colorScheme)
51+ . preferredColorScheme ( viewStore. state. colorScheme)
52+ }
53+ }
54+ }
55+
56+ struct ChatTabBar : View {
57+ let store : StoreOf < ChatPanelFeature >
58+
59+ struct TabBarState : Equatable {
60+ var tabInfo : [ ChatTabInfo ]
61+ var selectedTabId : String
62+ }
63+
64+ var body : some View {
65+ WithViewStore (
66+ store,
67+ observe: { TabBarState (
68+ tabInfo: $0. chatTapGroup. tabInfo,
69+ selectedTabId: $0. chatTapGroup. selectedTabId
70+ ?? $0. chatTapGroup. tabInfo. first? . id ?? " "
71+ ) }
72+ ) { viewStore in
73+ HStack ( spacing: 0 ) {
74+ ScrollView ( . horizontal) {
75+ HStack ( spacing: 0 ) {
76+ ForEach ( viewStore. state. tabInfo, id: \. id) { info in
77+ ChatTabBarButton (
78+ store: store,
79+ info: info,
80+ isSelected: info. id == viewStore. state. selectedTabId
81+ )
82+ }
83+ }
84+ }
85+
86+ Divider ( )
87+
88+ Button ( action: {
89+ store. send ( . createNewTapButtonClicked( type: " " ) )
90+ } ) {
91+ Image ( systemName: " plus " )
92+ . foregroundColor ( . secondary)
93+ . padding ( 8 )
94+ } . buttonStyle ( . plain)
95+ }
96+ }
97+ }
98+ }
99+
100+ struct ChatTabBarButton : View {
101+ let store : StoreOf < ChatPanelFeature >
102+ let info : ChatTabInfo
103+ let isSelected : Bool
104+ @State var isHovered : Bool = false
105+
106+ var body : some View {
107+ HStack ( spacing: 0 ) {
108+ Button ( action: {
109+ store. send ( . tabClicked( id: info. id) )
110+ } ) {
111+ Text ( info. title)
112+ . lineLimit ( 1 )
113+ . frame ( maxWidth: 120 )
114+ }
115+ . buttonStyle ( PlainButtonStyle ( ) )
116+ . padding ( . horizontal, 32 )
117+ . frame ( maxHeight: . infinity)
118+
119+ . overlay ( alignment: . leading) {
120+ Button ( action: {
121+ store. send ( . closeTabButtonClicked( id: info. id) )
122+ } ) {
123+ Image ( systemName: " xmark " )
124+ . foregroundColor ( . secondary)
125+ }
126+ . buttonStyle ( . plain)
127+ . padding ( 2 )
128+ . padding ( . leading, 10 )
129+ . opacity ( isHovered ? 1 : 0 )
130+ }
131+ . onHover { isHovered = $0 }
132+ . animation ( . linear( duration: 0.1 ) , value: isHovered)
133+ . animation ( . linear( duration: 0.1 ) , value: isSelected)
134+
135+ Divider ( ) . padding ( . vertical, 16 )
31136 }
137+ . background ( isSelected ? Color ( nsColor: . selectedControlColor) : Color . clear)
138+ }
139+ }
140+
141+ struct ChatTabContainer : View {
142+ let store : StoreOf < ChatPanelFeature >
143+
144+ struct TabContainerState : Equatable {
145+ var tabs : [ BaseChatTab ]
146+ var selectedTabId : String ?
147+ }
148+
149+ var body : some View {
150+ WithViewStore (
151+ store,
152+ observe: {
153+ TabContainerState (
154+ tabs: $0. chatTapGroup. tabs,
155+ selectedTabId: $0. chatTapGroup. selectedTabId
156+ ?? $0. chatTapGroup. tabInfo. first? . id ?? " "
157+ )
158+ }
159+ ) { viewStore in
160+ ZStack {
161+ if viewStore. state. tabs. isEmpty {
162+ Text ( " Empty " )
163+ } else {
164+ ForEach ( viewStore. state. tabs, id: \. id) { tab in
165+ tab. body
166+ . opacity ( tab. id == viewStore. state. selectedTabId ? 1 : 0 )
167+ . frame ( maxWidth: . infinity, maxHeight: . infinity)
168+ }
169+ }
170+ }
171+ }
172+ . onPreferenceChange ( ChatTabInfoPreferenceKey . self) { items in
173+ store. send ( . updateChatTabInfo( items) )
174+ }
175+ }
176+ }
177+
178+ struct ChatWindowView_Previews : PreviewProvider {
179+ class FakeChatTab : ChatTab {
180+ func buildView( ) -> any View {
181+ ChatPanel (
182+ chat: . init(
183+ history: [
184+ . init( id: " 1 " , role: . assistant, text: " Hello World " ) ,
185+ ] ,
186+ isReceivingMessage: false
187+ ) ,
188+ typedMessage: " Hello World! "
189+ )
190+ }
191+
192+ override init ( id: String , title: String ) {
193+ super. init ( id: id, title: title)
194+ }
195+ }
196+
197+ static var previews : some View {
198+ ChatWindowView (
199+ store: . init(
200+ initialState: . init(
201+ chatTapGroup: . init(
202+ tabs: [
203+ FakeChatTab ( id: " 1 " , title: " Hello I am a chatbot " ) ,
204+ EmptyChatTab ( id: " 2 " ) ,
205+ ] ,
206+ selectedTabId: " 1 "
207+ ) ,
208+ isPanelDisplayed: true
209+ ) ,
210+ reducer: ChatPanelFeature ( )
211+ )
212+ )
213+ . padding ( )
32214 }
33215}
34216
0 commit comments