1
- import type { WorkspaceApp } from "api/typesGenerated" ;
1
+ import type { WorkspaceAgent , WorkspaceApp } from "api/typesGenerated" ;
2
2
import { Button } from "components/Button/Button" ;
3
3
import {
4
4
DropdownMenu ,
@@ -8,13 +8,15 @@ import {
8
8
} from "components/DropdownMenu/DropdownMenu" ;
9
9
import { ExternalImage } from "components/ExternalImage/ExternalImage" ;
10
10
import { InfoTooltip } from "components/InfoTooltip/InfoTooltip" ;
11
+ import { Link } from "components/Link/Link" ;
11
12
import { ChevronDownIcon , LayoutGridIcon } from "lucide-react" ;
12
13
import { useAppLink } from "modules/apps/useAppLink" ;
13
14
import type { Task } from "modules/tasks/tasks" ;
14
15
import type React from "react" ;
15
16
import { type FC , useState } from "react" ;
16
17
import { Link as RouterLink } from "react-router-dom" ;
17
18
import { cn } from "utils/cn" ;
19
+ import { docs } from "utils/docs" ;
18
20
import { TaskAppIFrame } from "./TaskAppIframe" ;
19
21
20
22
type TaskAppsProps = {
@@ -37,25 +39,9 @@ export const TaskApps: FC<TaskAppsProps> = ({ task }) => {
37
39
const embeddedApps = apps . filter ( ( app ) => ! app . external ) ;
38
40
const externalApps = apps . filter ( ( app ) => app . external ) ;
39
41
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 ,
55
44
) ;
56
- if ( ! agent ) {
57
- throw new Error ( `Agent for app ${ activeAppId } not found in task workspace` ) ;
58
- }
59
45
60
46
return (
61
47
< main className = "flex flex-col" >
@@ -76,56 +62,104 @@ export const TaskApps: FC<TaskAppsProps> = ({ task }) => {
76
62
</ div >
77
63
78
64
{ 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
+ />
110
70
) }
111
71
</ div >
112
72
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
+ ) }
125
104
</ main >
126
105
) ;
127
106
} ;
128
107
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
+
129
163
type TaskAppTabProps = {
130
164
task : Task ;
131
165
app : WorkspaceApp ;
0 commit comments