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 { 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,20 @@ 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 = waitingStatuses . includes (
48
+ task ?. workspace . latest_build . status ,
49
+ ) ;
50
+ const buildLogs = useWorkspaceBuildLogs (
51
+ task ?. workspace . latest_build . id ?? "" ,
52
+ shouldStreamBuildLogs ,
53
+ ) ;
54
+
35
55
if ( error ) {
36
56
return (
37
57
< >
@@ -77,7 +97,6 @@ const TaskPage = () => {
77
97
}
78
98
79
99
let content :ReactNode = null ;
80
- const waitingStatuses :WorkspaceStatus [ ] = [ "starting" , "pending" ] ;
81
100
const terminatedStatuses :WorkspaceStatus [ ] = [
82
101
"canceled" ,
83
102
"canceling" ,
@@ -88,17 +107,27 @@ const TaskPage = () => {
88
107
] ;
89
108
90
109
if ( waitingStatuses . includes ( task . workspace . latest_build . status ) ) {
110
+ // If no template yet, use null values for an indeterminate progress bar.
111
+ const transition = ( template &&
112
+ ActiveTransition ( template , task . workspace ) ) || { P50 :null , P95 :null } ;
113
+ const lastStage = buildLogs ?. [ buildLogs . length - 1 ] ?. stage ;
91
114
content = (
92
- < div className = "w-full min-h-80 flex items-center justify-center" >
115
+ < div className = "w-full min-h-80 flexflex-col items-center justify-center gap-2 " >
93
116
< div className = "flex flex-col items-center" >
94
- < Spinner loading className = "mb-4" />
95
117
< h3 className = "m-0 font-medium text-content-primary text-base" >
96
118
Starting your workspace
97
119
</ h3 >
98
120
< span className = "text-content-secondary text-sm" >
99
121
This should take a few minutes
100
122
</ span >
101
123
</ div >
124
+ { lastStage && (
125
+ < div className = "text-content-secondary text-sm" > { lastStage } </ div >
126
+ ) }
127
+ < WorkspaceBuildProgress
128
+ workspace = { task . workspace }
129
+ transitionStats = { transition }
130
+ />
102
131
</ div >
103
132
) ;
104
133
} else if ( task . workspace . latest_build . status === "failed" ) {
@@ -186,13 +215,8 @@ export const data = {
186
215
const parameters = await API . getWorkspaceBuildParameters (
187
216
workspace . latest_build . id ,
188
217
) ;
189
- const prompt = parameters . find (
190
- ( p ) => p . name === AI_PROMPT_PARAMETER_NAME ,
191
- ) ?. value ;
192
-
193
- if ( ! prompt ) {
194
- return ;
195
- }
218
+ const prompt =
219
+ parameters . find ( ( p ) => p . name === AI_PROMPT_PARAMETER_NAME ) ?. value ?? "" ;
196
220
197
221
return {
198
222
workspace,