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

feat: add AI Tasks page#18047

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
BrunoQuaresma merged 14 commits intomainfrombq/agentic-tasks
May 27, 2025
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
Show all changes
14 commits
Select commitHold shift + click to select a range
3a5fc9a
WIP
BrunoQuaresmaMay 22, 2025
01c8268
Merge branch 'main' of https://github.com/coder/coder into bq/agentic…
BrunoQuaresmaMay 23, 2025
202cc38
Load AI templates
BrunoQuaresmaMay 24, 2025
4242c92
Load tasks
BrunoQuaresmaMay 26, 2025
455a618
Show AI apps when available
BrunoQuaresmaMay 26, 2025
63fe240
Add base stories
BrunoQuaresmaMay 26, 2025
2c2d624
Create tasks
BrunoQuaresmaMay 27, 2025
0d543d8
Remove console.log
BrunoQuaresmaMay 27, 2025
5ce5b3e
Merge branch 'main' of https://github.com/coder/coder into bq/agentic…
BrunoQuaresmaMay 27, 2025
e706e61
Fix react-query
BrunoQuaresmaMay 27, 2025
e09dae6
FMT
BrunoQuaresmaMay 27, 2025
1f109fe
Merge branch 'main' of https://github.com/coder/coder into bq/agentic…
BrunoQuaresmaMay 27, 2025
0a1b4d6
Use helper to proxy provider
BrunoQuaresmaMay 27, 2025
156c9d1
Add ai-tasks experiment
BrunoQuaresmaMay 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletionscoderd/apidoc/docs.go
View file
Open in desktop

Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.

7 changes: 5 additions & 2 deletionscoderd/apidoc/swagger.json
View file
Open in desktop

Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.

1 change: 1 addition & 0 deletionscodersdk/deployment.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -3346,6 +3346,7 @@ const (
ExperimentDynamicParameters Experiment = "dynamic-parameters" // Enables dynamic parameters when creating a workspace.
ExperimentWorkspacePrebuilds Experiment = "workspace-prebuilds" // Enables the new workspace prebuilds feature.
ExperimentAgenticChat Experiment = "agentic-chat" // Enables the new agentic AI chat feature.
ExperimentAITasks Experiment = "ai-tasks" // Enables the new AI tasks feature.
)

// ExperimentsSafe should include all experiments that are safe for
Expand Down
1 change: 1 addition & 0 deletionsdocs/reference/api/schemas.md
View file
Open in desktop

Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.

1 change: 1 addition & 0 deletionssite/src/api/typesGenerated.ts
View file
Open in desktop

Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.

19 changes: 17 additions & 2 deletionssite/src/modules/dashboard/Navbar/NavbarView.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
import { API } from "api/api";
import { experiments } from "api/queries/experiments";
import type * as TypesGen from "api/typesGenerated";
import { Button } from "components/Button/Button";
import { ExternalImage } from "components/ExternalImage/ExternalImage";
import { CoderIcon } from "components/Icons/CoderIcon";
import type { ProxyContextValue } from "contexts/ProxyContext";
import { useAgenticChat } from "contexts/useAgenticChat";
import { useWebpushNotifications } from "contexts/useWebpushNotifications";
import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata";
import { NotificationsInbox } from "modules/notifications/NotificationsInbox/NotificationsInbox";
import type { FC } from "react";
import { useQuery } from "react-query";
import { NavLink, useLocation } from "react-router-dom";
import { cn } from "utils/cn";
import { DeploymentDropdown } from "./DeploymentDropdown";
Expand DownExpand Up@@ -141,6 +144,8 @@ interface NavItemsProps {
const NavItems: FC<NavItemsProps> = ({ className }) => {
const location = useLocation();
const agenticChat = useAgenticChat();
const { metadata } = useEmbeddedMetadata();
const experimentsQuery = useQuery(experiments(metadata.experiments));

return (
<nav className={cn("flex items-center gap-4 h-full", className)}>
Expand All@@ -163,7 +168,7 @@ const NavItems: FC<NavItemsProps> = ({ className }) => {
>
Templates
</NavLink>
{agenticChat.enabled? (
{agenticChat.enabled&& (
<NavLink
className={({ isActive }) => {
return cn(linkStyles.default, isActive ? linkStyles.active : "");
Expand All@@ -172,7 +177,17 @@ const NavItems: FC<NavItemsProps> = ({ className }) => {
>
Chat
</NavLink>
) : null}
)}
{experimentsQuery.data?.includes("ai-tasks") && (
<NavLink
className={({ isActive }) => {
return cn(linkStyles.default, isActive ? linkStyles.active : "");
}}
to="/tasks"
>
Tasks
</NavLink>
)}
</nav>
);
};
191 changes: 191 additions & 0 deletionssite/src/pages/TasksPage/TasksPage.stories.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
import type { Meta, StoryObj } from "@storybook/react";
import { expect, spyOn, userEvent, within } from "@storybook/test";
import {
MockTemplate,
MockUserOwner,
MockWorkspace,
MockWorkspaceAppStatus,
mockApiError,
} from "testHelpers/entities";
import {
withAuthProvider,
withGlobalSnackbar,
withProxyProvider,
} from "testHelpers/storybook";
import TasksPage, { data } from "./TasksPage";

const meta: Meta<typeof TasksPage> = {
title: "pages/TasksPage",
component: TasksPage,
decorators: [withAuthProvider],
parameters: {
user: MockUserOwner,
},
};

export default meta;
type Story = StoryObj<typeof TasksPage>;

export const LoadingAITemplates: Story = {
beforeEach: () => {
spyOn(data, "fetchAITemplates").mockImplementation(
() => new Promise((res) => 1000 * 60 * 60),
);
},
};

export const LoadingAITemplatesError: Story = {
beforeEach: () => {
spyOn(data, "fetchAITemplates").mockRejectedValue(
mockApiError({
message: "Failed to load AI templates",
detail: "You don't have permission to access this resource.",
}),
);
},
};

export const EmptyAITemplates: Story = {
beforeEach: () => {
spyOn(data, "fetchAITemplates").mockResolvedValue([]);
},
};

export const LoadingTasks: Story = {
beforeEach: () => {
spyOn(data, "fetchAITemplates").mockResolvedValue([MockTemplate]);
spyOn(data, "fetchTasks").mockImplementation(
() => new Promise((res) => 1000 * 60 * 60),
);
},
play: async ({ canvasElement, step }) => {
const canvas = within(canvasElement);

await step("Select the first AI template", async () => {
const combobox = await canvas.findByRole("combobox");
expect(combobox).toHaveTextContent(MockTemplate.display_name);
});
},
};

export const LoadingTasksError: Story = {
beforeEach: () => {
spyOn(data, "fetchAITemplates").mockResolvedValue([MockTemplate]);
spyOn(data, "fetchTasks").mockRejectedValue(
mockApiError({
message: "Failed to load tasks",
}),
);
},
};

export const EmptyTasks: Story = {
beforeEach: () => {
spyOn(data, "fetchAITemplates").mockResolvedValue([MockTemplate]);
spyOn(data, "fetchTasks").mockResolvedValue([]);
},
};

export const LoadedTasks: Story = {
decorators: [withProxyProvider()],
beforeEach: () => {
spyOn(data, "fetchAITemplates").mockResolvedValue([MockTemplate]);
spyOn(data, "fetchTasks").mockResolvedValue(MockTasks);
},
};

export const CreateTaskSuccessfully: Story = {
decorators: [withProxyProvider()],
beforeEach: () => {
spyOn(data, "fetchAITemplates").mockResolvedValue([MockTemplate]);
spyOn(data, "fetchTasks").mockResolvedValue(MockTasks);
spyOn(data, "createTask").mockImplementation((prompt: string) => {
return Promise.resolve({
prompt,
workspace: {
...MockWorkspace,
latest_app_status: {
...MockWorkspaceAppStatus,
message: "Task created successfully!",
},
},
});
});
},
play: async ({ canvasElement, step }) => {
const canvas = within(canvasElement);

await step("Run task", async () => {
const prompt = await canvas.findByLabelText(/prompt/i);
await userEvent.type(prompt, "Create a new task");
const submitButton = canvas.getByRole("button", { name: /run task/i });
await userEvent.click(submitButton);
});

await step("Verify task in the table", async () => {
await canvas.findByRole("row", {
name: /create a new task/i,
});
});
},
};

export const CreateTaskError: Story = {
decorators: [withProxyProvider(), withGlobalSnackbar],
beforeEach: () => {
spyOn(data, "fetchAITemplates").mockResolvedValue([MockTemplate]);
spyOn(data, "fetchTasks").mockResolvedValue(MockTasks);
spyOn(data, "createTask").mockRejectedValue(
mockApiError({
message: "Failed to create task",
detail: "You don't have permission to create tasks.",
}),
);
},
play: async ({ canvasElement, step }) => {
const canvas = within(canvasElement);

await step("Run task", async () => {
const prompt = await canvas.findByLabelText(/prompt/i);
await userEvent.type(prompt, "Create a new task");
const submitButton = canvas.getByRole("button", { name: /run task/i });
await userEvent.click(submitButton);
});

await step("Verify error", async () => {
await canvas.findByText(/failed to create task/i);
});
},
};

const MockTasks = [
{
workspace: {
...MockWorkspace,
latest_app_status: MockWorkspaceAppStatus,
},
prompt: "Create competitors page",
},
{
workspace: {
...MockWorkspace,
id: "workspace-2",
latest_app_status: {
...MockWorkspaceAppStatus,
message: "Avatar size fixed!",
},
},
prompt: "Fix user avatar size",
},
{
workspace: {
...MockWorkspace,
id: "workspace-3",
latest_app_status: {
...MockWorkspaceAppStatus,
message: "Accessibility issues fixed!",
},
},
prompt: "Fix accessibility issues",
},
];
Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp