@@ -22,6 +22,7 @@ import {
22
22
SquarePen ,
23
23
Plus ,
24
24
CircleX ,
25
+ XIcon ,
25
26
} from "lucide-react" ;
26
27
import { useQueryState , parseAsBoolean } from "nuqs" ;
27
28
import { StickToBottom , useStickToBottomContext } from "use-stick-to-bottom" ;
@@ -39,6 +40,12 @@ import {
39
40
} from "../ui/tooltip" ;
40
41
import { useFileUpload } from "@/hooks/use-file-upload" ;
41
42
import { ContentBlocksPreview } from "./ContentBlocksPreview" ;
43
+ import {
44
+ useArtifactOpen ,
45
+ ArtifactContent ,
46
+ ArtifactTitle ,
47
+ useArtifactContext ,
48
+ } from "./artifact" ;
42
49
43
50
function StickyToBottomContent ( props : {
44
51
content : ReactNode ;
@@ -106,7 +113,10 @@ function OpenGitHubRepo() {
106
113
}
107
114
108
115
export function Thread ( ) {
109
- const [ threadId , setThreadId ] = useQueryState ( "threadId" ) ;
116
+ const [ _threadId , _setThreadId ] = useQueryState ( "threadId" ) ;
117
+
118
+ const [ artifactContext , setArtifactContext ] = useArtifactContext ( ) ;
119
+ const [ artifactOpen , closeArtifact ] = useArtifactOpen ( ) ;
110
120
const [ chatHistoryOpen , setChatHistoryOpen ] = useQueryState (
111
121
"chatHistoryOpen" ,
112
122
parseAsBoolean . withDefault ( false ) ,
@@ -133,6 +143,33 @@ export function Thread() {
133
143
134
144
const lastError = useRef < string | undefined > ( undefined ) ;
135
145
146
+ useEffect ( ( ) => {
147
+ if ( ! stream . error ) {
148
+ lastError . current = undefined ;
149
+ return ;
150
+ }
151
+ try {
152
+ const message = ( stream . error as any ) . message ;
153
+ if ( ! message || lastError . current === message ) {
154
+ // Message has already been logged. do not modify ref, return early.
155
+ return ;
156
+ }
157
+ // Message is defined, and it has not been logged yet. Save it, and send the error
158
+ lastError . current = message ;
159
+ toast . error ( "An error occurred. Please try again." , {
160
+ description : (
161
+ < p >
162
+ < strong > Error:</ strong > < code > { message } </ code >
163
+ </ p >
164
+ ) ,
165
+ richColors : true ,
166
+ closeButton : true ,
167
+ } ) ;
168
+ } catch {
169
+ // no-op
170
+ }
171
+ } , [ stream . error ] ) ;
172
+
136
173
// TODO: this should be part of the useStream hook
137
174
const prevMessageLength = useRef ( 0 ) ;
138
175
useEffect ( ( ) => {
@@ -164,11 +201,15 @@ export function Thread() {
164
201
165
202
const toolMessages = ensureToolCallsHaveResponses ( stream . messages ) ;
166
203
stream . submit (
167
- { messages : [ ...toolMessages , newHumanMessage ] } ,
204
+ {
205
+ messages : [ ...toolMessages , newHumanMessage ] ,
206
+ context : artifactContext ,
207
+ } ,
168
208
{
169
209
streamMode : [ "values" ] ,
170
210
optimisticValues : ( prev ) => ( {
171
211
...prev ,
212
+ context : artifactContext ,
172
213
messages : [
173
214
...( prev . messages ?? [ ] ) ,
174
215
...toolMessages ,
@@ -182,11 +223,6 @@ export function Thread() {
182
223
setContentBlocks ( [ ] ) ;
183
224
} ;
184
225
185
- const chatStarted = ! ! threadId || ! ! messages . length ;
186
- const hasNoAIOrToolMessages = ! messages . find (
187
- ( m ) => m . type === "ai" || m . type === "tool" ,
188
- ) ;
189
-
190
226
// Restore handleRegenerate
191
227
const handleRegenerate = (
192
228
parentCheckpoint : Checkpoint | null | undefined ,
@@ -200,6 +236,18 @@ export function Thread() {
200
236
} ) ;
201
237
} ;
202
238
239
+ const setThreadId = ( id : string | null ) => {
240
+ _setThreadId ( id ) ;
241
+ // close artifact and reset artifact context
242
+ closeArtifact ( ) ;
243
+ setArtifactContext ( { } ) ;
244
+ } ;
245
+ const threadId = _threadId ;
246
+ const chatStarted = ! ! threadId || ! ! messages . length ;
247
+ const hasNoAIOrToolMessages = ! messages . find (
248
+ ( m ) => m . type === "ai" || m . type === "tool" ,
249
+ ) ;
250
+
203
251
return (
204
252
< div className = "flex h-screen w-full overflow-hidden" >
205
253
< div className = "relative hidden lg:flex" >
@@ -476,6 +524,20 @@ export function Thread() {
476
524
/>
477
525
</ StickToBottom >
478
526
</ motion . div >
527
+ < div className = "relative flex flex-col border-l" >
528
+ < div className = "absolute inset-0 flex min-w-[30vw] flex-col" >
529
+ < div className = "grid grid-cols-[1fr_auto] border-b p-4" >
530
+ < ArtifactTitle className = "truncate overflow-hidden" />
531
+ < button
532
+ onClick = { closeArtifact }
533
+ className = "cursor-pointer"
534
+ >
535
+ < XIcon className = "size-5" />
536
+ </ button >
537
+ </ div >
538
+ < ArtifactContent className = "relative flex-grow" />
539
+ </ div >
540
+ </ div >
479
541
</ div >
480
542
) ;
481
543
}
0 commit comments