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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy