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

Commit9827c97

Browse files
feat: add AI Tasks page (#18047)
**Preview:**<img width="1624" alt="Screenshot 2025-05-26 at 21 25 04"src="https://github.com/user-attachments/assets/2a51915d-2527-4467-bf99-1f2d876b953b"/>
1 parentce134bc commit9827c97

File tree

9 files changed

+763
-6
lines changed

9 files changed

+763
-6
lines changed

‎coderd/apidoc/docs.go

Lines changed: 5 additions & 2 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎coderd/apidoc/swagger.json

Lines changed: 5 additions & 2 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎codersdk/deployment.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3346,6 +3346,7 @@ const (
33463346
ExperimentDynamicParametersExperiment="dynamic-parameters"// Enables dynamic parameters when creating a workspace.
33473347
ExperimentWorkspacePrebuildsExperiment="workspace-prebuilds"// Enables the new workspace prebuilds feature.
33483348
ExperimentAgenticChatExperiment="agentic-chat"// Enables the new agentic AI chat feature.
3349+
ExperimentAITasksExperiment="ai-tasks"// Enables the new AI tasks feature.
33493350
)
33503351

33513352
// ExperimentsSafe should include all experiments that are safe for

‎docs/reference/api/schemas.md

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎site/src/api/typesGenerated.ts

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎site/src/modules/dashboard/Navbar/NavbarView.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
import{API}from"api/api";
2+
import{experiments}from"api/queries/experiments";
23
importtype*asTypesGenfrom"api/typesGenerated";
34
import{Button}from"components/Button/Button";
45
import{ExternalImage}from"components/ExternalImage/ExternalImage";
56
import{CoderIcon}from"components/Icons/CoderIcon";
67
importtype{ProxyContextValue}from"contexts/ProxyContext";
78
import{useAgenticChat}from"contexts/useAgenticChat";
89
import{useWebpushNotifications}from"contexts/useWebpushNotifications";
10+
import{useEmbeddedMetadata}from"hooks/useEmbeddedMetadata";
911
import{NotificationsInbox}from"modules/notifications/NotificationsInbox/NotificationsInbox";
1012
importtype{FC}from"react";
13+
import{useQuery}from"react-query";
1114
import{NavLink,useLocation}from"react-router-dom";
1215
import{cn}from"utils/cn";
1316
import{DeploymentDropdown}from"./DeploymentDropdown";
@@ -141,6 +144,8 @@ interface NavItemsProps {
141144
constNavItems:FC<NavItemsProps>=({ className})=>{
142145
constlocation=useLocation();
143146
constagenticChat=useAgenticChat();
147+
const{ metadata}=useEmbeddedMetadata();
148+
constexperimentsQuery=useQuery(experiments(metadata.experiments));
144149

145150
return(
146151
<navclassName={cn("flex items-center gap-4 h-full",className)}>
@@ -163,7 +168,7 @@ const NavItems: FC<NavItemsProps> = ({ className }) => {
163168
>
164169
Templates
165170
</NavLink>
166-
{agenticChat.enabled?(
171+
{agenticChat.enabled&&(
167172
<NavLink
168173
className={({ isActive})=>{
169174
returncn(linkStyles.default,isActive ?linkStyles.active :"");
@@ -172,7 +177,17 @@ const NavItems: FC<NavItemsProps> = ({ className }) => {
172177
>
173178
Chat
174179
</NavLink>
175-
) :null}
180+
)}
181+
{experimentsQuery.data?.includes("ai-tasks")&&(
182+
<NavLink
183+
className={({ isActive})=>{
184+
returncn(linkStyles.default,isActive ?linkStyles.active :"");
185+
}}
186+
to="/tasks"
187+
>
188+
Tasks
189+
</NavLink>
190+
)}
176191
</nav>
177192
);
178193
};
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
importtype{Meta,StoryObj}from"@storybook/react";
2+
import{expect,spyOn,userEvent,within}from"@storybook/test";
3+
import{
4+
MockTemplate,
5+
MockUserOwner,
6+
MockWorkspace,
7+
MockWorkspaceAppStatus,
8+
mockApiError,
9+
}from"testHelpers/entities";
10+
import{
11+
withAuthProvider,
12+
withGlobalSnackbar,
13+
withProxyProvider,
14+
}from"testHelpers/storybook";
15+
importTasksPage,{data}from"./TasksPage";
16+
17+
constmeta:Meta<typeofTasksPage>={
18+
title:"pages/TasksPage",
19+
component:TasksPage,
20+
decorators:[withAuthProvider],
21+
parameters:{
22+
user:MockUserOwner,
23+
},
24+
};
25+
26+
exportdefaultmeta;
27+
typeStory=StoryObj<typeofTasksPage>;
28+
29+
exportconstLoadingAITemplates:Story={
30+
beforeEach:()=>{
31+
spyOn(data,"fetchAITemplates").mockImplementation(
32+
()=>newPromise((res)=>1000*60*60),
33+
);
34+
},
35+
};
36+
37+
exportconstLoadingAITemplatesError:Story={
38+
beforeEach:()=>{
39+
spyOn(data,"fetchAITemplates").mockRejectedValue(
40+
mockApiError({
41+
message:"Failed to load AI templates",
42+
detail:"You don't have permission to access this resource.",
43+
}),
44+
);
45+
},
46+
};
47+
48+
exportconstEmptyAITemplates:Story={
49+
beforeEach:()=>{
50+
spyOn(data,"fetchAITemplates").mockResolvedValue([]);
51+
},
52+
};
53+
54+
exportconstLoadingTasks:Story={
55+
beforeEach:()=>{
56+
spyOn(data,"fetchAITemplates").mockResolvedValue([MockTemplate]);
57+
spyOn(data,"fetchTasks").mockImplementation(
58+
()=>newPromise((res)=>1000*60*60),
59+
);
60+
},
61+
play:async({ canvasElement, step})=>{
62+
constcanvas=within(canvasElement);
63+
64+
awaitstep("Select the first AI template",async()=>{
65+
constcombobox=awaitcanvas.findByRole("combobox");
66+
expect(combobox).toHaveTextContent(MockTemplate.display_name);
67+
});
68+
},
69+
};
70+
71+
exportconstLoadingTasksError:Story={
72+
beforeEach:()=>{
73+
spyOn(data,"fetchAITemplates").mockResolvedValue([MockTemplate]);
74+
spyOn(data,"fetchTasks").mockRejectedValue(
75+
mockApiError({
76+
message:"Failed to load tasks",
77+
}),
78+
);
79+
},
80+
};
81+
82+
exportconstEmptyTasks:Story={
83+
beforeEach:()=>{
84+
spyOn(data,"fetchAITemplates").mockResolvedValue([MockTemplate]);
85+
spyOn(data,"fetchTasks").mockResolvedValue([]);
86+
},
87+
};
88+
89+
exportconstLoadedTasks:Story={
90+
decorators:[withProxyProvider()],
91+
beforeEach:()=>{
92+
spyOn(data,"fetchAITemplates").mockResolvedValue([MockTemplate]);
93+
spyOn(data,"fetchTasks").mockResolvedValue(MockTasks);
94+
},
95+
};
96+
97+
exportconstCreateTaskSuccessfully:Story={
98+
decorators:[withProxyProvider()],
99+
beforeEach:()=>{
100+
spyOn(data,"fetchAITemplates").mockResolvedValue([MockTemplate]);
101+
spyOn(data,"fetchTasks").mockResolvedValue(MockTasks);
102+
spyOn(data,"createTask").mockImplementation((prompt:string)=>{
103+
returnPromise.resolve({
104+
prompt,
105+
workspace:{
106+
...MockWorkspace,
107+
latest_app_status:{
108+
...MockWorkspaceAppStatus,
109+
message:"Task created successfully!",
110+
},
111+
},
112+
});
113+
});
114+
},
115+
play:async({ canvasElement, step})=>{
116+
constcanvas=within(canvasElement);
117+
118+
awaitstep("Run task",async()=>{
119+
constprompt=awaitcanvas.findByLabelText(/prompt/i);
120+
awaituserEvent.type(prompt,"Create a new task");
121+
constsubmitButton=canvas.getByRole("button",{name:/runtask/i});
122+
awaituserEvent.click(submitButton);
123+
});
124+
125+
awaitstep("Verify task in the table",async()=>{
126+
awaitcanvas.findByRole("row",{
127+
name:/createanewtask/i,
128+
});
129+
});
130+
},
131+
};
132+
133+
exportconstCreateTaskError:Story={
134+
decorators:[withProxyProvider(),withGlobalSnackbar],
135+
beforeEach:()=>{
136+
spyOn(data,"fetchAITemplates").mockResolvedValue([MockTemplate]);
137+
spyOn(data,"fetchTasks").mockResolvedValue(MockTasks);
138+
spyOn(data,"createTask").mockRejectedValue(
139+
mockApiError({
140+
message:"Failed to create task",
141+
detail:"You don't have permission to create tasks.",
142+
}),
143+
);
144+
},
145+
play:async({ canvasElement, step})=>{
146+
constcanvas=within(canvasElement);
147+
148+
awaitstep("Run task",async()=>{
149+
constprompt=awaitcanvas.findByLabelText(/prompt/i);
150+
awaituserEvent.type(prompt,"Create a new task");
151+
constsubmitButton=canvas.getByRole("button",{name:/runtask/i});
152+
awaituserEvent.click(submitButton);
153+
});
154+
155+
awaitstep("Verify error",async()=>{
156+
awaitcanvas.findByText(/failedtocreatetask/i);
157+
});
158+
},
159+
};
160+
161+
constMockTasks=[
162+
{
163+
workspace:{
164+
...MockWorkspace,
165+
latest_app_status:MockWorkspaceAppStatus,
166+
},
167+
prompt:"Create competitors page",
168+
},
169+
{
170+
workspace:{
171+
...MockWorkspace,
172+
id:"workspace-2",
173+
latest_app_status:{
174+
...MockWorkspaceAppStatus,
175+
message:"Avatar size fixed!",
176+
},
177+
},
178+
prompt:"Fix user avatar size",
179+
},
180+
{
181+
workspace:{
182+
...MockWorkspace,
183+
id:"workspace-3",
184+
latest_app_status:{
185+
...MockWorkspaceAppStatus,
186+
message:"Accessibility issues fixed!",
187+
},
188+
},
189+
prompt:"Fix accessibility issues",
190+
},
191+
];

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp