Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 21489b0

Browse files
committed
use the ai task information from a workspace build on the task page
1 parent dc24922 commit 21489b0

File tree

5 files changed

+231
-156
lines changed

5 files changed

+231
-156
lines changed

site/src/pages/TaskPage/TaskApps.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import { type FC, useState } from "react";
1515
import { Link as RouterLink } from "react-router-dom";
1616
import { cn } from "utils/cn";
1717
import { TaskAppIFrame } from "./TaskAppIframe";
18-
import { AI_APP_CHAT_SLUG } from "./constants";
1918

2019
type TaskAppsProps = {
2120
task: Task;
@@ -30,7 +29,9 @@ export const TaskApps: FC<TaskAppsProps> = ({ task }) => {
3029
// it here
3130
const apps = agents
3231
.flatMap((a) => a?.apps)
33-
.filter((a) => !!a && a.slug !== AI_APP_CHAT_SLUG);
32+
.filter(
33+
(a) => !!a && a.id !== task.workspace.latest_build.ai_task_sidebar_app_id,
34+
);
3435

3536
const embeddedApps = apps.filter((app) => !app.external);
3637
const externalApps = apps.filter((app) => app.external);

site/src/pages/TaskPage/TaskPage.stories.tsx

Lines changed: 133 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import type { Meta, StoryObj } from "@storybook/react";
22
import { expect, spyOn, within } from "@storybook/test";
3-
import type { Workspace, WorkspaceApp } from "api/typesGenerated";
3+
import type {
4+
Workspace,
5+
WorkspaceApp,
6+
WorkspaceResource,
7+
} from "api/typesGenerated";
48
import {
59
MockFailedWorkspace,
610
MockStartingWorkspace,
@@ -13,11 +17,12 @@ import {
1317
mockApiError,
1418
} from "testHelpers/entities";
1519
import { withProxyProvider } from "testHelpers/storybook";
16-
import TaskPage, { data } from "./TaskPage";
20+
import TaskPage, { data, WorkspaceDoesNotHaveAITaskError } from "./TaskPage";
1721

1822
const meta: Meta<typeof TaskPage> = {
1923
title: "pages/TaskPage",
2024
component: TaskPage,
25+
decorators: [withProxyProvider()],
2126
parameters: {
2227
layout: "fullscreen",
2328
},
@@ -96,61 +101,142 @@ export const TerminatedBuildWithStatus: Story = {
96101
},
97102
};
98103

99-
function activeWorkspace(apps: WorkspaceApp[]): Workspace {
104+
export const SidebarAppDisabled: Story = {
105+
beforeEach: () => {
106+
spyOn(data, "fetchTask").mockResolvedValue({
107+
prompt: "Create competitors page",
108+
workspace: {
109+
...MockWorkspace,
110+
latest_build: {
111+
...MockWorkspace.latest_build,
112+
has_ai_task: true,
113+
ai_task_sidebar_app_id: "claude-code",
114+
resources: mockResources({
115+
claudeCodeAppOverrides: {
116+
health: "disabled",
117+
},
118+
}),
119+
},
120+
},
121+
});
122+
},
123+
};
124+
125+
export const SidebarAppLoading: Story = {
126+
beforeEach: () => {
127+
spyOn(data, "fetchTask").mockResolvedValue({
128+
prompt: "Create competitors page",
129+
workspace: {
130+
...MockWorkspace,
131+
latest_build: {
132+
...MockWorkspace.latest_build,
133+
has_ai_task: true,
134+
ai_task_sidebar_app_id: "claude-code",
135+
resources: mockResources({
136+
claudeCodeAppOverrides: {
137+
health: "initializing",
138+
},
139+
}),
140+
},
141+
},
142+
});
143+
},
144+
};
145+
146+
export const SidebarAppHealthy: Story = {
147+
beforeEach: () => {
148+
spyOn(data, "fetchTask").mockResolvedValue({
149+
prompt: "Create competitors page",
150+
workspace: {
151+
...MockWorkspace,
152+
latest_build: {
153+
...MockWorkspace.latest_build,
154+
has_ai_task: true,
155+
ai_task_sidebar_app_id: "claude-code",
156+
resources: mockResources({
157+
claudeCodeAppOverrides: {
158+
health: "healthy",
159+
},
160+
}),
161+
},
162+
},
163+
});
164+
},
165+
};
166+
167+
export const BuildNoAITask: Story = {
168+
beforeEach: () => {
169+
spyOn(data, "fetchTask").mockImplementation(() => {
170+
throw new WorkspaceDoesNotHaveAITaskError(MockWorkspace);
171+
});
172+
},
173+
};
174+
175+
interface MockResourcesProps {
176+
apps?: WorkspaceApp[];
177+
claudeCodeAppOverrides?: Partial<WorkspaceApp>;
178+
}
179+
180+
const mockResources = (
181+
props?: MockResourcesProps,
182+
): readonly WorkspaceResource[] => [
183+
{
184+
...MockWorkspaceResource,
185+
agents: [
186+
{
187+
...MockWorkspaceAgent,
188+
apps: [
189+
...(props?.apps ?? []),
190+
{
191+
...MockWorkspaceApp,
192+
id: "claude-code",
193+
display_name: "Claude Code",
194+
slug: "claude-code",
195+
icon: "/icon/claude.svg",
196+
statuses: [
197+
MockWorkspaceAppStatus,
198+
{
199+
...MockWorkspaceAppStatus,
200+
id: "2",
201+
message: "Planning changes",
202+
state: "working",
203+
},
204+
],
205+
...(props?.claudeCodeAppOverrides ?? {}),
206+
},
207+
{
208+
...MockWorkspaceApp,
209+
id: "vscode",
210+
slug: "vscode",
211+
display_name: "VS Code Web",
212+
icon: "/icon/code.svg",
213+
},
214+
{
215+
...MockWorkspaceApp,
216+
slug: "zed",
217+
id: "zed",
218+
display_name: "Zed",
219+
icon: "/icon/zed.svg",
220+
},
221+
],
222+
},
223+
],
224+
},
225+
];
226+
227+
const activeWorkspace = (apps: WorkspaceApp[]): Workspace => {
100228
return {
101229
...MockWorkspace,
102230
latest_build: {
103231
...MockWorkspace.latest_build,
104-
resources: [
105-
{
106-
...MockWorkspaceResource,
107-
agents: [
108-
{
109-
...MockWorkspaceAgent,
110-
apps: [
111-
...apps,
112-
{
113-
...MockWorkspaceApp,
114-
id: "claude-code",
115-
display_name: "Claude Code",
116-
slug: "claude-code",
117-
icon: "/icon/claude.svg",
118-
statuses: [
119-
MockWorkspaceAppStatus,
120-
{
121-
...MockWorkspaceAppStatus,
122-
id: "2",
123-
message: "Planning changes",
124-
state: "working",
125-
},
126-
],
127-
},
128-
{
129-
...MockWorkspaceApp,
130-
id: "vscode",
131-
slug: "vscode",
132-
display_name: "VS Code Web",
133-
icon: "/icon/code.svg",
134-
},
135-
{
136-
...MockWorkspaceApp,
137-
slug: "zed",
138-
id: "zed",
139-
display_name: "Zed",
140-
icon: "/icon/zed.svg",
141-
},
142-
],
143-
},
144-
],
145-
},
146-
],
232+
resources: mockResources({ apps }),
147233
},
148234
latest_app_status: {
149235
...MockWorkspaceAppStatus,
150236
app_id: "claude-code",
151237
},
152238
};
153-
}
239+
};
154240

155241
export const Active: Story = {
156242
decorators: [withProxyProvider()],

site/src/pages/TaskPage/TaskPage.tsx

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { API } from "api/api";
22
import { getErrorDetail, getErrorMessage } from "api/errors";
3-
import type { WorkspaceStatus } from "api/typesGenerated";
3+
import type { Workspace, WorkspaceStatus } from "api/typesGenerated";
44
import { Button } from "components/Button/Button";
55
import { Loader } from "components/Loader/Loader";
66
import { Margins } from "components/Margins/Margins";
@@ -164,7 +164,7 @@ const TaskPage = () => {
164164
return (
165165
<>
166166
<Helmet>
167-
<title>{pageTitle(ellipsizeText(task.prompt, 64)!)}</title>
167+
<title>{pageTitle(ellipsizeText(task.prompt, 64) ?? "Task")}</title>
168168
</Helmet>
169169

170170
<div className="h-full flex justify-stretch">
@@ -177,22 +177,34 @@ const TaskPage = () => {
177177

178178
export default TaskPage;
179179

180+
export class WorkspaceDoesNotHaveAITaskError extends Error {
181+
constructor(workspace: Workspace) {
182+
super(
183+
`Workspace ${workspace.owner_name}/${workspace.name} is not running an AI task`,
184+
);
185+
this.name = "WorkspaceDoesNotHaveAITaskError";
186+
}
187+
}
188+
180189
export const data = {
181190
fetchTask: async (workspaceOwnerUsername: string, workspaceName: string) => {
182191
const workspace = await API.getWorkspaceByOwnerAndName(
183192
workspaceOwnerUsername,
184193
workspaceName,
185194
);
195+
if (
196+
workspace.latest_build.job.completed_at &&
197+
!workspace.latest_build.has_ai_task
198+
) {
199+
throw new WorkspaceDoesNotHaveAITaskError(workspace);
200+
}
201+
186202
const parameters = await API.getWorkspaceBuildParameters(
187203
workspace.latest_build.id,
188204
);
189-
const prompt = parameters.find(
190-
(p) => p.name === AI_PROMPT_PARAMETER_NAME,
191-
)?.value;
192-
193-
if (!prompt) {
194-
return;
195-
}
205+
const prompt =
206+
parameters.find((p) => p.name === AI_PROMPT_PARAMETER_NAME)?.value ??
207+
"Unknown prompt";
196208

197209
return {
198210
workspace,

0 commit comments

Comments
 (0)