1
1
import { API } from "api/api" ;
2
2
import { getErrorDetail , getErrorMessage } from "api/errors" ;
3
3
import { template as templateQueryOptions } from "api/queries/templates" ;
4
- import type { Workspace , WorkspaceStatus } from "api/typesGenerated" ;
4
+ import type {
5
+ Workspace ,
6
+ WorkspaceAgent ,
7
+ WorkspaceStatus ,
8
+ } from "api/typesGenerated" ;
5
9
import isChromatic from "chromatic/isChromatic" ;
6
10
import { Button } from "components/Button/Button" ;
7
11
import { Loader } from "components/Loader/Loader" ;
8
12
import { Margins } from "components/Margins/Margins" ;
9
13
import { ScrollArea } from "components/ScrollArea/ScrollArea" ;
10
14
import { useWorkspaceBuildLogs } from "hooks/useWorkspaceBuildLogs" ;
11
15
import { ArrowLeftIcon , RotateCcwIcon } from "lucide-react" ;
16
+ import { AgentLogs } from "modules/resources/AgentLogs/AgentLogs" ;
17
+ import { useAgentLogs } from "modules/resources/useAgentLogs" ;
12
18
import { AI_PROMPT_PARAMETER_NAME , type Task } from "modules/tasks/tasks" ;
13
19
import { WorkspaceBuildLogs } from "modules/workspaces/WorkspaceBuildLogs/WorkspaceBuildLogs" ;
14
- import { type FC , type ReactNode , useEffect , useRef } from "react" ;
20
+ import { type FC , type ReactNode , useLayoutEffect , useRef } from "react" ;
15
21
import { Helmet } from "react-helmet-async" ;
16
22
import { useQuery } from "react-query" ;
17
23
import { Panel , PanelGroup , PanelResizeHandle } from "react-resizable-panels" ;
18
24
import { Link as RouterLink , useParams } from "react-router" ;
25
+ import type { FixedSizeList } from "react-window" ;
19
26
import { pageTitle } from "utils/page" ;
20
27
import {
21
28
getActiveTransitionStats ,
@@ -87,6 +94,7 @@ const TaskPage = () => {
87
94
}
88
95
89
96
let content : ReactNode = null ;
97
+ const agent = selectAgent ( task ) ;
90
98
91
99
if ( waitingStatuses . includes ( task . workspace . latest_build . status ) ) {
92
100
content = < TaskBuildingWorkspace task = { task } /> ;
@@ -132,6 +140,8 @@ const TaskPage = () => {
132
140
</ div >
133
141
</ Margins >
134
142
) ;
143
+ } else if ( agent && [ "created" , "starting" ] . includes ( agent . lifecycle_state ) ) {
144
+ content = < TaskStartingAgent agent = { agent } /> ;
135
145
} else {
136
146
content = (
137
147
< PanelGroup autoSaveId = "task" direction = "horizontal" >
@@ -182,7 +192,7 @@ const TaskBuildingWorkspace: FC<TaskBuildingWorkspaceProps> = ({ task }) => {
182
192
183
193
const scrollAreaRef = useRef < HTMLDivElement > ( null ) ;
184
194
// biome-ignore lint/correctness/useExhaustiveDependencies: this effect should run when build logs change
185
- useEffect ( ( ) => {
195
+ useLayoutEffect ( ( ) => {
186
196
if ( isChromatic ( ) ) {
187
197
return ;
188
198
}
@@ -196,34 +206,86 @@ const TaskBuildingWorkspace: FC<TaskBuildingWorkspaceProps> = ({ task }) => {
196
206
} , [ buildLogs ] ) ;
197
207
198
208
return (
199
- < section className = "w-full h-full flex justify-center items-center p-6 overflow-y-auto" >
200
- < div className = "flex flex-col gap-6 items-center w-full" >
201
- < header className = "flex flex-col items-center text-center" >
202
- < h3 className = "m-0 font-medium text-content-primary text-xl" >
203
- Starting your workspace
204
- </ h3 >
205
- < div className = "text-content-secondary" >
206
- Your task will be running in a few moments
209
+ < section className = "p-16 overflow-y-auto" >
210
+ < div className = "flex justify-center items-center w-full" >
211
+ < div className = "flex flex-col gap-6 items-center w-full" >
212
+ < header className = "flex flex-col items-center text-center" >
213
+ < h3 className = "m-0 font-medium text-content-primary text-xl" >
214
+ Starting your workspace
215
+ </ h3 >
216
+ < p className = "text-content-secondary m-0" >
217
+ Your task will be running in a few moments
218
+ </ p >
219
+ </ header >
220
+
221
+ < div className = "w-full max-w-screen-lg flex flex-col gap-4 overflow-hidden" >
222
+ < WorkspaceBuildProgress
223
+ workspace = { task . workspace }
224
+ transitionStats = { transitionStats }
225
+ variant = "task"
226
+ />
227
+
228
+ < ScrollArea
229
+ ref = { scrollAreaRef }
230
+ className = "h-96 border border-solid border-border rounded-lg"
231
+ >
232
+ < WorkspaceBuildLogs
233
+ sticky
234
+ className = "border-0 rounded-none"
235
+ logs = { buildLogs ?? [ ] }
236
+ />
237
+ </ ScrollArea >
207
238
</ div >
208
- </ header >
239
+ </ div >
240
+ </ div >
241
+ </ section >
242
+ ) ;
243
+ } ;
244
+
245
+ type TaskStartingAgentProps = {
246
+ agent : WorkspaceAgent ;
247
+ } ;
209
248
210
- < div className = "w-full max-w-screen-lg flex flex-col gap-4 overflow-hidden" >
211
- < WorkspaceBuildProgress
212
- workspace = { task . workspace }
213
- transitionStats = { transitionStats }
214
- variant = "task"
215
- />
249
+ const TaskStartingAgent : FC < TaskStartingAgentProps > = ( { agent } ) => {
250
+ const logs = useAgentLogs ( agent , true ) ;
251
+ const listRef = useRef < FixedSizeList > ( null ) ;
216
252
217
- < ScrollArea
218
- ref = { scrollAreaRef }
219
- className = "h-96 border border-solid border-border rounded-lg"
220
- >
221
- < WorkspaceBuildLogs
222
- sticky
223
- className = "border-0 rounded-none"
224
- logs = { buildLogs ?? [ ] }
225
- />
226
- </ ScrollArea >
253
+ useLayoutEffect ( ( ) => {
254
+ if ( listRef . current ) {
255
+ listRef . current . scrollToItem ( logs . length - 1 , "end" ) ;
256
+ }
257
+ } , [ logs ] ) ;
258
+
259
+ return (
260
+ < section className = "p-16 overflow-y-auto" >
261
+ < div className = "flex justify-center items-center w-full" >
262
+ < div className = "flex flex-col gap-8 items-center w-full" >
263
+ < header className = "flex flex-col items-center text-center" >
264
+ < h3 className = "m-0 font-medium text-content-primary text-xl" >
265
+ Running startup scripts
266
+ </ h3 >
267
+ < p className = "text-content-secondary m-0" >
268
+ Your task will be running in a few moments
269
+ </ p >
270
+ </ header >
271
+
272
+ < div className = "w-full max-w-screen-lg flex flex-col gap-4 overflow-hidden" >
273
+ < div className = "h-96 border border-solid border-border rounded-lg" >
274
+ < AgentLogs
275
+ logs = { logs . map ( ( l ) => ( {
276
+ id : l . id ,
277
+ level : l . level ,
278
+ output : l . output ,
279
+ sourceId : l . source_id ,
280
+ time : l . created_at ,
281
+ } ) ) }
282
+ sources = { agent . log_sources }
283
+ height = { 96 * 4 }
284
+ width = "100%"
285
+ ref = { listRef }
286
+ />
287
+ </ div >
288
+ </ div >
227
289
</ div >
228
290
</ div >
229
291
</ section >
@@ -265,3 +327,11 @@ export const data = {
265
327
} satisfies Task ;
266
328
} ,
267
329
} ;
330
+
331
+ function selectAgent ( task : Task ) {
332
+ const agents = task . workspace . latest_build . resources
333
+ . flatMap ( ( r ) => r . agents )
334
+ . filter ( ( a ) => ! ! a ) ;
335
+
336
+ return agents . at ( 0 ) ;
337
+ }
0 commit comments