1
+ import type { Message } from 'ai' ;
1
2
import type { LegacyRef } from 'react' ;
2
3
import React from 'react' ;
3
4
import { ClientOnly } from 'remix-utils/client-only' ;
4
5
import { IconButton } from '~/components/ui/IconButton' ;
6
+ import { Workspace } from '~/components/workspace/Workspace.client' ;
5
7
import { classNames } from '~/utils/classNames' ;
8
+ import { Messages } from './Messages.client' ;
6
9
import { SendButton } from './SendButton.client' ;
7
10
8
11
interface BaseChatProps {
9
12
textareaRef ?: LegacyRef < HTMLTextAreaElement > | undefined ;
10
13
messagesSlot ?: React . ReactNode ;
11
14
workspaceSlot ?: React . ReactNode ;
12
15
chatStarted ?: boolean ;
16
+ isStreaming ?: boolean ;
17
+ messages ?: Message [ ] ;
13
18
enhancingPrompt ?: boolean ;
14
19
promptEnhanced ?: boolean ;
15
20
input ?: string ;
@@ -27,10 +32,10 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
27
32
{
28
33
textareaRef,
29
34
chatStarted = false ,
35
+ isStreaming = false ,
30
36
enhancingPrompt = false ,
31
37
promptEnhanced = false ,
32
- messagesSlot,
33
- workspaceSlot,
38
+ messages,
34
39
input = '' ,
35
40
sendMessage,
36
41
handleInputChange,
@@ -41,14 +46,14 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
41
46
const TEXTAREA_MAX_HEIGHT = chatStarted ? 400 : 200 ;
42
47
43
48
return (
44
- < div ref = { ref } className = "h-full flex w-full overflow-scroll px-6 " >
45
- < div className = "flex flex-col items-center w-full h-full" >
46
- < div id = "chat" className = "w-full" >
49
+ < div ref = { ref } className = "relative flex h-full w-full overflow-hidden " >
50
+ < div className = "flex overflow-scroll w-full h-full" >
51
+ < div id = "chat" className = "flex flex-col w-full h-full px-6 " >
47
52
{ ! chatStarted && (
48
- < div id = "intro" className = "mt-[20vh] mb-14 max-w-2xl mx-auto" >
53
+ < div id = "intro" className = "mt-[20vh] mb-14 max-w-3xl mx-auto" >
49
54
< h2 className = "text-4xl text-center font-bold text-slate-800 mb-2" > Where ideas begin.</ h2 >
50
55
< p className = "mb-14 text-center" > Bring ideas to life in seconds or get help on existing projects.</ p >
51
- < div className = "grid max-md:grid-cols-[repeat(2 ,1fr)] md:grid-cols-[repeat(2,minmax(200px ,1fr))] gap-4" >
56
+ < div className = "grid max-md:grid-cols-[repeat(1 ,1fr)] md:grid-cols-[repeat(2,minmax(300px ,1fr))] gap-4" >
52
57
{ EXAMPLES . map ( ( suggestion , index ) => (
53
58
< button key = { index } className = "p-4 rounded-lg shadow-xs bg-white border border-gray-200 text-left" >
54
59
{ suggestion . text }
@@ -57,83 +62,95 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
57
62
</ div >
58
63
</ div >
59
64
) }
60
- { messagesSlot }
61
- </ div >
62
- < div
63
- className = { classNames ( 'w-full md:max-w-[720px] mx-auto' , {
64
- 'fixed bg-bolt-elements-app-backgroundColor bottom-0' : chatStarted ,
65
- } ) }
66
- >
67
65
< div
68
- className = { classNames (
69
- 'relative shadow-sm border border-gray-200 md:mb-6 bg-white rounded-lg overflow-hidden' ,
70
- {
71
- 'max-md:rounded-none max-md:border-x-none' : chatStarted ,
72
- } ,
73
- ) }
66
+ className = { classNames ( 'pt-10' , {
67
+ 'h-full flex flex-col' : chatStarted ,
68
+ } ) }
74
69
>
75
- < textarea
76
- ref = { textareaRef }
77
- onKeyDown = { ( event ) => {
78
- if ( event . key === 'Enter' ) {
79
- if ( event . shiftKey ) {
80
- return ;
81
- }
70
+ < ClientOnly >
71
+ { ( ) => {
72
+ return chatStarted ? (
73
+ < Messages
74
+ className = "flex flex-col w-full flex-1 max-w-3xl px-4 pb-10 mx-auto z-1"
75
+ messages = { messages }
76
+ isStreaming = { isStreaming }
77
+ />
78
+ ) : null ;
79
+ } }
80
+ </ ClientOnly >
81
+ < div
82
+ className = { classNames ( 'relative w-full max-w-3xl md:mx-auto z-2' , {
83
+ 'sticky bottom-0 bg-bolt-elements-app-backgroundColor' : chatStarted ,
84
+ } ) }
85
+ >
86
+ < div
87
+ className = { classNames ( 'shadow-sm mb-6 border border-gray-200 bg-white rounded-lg overflow-hidden' ) }
88
+ >
89
+ < textarea
90
+ ref = { textareaRef }
91
+ onKeyDown = { ( event ) => {
92
+ if ( event . key === 'Enter' ) {
93
+ if ( event . shiftKey ) {
94
+ return ;
95
+ }
82
96
83
- event . preventDefault ( ) ;
97
+ event . preventDefault ( ) ;
84
98
85
- sendMessage ?.( ) ;
86
- }
87
- } }
88
- value = { input }
89
- onChange = { ( event ) => {
90
- handleInputChange ?.( event ) ;
91
- } }
92
- className = { `w-full pl-4 pt-4 pr-16 focus:outline-none resize-none` }
93
- style = { {
94
- minHeight : TEXTAREA_MIN_HEIGHT ,
95
- maxHeight : TEXTAREA_MAX_HEIGHT ,
96
- } }
97
- placeholder = "How can Bolt help you today?"
98
- translate = "no"
99
- />
100
- < ClientOnly > { ( ) => < SendButton show = { input . length > 0 } onClick = { sendMessage } /> } </ ClientOnly >
101
- < div className = "flex justify-between text-sm p-4 pt-2" >
102
- < div className = "flex gap-1 items-center" >
103
- < IconButton icon = "i-ph:microphone-duotone" className = "-ml-1" />
104
- < IconButton icon = "i-ph:plus-circle-duotone" />
105
- < IconButton
106
- disabled = { input . length === 0 || enhancingPrompt }
107
- className = { classNames ( {
108
- 'opacity-100!' : enhancingPrompt ,
109
- 'text-accent! pr-1.5 enabled:hover:bg-accent/12!' : promptEnhanced ,
110
- } ) }
111
- onClick = { ( ) => enhancePrompt ?.( ) }
112
- >
113
- { enhancingPrompt ? (
114
- < >
115
- < div className = "i-svg-spinners:90-ring-with-bg text-black text-xl" > </ div >
116
- < div className = "ml-1.5" > Enhancing prompt...</ div >
117
- </ >
118
- ) : (
119
- < >
120
- < div className = "i-blitz:stars text-xl" > </ div >
121
- { promptEnhanced && < div className = "ml-1.5" > Prompt enhanced</ div > }
122
- </ >
123
- ) }
124
- </ IconButton >
125
- </ div >
126
- { input . length > 3 ? (
127
- < div className = "text-xs" >
128
- Use < kbd className = "bg-gray-100 p-1 rounded-md" > Shift</ kbd > +{ ' ' }
129
- < kbd className = "bg-gray-100 p-1 rounded-md" > Return</ kbd > for a new line
99
+ sendMessage ?.( ) ;
100
+ }
101
+ } }
102
+ value = { input }
103
+ onChange = { ( event ) => {
104
+ handleInputChange ?.( event ) ;
105
+ } }
106
+ className = { `w-full pl-4 pt-4 pr-16 focus:outline-none resize-none` }
107
+ style = { {
108
+ minHeight : TEXTAREA_MIN_HEIGHT ,
109
+ maxHeight : TEXTAREA_MAX_HEIGHT ,
110
+ } }
111
+ placeholder = "How can Bolt help you today?"
112
+ translate = "no"
113
+ />
114
+ < ClientOnly > { ( ) => < SendButton show = { input . length > 0 } onClick = { sendMessage } /> } </ ClientOnly >
115
+ < div className = "flex justify-between text-sm p-4 pt-2" >
116
+ < div className = "flex gap-1 items-center" >
117
+ < IconButton icon = "i-ph:microphone-duotone" className = "-ml-1" />
118
+ < IconButton icon = "i-ph:plus-circle-duotone" />
119
+ < IconButton icon = "i-ph:pencil-simple-duotone" />
120
+ < IconButton
121
+ disabled = { input . length === 0 || enhancingPrompt }
122
+ className = { classNames ( {
123
+ 'opacity-100!' : enhancingPrompt ,
124
+ 'text-accent! pr-1.5 enabled:hover:bg-accent/12!' : promptEnhanced ,
125
+ } ) }
126
+ onClick = { ( ) => enhancePrompt ?.( ) }
127
+ >
128
+ { enhancingPrompt ? (
129
+ < >
130
+ < div className = "i-svg-spinners:90-ring-with-bg text-black text-xl" > </ div >
131
+ < div className = "ml-1.5" > Enhancing prompt...</ div >
132
+ </ >
133
+ ) : (
134
+ < >
135
+ < div className = "i-blitz:stars text-xl" > </ div >
136
+ { promptEnhanced && < div className = "ml-1.5" > Prompt enhanced</ div > }
137
+ </ >
138
+ ) }
139
+ </ IconButton >
140
+ </ div >
141
+ { input . length > 3 ? (
142
+ < div className = "text-xs" >
143
+ Use < kbd className = "bg-gray-100 p-1 rounded-md" > Shift</ kbd > +{ ' ' }
144
+ < kbd className = "bg-gray-100 p-1 rounded-md" > Return</ kbd > for a new line
145
+ </ div >
146
+ ) : null }
130
147
</ div >
131
- ) : null }
148
+ </ div >
132
149
</ div >
133
150
</ div >
134
151
</ div >
152
+ < ClientOnly > { ( ) => < Workspace chatStarted = { chatStarted } /> } </ ClientOnly >
135
153
</ div >
136
- { workspaceSlot }
137
154
</ div >
138
155
) ;
139
156
} ,
0 commit comments