11import { FairyProject , FairyTask } from '@tldraw/fairy-shared'
2- import { MouseEvent , useCallback , useEffect , useRef , useState } from 'react'
2+ import { MouseEvent , useCallback , useEffect , useMemo , useRef , useState } from 'react'
33import {
44 PORTRAIT_BREAKPOINT ,
55 TldrawUiButton ,
@@ -15,11 +15,12 @@ import '../tla/styles/fairy.css'
1515import { F , useMsg } from '../tla/utils/i18n'
1616import { FairyAgent } from './fairy-agent/agent/FairyAgent'
1717import { FairyChatHistory } from './fairy-agent/chat/FairyChatHistory'
18- import { FairyBasicInput } from './fairy-agent/input/FairyBasicInput '
18+ import { FairySingleChatInput } from './fairy-agent/input/FairySingleChatInput '
1919import { fairyMessages } from './fairy-messages'
2020import { FairyDropdownContent } from './FairyDropdownContent'
21- import { FairyGroupChat } from './FairyGroupChat'
2221import { FairyListSidebar } from './FairyListSidebar'
22+ import { $fairyProjects , getProjectOrchestrator } from './FairyProjects'
23+ import { FairyProjectView } from './FairyProjectView'
2324import { $fairyTasks } from './FairyTaskList'
2425import { FairyTaskListDropdownContent } from './FairyTaskListDropdownContent'
2526import { FairyTaskListInline } from './FairyTaskListInline'
@@ -55,9 +56,7 @@ function FairyHUDHeader({
5556 const project = useValue ( 'project' , ( ) => shownFairy ?. getProject ( ) , [ shownFairy ] )
5657
5758 // Check if the project has been started (has an orchestrator)
58- const isProjectStarted = project ?. members . some (
59- ( member ) => member . role === 'orchestrator' || member . role === 'duo-orchestrator'
60- )
59+ const isProjectStarted = project && getProjectOrchestrator ( project )
6160
6261 const fairyClickable = useValue (
6362 'fairy clickable' ,
@@ -190,6 +189,22 @@ export function FairyHUD({ agents }: { agents: FairyAgent[] }) {
190189 [ agents ]
191190 )
192191
192+ const activeOrchestratorAgent = useValue (
193+ 'shown-orchestrator' ,
194+ ( ) => {
195+ if ( ! shownFairy ) return null
196+ const project = shownFairy . getProject ( )
197+ if ( ! project ) return null
198+
199+ const orchestratorMember = getProjectOrchestrator ( project )
200+ if ( ! orchestratorMember ) return null
201+
202+ // Return the actual FairyAgent, not just the member
203+ return agents . find ( ( agent ) => agent . id === orchestratorMember . id ) ?? null
204+ } ,
205+ [ shownFairy , agents ]
206+ )
207+
193208 // Update the chosen fairy when the selected fairies change
194209 useEffect ( ( ) => {
195210 if ( selectedFairies . length === 1 ) {
@@ -223,9 +238,7 @@ export function FairyHUD({ agents }: { agents: FairyAgent[] }) {
223238 }
224239
225240 // Check if project has an orchestrator (meaning it's been started)
226- const orchestratorMember = project . members . find (
227- ( member ) => member . role === 'orchestrator' || member . role === 'duo-orchestrator'
228- )
241+ const orchestratorMember = getProjectOrchestrator ( project )
229242
230243 if ( orchestratorMember ) {
231244 // Project has been started, show the orchestrator's chat
@@ -303,10 +316,25 @@ export function FairyHUD({ agents }: { agents: FairyAgent[] }) {
303316 const handleDoubleClickFairy = useCallback (
304317 ( clickedAgent : FairyAgent ) => {
305318 clickedAgent . zoomTo ( )
319+
320+ // If the clicked fairy is part of an active project, select the orchestrator instead
321+ const project = clickedAgent . getProject ( )
322+ if ( project ) {
323+ const orchestratorMember = getProjectOrchestrator ( project )
324+ if ( orchestratorMember ) {
325+ const orchestratorAgent = agents . find ( ( agent ) => agent . id === orchestratorMember . id )
326+ if ( orchestratorAgent ) {
327+ selectFairy ( orchestratorAgent )
328+ setPanelState ( 'fairy' )
329+ return
330+ }
331+ }
332+ }
333+
306334 selectFairy ( clickedAgent )
307335 setPanelState ( 'fairy' )
308336 } ,
309- [ selectFairy ]
337+ [ selectFairy , agents ]
310338 )
311339
312340 const handleTogglePanel = useCallback ( ( ) => {
@@ -387,6 +415,38 @@ export function FairyHUD({ agents }: { agents: FairyAgent[] }) {
387415 return ( ) => window . removeEventListener ( 'resize' , updatePosition )
388416 } , [ isMobile ] )
389417
418+ // Unused atm, wip
419+ // Check if any fairies in projects are idling
420+ const projects = useValue ( 'fairy-projects' , ( ) => $fairyProjects . get ( ) , [ $fairyProjects ] )
421+ const badStateProjectIds = useMemo ( ( ) => {
422+ const projectIds = new Set < string > ( )
423+ for ( const project of projects ) {
424+ for ( const member of project . members ) {
425+ const agent = agents . find ( ( a ) => a . id === member . id )
426+ if ( agent && agent . getMode ( ) === 'idling' ) {
427+ projectIds . add ( project . id )
428+ }
429+ }
430+ }
431+ return Array . from ( projectIds )
432+ } , [ agents , projects ] )
433+ useEffect ( ( ) => {
434+ for ( const project of projects ) {
435+ if ( badStateProjectIds . includes ( project . id ) ) {
436+ for ( const member of project . members ) {
437+ const agent = agents . find ( ( a ) => a . id === member . id )
438+ if ( agent && agent . getMode ( ) === 'idling' ) {
439+ const fairyName = agent . $fairyConfig . get ( ) ?. name ?? 'unknown'
440+ // eslint-disable-next-line no-console
441+ console . log (
442+ `Fairy in project but idling: projectId=${ project . id } , fairyId=${ agent . id } , name=${ fairyName } `
443+ )
444+ }
445+ }
446+ }
447+ }
448+ } , [ badStateProjectIds , agents , projects ] )
449+
390450 return (
391451 < >
392452 < div
@@ -436,22 +496,44 @@ export function FairyHUD({ agents }: { agents: FairyAgent[] }) {
436496 < F defaultMessage = "Select a fairy on the right to chat with" />
437497 </ div >
438498 ) }
499+ { /* Solo fairy mode - no project */ }
439500 { panelState === 'fairy' &&
440501 selectedFairies . length <= 1 &&
441- shownFairy && ( // if there's a single shown fairy, still show the chat history and input even if the user deselects it
502+ shownFairy &&
503+ ! activeOrchestratorAgent && (
442504 < >
443505 < FairyChatHistory agent = { shownFairy } />
444- < FairyBasicInput agent = { shownFairy } onCancel = { ( ) => setPanelState ( 'closed' ) } />
506+ < FairySingleChatInput
507+ agent = { shownFairy }
508+ onCancel = { ( ) => setPanelState ( 'closed' ) }
509+ />
445510 </ >
446511 ) }
447512
513+ { /* Project mode - ongoing project with orchestrator */ }
514+ { panelState === 'fairy' &&
515+ selectedFairies . length <= 1 &&
516+ shownFairy &&
517+ activeOrchestratorAgent && (
518+ < FairyProjectView
519+ editor = { editor }
520+ agents = { agents }
521+ orchestratorAgent = { activeOrchestratorAgent }
522+ onClose = { ( ) => setPanelState ( 'closed' ) }
523+ />
524+ ) }
525+
526+ { /* Pre-project mode - multiple fairies selected, no project yet */ }
448527 { panelState === 'fairy' && selectedFairies . length > 1 && (
449- < FairyGroupChat
528+ < FairyProjectView
529+ editor = { editor }
450530 agents = { selectedFairies }
451- onStartProject = { ( orchestratorAgent ) => {
452- selectFairy ( orchestratorAgent )
531+ orchestratorAgent = { null }
532+ onProjectStarted = { ( orchestrator ) => {
533+ selectFairy ( orchestrator )
453534 setPanelState ( 'fairy' )
454535 } }
536+ onClose = { ( ) => setPanelState ( 'closed' ) }
455537 />
456538 ) }
457539
0 commit comments