1
1
import { API } from "api/api" ;
2
2
import { getErrorDetail , getErrorMessage } from "api/errors" ;
3
+ import { template as templateQueryOptions } from "api/queries/templates" ;
3
4
import type { Workspace , WorkspaceStatus } from "api/typesGenerated" ;
4
5
import { Button } from "components/Button/Button" ;
5
6
import { Loader } from "components/Loader/Loader" ;
6
7
import { Margins } from "components/Margins/Margins" ;
7
8
import { Spinner } from "components/Spinner/Spinner" ;
9
+ import { useWorkspaceBuildLogs } from "hooks/useWorkspaceBuildLogs" ;
8
10
import { ArrowLeftIcon , RotateCcwIcon } from "lucide-react" ;
9
11
import { AI_PROMPT_PARAMETER_NAME , type Task } from "modules/tasks/tasks" ;
10
12
import type { ReactNode } from "react" ;
@@ -14,6 +16,10 @@ import { useParams } from "react-router-dom";
14
16
import { Link as RouterLink } from "react-router-dom" ;
15
17
import { ellipsizeText } from "utils/ellipsizeText" ;
16
18
import { pageTitle } from "utils/page" ;
19
+ import {
20
+ ActiveTransition ,
21
+ WorkspaceBuildProgress ,
22
+ } from "../WorkspacePage/WorkspaceBuildProgress" ;
17
23
import { TaskApps } from "./TaskApps" ;
18
24
import { TaskSidebar } from "./TaskSidebar" ;
19
25
@@ -32,6 +38,19 @@ const TaskPage = () => {
32
38
refetchInterval :5_000 ,
33
39
} ) ;
34
40
41
+ const { data :template } = useQuery ( {
42
+ ...templateQueryOptions ( task ?. workspace . template_id ?? "" ) ,
43
+ enabled :Boolean ( task ) ,
44
+ } ) ;
45
+
46
+ const waitingStatuses :WorkspaceStatus [ ] = [ "starting" , "pending" ] ;
47
+ const shouldStreamBuildLogs =
48
+ task && waitingStatuses . includes ( task . workspace . latest_build . status ) ;
49
+ const buildLogs = useWorkspaceBuildLogs (
50
+ task ?. workspace . latest_build . id ?? "" ,
51
+ shouldStreamBuildLogs ,
52
+ ) ;
53
+
35
54
if ( error ) {
36
55
return (
37
56
< >
@@ -77,7 +96,6 @@ const TaskPage = () => {
77
96
}
78
97
79
98
let content :ReactNode = null ;
80
- const waitingStatuses :WorkspaceStatus [ ] = [ "starting" , "pending" ] ;
81
99
const terminatedStatuses :WorkspaceStatus [ ] = [
82
100
"canceled" ,
83
101
"canceling" ,
@@ -88,17 +106,29 @@ const TaskPage = () => {
88
106
] ;
89
107
90
108
if ( waitingStatuses . includes ( task . workspace . latest_build . status ) ) {
109
+ // If no template yet, use an indeterminate progress bar.
110
+ const transition = ( template &&
111
+ ActiveTransition ( template , task . workspace ) ) || { P50 :0 , P95 :null } ;
112
+ const lastStage = buildLogs ?. [ buildLogs . length - 1 ] ?. stage ;
91
113
content = (
92
- < div className = "w-full min-h-80 flex items-center justify-center" >
114
+ < div className = "w-full min-h-80 flexflex-col items-center justify-center gap-2 " >
93
115
< div className = "flex flex-col items-center" >
94
- < Spinner loading className = "mb-4" />
95
116
< h3 className = "m-0 font-medium text-content-primary text-base" >
96
117
Starting your workspace
97
118
</ h3 >
98
119
< span className = "text-content-secondary text-sm" >
99
120
This should take a few minutes
100
121
</ span >
101
122
</ div >
123
+ { lastStage && (
124
+ < div className = "text-content-secondary text-sm" > { lastStage } </ div >
125
+ ) }
126
+ < div css = { { minWidth :315 } } >
127
+ < WorkspaceBuildProgress
128
+ workspace = { task . workspace }
129
+ transitionStats = { transition }
130
+ />
131
+ </ div >
102
132
</ div >
103
133
) ;
104
134
} else if ( task . workspace . latest_build . status === "failed" ) {