Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit8aafbcb

Browse files
feat: show workspace build logs during tasks creation (#19413)
This is part of#19363**Screenshot:**<img width="1610" height="974" alt="Screenshot 2025-08-19 at 12 32 54"src="https://github.com/user-attachments/assets/c7435b67-49ac-4b88-ae2d-014787cea5f2"/>**Video demo:**https://github.com/user-attachments/assets/2249affd-3d51-4ff0-8a5f-a0358a90d659
1 parent54440af commit8aafbcb

File tree

8 files changed

+150
-167
lines changed

8 files changed

+150
-167
lines changed

‎site/src/pages/TaskPage/TaskPage.tsx‎

Lines changed: 94 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,28 @@ import { API } from "api/api";
22
import{getErrorDetail,getErrorMessage}from"api/errors";
33
import{templateastemplateQueryOptions}from"api/queries/templates";
44
importtype{Workspace,WorkspaceStatus}from"api/typesGenerated";
5+
importisChromaticfrom"chromatic/isChromatic";
56
import{Button}from"components/Button/Button";
67
import{Loader}from"components/Loader/Loader";
78
import{Margins}from"components/Margins/Margins";
9+
import{ScrollArea}from"components/ScrollArea/ScrollArea";
810
import{useWorkspaceBuildLogs}from"hooks/useWorkspaceBuildLogs";
911
import{ArrowLeftIcon,RotateCcwIcon}from"lucide-react";
1012
import{AI_PROMPT_PARAMETER_NAME,typeTask}from"modules/tasks/tasks";
11-
importtype{ReactNode}from"react";
13+
import{WorkspaceBuildLogs}from"modules/workspaces/WorkspaceBuildLogs/WorkspaceBuildLogs";
14+
import{typeFC,typeReactNode,useEffect,useRef}from"react";
1215
import{Helmet}from"react-helmet-async";
1316
import{useQuery}from"react-query";
1417
import{Panel,PanelGroup,PanelResizeHandle}from"react-resizable-panels";
1518
import{LinkasRouterLink,useParams}from"react-router";
16-
import{ellipsizeText}from"utils/ellipsizeText";
1719
import{pageTitle}from"utils/page";
1820
import{
19-
ActiveTransition,
21+
getActiveTransitionStats,
2022
WorkspaceBuildProgress,
2123
}from"../WorkspacePage/WorkspaceBuildProgress";
2224
import{TaskApps}from"./TaskApps";
2325
import{TaskSidebar}from"./TaskSidebar";
26+
import{TaskTopbar}from"./TaskTopbar";
2427

2528
constTaskPage=()=>{
2629
const{workspace:workspaceName, username}=useParams()as{
@@ -37,18 +40,7 @@ const TaskPage = () => {
3740
refetchInterval:5_000,
3841
});
3942

40-
const{data:template}=useQuery({
41-
...templateQueryOptions(task?.workspace.template_id??""),
42-
enabled:Boolean(task),
43-
});
44-
4543
constwaitingStatuses:WorkspaceStatus[]=["starting","pending"];
46-
constshouldStreamBuildLogs=
47-
task&&waitingStatuses.includes(task.workspace.latest_build.status);
48-
constbuildLogs=useWorkspaceBuildLogs(
49-
task?.workspace.latest_build.id??"",
50-
shouldStreamBuildLogs,
51-
);
5244

5345
if(error){
5446
return(
@@ -95,38 +87,9 @@ const TaskPage = () => {
9587
}
9688

9789
letcontent:ReactNode=null;
98-
const_terminatedStatuses:WorkspaceStatus[]=[
99-
"canceled",
100-
"canceling",
101-
"deleted",
102-
"deleting",
103-
"stopped",
104-
"stopping",
105-
];
10690

10791
if(waitingStatuses.includes(task.workspace.latest_build.status)){
108-
// If no template yet, use an indeterminate progress bar.
109-
consttransition=(template&&
110-
ActiveTransition(template,task.workspace))||{P50:0,P95:null};
111-
constlastStage=
112-
buildLogs?.[buildLogs.length-1]?.stage||"Waiting for build status";
113-
content=(
114-
<divclassName="w-full min-h-80 flex flex-col">
115-
<divclassName="flex flex-col items-center grow justify-center">
116-
<h3className="m-0 font-medium text-content-primary text-base">
117-
Starting your workspace
118-
</h3>
119-
<divclassName="text-content-secondary text-sm">{lastStage}</div>
120-
</div>
121-
<divclassName="w-full">
122-
<WorkspaceBuildProgress
123-
workspace={task.workspace}
124-
transitionStats={transition}
125-
variant="task"
126-
/>
127-
</div>
128-
</div>
129-
);
92+
content=<TaskBuildingWorkspacetask={task}/>;
13093
}elseif(task.workspace.latest_build.status==="failed"){
13194
content=(
13295
<divclassName="w-full min-h-80 flex items-center justify-center">
@@ -170,29 +133,103 @@ const TaskPage = () => {
170133
</Margins>
171134
);
172135
}else{
173-
content=<TaskAppstask={task}/>;
174-
}
175-
176-
return(
177-
<>
178-
<Helmet>
179-
<title>{pageTitle(ellipsizeText(task.prompt,64)??"Task")}</title>
180-
</Helmet>
136+
content=(
181137
<PanelGroupautoSaveId="task"direction="horizontal">
182138
<PaneldefaultSize={25}minSize={20}>
183139
<TaskSidebartask={task}/>
184140
</Panel>
185141
<PanelResizeHandle>
186142
<divclassName="w-1 bg-border h-full hover:bg-border-hover transition-all relative"/>
187143
</PanelResizeHandle>
188-
<PanelclassName="[&>*]:h-full">{content}</Panel>
144+
<PanelclassName="[&>*]:h-full">
145+
<TaskAppstask={task}/>
146+
</Panel>
189147
</PanelGroup>
148+
);
149+
}
150+
151+
return(
152+
<>
153+
<Helmet>
154+
<title>{pageTitle(ellipsizeText(task.prompt,64))}</title>
155+
</Helmet>
156+
157+
<divclassName="flex flex-col h-full">
158+
<TaskTopbartask={task}/>
159+
{content}
160+
</div>
190161
</>
191162
);
192163
};
193164

194165
exportdefaultTaskPage;
195166

167+
typeTaskBuildingWorkspaceProps={task:Task};
168+
169+
constTaskBuildingWorkspace:FC<TaskBuildingWorkspaceProps>=({ task})=>{
170+
const{data:template}=useQuery(
171+
templateQueryOptions(task.workspace.template_id),
172+
);
173+
174+
constbuildLogs=useWorkspaceBuildLogs(task?.workspace.latest_build.id);
175+
176+
// If no template yet, use an indeterminate progress bar.
177+
consttransitionStats=(template&&
178+
getActiveTransitionStats(template,task.workspace))||{
179+
P50:0,
180+
P95:null,
181+
};
182+
183+
constscrollAreaRef=useRef<HTMLDivElement>(null);
184+
// biome-ignore lint/correctness/useExhaustiveDependencies: this effect should run when build logs change
185+
useEffect(()=>{
186+
if(isChromatic()){
187+
return;
188+
}
189+
constscrollAreaEl=scrollAreaRef.current;
190+
constscrollAreaViewportEl=scrollAreaEl?.querySelector<HTMLDivElement>(
191+
"[data-radix-scroll-area-viewport]",
192+
);
193+
if(scrollAreaViewportEl){
194+
scrollAreaViewportEl.scrollTop=scrollAreaViewportEl.scrollHeight;
195+
}
196+
},[buildLogs]);
197+
198+
return(
199+
<sectionclassName="w-full h-full flex justify-center items-center p-6 overflow-y-auto">
200+
<divclassName="flex flex-col gap-6 items-center w-full">
201+
<headerclassName="flex flex-col items-center text-center">
202+
<h3className="m-0 font-medium text-content-primary text-xl">
203+
Starting your workspace
204+
</h3>
205+
<divclassName="text-content-secondary">
206+
Your task will be running in a few moments
207+
</div>
208+
</header>
209+
210+
<divclassName="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+
/>
216+
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>
227+
</div>
228+
</div>
229+
</section>
230+
);
231+
};
232+
196233
exportclassWorkspaceDoesNotHaveAITaskErrorextendsError{
197234
constructor(workspace:Workspace){
198235
super(
@@ -228,3 +265,7 @@ export const data = {
228265
}satisfiesTask;
229266
},
230267
};
268+
269+
constellipsizeText=(text:string,maxLength=80):string=>{
270+
returntext.length<=maxLength ?text :`${text.slice(0,maxLength-3)}...`;
271+
};

‎site/src/pages/TaskPage/TaskSidebar.tsx‎

Lines changed: 0 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,8 @@
11
importtype{WorkspaceApp}from"api/typesGenerated";
2-
import{Button}from"components/Button/Button";
3-
import{
4-
DropdownMenu,
5-
DropdownMenuContent,
6-
DropdownMenuItem,
7-
DropdownMenuTrigger,
8-
}from"components/DropdownMenu/DropdownMenu";
92
import{Spinner}from"components/Spinner/Spinner";
10-
import{
11-
Tooltip,
12-
TooltipContent,
13-
TooltipProvider,
14-
TooltipTrigger,
15-
}from"components/Tooltip/Tooltip";
16-
import{ArrowLeftIcon,EllipsisVerticalIcon}from"lucide-react";
173
importtype{Task}from"modules/tasks/tasks";
184
importtype{FC}from"react";
19-
import{LinkasRouterLink}from"react-router";
205
import{TaskAppIFrame}from"./TaskAppIframe";
21-
import{TaskStatusLink}from"./TaskStatusLink";
226

237
typeTaskSidebarProps={
248
task:Task;
@@ -84,60 +68,6 @@ export const TaskSidebar: FC<TaskSidebarProps> = ({ task }) => {
8468

8569
return(
8670
<asideclassName="flex flex-col h-full shrink-0 w-full">
87-
<headerclassName="border-0 border-b border-solid border-border p-4 pt-0">
88-
<divclassName="flex items-center justify-between py-1">
89-
<TooltipProvider>
90-
<Tooltip>
91-
<TooltipTriggerasChild>
92-
<Buttonsize="icon"variant="subtle"asChildclassName="-ml-2">
93-
<RouterLinkto="/tasks">
94-
<ArrowLeftIcon/>
95-
<spanclassName="sr-only">Back to tasks</span>
96-
</RouterLink>
97-
</Button>
98-
</TooltipTrigger>
99-
<TooltipContent>Back to tasks</TooltipContent>
100-
</Tooltip>
101-
</TooltipProvider>
102-
103-
<DropdownMenu>
104-
<TooltipProvider>
105-
<Tooltip>
106-
<TooltipTriggerasChild>
107-
<DropdownMenuTriggerasChild>
108-
<Buttonsize="icon"variant="subtle"className="-mr-2">
109-
<EllipsisVerticalIcon/>
110-
<spanclassName="sr-only">Settings</span>
111-
</Button>
112-
</DropdownMenuTrigger>
113-
</TooltipTrigger>
114-
<TooltipContent>Settings</TooltipContent>
115-
</Tooltip>
116-
</TooltipProvider>
117-
118-
<DropdownMenuContent>
119-
<DropdownMenuItemasChild>
120-
<RouterLink
121-
to={`/@${task.workspace.owner_name}/${task.workspace.name}`}
122-
>
123-
View workspace
124-
</RouterLink>
125-
</DropdownMenuItem>
126-
</DropdownMenuContent>
127-
</DropdownMenu>
128-
</div>
129-
130-
<h1className="m-0 mt-1 text-base font-medium truncate">
131-
{task.prompt||task.workspace.name}
132-
</h1>
133-
134-
{task.workspace.latest_app_status?.uri&&(
135-
<divclassName="flex items-center gap-2 mt-2 flex-wrap">
136-
<TaskStatusLinkuri={task.workspace.latest_app_status.uri}/>
137-
</div>
138-
)}
139-
</header>
140-
14171
{sidebarAppStatus==="healthy"&&sidebarApp ?(
14272
<TaskAppIFrame
14373
active
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import{Button}from"components/Button/Button";
2+
import{
3+
Tooltip,
4+
TooltipContent,
5+
TooltipProvider,
6+
TooltipTrigger,
7+
}from"components/Tooltip/Tooltip";
8+
import{ArrowLeftIcon}from"lucide-react";
9+
importtype{Task}from"modules/tasks/tasks";
10+
importtype{FC}from"react";
11+
import{LinkasRouterLink}from"react-router";
12+
import{TaskStatusLink}from"./TaskStatusLink";
13+
14+
typeTaskTopbarProps={task:Task};
15+
16+
exportconstTaskTopbar:FC<TaskTopbarProps>=({ task})=>{
17+
return(
18+
<headerclassName="flex items-center px-3 h-14 border-solid border-border border-0 border-b">
19+
<TooltipProvider>
20+
<Tooltip>
21+
<TooltipTriggerasChild>
22+
<Buttonsize="icon"variant="subtle"asChild>
23+
<RouterLinkto="/tasks">
24+
<ArrowLeftIcon/>
25+
<spanclassName="sr-only">Back to tasks</span>
26+
</RouterLink>
27+
</Button>
28+
</TooltipTrigger>
29+
<TooltipContent>Back to tasks</TooltipContent>
30+
</Tooltip>
31+
</TooltipProvider>
32+
33+
<h1className="m-0 text-base font-medium truncate">{task.prompt}</h1>
34+
35+
{task.workspace.latest_app_status?.uri&&(
36+
<divclassName="flex items-center gap-2 flex-wrap ml-4">
37+
<TaskStatusLinkuri={task.workspace.latest_app_status.uri}/>
38+
</div>
39+
)}
40+
41+
<ButtonasChildsize="sm"variant="outline"className="ml-auto">
42+
<RouterLink
43+
to={`/@${task.workspace.owner_name}/${task.workspace.name}`}
44+
>
45+
View workspace
46+
</RouterLink>
47+
</Button>
48+
</header>
49+
);
50+
};

‎site/src/pages/WorkspacePage/Workspace.tsx‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { ResourcesSidebar } from "./ResourcesSidebar";
1717
import{resourceOptionValue,useResourcesNav}from"./useResourcesNav";
1818
import{WorkspaceBuildLogsSection}from"./WorkspaceBuildLogsSection";
1919
import{
20-
ActiveTransition,
20+
getActiveTransitionStats,
2121
WorkspaceBuildProgress,
2222
}from"./WorkspaceBuildProgress";
2323
import{WorkspaceDeletedBanner}from"./WorkspaceDeletedBanner";
@@ -68,7 +68,9 @@ export const Workspace: FC<WorkspaceProps> = ({
6868
constnavigate=useNavigate();
6969

7070
consttransitionStats=
71-
template!==undefined ?ActiveTransition(template,workspace) :undefined;
71+
template!==undefined
72+
?getActiveTransitionStats(template,workspace)
73+
:undefined;
7274

7375
constsidebarOption=useSearchParamsKey({key:"sidebar"});
7476
constsetSidebarOption=(newOption:string)=>{

‎site/src/pages/WorkspacePage/WorkspaceBuildProgress.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ import { type FC, useEffect, useState } from "react";
99

1010
dayjs.extend(duration);
1111

12-
//ActiveTransition gets the build estimate for the workspace,
12+
//getActiveTransitionStats gets the build estimate for the workspace,
1313
// if it is in a transition state.
14-
exportconstActiveTransition=(
14+
exportconstgetActiveTransitionStats=(
1515
template:Template,
1616
workspace:Workspace,
1717
):TransitionStats|undefined=>{

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp