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

Skip to content

Commit e3c77d1

Browse files
committed
use the ai task information from a workspace build on the task page
1 parent 6a03c80 commit e3c77d1

File tree

7 files changed

+211
-153
lines changed

7 files changed

+211
-153
lines changed

site/src/modules/tasks/tasks.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ export const AI_PROMPT_PARAMETER_NAME = "AI Prompt";
44

55
export type Task = {
66
workspace: Workspace;
7-
prompt: string;
7+
prompt?: string;
88
};

site/src/pages/TaskPage/TaskAppIframe.tsx

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ export const TaskAppIFrame: FC<TaskAppIFrameProps> = ({
1515
task,
1616
app,
1717
active,
18-
pathname,
1918
}) => {
2019
const agent = task.workspace.latest_build.resources
2120
.flatMap((r) => r.agents)
@@ -34,9 +33,6 @@ export const TaskAppIFrame: FC<TaskAppIFrameProps> = ({
3433
let href = link.href;
3534
try {
3635
const url = new URL(link.href);
37-
if (pathname) {
38-
url.pathname = pathname;
39-
}
4036
href = url.toString();
4137
} catch (err) {
4238
console.warn(`Failed to parse URL ${link.href} for app ${app.id}`, err);

site/src/pages/TaskPage/TaskApps.tsx

Lines changed: 4 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,10 @@ 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) =>
34+
!!a && a.id !== task.workspace.latest_build.ai_tasks_sidebar_app_id,
35+
);
3436

3537
const embeddedApps = apps.filter((app) => !app.external);
3638
const externalApps = apps.filter((app) => app.external);

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

Lines changed: 115 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { Meta, StoryObj } from "@storybook/react";
22
import { expect, spyOn, within } from "@storybook/test";
3+
import type { WorkspaceApp, WorkspaceResource } from "api/typesGenerated";
34
import {
45
MockFailedWorkspace,
56
MockStartingWorkspace,
@@ -12,11 +13,12 @@ import {
1213
mockApiError,
1314
} from "testHelpers/entities";
1415
import { withProxyProvider } from "testHelpers/storybook";
15-
import TaskPage, { data } from "./TaskPage";
16+
import TaskPage, { data, WorkspaceDoesNotHaveAITaskError } from "./TaskPage";
1617

1718
const meta: Meta<typeof TaskPage> = {
1819
title: "pages/TaskPage",
1920
component: TaskPage,
21+
decorators: [withProxyProvider()],
2022
parameters: {
2123
layout: "fullscreen",
2224
},
@@ -95,6 +97,117 @@ export const TerminatedBuildWithStatus: Story = {
9597
},
9698
};
9799

100+
export const SidebarAppDisabled: Story = {
101+
beforeEach: () => {
102+
spyOn(data, "fetchTask").mockResolvedValue({
103+
prompt: "Create competitors page",
104+
workspace: {
105+
...MockWorkspace,
106+
latest_build: {
107+
...MockWorkspace.latest_build,
108+
has_ai_task: true,
109+
ai_tasks_sidebar_app_id: "claude-code",
110+
resources: mockResources({
111+
health: "disabled",
112+
}),
113+
},
114+
},
115+
});
116+
},
117+
};
118+
119+
export const SidebarAppLoading: Story = {
120+
beforeEach: () => {
121+
spyOn(data, "fetchTask").mockResolvedValue({
122+
prompt: "Create competitors page",
123+
workspace: {
124+
...MockWorkspace,
125+
latest_build: {
126+
...MockWorkspace.latest_build,
127+
has_ai_task: true,
128+
ai_tasks_sidebar_app_id: "claude-code",
129+
resources: mockResources({
130+
health: "initializing",
131+
}),
132+
},
133+
},
134+
});
135+
},
136+
};
137+
138+
export const SidebarAppHealthy: Story = {
139+
beforeEach: () => {
140+
spyOn(data, "fetchTask").mockResolvedValue({
141+
prompt: "Create competitors page",
142+
workspace: {
143+
...MockWorkspace,
144+
latest_build: {
145+
...MockWorkspace.latest_build,
146+
has_ai_task: true,
147+
ai_tasks_sidebar_app_id: "claude-code",
148+
resources: mockResources({
149+
health: "healthy",
150+
}),
151+
},
152+
},
153+
});
154+
},
155+
};
156+
157+
export const BuildNoAITask: Story = {
158+
beforeEach: () => {
159+
spyOn(data, "fetchTask").mockImplementation(() => {
160+
throw new WorkspaceDoesNotHaveAITaskError(MockWorkspace);
161+
});
162+
},
163+
};
164+
165+
const mockResources = (
166+
claudeCodeAppOverrides?: Partial<WorkspaceApp>,
167+
): readonly WorkspaceResource[] => [
168+
{
169+
...MockWorkspaceResource,
170+
agents: [
171+
{
172+
...MockWorkspaceAgent,
173+
apps: [
174+
{
175+
...MockWorkspaceApp,
176+
id: "claude-code",
177+
display_name: "Claude Code",
178+
slug: "claude-code",
179+
icon: "/icon/claude.svg",
180+
statuses: [
181+
MockWorkspaceAppStatus,
182+
{
183+
...MockWorkspaceAppStatus,
184+
id: "2",
185+
message: "Planning changes",
186+
state: "working",
187+
},
188+
],
189+
...claudeCodeAppOverrides,
190+
},
191+
{
192+
...MockWorkspaceApp,
193+
id: "vscode",
194+
slug: "vscode",
195+
display_name: "VS Code Web",
196+
icon: "/icon/code.svg",
197+
},
198+
{
199+
...MockWorkspaceApp,
200+
slug: "zed",
201+
id: "zed",
202+
display_name: "Zed",
203+
icon: "/icon/zed.svg",
204+
},
205+
],
206+
},
207+
],
208+
},
209+
];
210+
98211
export const Active: Story = {
99212
decorators: [withProxyProvider()],
100213
beforeEach: () => {
@@ -104,48 +217,7 @@ export const Active: Story = {
104217
...MockWorkspace,
105218
latest_build: {
106219
...MockWorkspace.latest_build,
107-
resources: [
108-
{
109-
...MockWorkspaceResource,
110-
agents: [
111-
{
112-
...MockWorkspaceAgent,
113-
apps: [
114-
{
115-
...MockWorkspaceApp,
116-
id: "claude-code",
117-
display_name: "Claude Code",
118-
slug: "claude-code",
119-
icon: "/icon/claude.svg",
120-
statuses: [
121-
MockWorkspaceAppStatus,
122-
{
123-
...MockWorkspaceAppStatus,
124-
id: "2",
125-
message: "Planning changes",
126-
state: "working",
127-
},
128-
],
129-
},
130-
{
131-
...MockWorkspaceApp,
132-
id: "vscode",
133-
slug: "vscode",
134-
display_name: "VS Code Web",
135-
icon: "/icon/code.svg",
136-
},
137-
{
138-
...MockWorkspaceApp,
139-
slug: "zed",
140-
id: "zed",
141-
display_name: "Zed",
142-
icon: "/icon/zed.svg",
143-
},
144-
],
145-
},
146-
],
147-
},
148-
],
220+
resources: mockResources(),
149221
},
150222
latest_app_status: {
151223
...MockWorkspaceAppStatus,

site/src/pages/TaskPage/TaskPage.tsx

Lines changed: 17 additions & 5 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";
@@ -177,23 +177,35 @@ 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
);
189205
const prompt = parameters.find(
190206
(p) => p.name === AI_PROMPT_PARAMETER_NAME,
191207
)?.value;
192208

193-
if (!prompt) {
194-
return;
195-
}
196-
197209
return {
198210
workspace,
199211
prompt,

0 commit comments

Comments
 (0)