File size: 4,294 Bytes
0c291c0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
import { useEffect, useMemo, useState } from 'react'
import { StudioPermissionModeModal } from './controls/StudioPermissionModeModal'
import { StudioCommandPanel } from './components/StudioCommandPanel'
import { useStudioSession } from './hooks/use-studio-session'
import { PlotPreviewPanel } from './plot/PlotPreviewPanel'

interface PlotStudioShellProps {
  onExit: () => void
  isExiting?: boolean
}

export function PlotStudioShell({ onExit, isExiting }: PlotStudioShellProps) {
  const studio = useStudioSession({
    studioKind: 'plot',
    title: 'Plot Studio'
  })
  const [selectedWorkId, setSelectedWorkId] = useState<string | null>(null)
  const [orderedWorkIds, setOrderedWorkIds] = useState<string[]>([])
  const incomingIds = studio.workSummaries.map((entry) => entry.work.id)
  const incomingIdsKey = incomingIds.join('|')

  useEffect(() => {
    setOrderedWorkIds((current) => {
      const preserved = current.filter((id) => incomingIds.includes(id))
      const appended = incomingIds.filter((id) => !preserved.includes(id))
      const next = [...appended, ...preserved]
      return areSameIds(current, next) ? current : next
    })
  }, [incomingIdsKey])

  const orderedWorkSummaries = useMemo(() => {
    const byId = new Map(studio.workSummaries.map((entry) => [entry.work.id, entry]))
    return orderedWorkIds
      .map((id) => byId.get(id))
      .filter((entry): entry is NonNullable<typeof entry> => Boolean(entry))
  }, [orderedWorkIds, studio.workSummaries])

  const latestWorkId = orderedWorkSummaries[0]?.work.id ?? null

  useEffect(() => {
    if (!latestWorkId) {
      return
    }

    setSelectedWorkId((current) => (current === latestWorkId ? current : latestWorkId))
  }, [latestWorkId])

  const effectiveSelectedWorkId =
    selectedWorkId && orderedWorkSummaries.some((entry) => entry.work.id === selectedWorkId)
      ? selectedWorkId
      : orderedWorkSummaries[0]?.work.id ?? null
  const selected = studio.selectWork(effectiveSelectedWorkId)

  const handleReorderWorks = (nextWorkIds: string[]) => {
    setOrderedWorkIds((current) => (areSameIds(current, nextWorkIds) ? current : nextWorkIds))
  }

  return (
    <>
      <div
        className={`h-screen overflow-hidden bg-bg-primary text-text-primary ${
          isExiting ? 'animate-studio-exit' : 'animate-studio-entrance'
        }`}
      >
        <div className="relative h-screen overflow-hidden">
          <div className="flex h-full min-h-0 flex-col xl:flex-row">
            <div className="min-h-0 bg-white/72 shadow-[12px_0_36px_rgba(15,23,42,0.06)] backdrop-blur-xl xl:w-[36%] xl:min-w-[360px] xl:max-w-[500px]">
              <StudioCommandPanel
                session={studio.session}
                messages={studio.messages}
                latestAssistantText={studio.latestAssistantText}
                isBusy={studio.isBusy}
                disabled={studio.isBusy || studio.state.connection.snapshotStatus !== 'ready'}
                onRun={studio.runCommand}
                onExit={onExit}
              />
            </div>

            <div className="min-h-0 flex-1">
              <PlotPreviewPanel
                session={studio.session}
                works={orderedWorkSummaries}
                selectedWorkId={effectiveSelectedWorkId}
                work={selected.work}
                result={selected.result}
                latestRun={studio.latestRun}
                tasks={selected.tasks}
                requests={studio.pendingPermissions}
                replyingPermissionIds={studio.replyingPermissionIds}
                latestAssistantText={studio.latestAssistantText}
                errorMessage={studio.state.error ?? studio.state.connection.eventError}
                onSelectWork={setSelectedWorkId}
                onReorderWorks={handleReorderWorks}
                onReply={studio.replyPermission}
              />
            </div>
          </div>
        </div>
      </div>

      <StudioPermissionModeModal {...studio.permissionModeModal} />
    </>
  )
}

function areSameIds(left: string[], right: string[]) {
  if (left.length !== right.length) {
    return false
  }

  for (let index = 0; index < left.length; index += 1) {
    if (left[index] !== right[index]) {
      return false
    }
  }

  return true
}