@@ -2,13 +2,12 @@ import Skeleton from "@mui/material/Skeleton";
2
2
import { API } from "api/api" ;
3
3
import { getErrorDetail , getErrorMessage } from "api/errors" ;
4
4
import { disabledRefetchOptions } from "api/queries/util" ;
5
- import type { Template } from "api/typesGenerated" ;
5
+ import type { Template , TemplateVersionExternalAuth } from "api/typesGenerated" ;
6
6
import { ErrorAlert } from "components/Alert/ErrorAlert" ;
7
7
import { Avatar } from "components/Avatar/Avatar" ;
8
8
import { AvatarData } from "components/Avatar/AvatarData" ;
9
9
import { AvatarDataSkeleton } from "components/Avatar/AvatarDataSkeleton" ;
10
10
import { Button } from "components/Button/Button" ;
11
- import { Form , FormFields , FormSection } from "components/Form/Form" ;
12
11
import { displayError } from "components/GlobalSnackbar/utils" ;
13
12
import { Margins } from "components/Margins/Margins" ;
14
13
import {
@@ -39,7 +38,7 @@ import {
39
38
40
39
import { useAuthenticated } from "hooks" ;
41
40
import { useExternalAuth } from "hooks/useExternalAuth" ;
42
- import { RotateCcwIcon , SendIcon } from "lucide-react" ;
41
+ import { AlertTriangleIcon , RotateCcwIcon , SendIcon } from "lucide-react" ;
43
42
import { AI_PROMPT_PARAMETER_NAME , type Task } from "modules/tasks/tasks" ;
44
43
import { WorkspaceAppStatus } from "modules/workspaces/WorkspaceAppStatus/WorkspaceAppStatus" ;
45
44
import { generateWorkspaceName } from "modules/workspaces/generateWorkspaceName" ;
@@ -52,10 +51,12 @@ import { pageTitle } from "utils/page";
52
51
import { relativeTime } from "utils/time" ;
53
52
import { ExternalAuthButton } from "../CreateWorkspacePage/ExternalAuthButton" ;
54
53
import { type UserOption , UsersCombobox } from "./UsersCombobox" ;
54
+ import { ExternalImage } from "components/ExternalImage/ExternalImage" ;
55
55
56
56
type TasksFilter = {
57
57
user : UserOption | undefined ;
58
58
} ;
59
+
59
60
const TasksPage : FC = ( ) => {
60
61
const { user, permissions } = useAuthenticated ( ) ;
61
62
const [ filter , setFilter ] = useState < TasksFilter > ( {
@@ -201,21 +202,20 @@ type TaskFormProps = {
201
202
const TaskForm : FC < TaskFormProps > = ( { templates } ) => {
202
203
const { user } = useAuthenticated ( ) ;
203
204
const queryClient = useQueryClient ( ) ;
204
-
205
- const [ templateId , setTemplateId ] = useState < string > ( templates [ 0 ] . id ) ;
206
- const {
207
- externalAuth,
208
- externalAuthPollingState,
209
- startPollingExternalAuth,
210
- isLoadingExternalAuth,
211
- externalAuthError,
212
- } = useExternalAuth (
213
- templates . find ( ( t ) => t . id === templateId ) ?. active_version_id ,
205
+ const [ selectedTemplateId , setSelectedTemplateId ] = useState < string > (
206
+ templates [ 0 ] . id ,
214
207
) ;
215
-
216
- const hasAllRequiredExternalAuth = externalAuth ?. every (
217
- ( auth ) => auth . optional || auth . authenticated ,
208
+ const selectedTemplate = templates . find (
209
+ ( t ) => t . id === selectedTemplateId ,
210
+ ) as Template ;
211
+ const { externalAuth, isLoadingExternalAuth, externalAuthError } =
212
+ useExternalAuth ( selectedTemplate . active_version_id ) ;
213
+ const missedExternalAuth = externalAuth ?. filter (
214
+ ( auth ) => ! auth . optional && ! auth . authenticated ,
218
215
) ;
216
+ const isMissingExternalAuth = missedExternalAuth
217
+ ? missedExternalAuth . length > 0
218
+ : true ;
219
219
220
220
const createTaskMutation = useMutation ( {
221
221
mutationFn : async ( { prompt, templateId } : CreateTaskMutationFnProps ) =>
@@ -235,10 +235,6 @@ const TaskForm: FC<TaskFormProps> = ({ templates }) => {
235
235
const prompt = formData . get ( "prompt" ) as string ;
236
236
const templateID = formData . get ( "templateID" ) as string ;
237
237
238
- if ( ! prompt || ! templateID ) {
239
- return ;
240
- }
241
-
242
238
try {
243
239
await createTaskMutation . mutateAsync ( {
244
240
prompt,
@@ -253,8 +249,12 @@ const TaskForm: FC<TaskFormProps> = ({ templates }) => {
253
249
} ;
254
250
255
251
return (
256
- < Form onSubmit = { onSubmit } aria-label = "Create AI task" >
257
- { Boolean ( externalAuthError ) && < ErrorAlert error = { externalAuthError } /> }
252
+ < form
253
+ onSubmit = { onSubmit }
254
+ aria-label = "Create AI task"
255
+ className = "flex flex-col gap-4"
256
+ >
257
+ { externalAuthError && < ErrorAlert error = { externalAuthError } /> }
258
258
259
259
< fieldset
260
260
className = "border border-border border-solid rounded-lg p-4"
@@ -274,7 +274,7 @@ const TaskForm: FC<TaskFormProps> = ({ templates }) => {
274
274
< div className = "flex items-center justify-between pt-2" >
275
275
< Select
276
276
name = "templateID"
277
- onValueChange = { ( value ) => setTemplateId ( value ) }
277
+ onValueChange = { ( value ) => setSelectedTemplateId ( value ) }
278
278
defaultValue = { templates [ 0 ] . id }
279
279
required
280
280
>
@@ -294,43 +294,61 @@ const TaskForm: FC<TaskFormProps> = ({ templates }) => {
294
294
</ SelectContent >
295
295
</ Select >
296
296
297
- < Button
298
- size = "sm"
299
- type = "submit"
300
- disabled = { ! hasAllRequiredExternalAuth }
301
- >
302
- < Spinner
303
- loading = { createTaskMutation . isPending || isLoadingExternalAuth }
304
- >
305
- < SendIcon />
306
- </ Spinner >
307
- Run task
308
- </ Button >
297
+ < div className = "flex items-center gap-2" >
298
+ { missedExternalAuth && isMissingExternalAuth && (
299
+ < ExternalAuthButtons
300
+ template = { selectedTemplate }
301
+ missedExternalAuth = { missedExternalAuth }
302
+ />
303
+ ) }
304
+
305
+ < Button size = "sm" type = "submit" disabled = { isMissingExternalAuth } >
306
+ < Spinner
307
+ loading = { createTaskMutation . isPending || isLoadingExternalAuth }
308
+ >
309
+ < SendIcon />
310
+ </ Spinner >
311
+ Run task
312
+ </ Button >
313
+ </ div >
309
314
</ div >
310
315
</ fieldset >
316
+ </ form >
317
+ ) ;
318
+ } ;
311
319
312
- { ! hasAllRequiredExternalAuth &&
313
- externalAuth &&
314
- externalAuth . length > 0 && (
315
- < FormSection
316
- title = "External Authentication"
317
- description = "This template uses external services for authentication."
318
- >
319
- < FormFields >
320
- { externalAuth . map ( ( auth ) => (
321
- < ExternalAuthButton
322
- key = { auth . id }
323
- auth = { auth }
324
- isLoading = { externalAuthPollingState === "polling" }
325
- onStartPolling = { startPollingExternalAuth }
326
- displayRetry = { externalAuthPollingState === "abandoned" }
327
- />
328
- ) ) }
329
- </ FormFields >
330
- </ FormSection >
331
- ) }
332
- </ Form >
320
+ type ExternalAuthButtonProps = {
321
+ template : Template ;
322
+ missedExternalAuth : TemplateVersionExternalAuth [ ] ;
323
+ } ;
324
+
325
+ const ExternalAuthButtons : FC < ExternalAuthButtonProps > = ( {
326
+ template,
327
+ missedExternalAuth,
328
+ } ) => {
329
+ const { startPollingExternalAuth, isLoadingExternalAuth } = useExternalAuth (
330
+ template . active_version_id ,
333
331
) ;
332
+
333
+ return missedExternalAuth . map ( ( auth ) => {
334
+ return (
335
+ < Button
336
+ variant = "outline"
337
+ key = { auth . id }
338
+ size = "sm"
339
+ disabled = { isLoadingExternalAuth || auth . authenticated }
340
+ onClick = { ( ) => {
341
+ window . open ( auth . authenticate_url , "_blank" , "width=900,height=600" ) ;
342
+ startPollingExternalAuth ( ) ;
343
+ } }
344
+ >
345
+ < Spinner loading = { isLoadingExternalAuth } >
346
+ < ExternalImage src = { auth . display_icon } />
347
+ </ Spinner >
348
+ Login with { auth . display_name }
349
+ </ Button >
350
+ ) ;
351
+ } ) ;
334
352
} ;
335
353
336
354
type TasksFilterProps = {
0 commit comments