1- import type { WorkspaceApp } from "api/typesGenerated" ;
1+ import type { WorkspaceAgent , WorkspaceApp } from "api/typesGenerated" ;
22import { Button } from "components/Button/Button" ;
33import {
44DropdownMenu ,
@@ -8,13 +8,15 @@ import {
88} from "components/DropdownMenu/DropdownMenu" ;
99import { ExternalImage } from "components/ExternalImage/ExternalImage" ;
1010import { InfoTooltip } from "components/InfoTooltip/InfoTooltip" ;
11+ import { Link } from "components/Link/Link" ;
1112import { ChevronDownIcon , LayoutGridIcon } from "lucide-react" ;
1213import { useAppLink } from "modules/apps/useAppLink" ;
1314import type { Task } from "modules/tasks/tasks" ;
1415import type React from "react" ;
1516import { type FC , useState } from "react" ;
1617import { Link as RouterLink } from "react-router-dom" ;
1718import { cn } from "utils/cn" ;
19+ import { docs } from "utils/docs" ;
1820import { TaskAppIFrame } from "./TaskAppIframe" ;
1921
2022type TaskAppsProps = {
@@ -37,25 +39,9 @@ export const TaskApps: FC<TaskAppsProps> = ({ task }) => {
3739const embeddedApps = apps . filter ( ( app ) => ! app . external ) ;
3840const externalApps = apps . filter ( ( app ) => app . external ) ;
3941
40- const [ activeAppId , setActiveAppId ] = useState < string > ( ( ) => {
41- const appId = embeddedApps [ 0 ] ?. id ;
42- if ( ! appId ) {
43- throw new Error ( "No apps found in task" ) ;
44- }
45- return appId ;
46- } ) ;
47-
48- const activeApp = apps . find ( ( app ) => app . id === activeAppId ) ;
49- if ( ! activeApp ) {
50- throw new Error ( `Active app with ID${ activeAppId } not found in task` ) ;
51- }
52-
53- const agent = agents . find ( ( a ) =>
54- a . apps . some ( ( app ) => app . id === activeAppId ) ,
42+ const [ activeAppId , setActiveAppId ] = useState < string | undefined > (
43+ embeddedApps [ 0 ] ?. id ,
5544) ;
56- if ( ! agent ) {
57- throw new Error ( `Agent for app${ activeAppId } not found in task workspace` ) ;
58- }
5945
6046return (
6147< main className = "flex flex-col" >
@@ -76,56 +62,104 @@ export const TaskApps: FC<TaskAppsProps> = ({ task }) => {
7662</ div >
7763
7864{ externalApps . length > 0 && (
79- < div className = "ml-auto" >
80- < DropdownMenu >
81- < DropdownMenuTrigger asChild >
82- < Button size = "sm" variant = "subtle" >
83- Open locally
84- < ChevronDownIcon />
85- </ Button >
86- </ DropdownMenuTrigger >
87- < DropdownMenuContent >
88- { externalApps . map ( ( app ) => {
89- const link = useAppLink ( app , {
90- agent,
91- workspace :task . workspace ,
92- } ) ;
93-
94- return (
95- < DropdownMenuItem key = { app . id } asChild >
96- < RouterLink to = { link . href } >
97- { app . icon ?(
98- < ExternalImage src = { app . icon } />
99- ) :(
100- < LayoutGridIcon />
101- ) }
102- { link . label }
103- </ RouterLink >
104- </ DropdownMenuItem >
105- ) ;
106- } ) }
107- </ DropdownMenuContent >
108- </ DropdownMenu >
109- </ div >
65+ < TaskExternalAppsDropdown
66+ task = { task }
67+ agents = { agents }
68+ externalApps = { externalApps }
69+ />
11070) }
11171</ div >
11272
113- < div className = "flex-1" >
114- { embeddedApps . map ( ( app ) => {
115- return (
116- < TaskAppIFrame
117- key = { app . id }
118- active = { activeAppId === app . id }
119- app = { app }
120- task = { task }
121- />
122- ) ;
123- } ) }
124- </ div >
73+ { embeddedApps . length > 0 ?(
74+ < div className = "flex-1" >
75+ { embeddedApps . map ( ( app ) => {
76+ return (
77+ < TaskAppIFrame
78+ key = { app . id }
79+ active = { activeAppId === app . id }
80+ app = { app }
81+ task = { task }
82+ />
83+ ) ;
84+ } ) }
85+ </ div >
86+ ) :(
87+ < div className = "mx-auto my-auto flex flex-col items-center" >
88+ < h3 className = "font-medium text-content-primary text-base" >
89+ No embedded apps found.
90+ </ h3 >
91+
92+ < span className = "text-content-secondary text-sm" >
93+ < Link
94+ href = { docs ( "/ai-coder/tasks" ) }
95+ target = "_blank"
96+ rel = "noreferrer"
97+ >
98+ Learn how to configure apps
99+ </ Link > { " " }
100+ for your tasks.
101+ </ span >
102+ </ div >
103+ ) }
125104</ main >
126105) ;
127106} ;
128107
108+ type TaskExternalAppsDropdownProps = {
109+ task :Task ;
110+ agents :WorkspaceAgent [ ] ;
111+ externalApps :WorkspaceApp [ ] ;
112+ } ;
113+
114+ const TaskExternalAppsDropdown :FC < TaskExternalAppsDropdownProps > = ( {
115+ task,
116+ agents,
117+ externalApps,
118+ } ) => {
119+ return (
120+ < div className = "ml-auto" >
121+ < DropdownMenu >
122+ < DropdownMenuTrigger asChild >
123+ < Button size = "sm" variant = "subtle" >
124+ Open locally
125+ < ChevronDownIcon />
126+ </ Button >
127+ </ DropdownMenuTrigger >
128+ < DropdownMenuContent >
129+ { externalApps . map ( ( app ) => {
130+ const agent = agents . find ( ( agent ) =>
131+ agent . apps . some ( ( a ) => a . id === app . id ) ,
132+ ) ;
133+ if ( ! agent ) {
134+ throw new Error (
135+ `Agent for app${ app . id } not found in task workspace` ,
136+ ) ;
137+ }
138+
139+ const link = useAppLink ( app , {
140+ agent,
141+ workspace :task . workspace ,
142+ } ) ;
143+
144+ return (
145+ < DropdownMenuItem key = { app . id } asChild >
146+ < RouterLink to = { link . href } >
147+ { app . icon ?(
148+ < ExternalImage src = { app . icon } />
149+ ) :(
150+ < LayoutGridIcon />
151+ ) }
152+ { link . label }
153+ </ RouterLink >
154+ </ DropdownMenuItem >
155+ ) ;
156+ } ) }
157+ </ DropdownMenuContent >
158+ </ DropdownMenu >
159+ </ div >
160+ ) ;
161+ } ;
162+
129163type TaskAppTabProps = {
130164task :Task ;
131165app :WorkspaceApp ;