@@ -2,13 +2,12 @@ import Skeleton from "@mui/material/Skeleton";
22import { API } from "api/api" ;
33import { getErrorDetail , getErrorMessage } from "api/errors" ;
44import { disabledRefetchOptions } from "api/queries/util" ;
5- import type { Template } from "api/typesGenerated" ;
5+ import type { Template , TemplateVersionExternalAuth } from "api/typesGenerated" ;
66import { ErrorAlert } from "components/Alert/ErrorAlert" ;
77import { Avatar } from "components/Avatar/Avatar" ;
88import { AvatarData } from "components/Avatar/AvatarData" ;
99import { AvatarDataSkeleton } from "components/Avatar/AvatarDataSkeleton" ;
1010import { Button } from "components/Button/Button" ;
11- import { Form , FormFields , FormSection } from "components/Form/Form" ;
1211import { displayError } from "components/GlobalSnackbar/utils" ;
1312import { Margins } from "components/Margins/Margins" ;
1413import {
@@ -37,9 +36,16 @@ import {
3736 TableRowSkeleton ,
3837} from "components/TableLoader/TableLoader" ;
3938
39+ import { ExternalImage } from "components/ExternalImage/ExternalImage" ;
40+ import {
41+ Tooltip ,
42+ TooltipContent ,
43+ TooltipProvider ,
44+ TooltipTrigger ,
45+ } from "components/Tooltip/Tooltip" ;
4046import { useAuthenticated } from "hooks" ;
4147import { useExternalAuth } from "hooks/useExternalAuth" ;
42- import { RotateCcwIcon , SendIcon } from "lucide-react" ;
48+ import { RedoIcon , RotateCcwIcon , SendIcon } from "lucide-react" ;
4349import { AI_PROMPT_PARAMETER_NAME , type Task } from "modules/tasks/tasks" ;
4450import { WorkspaceAppStatus } from "modules/workspaces/WorkspaceAppStatus/WorkspaceAppStatus" ;
4551import { generateWorkspaceName } from "modules/workspaces/generateWorkspaceName" ;
@@ -50,12 +56,12 @@ import { Link as RouterLink } from "react-router-dom";
5056import TextareaAutosize from "react-textarea-autosize" ;
5157import { pageTitle } from "utils/page" ;
5258import { relativeTime } from "utils/time" ;
53- import { ExternalAuthButton } from "../CreateWorkspacePage/ExternalAuthButton" ;
5459import { type UserOption , UsersCombobox } from "./UsersCombobox" ;
5560
5661type TasksFilter = {
5762 user : UserOption | undefined ;
5863} ;
64+
5965const TasksPage : FC = ( ) => {
6066 const { user, permissions } = useAuthenticated ( ) ;
6167 const [ filter , setFilter ] = useState < TasksFilter > ( {
@@ -201,21 +207,24 @@ type TaskFormProps = {
201207const TaskForm : FC < TaskFormProps > = ( { templates } ) => {
202208 const { user } = useAuthenticated ( ) ;
203209 const queryClient = useQueryClient ( ) ;
204-
205- const [ templateId , setTemplateId ] = useState < string > ( templates [ 0 ] . id ) ;
210+ const [ selectedTemplateId , setSelectedTemplateId ] = useState < string > (
211+ templates [ 0 ] . id ,
212+ ) ;
213+ const selectedTemplate = templates . find (
214+ ( t ) => t . id === selectedTemplateId ,
215+ ) as Template ;
206216 const {
207217 externalAuth,
208- externalAuthPollingState,
209- startPollingExternalAuth,
210- isLoadingExternalAuth,
211218 externalAuthError,
212- } = useExternalAuth (
213- templates . find ( ( t ) => t . id === templateId ) ?. active_version_id ,
214- ) ;
215-
216- const hasAllRequiredExternalAuth = externalAuth ?. every (
217- ( auth ) => auth . optional || auth . authenticated ,
219+ isPollingExternalAuth,
220+ isLoadingExternalAuth,
221+ } = useExternalAuth ( selectedTemplate . active_version_id ) ;
222+ const missedExternalAuth = externalAuth ?. filter (
223+ ( auth ) => ! auth . optional && ! auth . authenticated ,
218224 ) ;
225+ const isMissingExternalAuth = missedExternalAuth
226+ ? missedExternalAuth . length > 0
227+ : true ;
219228
220229 const createTaskMutation = useMutation ( {
221230 mutationFn : async ( { prompt, templateId } : CreateTaskMutationFnProps ) =>
@@ -235,10 +244,6 @@ const TaskForm: FC<TaskFormProps> = ({ templates }) => {
235244 const prompt = formData . get ( "prompt" ) as string ;
236245 const templateID = formData . get ( "templateID" ) as string ;
237246
238- if ( ! prompt || ! templateID ) {
239- return ;
240- }
241-
242247 try {
243248 await createTaskMutation . mutateAsync ( {
244249 prompt,
@@ -253,8 +258,12 @@ const TaskForm: FC<TaskFormProps> = ({ templates }) => {
253258 } ;
254259
255260 return (
256- < Form onSubmit = { onSubmit } aria-label = "Create AI task" >
257- { Boolean ( externalAuthError ) && < ErrorAlert error = { externalAuthError } /> }
261+ < form
262+ onSubmit = { onSubmit }
263+ aria-label = "Create AI task"
264+ className = "flex flex-col gap-4"
265+ >
266+ { externalAuthError && < ErrorAlert error = { externalAuthError } /> }
258267
259268 < fieldset
260269 className = "border border-border border-solid rounded-lg p-4"
@@ -274,7 +283,7 @@ const TaskForm: FC<TaskFormProps> = ({ templates }) => {
274283 < div className = "flex items-center justify-between pt-2" >
275284 < Select
276285 name = "templateID"
277- onValueChange = { ( value ) => setTemplateId ( value ) }
286+ onValueChange = { ( value ) => setSelectedTemplateId ( value ) }
278287 defaultValue = { templates [ 0 ] . id }
279288 required
280289 >
@@ -294,43 +303,93 @@ const TaskForm: FC<TaskFormProps> = ({ templates }) => {
294303 </ SelectContent >
295304 </ Select >
296305
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 >
306+ < div className = "flex items-center gap-2" >
307+ { missedExternalAuth && (
308+ < ExternalAuthButtons
309+ template = { selectedTemplate }
310+ missedExternalAuth = { missedExternalAuth }
311+ />
312+ ) }
313+
314+ < Button size = "sm" type = "submit" disabled = { isMissingExternalAuth } >
315+ < Spinner
316+ loading = {
317+ isLoadingExternalAuth ||
318+ isPollingExternalAuth ||
319+ createTaskMutation . isPending
320+ }
321+ >
322+ < SendIcon />
323+ </ Spinner >
324+ Run task
325+ </ Button >
326+ </ div >
309327 </ div >
310328 </ fieldset >
329+ </ form >
330+ ) ;
331+ } ;
311332
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 >
333+ type ExternalAuthButtonProps = {
334+ template : Template ;
335+ missedExternalAuth : TemplateVersionExternalAuth [ ] ;
336+ } ;
337+
338+ const ExternalAuthButtons : FC < ExternalAuthButtonProps > = ( {
339+ template,
340+ missedExternalAuth,
341+ } ) => {
342+ const {
343+ startPollingExternalAuth,
344+ isPollingExternalAuth,
345+ externalAuthPollingState,
346+ } = useExternalAuth ( template . active_version_id ) ;
347+ const shouldRetry = externalAuthPollingState === "abandoned" ;
348+
349+ return missedExternalAuth . map ( ( auth ) => {
350+ return (
351+ < div className = "flex items-center gap-2" key = { auth . id } >
352+ < Button
353+ variant = "outline"
354+ size = "sm"
355+ disabled = { isPollingExternalAuth || auth . authenticated }
356+ onClick = { ( ) => {
357+ window . open (
358+ auth . authenticate_url ,
359+ "_blank" ,
360+ "width=900,height=600" ,
361+ ) ;
362+ startPollingExternalAuth ( ) ;
363+ } }
364+ >
365+ < Spinner loading = { isPollingExternalAuth } >
366+ < ExternalImage src = { auth . display_icon } />
367+ </ Spinner >
368+ Connect to { auth . display_name }
369+ </ Button >
370+
371+ { shouldRetry && ! auth . authenticated && (
372+ < TooltipProvider >
373+ < Tooltip delayDuration = { 100 } >
374+ < TooltipTrigger asChild >
375+ < Button
376+ variant = "outline"
377+ size = "icon"
378+ onClick = { startPollingExternalAuth }
379+ >
380+ < RedoIcon />
381+ < span className = "sr-only" > Refresh external auth</ span >
382+ </ Button >
383+ </ TooltipTrigger >
384+ < TooltipContent >
385+ Retry connecting to { auth . display_name }
386+ </ TooltipContent >
387+ </ Tooltip >
388+ </ TooltipProvider >
331389 ) }
332- </ Form >
333- ) ;
390+ </ div >
391+ ) ;
392+ } ) ;
334393} ;
335394
336395type TasksFilterProps = {
0 commit comments