@jkwd/inbase 0.1.21 → 0.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -7
- package/apps/explorer/package.json +1 -0
- package/apps/explorer/scripts/explain-store.d.ts +135 -0
- package/apps/explorer/scripts/explain-store.mjs +666 -0
- package/apps/explorer/scripts/patch-lib.mjs +4 -0
- package/apps/explorer/scripts/scan-target.mjs +22 -5
- package/apps/explorer/scripts/session-store.d.ts +86 -11
- package/apps/explorer/scripts/session-store.mjs +371 -58
- package/apps/explorer/scripts/target-config.d.ts +38 -3
- package/apps/explorer/scripts/target-config.mjs +147 -3
- package/apps/explorer/src/App.tsx +1073 -158
- package/apps/explorer/src/agentIntent.ts +61 -7
- package/apps/explorer/src/codebase.ts +1 -1
- package/apps/explorer/src/devTargets.ts +66 -0
- package/apps/explorer/src/explain.ts +312 -0
- package/apps/explorer/src/index.css +874 -222
- package/apps/explorer/src/layout.ts +55 -0
- package/apps/explorer/src/scene/DistantFileBlocks.tsx +4 -2
- package/apps/explorer/src/scene/FileBlock.tsx +95 -72
- package/apps/explorer/src/scene/FolderArea.tsx +55 -32
- package/apps/explorer/src/scene/MapView.tsx +506 -33
- package/apps/explorer/src/scene/RelationLines.tsx +7 -0
- package/apps/explorer/src/scene/World.tsx +146 -32
- package/apps/explorer/src/speech.ts +228 -0
- package/apps/explorer/src/theme.ts +29 -0
- package/apps/explorer/src/types.ts +98 -8
- package/apps/explorer/src/ui/CanvasErrorBoundary.tsx +1 -1
- package/apps/explorer/src/ui/ExplainAskCard.tsx +142 -0
- package/apps/explorer/src/ui/ExplainHud.tsx +524 -0
- package/apps/explorer/src/ui/ExplainInfoPanel.tsx +135 -0
- package/apps/explorer/src/ui/ExplainPointer.tsx +73 -0
- package/apps/explorer/src/ui/EyeIcon.tsx +38 -1
- package/apps/explorer/src/ui/HUD.tsx +1067 -795
- package/apps/explorer/src/ui/NameInput.tsx +114 -5
- package/apps/explorer/src/userContext.ts +0 -11
- package/apps/explorer/src/userCreated.ts +54 -1
- package/apps/explorer/vite.config.ts +173 -26
- package/bin/inbase.mjs +11 -2
- package/bin/project.mjs +6 -4
- package/bin/session.mjs +287 -38
- package/package.json +4 -1
- package/skill/commands/amber.md +23 -0
- package/skill/commands/blue.md +13 -0
- package/skill/commands/coral.md +23 -0
- package/skill/commands/explain.md +77 -0
- package/skill/commands/green.md +23 -0
- package/skill/commands/inbase.md +7 -5
- package/skill/commands/lime.md +23 -0
- package/skill/commands/orange.md +23 -0
- package/skill/commands/purple.md +23 -0
- package/skill/commands/red.md +23 -0
- package/skill/commands/skipinbase.md +1 -1
- package/skill/commands/violet.md +23 -0
- package/skill/commands/yellow.md +23 -0
- package/skill/inbase/SKILL.md +124 -76
- package/apps/explorer/src/scene/BlockPlacer.tsx +0 -78
- package/apps/explorer/src/scene/IslandPlacer.tsx +0 -31
- package/apps/explorer/src/scene/SelectionThumbnail.tsx +0 -1069
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
import { useEffect, useRef, useState, type ReactNode } from 'react'
|
|
2
|
-
import {
|
|
3
|
-
import { NameInput } from './NameInput'
|
|
4
|
-
import { SelectionThumbnail } from '../scene/SelectionThumbnail'
|
|
5
|
-
import { CanvasErrorBoundary } from './CanvasErrorBoundary'
|
|
1
|
+
import { useEffect, useLayoutEffect, useRef, useState, type CSSProperties, type ReactNode } from 'react'
|
|
2
|
+
import { createPortal } from 'react-dom'
|
|
3
|
+
import { InfoNameField, NameInput } from './NameInput'
|
|
6
4
|
import {
|
|
7
|
-
canStopSession,
|
|
8
5
|
isReviewingIntent,
|
|
9
6
|
type AgentIntent,
|
|
10
7
|
type AgentIntentStatus,
|
|
11
8
|
type AimedRelation,
|
|
12
9
|
type BlueprintNote,
|
|
13
10
|
type BlueprintNoteKind,
|
|
11
|
+
GLOBAL_BLUEPRINT_COLOR,
|
|
12
|
+
type BlueprintOption,
|
|
14
13
|
type BlueprintPointer,
|
|
15
14
|
type BlueprintPointerKind,
|
|
16
15
|
type BranchChanges,
|
|
17
16
|
type CodebaseGraph,
|
|
17
|
+
type ExplainTargetKind,
|
|
18
18
|
type PatchImportAddition,
|
|
19
19
|
type PatchSymbolAddition,
|
|
20
20
|
type ViewMode,
|
|
21
|
-
type WorldLayout,
|
|
22
21
|
type WorkflowAction,
|
|
23
22
|
} from '../types'
|
|
24
23
|
import { findBlueprintNote, findBlueprintPointer } from '../userCreated'
|
|
25
24
|
import { EyeIcon } from './EyeIcon'
|
|
26
25
|
import { beginKeyboardIsolation, shouldIgnoreShortcut } from '../keyboard'
|
|
26
|
+
import type { DevTargetsState } from '../devTargets'
|
|
27
27
|
|
|
28
28
|
function reviewTitle(status: AgentIntentStatus) {
|
|
29
29
|
if (status === 'blueprint_ask') return 'Setup blueprint'
|
|
@@ -43,14 +43,270 @@ function sessionLabel(intent: AgentIntent) {
|
|
|
43
43
|
return intent.name?.trim() || intent.feature?.trim() || ''
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
function
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
function sessionColorName(intent: AgentIntent) {
|
|
47
|
+
return intent.colorName?.trim() || ''
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function sessionSlashCommand(intent: Pick<AgentIntent, 'color'>) {
|
|
51
|
+
const color = intent.color?.trim()
|
|
52
|
+
if (!color || color === 'blue') return null
|
|
53
|
+
return `/${color}`
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function ColorConnectHint({
|
|
57
|
+
colorCommand,
|
|
58
|
+
queued,
|
|
59
|
+
}: {
|
|
60
|
+
colorCommand?: string | null
|
|
61
|
+
queued?: boolean
|
|
62
|
+
}) {
|
|
63
|
+
return (
|
|
64
|
+
<p>
|
|
65
|
+
{queued && colorCommand ? (
|
|
66
|
+
<>
|
|
67
|
+
Type <kbd>{colorCommand}</kbd> in a Cursor chat to skip the queue and
|
|
68
|
+
connect here.{' '}
|
|
69
|
+
</>
|
|
70
|
+
) : null}
|
|
71
|
+
Use <kbd>/coral</kbd>, <kbd>/amber</kbd>, <kbd>/lime</kbd>,{' '}
|
|
72
|
+
<kbd>/orange</kbd>, or <kbd>/violet</kbd> to connect to that color
|
|
73
|
+
{!queued && colorCommand ? (
|
|
74
|
+
<>
|
|
75
|
+
{' '}
|
|
76
|
+
— this session is <kbd>{colorCommand}</kbd>
|
|
77
|
+
</>
|
|
78
|
+
) : null}
|
|
79
|
+
. Aliases: <kbd>/red</kbd>, <kbd>/yellow</kbd>, <kbd>/green</kbd>,{' '}
|
|
80
|
+
<kbd>/purple</kbd>. <kbd>/blue</kbd> is the global blueprint, not a chat.
|
|
81
|
+
</p>
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function sessionDisplayName(intent: AgentIntent) {
|
|
86
|
+
return sessionLabel(intent) || sessionColorName(intent)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function SessionSwatch({
|
|
90
|
+
colorHex,
|
|
91
|
+
className = 'hud-session-swatch',
|
|
92
|
+
}: {
|
|
93
|
+
colorHex?: string | null
|
|
94
|
+
className?: string
|
|
95
|
+
}) {
|
|
96
|
+
return (
|
|
97
|
+
<span
|
|
98
|
+
className={className}
|
|
99
|
+
aria-hidden="true"
|
|
100
|
+
style={
|
|
101
|
+
colorHex
|
|
102
|
+
? ({ '--session-color': colorHex } as CSSProperties)
|
|
103
|
+
: undefined
|
|
104
|
+
}
|
|
105
|
+
/>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
type BlueprintColorOption = BlueprintOption & { pointers: BlueprintPointer[] }
|
|
110
|
+
|
|
111
|
+
function optionPointed(
|
|
112
|
+
option: BlueprintColorOption,
|
|
113
|
+
target: { kind: BlueprintPointerKind; path: string; name?: string },
|
|
114
|
+
) {
|
|
115
|
+
return findBlueprintPointer(
|
|
116
|
+
option.pointers,
|
|
117
|
+
target.kind,
|
|
118
|
+
target.path,
|
|
119
|
+
target.name,
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function placeAnchoredMenu(
|
|
124
|
+
trigger: DOMRect,
|
|
125
|
+
menuHeight: number,
|
|
126
|
+
width: number,
|
|
127
|
+
alignEnd: boolean,
|
|
128
|
+
) {
|
|
129
|
+
const gap = 4
|
|
130
|
+
const pad = 8
|
|
131
|
+
const left = Math.min(
|
|
132
|
+
Math.max(pad, alignEnd ? trigger.right - width : trigger.left),
|
|
133
|
+
window.innerWidth - width - pad,
|
|
134
|
+
)
|
|
135
|
+
const spaceBelow = window.innerHeight - trigger.bottom - pad
|
|
136
|
+
const spaceAbove = trigger.top - pad
|
|
137
|
+
const openAbove =
|
|
138
|
+
menuHeight > 0 &&
|
|
139
|
+
menuHeight + gap > spaceBelow &&
|
|
140
|
+
spaceAbove > spaceBelow
|
|
141
|
+
const maxHeight = Math.max(0, (openAbove ? spaceAbove : spaceBelow) - gap)
|
|
142
|
+
const usedHeight = menuHeight > 0 ? Math.min(menuHeight, maxHeight) : 0
|
|
143
|
+
const top = openAbove
|
|
144
|
+
? Math.max(pad, trigger.top - usedHeight - gap)
|
|
145
|
+
: trigger.bottom + gap
|
|
146
|
+
return { top, left, width, maxHeight }
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function PointColorControl({
|
|
150
|
+
target,
|
|
151
|
+
colorPointers = [],
|
|
152
|
+
currentColorId,
|
|
153
|
+
onToggle,
|
|
154
|
+
compact = false,
|
|
155
|
+
idleLabel,
|
|
156
|
+
pointedLabel,
|
|
157
|
+
disabled = false,
|
|
158
|
+
}: {
|
|
159
|
+
target: { kind: BlueprintPointerKind; path: string; name?: string }
|
|
160
|
+
colorPointers?: BlueprintColorOption[]
|
|
161
|
+
currentColorId?: string | null
|
|
162
|
+
onToggle: (color?: string) => void
|
|
163
|
+
compact?: boolean
|
|
164
|
+
idleLabel: string
|
|
165
|
+
pointedLabel: string
|
|
166
|
+
disabled?: boolean
|
|
167
|
+
}) {
|
|
168
|
+
const triggerRef = useRef<HTMLDivElement>(null)
|
|
169
|
+
const menuRef = useRef<HTMLDivElement>(null)
|
|
170
|
+
const [open, setOpen] = useState(false)
|
|
171
|
+
const current =
|
|
172
|
+
colorPointers.find((option) => option.id === (currentColorId ?? 'global')) ??
|
|
173
|
+
colorPointers[0]
|
|
174
|
+
const currentHex = current?.hex ?? GLOBAL_BLUEPRINT_COLOR.hex
|
|
175
|
+
const pointed = current ? optionPointed(current, target) : false
|
|
176
|
+
const showMenu = colorPointers.length > 0
|
|
177
|
+
|
|
178
|
+
useLayoutEffect(() => {
|
|
179
|
+
if (!open) return
|
|
180
|
+
const place = () => {
|
|
181
|
+
const trigger = triggerRef.current
|
|
182
|
+
const menu = menuRef.current
|
|
183
|
+
if (!trigger || !menu) return
|
|
184
|
+
const rect = trigger.getBoundingClientRect()
|
|
185
|
+
const width = compact ? 168 : Math.max(rect.width, 168)
|
|
186
|
+
menu.style.maxHeight = 'none'
|
|
187
|
+
const { top, left, maxHeight } = placeAnchoredMenu(
|
|
188
|
+
rect,
|
|
189
|
+
menu.offsetHeight,
|
|
190
|
+
width,
|
|
191
|
+
compact,
|
|
192
|
+
)
|
|
193
|
+
menu.style.top = `${top}px`
|
|
194
|
+
menu.style.left = `${left}px`
|
|
195
|
+
menu.style.width = `${width}px`
|
|
196
|
+
menu.style.maxHeight = `${maxHeight}px`
|
|
197
|
+
menu.style.visibility = 'visible'
|
|
198
|
+
}
|
|
199
|
+
place()
|
|
200
|
+
window.addEventListener('resize', place)
|
|
201
|
+
window.addEventListener('scroll', place, true)
|
|
202
|
+
return () => {
|
|
203
|
+
window.removeEventListener('resize', place)
|
|
204
|
+
window.removeEventListener('scroll', place, true)
|
|
205
|
+
}
|
|
206
|
+
}, [compact, open, colorPointers.length])
|
|
207
|
+
|
|
208
|
+
useEffect(() => {
|
|
209
|
+
if (!open) return
|
|
210
|
+
const onKey = (event: KeyboardEvent) => {
|
|
211
|
+
if (event.code !== 'Escape') return
|
|
212
|
+
if (shouldIgnoreShortcut(event)) return
|
|
213
|
+
event.preventDefault()
|
|
214
|
+
setOpen(false)
|
|
215
|
+
}
|
|
216
|
+
const onPointerDown = (event: PointerEvent) => {
|
|
217
|
+
const node = event.target
|
|
218
|
+
if (
|
|
219
|
+
node instanceof Element &&
|
|
220
|
+
(triggerRef.current?.contains(node) ||
|
|
221
|
+
node.closest('.hud-point-dropdown'))
|
|
222
|
+
) {
|
|
223
|
+
return
|
|
224
|
+
}
|
|
225
|
+
setOpen(false)
|
|
226
|
+
}
|
|
227
|
+
window.addEventListener('keydown', onKey, true)
|
|
228
|
+
window.addEventListener('pointerdown', onPointerDown, true)
|
|
229
|
+
return () => {
|
|
230
|
+
window.removeEventListener('keydown', onKey, true)
|
|
231
|
+
window.removeEventListener('pointerdown', onPointerDown, true)
|
|
232
|
+
}
|
|
233
|
+
}, [open])
|
|
234
|
+
|
|
235
|
+
return (
|
|
236
|
+
<div
|
|
237
|
+
ref={triggerRef}
|
|
238
|
+
className={
|
|
239
|
+
compact ? 'hud-point-menu hud-point-menu-compact' : 'hud-point-menu'
|
|
240
|
+
}
|
|
241
|
+
>
|
|
242
|
+
<button
|
|
243
|
+
className={
|
|
244
|
+
compact ? 'hud-item-point' : 'hud-button hud-inspect hud-point'
|
|
245
|
+
}
|
|
246
|
+
type="button"
|
|
247
|
+
data-pointed={pointed ? 'true' : 'false'}
|
|
248
|
+
data-active={open}
|
|
249
|
+
aria-pressed={pointed}
|
|
250
|
+
aria-haspopup={showMenu ? 'menu' : undefined}
|
|
251
|
+
aria-expanded={showMenu ? open : undefined}
|
|
252
|
+
aria-label={compact ? (pointed ? pointedLabel : idleLabel) : undefined}
|
|
253
|
+
disabled={disabled}
|
|
254
|
+
style={
|
|
255
|
+
{
|
|
256
|
+
'--session-color': currentHex,
|
|
257
|
+
} as CSSProperties
|
|
258
|
+
}
|
|
259
|
+
onClick={() => {
|
|
260
|
+
if (!showMenu || disabled) return
|
|
261
|
+
setOpen((currentOpen) => !currentOpen)
|
|
262
|
+
}}
|
|
263
|
+
>
|
|
264
|
+
<EyeIcon size={compact ? 13 : 15} />
|
|
265
|
+
{compact ? null : pointed ? pointedLabel : idleLabel}
|
|
266
|
+
</button>
|
|
267
|
+
{open &&
|
|
268
|
+
showMenu &&
|
|
269
|
+
createPortal(
|
|
270
|
+
<div ref={menuRef} className="hud-point-dropdown" role="menu">
|
|
271
|
+
<button
|
|
272
|
+
type="button"
|
|
273
|
+
role="menuitem"
|
|
274
|
+
className="hud-point-dropdown-cancel"
|
|
275
|
+
onClick={() => setOpen(false)}
|
|
276
|
+
>
|
|
277
|
+
Cancel
|
|
278
|
+
</button>
|
|
279
|
+
{colorPointers.map((option) => {
|
|
280
|
+
const selected = optionPointed(option, target)
|
|
281
|
+
return (
|
|
282
|
+
<button
|
|
283
|
+
key={option.id}
|
|
284
|
+
type="button"
|
|
285
|
+
role="menuitem"
|
|
286
|
+
data-pointed={selected ? 'true' : 'false'}
|
|
287
|
+
aria-pressed={selected}
|
|
288
|
+
style={
|
|
289
|
+
{
|
|
290
|
+
'--session-color': option.hex,
|
|
291
|
+
} as CSSProperties
|
|
292
|
+
}
|
|
293
|
+
onClick={() => {
|
|
294
|
+
onToggle(option.id)
|
|
295
|
+
setOpen(false)
|
|
296
|
+
}}
|
|
297
|
+
>
|
|
298
|
+
<EyeIcon size={15} />
|
|
299
|
+
<span>
|
|
300
|
+
{option.kind === 'global' ? 'Global' : option.name}
|
|
301
|
+
</span>
|
|
302
|
+
</button>
|
|
303
|
+
)
|
|
304
|
+
})}
|
|
305
|
+
</div>,
|
|
306
|
+
document.body,
|
|
307
|
+
)}
|
|
308
|
+
</div>
|
|
50
309
|
)
|
|
51
|
-
if (same.length < 2) return title
|
|
52
|
-
const id = intent.sessionId ?? ''
|
|
53
|
-
return `${title} · ${id.slice(-4)}`
|
|
54
310
|
}
|
|
55
311
|
|
|
56
312
|
function fileBase(id: string) {
|
|
@@ -193,9 +449,15 @@ function MutationFold({
|
|
|
193
449
|
function AddIntentRow({
|
|
194
450
|
placeholder,
|
|
195
451
|
onAdd,
|
|
452
|
+
pickLabel,
|
|
453
|
+
pickActive = false,
|
|
454
|
+
onTogglePick,
|
|
196
455
|
}: {
|
|
197
456
|
placeholder: string
|
|
198
457
|
onAdd: (value: string) => boolean
|
|
458
|
+
pickLabel?: string
|
|
459
|
+
pickActive?: boolean
|
|
460
|
+
onTogglePick?: () => void
|
|
199
461
|
}) {
|
|
200
462
|
const [value, setValue] = useState('')
|
|
201
463
|
return (
|
|
@@ -218,6 +480,17 @@ function AddIntentRow({
|
|
|
218
480
|
<button className="hud-button" type="submit">
|
|
219
481
|
Add
|
|
220
482
|
</button>
|
|
483
|
+
{onTogglePick && pickLabel && (
|
|
484
|
+
<button
|
|
485
|
+
className="hud-button"
|
|
486
|
+
type="button"
|
|
487
|
+
data-active={pickActive ? 'true' : undefined}
|
|
488
|
+
aria-pressed={pickActive}
|
|
489
|
+
onClick={onTogglePick}
|
|
490
|
+
>
|
|
491
|
+
{pickLabel}
|
|
492
|
+
</button>
|
|
493
|
+
)}
|
|
221
494
|
</form>
|
|
222
495
|
)
|
|
223
496
|
}
|
|
@@ -315,54 +588,71 @@ function BlueprintSymbolRow({
|
|
|
315
588
|
className,
|
|
316
589
|
hasNote,
|
|
317
590
|
noteOpen,
|
|
318
|
-
pointed,
|
|
319
591
|
canEdit,
|
|
320
592
|
canRemove,
|
|
321
593
|
onRemove,
|
|
322
594
|
onOpenNote,
|
|
595
|
+
onExplain,
|
|
596
|
+
pointerTarget,
|
|
597
|
+
colorPointers,
|
|
598
|
+
currentColorId,
|
|
323
599
|
onTogglePoint,
|
|
324
600
|
}: {
|
|
325
601
|
name: string
|
|
326
602
|
className?: string
|
|
327
603
|
hasNote: boolean
|
|
328
604
|
noteOpen?: boolean
|
|
329
|
-
pointed?: boolean
|
|
330
605
|
canEdit: boolean
|
|
331
606
|
canRemove?: boolean
|
|
332
607
|
onRemove?: () => void
|
|
333
608
|
onOpenNote: () => void
|
|
334
|
-
|
|
609
|
+
onExplain?: () => void
|
|
610
|
+
pointerTarget?: {
|
|
611
|
+
kind: BlueprintPointerKind
|
|
612
|
+
path: string
|
|
613
|
+
name: string
|
|
614
|
+
}
|
|
615
|
+
colorPointers?: BlueprintColorOption[]
|
|
616
|
+
currentColorId?: string | null
|
|
617
|
+
onTogglePoint?: (color?: string) => void
|
|
335
618
|
}) {
|
|
619
|
+
const showActions = Boolean(onExplain) || canEdit
|
|
336
620
|
return (
|
|
337
621
|
<li>
|
|
338
622
|
<span className={className}>{name}</span>
|
|
339
|
-
{
|
|
623
|
+
{showActions && (
|
|
340
624
|
<div className="hud-item-actions">
|
|
341
|
-
{
|
|
625
|
+
{onExplain ? (
|
|
626
|
+
<ExplainButton
|
|
627
|
+
compact
|
|
628
|
+
label={`Explain ${name}`}
|
|
629
|
+
onClick={onExplain}
|
|
630
|
+
/>
|
|
631
|
+
) : null}
|
|
632
|
+
{canEdit && onTogglePoint && pointerTarget && (
|
|
633
|
+
<PointColorControl
|
|
634
|
+
compact
|
|
635
|
+
target={pointerTarget}
|
|
636
|
+
colorPointers={colorPointers}
|
|
637
|
+
currentColorId={currentColorId}
|
|
638
|
+
idleLabel={`Point to ${name}`}
|
|
639
|
+
pointedLabel={`Stop pointing to ${name}`}
|
|
640
|
+
onToggle={onTogglePoint}
|
|
641
|
+
/>
|
|
642
|
+
)}
|
|
643
|
+
{canEdit && (
|
|
342
644
|
<button
|
|
343
|
-
className="hud-item-
|
|
645
|
+
className="hud-item-note"
|
|
344
646
|
type="button"
|
|
345
|
-
data-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
}
|
|
349
|
-
aria-pressed={Boolean(pointed)}
|
|
350
|
-
onClick={onTogglePoint}
|
|
647
|
+
data-has-note={hasNote ? 'true' : 'false'}
|
|
648
|
+
data-open={noteOpen ? 'true' : 'false'}
|
|
649
|
+
aria-label={`Edit note for ${name}`}
|
|
650
|
+
onClick={onOpenNote}
|
|
351
651
|
>
|
|
352
|
-
|
|
652
|
+
Note
|
|
353
653
|
</button>
|
|
354
654
|
)}
|
|
355
|
-
|
|
356
|
-
className="hud-item-note"
|
|
357
|
-
type="button"
|
|
358
|
-
data-has-note={hasNote ? 'true' : 'false'}
|
|
359
|
-
data-open={noteOpen ? 'true' : 'false'}
|
|
360
|
-
aria-label={`Edit note for ${name}`}
|
|
361
|
-
onClick={onOpenNote}
|
|
362
|
-
>
|
|
363
|
-
Note
|
|
364
|
-
</button>
|
|
365
|
-
{canRemove && (
|
|
655
|
+
{canEdit && canRemove && (
|
|
366
656
|
<button
|
|
367
657
|
className="hud-item-remove"
|
|
368
658
|
type="button"
|
|
@@ -391,6 +681,35 @@ function AttachStateBadge({ attached }: { attached: boolean }) {
|
|
|
391
681
|
)
|
|
392
682
|
}
|
|
393
683
|
|
|
684
|
+
function ExplainButton({
|
|
685
|
+
label,
|
|
686
|
+
onClick,
|
|
687
|
+
compact = false,
|
|
688
|
+
}: {
|
|
689
|
+
label: string
|
|
690
|
+
onClick: () => void
|
|
691
|
+
compact?: boolean
|
|
692
|
+
}) {
|
|
693
|
+
return (
|
|
694
|
+
<button
|
|
695
|
+
className={
|
|
696
|
+
compact
|
|
697
|
+
? 'hud-explain-button hud-explain-inline'
|
|
698
|
+
: 'hud-explain-button'
|
|
699
|
+
}
|
|
700
|
+
type="button"
|
|
701
|
+
aria-label={label}
|
|
702
|
+
title={label}
|
|
703
|
+
onClick={(event) => {
|
|
704
|
+
event.stopPropagation()
|
|
705
|
+
onClick()
|
|
706
|
+
}}
|
|
707
|
+
>
|
|
708
|
+
?
|
|
709
|
+
</button>
|
|
710
|
+
)
|
|
711
|
+
}
|
|
712
|
+
|
|
394
713
|
function PanelChrome({
|
|
395
714
|
title,
|
|
396
715
|
subtitle,
|
|
@@ -398,8 +717,8 @@ function PanelChrome({
|
|
|
398
717
|
minimized = false,
|
|
399
718
|
onMinimize,
|
|
400
719
|
onClose,
|
|
401
|
-
|
|
402
|
-
|
|
720
|
+
onExplain,
|
|
721
|
+
explainLabel,
|
|
403
722
|
}: {
|
|
404
723
|
title: ReactNode
|
|
405
724
|
subtitle?: ReactNode
|
|
@@ -407,14 +726,22 @@ function PanelChrome({
|
|
|
407
726
|
minimized?: boolean
|
|
408
727
|
onMinimize?: () => void
|
|
409
728
|
onClose?: () => void
|
|
410
|
-
|
|
411
|
-
|
|
729
|
+
onExplain?: () => void
|
|
730
|
+
explainLabel?: string
|
|
412
731
|
}) {
|
|
413
732
|
return (
|
|
414
733
|
<div className="hud-panel-chrome">
|
|
415
734
|
<div className="hud-panel-chrome-heading">
|
|
416
735
|
<div className="hud-panel-chrome-title-row">
|
|
417
|
-
<div className="hud-panel-chrome-title">
|
|
736
|
+
<div className="hud-panel-chrome-title">
|
|
737
|
+
{title}
|
|
738
|
+
{onExplain ? (
|
|
739
|
+
<ExplainButton
|
|
740
|
+
label={explainLabel ?? 'Explain'}
|
|
741
|
+
onClick={onExplain}
|
|
742
|
+
/>
|
|
743
|
+
) : null}
|
|
744
|
+
</div>
|
|
418
745
|
{badge}
|
|
419
746
|
</div>
|
|
420
747
|
{subtitle ? (
|
|
@@ -434,13 +761,9 @@ function PanelChrome({
|
|
|
434
761
|
)}
|
|
435
762
|
{onClose && (
|
|
436
763
|
<button
|
|
437
|
-
className=
|
|
438
|
-
closeReject
|
|
439
|
-
? 'hud-button hud-icon-button hud-panel-control hud-button-reject'
|
|
440
|
-
: 'hud-button hud-icon-button hud-panel-control'
|
|
441
|
-
}
|
|
764
|
+
className="hud-button hud-icon-button hud-panel-control"
|
|
442
765
|
type="button"
|
|
443
|
-
aria-label=
|
|
766
|
+
aria-label="Close"
|
|
444
767
|
onClick={onClose}
|
|
445
768
|
>
|
|
446
769
|
×
|
|
@@ -451,142 +774,9 @@ function PanelChrome({
|
|
|
451
774
|
)
|
|
452
775
|
}
|
|
453
776
|
|
|
454
|
-
function InitialInstructionField({
|
|
455
|
-
value,
|
|
456
|
-
onChange,
|
|
457
|
-
}: {
|
|
458
|
-
value: string
|
|
459
|
-
onChange: (value: string) => void
|
|
460
|
-
}) {
|
|
461
|
-
return (
|
|
462
|
-
<label className="hud-instruction">
|
|
463
|
-
<textarea
|
|
464
|
-
value={value}
|
|
465
|
-
maxLength={4000}
|
|
466
|
-
rows={4}
|
|
467
|
-
placeholder="What should the LLM build?"
|
|
468
|
-
onChange={(event) => onChange(event.target.value)}
|
|
469
|
-
onKeyDown={(event) => event.stopPropagation()}
|
|
470
|
-
/>
|
|
471
|
-
</label>
|
|
472
|
-
)
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
function formatFileSize(bytes: number) {
|
|
476
|
-
if (bytes < 1024) return `${bytes} B`
|
|
477
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
|
|
478
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
async function fileToBase64(file: File) {
|
|
482
|
-
const bytes = new Uint8Array(await file.arrayBuffer())
|
|
483
|
-
const chunk = 0x8000
|
|
484
|
-
let binary = ''
|
|
485
|
-
for (let offset = 0; offset < bytes.length; offset += chunk) {
|
|
486
|
-
binary += String.fromCharCode(...bytes.subarray(offset, offset + chunk))
|
|
487
|
-
}
|
|
488
|
-
return btoa(binary)
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
type ContextFileInfo = {
|
|
492
|
-
id: string
|
|
493
|
-
name: string
|
|
494
|
-
mimeType: string
|
|
495
|
-
size: number
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
function ContextFileDrop({
|
|
499
|
-
files,
|
|
500
|
-
busy,
|
|
501
|
-
error,
|
|
502
|
-
onAdd,
|
|
503
|
-
onRemove,
|
|
504
|
-
}: {
|
|
505
|
-
files: ContextFileInfo[]
|
|
506
|
-
busy: boolean
|
|
507
|
-
error: string | null
|
|
508
|
-
onAdd: (files: File[]) => void
|
|
509
|
-
onRemove: (fileId: string) => void
|
|
510
|
-
}) {
|
|
511
|
-
const inputRef = useRef<HTMLInputElement>(null)
|
|
512
|
-
const [over, setOver] = useState(false)
|
|
513
|
-
|
|
514
|
-
const takeFiles = (list: FileList | File[] | null) => {
|
|
515
|
-
if (!list || busy) return
|
|
516
|
-
const next = [...list].filter((file) => file.size > 0)
|
|
517
|
-
if (next.length > 0) onAdd(next)
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
return (
|
|
521
|
-
<div className="hud-context">
|
|
522
|
-
<button
|
|
523
|
-
className="hud-context-drop"
|
|
524
|
-
type="button"
|
|
525
|
-
data-over={over}
|
|
526
|
-
data-busy={busy}
|
|
527
|
-
disabled={busy}
|
|
528
|
-
aria-label="Attach files for the LLM"
|
|
529
|
-
onDragEnter={(event) => {
|
|
530
|
-
event.preventDefault()
|
|
531
|
-
if (event.dataTransfer.types.includes('Files')) setOver(true)
|
|
532
|
-
}}
|
|
533
|
-
onDragOver={(event) => {
|
|
534
|
-
event.preventDefault()
|
|
535
|
-
event.dataTransfer.dropEffect = 'copy'
|
|
536
|
-
}}
|
|
537
|
-
onDragLeave={(event) => {
|
|
538
|
-
if (event.currentTarget.contains(event.relatedTarget as Node)) return
|
|
539
|
-
setOver(false)
|
|
540
|
-
}}
|
|
541
|
-
onDrop={(event) => {
|
|
542
|
-
event.preventDefault()
|
|
543
|
-
event.stopPropagation()
|
|
544
|
-
setOver(false)
|
|
545
|
-
takeFiles(event.dataTransfer.files)
|
|
546
|
-
}}
|
|
547
|
-
onKeyDown={(event) => event.stopPropagation()}
|
|
548
|
-
onClick={() => inputRef.current?.click()}
|
|
549
|
-
>
|
|
550
|
-
<input
|
|
551
|
-
ref={inputRef}
|
|
552
|
-
type="file"
|
|
553
|
-
multiple
|
|
554
|
-
hidden
|
|
555
|
-
onChange={(event) => {
|
|
556
|
-
takeFiles(event.target.files)
|
|
557
|
-
event.target.value = ''
|
|
558
|
-
}}
|
|
559
|
-
/>
|
|
560
|
-
{busy ? 'Attaching…' : 'Drop files or click to attach'}
|
|
561
|
-
</button>
|
|
562
|
-
{files.length > 0 && (
|
|
563
|
-
<ul className="hud-context-list">
|
|
564
|
-
{files.map((file) => (
|
|
565
|
-
<li key={file.id}>
|
|
566
|
-
<span className="hud-context-name" title={file.name}>
|
|
567
|
-
{file.name}
|
|
568
|
-
</span>
|
|
569
|
-
<span className="hud-context-size">{formatFileSize(file.size)}</span>
|
|
570
|
-
<button
|
|
571
|
-
className="hud-item-remove"
|
|
572
|
-
type="button"
|
|
573
|
-
aria-label={`Remove ${file.name}`}
|
|
574
|
-
disabled={busy}
|
|
575
|
-
onClick={() => onRemove(file.id)}
|
|
576
|
-
>
|
|
577
|
-
×
|
|
578
|
-
</button>
|
|
579
|
-
</li>
|
|
580
|
-
))}
|
|
581
|
-
</ul>
|
|
582
|
-
)}
|
|
583
|
-
{error ? <p className="hud-context-error">{error}</p> : null}
|
|
584
|
-
</div>
|
|
585
|
-
)
|
|
586
|
-
}
|
|
587
|
-
|
|
588
777
|
function blueprintIsDefined(intent: AgentIntent) {
|
|
589
778
|
return (
|
|
779
|
+
Boolean(intent.localBlueprintEnabled) ||
|
|
590
780
|
(intent.userCreatedBlocks?.length ?? 0) > 0 ||
|
|
591
781
|
(intent.userCreatedIslands?.length ?? 0) > 0 ||
|
|
592
782
|
(intent.blueprintFunctions?.length ?? 0) > 0 ||
|
|
@@ -598,56 +788,18 @@ function blueprintIsDefined(intent: AgentIntent) {
|
|
|
598
788
|
}
|
|
599
789
|
|
|
600
790
|
function HandshakeSetup({
|
|
601
|
-
instruction,
|
|
602
|
-
onInstructionChange,
|
|
603
|
-
contextFiles,
|
|
604
|
-
contextBusy,
|
|
605
|
-
contextError,
|
|
606
|
-
onAddContextFiles,
|
|
607
|
-
onRemoveContextFile,
|
|
608
791
|
blueprintDefined,
|
|
609
792
|
awaitingAttach,
|
|
610
793
|
nextAttachLabel,
|
|
794
|
+
colorCommand,
|
|
611
795
|
}: {
|
|
612
|
-
instruction: string
|
|
613
|
-
onInstructionChange: (value: string) => void
|
|
614
|
-
contextFiles: ContextFileInfo[]
|
|
615
|
-
contextBusy: boolean
|
|
616
|
-
contextError: string | null
|
|
617
|
-
onAddContextFiles: (files: File[]) => void
|
|
618
|
-
onRemoveContextFile: (fileId: string) => void
|
|
619
796
|
blueprintDefined: boolean
|
|
620
797
|
awaitingAttach: boolean
|
|
621
798
|
nextAttachLabel: string | null
|
|
799
|
+
colorCommand?: string | null
|
|
622
800
|
}) {
|
|
623
801
|
return (
|
|
624
802
|
<div className="hud-setup">
|
|
625
|
-
<section
|
|
626
|
-
className="hud-setup-section"
|
|
627
|
-
onDragOver={(event) => {
|
|
628
|
-
if (event.dataTransfer.types.includes('Files')) event.preventDefault()
|
|
629
|
-
}}
|
|
630
|
-
onDrop={(event) => {
|
|
631
|
-
event.preventDefault()
|
|
632
|
-
const dropped = [...(event.dataTransfer.files ?? [])].filter(
|
|
633
|
-
(file) => file.size > 0,
|
|
634
|
-
)
|
|
635
|
-
if (dropped.length > 0) onAddContextFiles(dropped)
|
|
636
|
-
}}
|
|
637
|
-
>
|
|
638
|
-
<h2 className="hud-setup-heading">Instructions</h2>
|
|
639
|
-
<InitialInstructionField
|
|
640
|
-
value={instruction}
|
|
641
|
-
onChange={onInstructionChange}
|
|
642
|
-
/>
|
|
643
|
-
<ContextFileDrop
|
|
644
|
-
files={contextFiles}
|
|
645
|
-
busy={contextBusy}
|
|
646
|
-
error={contextError}
|
|
647
|
-
onAdd={onAddContextFiles}
|
|
648
|
-
onRemove={onRemoveContextFile}
|
|
649
|
-
/>
|
|
650
|
-
</section>
|
|
651
803
|
<section className="hud-setup-section">
|
|
652
804
|
<h2 className="hud-setup-heading">
|
|
653
805
|
Blueprint
|
|
@@ -659,27 +811,32 @@ function HandshakeSetup({
|
|
|
659
811
|
</span>
|
|
660
812
|
</h2>
|
|
661
813
|
<p>
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
814
|
+
Right-click to create files and folders. Open a file's
|
|
815
|
+
info panel to add functions, vars, and notes (instructions or
|
|
816
|
+
pseudo code). Every chat receives the global (blue) blueprint plus
|
|
817
|
+
this session's color.
|
|
666
818
|
</p>
|
|
667
819
|
</section>
|
|
668
820
|
<section className="hud-setup-section">
|
|
669
821
|
<h2 className="hud-setup-heading">Start</h2>
|
|
670
822
|
{awaitingAttach && nextAttachLabel ? (
|
|
671
|
-
|
|
672
|
-
<
|
|
673
|
-
|
|
674
|
-
|
|
823
|
+
<>
|
|
824
|
+
<p>
|
|
825
|
+
The next Cursor chat connects to {nextAttachLabel} first. This
|
|
826
|
+
session stays in the queue.
|
|
827
|
+
</p>
|
|
828
|
+
<ColorConnectHint colorCommand={colorCommand} queued />
|
|
829
|
+
</>
|
|
675
830
|
) : awaitingAttach ? (
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
831
|
+
<>
|
|
832
|
+
<p>
|
|
833
|
+
Open a Cursor chat to connect and start. A regular chat takes
|
|
834
|
+
the next empty slot.
|
|
835
|
+
</p>
|
|
836
|
+
<ColorConnectHint colorCommand={colorCommand} />
|
|
837
|
+
</>
|
|
679
838
|
) : (
|
|
680
|
-
<p>
|
|
681
|
-
This window is attached. Starting from <kbd>/inbase</kbd>…
|
|
682
|
-
</p>
|
|
839
|
+
<p>This window is attached. Starting from the Cursor chat…</p>
|
|
683
840
|
)}
|
|
684
841
|
</section>
|
|
685
842
|
</div>
|
|
@@ -701,7 +858,16 @@ function sessionLiveStatus(intent: AgentIntent) {
|
|
|
701
858
|
return { text: 'LLM wait timed out', busy: false }
|
|
702
859
|
}
|
|
703
860
|
if (intent.awaitingAttach) {
|
|
704
|
-
return { text: 'Waiting for
|
|
861
|
+
return { text: 'Waiting for a Cursor chat', busy: false }
|
|
862
|
+
}
|
|
863
|
+
if (intent.llmIdle) {
|
|
864
|
+
return { text: 'LLM disconnected', busy: false }
|
|
865
|
+
}
|
|
866
|
+
if (intent.pendingExplain) {
|
|
867
|
+
return { text: 'Starting an explanation…', busy: true }
|
|
868
|
+
}
|
|
869
|
+
if (kind === 'explain' && !intent.listening) {
|
|
870
|
+
return { text: 'LLM is explaining this proposal on the map', busy: true }
|
|
705
871
|
}
|
|
706
872
|
if (intent.status === 'pending') {
|
|
707
873
|
return intent.listening
|
|
@@ -743,9 +909,6 @@ function sessionLiveStatus(intent: AgentIntent) {
|
|
|
743
909
|
) {
|
|
744
910
|
return { text: 'LLM attached', busy: true }
|
|
745
911
|
}
|
|
746
|
-
if (intent.llmIdle) {
|
|
747
|
-
return { text: 'LLM is idle', busy: false }
|
|
748
|
-
}
|
|
749
912
|
return { text: 'LLM connected', busy: true }
|
|
750
913
|
}
|
|
751
914
|
|
|
@@ -753,12 +916,16 @@ function LiveStatus({
|
|
|
753
916
|
intent,
|
|
754
917
|
showStop = false,
|
|
755
918
|
onStop,
|
|
919
|
+
startingExplain = false,
|
|
756
920
|
}: {
|
|
757
921
|
intent: AgentIntent
|
|
758
922
|
showStop?: boolean
|
|
759
923
|
onStop?: () => void
|
|
924
|
+
startingExplain?: boolean
|
|
760
925
|
}) {
|
|
761
|
-
const status =
|
|
926
|
+
const status = startingExplain && !intent.pendingExplain
|
|
927
|
+
? { text: 'Starting an explanation…', busy: true }
|
|
928
|
+
: sessionLiveStatus(intent)
|
|
762
929
|
const [flash, setFlash] = useState(false)
|
|
763
930
|
const lastAt = intent.lastAck?.at
|
|
764
931
|
|
|
@@ -786,6 +953,12 @@ function LiveStatus({
|
|
|
786
953
|
)
|
|
787
954
|
}
|
|
788
955
|
|
|
956
|
+
function PlaceFilesHint() {
|
|
957
|
+
return (
|
|
958
|
+
<p className="hud-place-hint">Right-click to create files and folders.</p>
|
|
959
|
+
)
|
|
960
|
+
}
|
|
961
|
+
|
|
789
962
|
type SessionPanelProps = {
|
|
790
963
|
intent: AgentIntent
|
|
791
964
|
focused: boolean
|
|
@@ -811,14 +984,7 @@ function SessionPanel({
|
|
|
811
984
|
}: SessionPanelProps) {
|
|
812
985
|
const [minimized, setMinimized] = useState(false)
|
|
813
986
|
const [instruction, setInstruction] = useState('')
|
|
814
|
-
const [
|
|
815
|
-
() => intent.initialInstruction ?? '',
|
|
816
|
-
)
|
|
817
|
-
const [contextFiles, setContextFiles] = useState<ContextFileInfo[]>(
|
|
818
|
-
() => intent.contextFiles ?? [],
|
|
819
|
-
)
|
|
820
|
-
const [contextBusy, setContextBusy] = useState(false)
|
|
821
|
-
const [contextError, setContextError] = useState<string | null>(null)
|
|
987
|
+
const [startingExplain, setStartingExplain] = useState(false)
|
|
822
988
|
const sessionId = intent.sessionId
|
|
823
989
|
const latestEntry = intent.isActiveDiff ? intent.chain.at(-1) : null
|
|
824
990
|
const pending =
|
|
@@ -863,8 +1029,10 @@ function SessionPanel({
|
|
|
863
1029
|
planReady && !working && proposalStep === null
|
|
864
1030
|
? (intent.steps.find((step) => !acceptedSteps.has(step.index)) ?? null)
|
|
865
1031
|
: null
|
|
866
|
-
const
|
|
867
|
-
|
|
1032
|
+
const llmDisconnected =
|
|
1033
|
+
Boolean(intent.llmIdle) && intent.awaitingAttach === false
|
|
1034
|
+
const canRunNext = stepByStep && Boolean(invokeStep) && !llmDisconnected
|
|
1035
|
+
const canAcceptProposal = proposalStep !== null && !llmDisconnected
|
|
868
1036
|
const lastStep =
|
|
869
1037
|
typeof proposalStep === 'number' &&
|
|
870
1038
|
intent.steps.length > 0 &&
|
|
@@ -877,75 +1045,29 @@ function SessionPanel({
|
|
|
877
1045
|
|
|
878
1046
|
useEffect(() => {
|
|
879
1047
|
setInstruction('')
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
useEffect(() => {
|
|
883
|
-
setInitialInstruction(intent.initialInstruction ?? '')
|
|
884
|
-
setContextFiles(intent.contextFiles ?? [])
|
|
885
|
-
setContextError(null)
|
|
886
|
-
}, [sessionId])
|
|
1048
|
+
setStartingExplain(false)
|
|
1049
|
+
}, [intent.diffId, sessionId])
|
|
887
1050
|
|
|
888
1051
|
useEffect(() => {
|
|
889
|
-
if (
|
|
890
|
-
|
|
891
|
-
}, [contextBusy, intent.contextFiles])
|
|
1052
|
+
if (intent.pendingExplain) setStartingExplain(false)
|
|
1053
|
+
}, [intent.pendingExplain])
|
|
892
1054
|
|
|
893
1055
|
if (!sessionId || !isReviewingIntent(intent.status)) return null
|
|
894
1056
|
|
|
895
|
-
const
|
|
896
|
-
setInitialInstruction(value)
|
|
897
|
-
persistInitialInstruction(sessionId, value)
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
const addContextFiles = (files: File[]) => {
|
|
901
|
-
setContextBusy(true)
|
|
902
|
-
setContextError(null)
|
|
903
|
-
void Promise.all(
|
|
904
|
-
files.map(async (file) => ({
|
|
905
|
-
name: file.name,
|
|
906
|
-
mimeType: file.type || 'application/octet-stream',
|
|
907
|
-
contentBase64: await fileToBase64(file),
|
|
908
|
-
})),
|
|
909
|
-
)
|
|
910
|
-
.then((payload) => persistAddContextFiles(sessionId, payload))
|
|
911
|
-
.then((next) => {
|
|
912
|
-
setContextFiles(next.contextFiles ?? [])
|
|
913
|
-
})
|
|
914
|
-
.catch((caught) => {
|
|
915
|
-
setContextError(
|
|
916
|
-
caught instanceof Error ? caught.message : 'Could not attach files',
|
|
917
|
-
)
|
|
918
|
-
})
|
|
919
|
-
.finally(() => setContextBusy(false))
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
const removeContextFile = (fileId: string) => {
|
|
923
|
-
setContextBusy(true)
|
|
924
|
-
setContextError(null)
|
|
925
|
-
void persistRemoveContextFile(sessionId, fileId)
|
|
926
|
-
.then((next) => {
|
|
927
|
-
setContextFiles(next.contextFiles ?? [])
|
|
928
|
-
})
|
|
929
|
-
.catch((caught) => {
|
|
930
|
-
setContextError(
|
|
931
|
-
caught instanceof Error ? caught.message : 'Could not remove file',
|
|
932
|
-
)
|
|
933
|
-
})
|
|
934
|
-
.finally(() => setContextBusy(false))
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
const showInitialInstruction =
|
|
1057
|
+
const handshakeSetup =
|
|
938
1058
|
Boolean(intent.awaitingAttach) &&
|
|
939
1059
|
(askingBlueprint || sendingBlueprint || preparing)
|
|
940
|
-
const handshakeSetup = showInitialInstruction
|
|
941
1060
|
const llmConnected = intent.awaitingAttach === false
|
|
942
1061
|
const showConnectedProgress =
|
|
943
|
-
llmConnected && (askingBlueprint || sendingBlueprint || preparing)
|
|
1062
|
+
llmConnected && !llmDisconnected && (askingBlueprint || sendingBlueprint || preparing)
|
|
1063
|
+
const showPlaceHint =
|
|
1064
|
+
canPlace && !intent.working && !askingBlueprint && !sendingBlueprint
|
|
1065
|
+
const liveHasStop = showConnectedProgress || working || llmDisconnected
|
|
944
1066
|
const queuedBehind =
|
|
945
1067
|
intent.awaitingAttach &&
|
|
946
1068
|
nextAttachSession &&
|
|
947
1069
|
nextAttachSession.sessionId !== sessionId
|
|
948
|
-
?
|
|
1070
|
+
? sessionDisplayName(nextAttachSession) || 'an earlier session'
|
|
949
1071
|
: null
|
|
950
1072
|
|
|
951
1073
|
const act = (
|
|
@@ -962,50 +1084,64 @@ function SessionPanel({
|
|
|
962
1084
|
}
|
|
963
1085
|
data-minimized={minimized}
|
|
964
1086
|
data-focused={focused}
|
|
965
|
-
data-attached={llmConnected}
|
|
1087
|
+
data-attached={llmConnected && !llmDisconnected}
|
|
966
1088
|
onPointerDown={onFocus}
|
|
967
1089
|
>
|
|
968
1090
|
<PanelChrome
|
|
969
1091
|
title={
|
|
970
|
-
|
|
971
|
-
|
|
1092
|
+
<>
|
|
1093
|
+
<SessionSwatch colorHex={intent.colorHex} />
|
|
1094
|
+
<span className="hud-panel-chrome-title-text">
|
|
1095
|
+
{sessionDisplayName(intent) ||
|
|
1096
|
+
(showConnectedProgress
|
|
1097
|
+
? 'LLM connected'
|
|
1098
|
+
: reviewTitle(intent.status))}
|
|
1099
|
+
</span>
|
|
1100
|
+
</>
|
|
972
1101
|
}
|
|
973
1102
|
subtitle={
|
|
974
1103
|
sessionLabel(intent)
|
|
975
1104
|
? showConnectedProgress
|
|
976
1105
|
? 'LLM connected'
|
|
977
1106
|
: reviewTitle(intent.status)
|
|
978
|
-
:
|
|
1107
|
+
: sessionColorName(intent)
|
|
1108
|
+
? showConnectedProgress
|
|
1109
|
+
? 'LLM connected'
|
|
1110
|
+
: reviewTitle(intent.status)
|
|
1111
|
+
: undefined
|
|
979
1112
|
}
|
|
980
|
-
badge={<AttachStateBadge attached={llmConnected} />}
|
|
1113
|
+
badge={<AttachStateBadge attached={llmConnected && !llmDisconnected} />}
|
|
981
1114
|
minimized={minimized}
|
|
982
1115
|
onMinimize={() => setMinimized((current) => !current)}
|
|
983
|
-
onClose={() => act('stop')}
|
|
984
|
-
closeLabel="Stop session"
|
|
985
|
-
closeReject
|
|
986
1116
|
/>
|
|
987
1117
|
{!minimized && (
|
|
988
1118
|
<>
|
|
989
1119
|
{!intent.awaitingAttach && (
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
1120
|
+
<>
|
|
1121
|
+
{showPlaceHint && !planReady && !pending && <PlaceFilesHint />}
|
|
1122
|
+
<LiveStatus
|
|
1123
|
+
intent={intent}
|
|
1124
|
+
showStop={liveHasStop}
|
|
1125
|
+
startingExplain={startingExplain}
|
|
1126
|
+
onStop={() => act('stop')}
|
|
1127
|
+
/>
|
|
1128
|
+
</>
|
|
995
1129
|
)}
|
|
996
|
-
|
|
997
|
-
<
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1130
|
+
{!llmDisconnected && (
|
|
1131
|
+
<label className="hud-mode-switch">
|
|
1132
|
+
<span>Step by step</span>
|
|
1133
|
+
<button
|
|
1134
|
+
className="hud-switch"
|
|
1135
|
+
type="button"
|
|
1136
|
+
role="switch"
|
|
1137
|
+
aria-checked={stepByStep}
|
|
1138
|
+
aria-label="Step by step"
|
|
1139
|
+
onKeyDown={(event) => event.stopPropagation()}
|
|
1140
|
+
onClick={() => act('set_step_by_step', { stepByStep: !stepByStep })}
|
|
1141
|
+
/>
|
|
1142
|
+
</label>
|
|
1143
|
+
)}
|
|
1144
|
+
{!llmDisconnected && !stepByStep && (
|
|
1009
1145
|
<p className="hud-mode-hint">
|
|
1010
1146
|
LLM implements the full plan. You can still walk the diffs, then
|
|
1011
1147
|
Accept proposal.
|
|
@@ -1013,41 +1149,49 @@ function SessionPanel({
|
|
|
1013
1149
|
)}
|
|
1014
1150
|
{handshakeSetup ? (
|
|
1015
1151
|
<HandshakeSetup
|
|
1016
|
-
instruction={initialInstruction}
|
|
1017
|
-
onInstructionChange={updateInitialInstruction}
|
|
1018
|
-
contextFiles={contextFiles}
|
|
1019
|
-
contextBusy={contextBusy}
|
|
1020
|
-
contextError={contextError}
|
|
1021
|
-
onAddContextFiles={addContextFiles}
|
|
1022
|
-
onRemoveContextFile={removeContextFile}
|
|
1023
1152
|
blueprintDefined={blueprintIsDefined(intent)}
|
|
1024
1153
|
awaitingAttach={Boolean(intent.awaitingAttach)}
|
|
1025
1154
|
nextAttachLabel={queuedBehind}
|
|
1155
|
+
colorCommand={sessionSlashCommand(intent)}
|
|
1026
1156
|
/>
|
|
1027
1157
|
) : intent.awaitingAttach ? (
|
|
1028
1158
|
queuedBehind ? (
|
|
1029
|
-
<
|
|
1030
|
-
<
|
|
1031
|
-
|
|
1032
|
-
|
|
1159
|
+
<div className="hud-mode-hint">
|
|
1160
|
+
<p>
|
|
1161
|
+
The next Cursor chat connects to {queuedBehind} first. This
|
|
1162
|
+
session stays in the queue.
|
|
1163
|
+
</p>
|
|
1164
|
+
<ColorConnectHint
|
|
1165
|
+
colorCommand={sessionSlashCommand(intent)}
|
|
1166
|
+
queued
|
|
1167
|
+
/>
|
|
1168
|
+
</div>
|
|
1033
1169
|
) : (
|
|
1034
|
-
<
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1170
|
+
<div className="hud-mode-hint">
|
|
1171
|
+
<p>
|
|
1172
|
+
No LLM is attached. Open a Cursor chat — it connects to the
|
|
1173
|
+
next waiting session.
|
|
1174
|
+
</p>
|
|
1175
|
+
<ColorConnectHint colorCommand={sessionSlashCommand(intent)} />
|
|
1176
|
+
</div>
|
|
1038
1177
|
)
|
|
1039
1178
|
) : null}
|
|
1179
|
+
{llmDisconnected ? (
|
|
1180
|
+
<p className="hud-mode-hint">
|
|
1181
|
+
This chat is no longer connected. The session will reset.
|
|
1182
|
+
</p>
|
|
1183
|
+
) : null}
|
|
1040
1184
|
{intent.feature &&
|
|
1041
1185
|
!handshakeSetup &&
|
|
1042
1186
|
intent.feature.trim() !== sessionLabel(intent) && (
|
|
1043
1187
|
<p className="hud-feature">{intent.feature}</p>
|
|
1044
1188
|
)}
|
|
1045
|
-
{askingBlueprint && !handshakeSetup && !showConnectedProgress ? (
|
|
1189
|
+
{askingBlueprint && !handshakeSetup && !showConnectedProgress && !llmDisconnected ? (
|
|
1046
1190
|
<>
|
|
1047
1191
|
<p>
|
|
1048
|
-
|
|
1049
|
-
session to the LLM, or skip and let it
|
|
1050
|
-
layout.
|
|
1192
|
+
This chat receives the global (blue) blueprint and this
|
|
1193
|
+
session's color. Send it to the LLM, or skip and let it
|
|
1194
|
+
continue with the current layout.
|
|
1051
1195
|
</p>
|
|
1052
1196
|
<div className="hud-decide">
|
|
1053
1197
|
<button
|
|
@@ -1076,20 +1220,13 @@ function SessionPanel({
|
|
|
1076
1220
|
) : handshakeSetup ? null : intent.status === 'finished' ? (
|
|
1077
1221
|
<p>All plan steps were applied.</p>
|
|
1078
1222
|
) : showConnectedProgress || preparing ? null : (
|
|
1079
|
-
<p>
|
|
1223
|
+
<p className="hud-step-label">
|
|
1080
1224
|
{stepLabel}
|
|
1081
1225
|
{intent.reason ? ` · ${intent.reason}` : ''}
|
|
1082
1226
|
</p>
|
|
1083
1227
|
)}
|
|
1084
1228
|
{!askingBlueprint && !sendingBlueprint && (
|
|
1085
1229
|
<>
|
|
1086
|
-
{canPlace && !intent.working && (
|
|
1087
|
-
<p>
|
|
1088
|
-
On the map, right-click to add a file or folder.{' '}
|
|
1089
|
-
<kbd>Space</kbd> places a file, <kbd>B</kbd> an island while
|
|
1090
|
-
walking.
|
|
1091
|
-
</p>
|
|
1092
|
-
)}
|
|
1093
1230
|
{intent.steps?.length > 0 && (
|
|
1094
1231
|
<ol className="hud-steps">
|
|
1095
1232
|
{intent.steps.map((step) => {
|
|
@@ -1097,6 +1234,8 @@ function SessionPanel({
|
|
|
1097
1234
|
const processing = processingStep === step.index
|
|
1098
1235
|
const accepted = acceptedSteps.has(step.index) && !proposed
|
|
1099
1236
|
const creating = processing && !proposed
|
|
1237
|
+
const explaining =
|
|
1238
|
+
startingExplain || Boolean(intent.pendingExplain)
|
|
1100
1239
|
const showStepAction =
|
|
1101
1240
|
creating ||
|
|
1102
1241
|
(canRunNext && invokeStep?.index === step.index) ||
|
|
@@ -1111,29 +1250,54 @@ function SessionPanel({
|
|
|
1111
1250
|
<span className="hud-step-main">
|
|
1112
1251
|
<span className="hud-step-title">{step.title}</span>
|
|
1113
1252
|
{showStepAction && (
|
|
1114
|
-
<
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
?
|
|
1253
|
+
<span className="hud-step-actions">
|
|
1254
|
+
<button
|
|
1255
|
+
className="hud-button hud-button-approve hud-run-step"
|
|
1256
|
+
type="button"
|
|
1257
|
+
disabled={creating}
|
|
1258
|
+
aria-busy={creating}
|
|
1259
|
+
onClick={() =>
|
|
1260
|
+
proposed
|
|
1261
|
+
? lastStep
|
|
1262
|
+
? act('continue')
|
|
1263
|
+
: act('invoke', {
|
|
1264
|
+
step: step.index + 1,
|
|
1265
|
+
})
|
|
1123
1266
|
: act('invoke', {
|
|
1124
|
-
step: step.index
|
|
1267
|
+
step: step.index,
|
|
1125
1268
|
})
|
|
1126
|
-
|
|
1127
|
-
|
|
1269
|
+
}
|
|
1270
|
+
>
|
|
1271
|
+
{proposed
|
|
1272
|
+
? 'Accept proposal'
|
|
1273
|
+
: creating
|
|
1274
|
+
? 'Creating proposal…'
|
|
1275
|
+
: 'Create proposal'}
|
|
1276
|
+
</button>
|
|
1277
|
+
{!creating && (
|
|
1278
|
+
<button
|
|
1279
|
+
className="hud-button hud-button-approve hud-run-step"
|
|
1280
|
+
type="button"
|
|
1281
|
+
disabled={explaining}
|
|
1282
|
+
aria-busy={explaining}
|
|
1283
|
+
onClick={() => {
|
|
1284
|
+
setStartingExplain(true)
|
|
1285
|
+
void Promise.resolve(
|
|
1286
|
+
onWorkflowAction(
|
|
1287
|
+
sessionId,
|
|
1288
|
+
'explain_proposal',
|
|
1289
|
+
),
|
|
1290
|
+
).then((ok) => {
|
|
1291
|
+
if (ok === false) setStartingExplain(false)
|
|
1128
1292
|
})
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
</
|
|
1293
|
+
}}
|
|
1294
|
+
>
|
|
1295
|
+
{explaining
|
|
1296
|
+
? 'Starting explanation…'
|
|
1297
|
+
: 'Explain proposal'}
|
|
1298
|
+
</button>
|
|
1299
|
+
)}
|
|
1300
|
+
</span>
|
|
1137
1301
|
)}
|
|
1138
1302
|
</span>
|
|
1139
1303
|
</li>
|
|
@@ -1200,7 +1364,7 @@ function SessionPanel({
|
|
|
1200
1364
|
{(intent.createFolders ?? []).length > 0 && (
|
|
1201
1365
|
<>
|
|
1202
1366
|
<div className="hud-section-title hud-section-title-add">
|
|
1203
|
-
Added
|
|
1367
|
+
Added folders
|
|
1204
1368
|
</div>
|
|
1205
1369
|
<ul>
|
|
1206
1370
|
{intent.createFolders.map((id) => (
|
|
@@ -1276,17 +1440,20 @@ function SessionPanel({
|
|
|
1276
1440
|
)}
|
|
1277
1441
|
</MutationFold>
|
|
1278
1442
|
{planReady && (
|
|
1279
|
-
<div className="hud-
|
|
1280
|
-
<
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1443
|
+
<div className="hud-session-actions">
|
|
1444
|
+
{showPlaceHint && <PlaceFilesHint />}
|
|
1445
|
+
<div className="hud-decide">
|
|
1446
|
+
<button
|
|
1447
|
+
className="hud-button hud-button-reject"
|
|
1448
|
+
type="button"
|
|
1449
|
+
onClick={() => act('stop')}
|
|
1450
|
+
>
|
|
1451
|
+
Stop
|
|
1452
|
+
</button>
|
|
1453
|
+
</div>
|
|
1287
1454
|
</div>
|
|
1288
1455
|
)}
|
|
1289
|
-
{pending && (
|
|
1456
|
+
{pending && !llmDisconnected && (
|
|
1290
1457
|
<>
|
|
1291
1458
|
<label className="hud-instruction">
|
|
1292
1459
|
<span>Alternative instruction for the LLM</span>
|
|
@@ -1299,24 +1466,27 @@ function SessionPanel({
|
|
|
1299
1466
|
onKeyDown={(event) => event.stopPropagation()}
|
|
1300
1467
|
/>
|
|
1301
1468
|
</label>
|
|
1302
|
-
<div className="hud-
|
|
1303
|
-
<
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1469
|
+
<div className="hud-session-actions">
|
|
1470
|
+
{showPlaceHint && <PlaceFilesHint />}
|
|
1471
|
+
<div className="hud-decide">
|
|
1472
|
+
<button
|
|
1473
|
+
className="hud-button hud-button-extend"
|
|
1474
|
+
type="button"
|
|
1475
|
+
disabled={!instruction.trim()}
|
|
1476
|
+
onClick={() =>
|
|
1477
|
+
act('instruct', { instruction })
|
|
1478
|
+
}
|
|
1479
|
+
>
|
|
1480
|
+
Send instruction
|
|
1481
|
+
</button>
|
|
1482
|
+
<button
|
|
1483
|
+
className="hud-button hud-button-reject"
|
|
1484
|
+
type="button"
|
|
1485
|
+
onClick={() => act('stop')}
|
|
1486
|
+
>
|
|
1487
|
+
Stop
|
|
1488
|
+
</button>
|
|
1489
|
+
</div>
|
|
1320
1490
|
</div>
|
|
1321
1491
|
</>
|
|
1322
1492
|
)}
|
|
@@ -1391,7 +1561,7 @@ function BranchChangesPanel({
|
|
|
1391
1561
|
{(changes.createFolders ?? []).length > 0 && (
|
|
1392
1562
|
<>
|
|
1393
1563
|
<div className="hud-section-title hud-section-title-add">
|
|
1394
|
-
Added
|
|
1564
|
+
Added folders
|
|
1395
1565
|
</div>
|
|
1396
1566
|
<ul>
|
|
1397
1567
|
{changes.createFolders.map((id) => (
|
|
@@ -1459,7 +1629,7 @@ type ExplorerInstruction = {
|
|
|
1459
1629
|
label: string
|
|
1460
1630
|
}
|
|
1461
1631
|
|
|
1462
|
-
type InstructionView = 'walk' | '
|
|
1632
|
+
type InstructionView = 'walk' | 'map'
|
|
1463
1633
|
|
|
1464
1634
|
type ExplorerInstructionSection = {
|
|
1465
1635
|
id: InstructionView
|
|
@@ -1492,10 +1662,7 @@ function explorerInstructions({
|
|
|
1492
1662
|
changePathsOnly,
|
|
1493
1663
|
selectedUserCreated,
|
|
1494
1664
|
infoVisible,
|
|
1495
|
-
thumbnailVisible,
|
|
1496
1665
|
importedBy,
|
|
1497
|
-
canStop,
|
|
1498
|
-
sessionCount,
|
|
1499
1666
|
showBranchChanges,
|
|
1500
1667
|
canShowBranchChanges,
|
|
1501
1668
|
}: {
|
|
@@ -1504,10 +1671,7 @@ function explorerInstructions({
|
|
|
1504
1671
|
changePathsOnly: boolean
|
|
1505
1672
|
selectedUserCreated: boolean
|
|
1506
1673
|
infoVisible: boolean
|
|
1507
|
-
thumbnailVisible: boolean
|
|
1508
1674
|
importedBy: boolean
|
|
1509
|
-
canStop: boolean
|
|
1510
|
-
sessionCount: number
|
|
1511
1675
|
showBranchChanges: boolean
|
|
1512
1676
|
canShowBranchChanges: boolean
|
|
1513
1677
|
}): ExplorerInstructionSection[] {
|
|
@@ -1541,23 +1705,6 @@ function explorerInstructions({
|
|
|
1541
1705
|
},
|
|
1542
1706
|
]
|
|
1543
1707
|
: []
|
|
1544
|
-
const stop: ExplorerInstruction[] = canStop
|
|
1545
|
-
? [
|
|
1546
|
-
{
|
|
1547
|
-
id: 'stop',
|
|
1548
|
-
keys: ['Stop'],
|
|
1549
|
-
label:
|
|
1550
|
-
sessionCount > 1
|
|
1551
|
-
? 'Ends the focused LLM session'
|
|
1552
|
-
: 'Ends this LLM session',
|
|
1553
|
-
},
|
|
1554
|
-
]
|
|
1555
|
-
: []
|
|
1556
|
-
const thumbnail: ExplorerInstruction = {
|
|
1557
|
-
id: 'thumbnail',
|
|
1558
|
-
keys: ['T'],
|
|
1559
|
-
label: thumbnailVisible ? 'Hide 3D view' : 'Show 3D view',
|
|
1560
|
-
}
|
|
1561
1708
|
return [
|
|
1562
1709
|
{
|
|
1563
1710
|
id: 'walk',
|
|
@@ -1568,8 +1715,6 @@ function explorerInstructions({
|
|
|
1568
1715
|
{ id: 'shift', keys: ['Shift'], label: 'Sprint' },
|
|
1569
1716
|
...(canPlace
|
|
1570
1717
|
? [
|
|
1571
|
-
{ id: 'space', keys: ['Space'], label: 'Place file' },
|
|
1572
|
-
{ id: 'b-island', keys: ['B'], label: 'Place island' },
|
|
1573
1718
|
{
|
|
1574
1719
|
id: 'point-to',
|
|
1575
1720
|
keys: ['Point to'],
|
|
@@ -1581,7 +1726,7 @@ function explorerInstructions({
|
|
|
1581
1726
|
{
|
|
1582
1727
|
id: 'dblclick-info',
|
|
1583
1728
|
keys: ['Double-click'],
|
|
1584
|
-
label: 'A
|
|
1729
|
+
label: 'A file or folder for info',
|
|
1585
1730
|
},
|
|
1586
1731
|
{ id: 'aim-line', keys: ['Click'], label: 'Aim a line to fly' },
|
|
1587
1732
|
...info,
|
|
@@ -1590,26 +1735,33 @@ function explorerInstructions({
|
|
|
1590
1735
|
{
|
|
1591
1736
|
id: 'update-model',
|
|
1592
1737
|
keys: ['Update model'],
|
|
1593
|
-
label: 'Rescan and
|
|
1738
|
+
label: 'Rescan files and folders',
|
|
1739
|
+
},
|
|
1740
|
+
{
|
|
1741
|
+
id: 'cursor-chat',
|
|
1742
|
+
keys: ['Cursor chat'],
|
|
1743
|
+
label:
|
|
1744
|
+
'Connects to the next empty session, or /coral /amber /lime /orange /violet for that color; /explain for explain mode; 5 chats at once',
|
|
1594
1745
|
},
|
|
1595
1746
|
{
|
|
1596
|
-
id: '
|
|
1597
|
-
keys: ['
|
|
1598
|
-
label:
|
|
1747
|
+
id: 'blueprint-select',
|
|
1748
|
+
keys: ['Blueprint colors'],
|
|
1749
|
+
label:
|
|
1750
|
+
'All colors stay visible; the selected color is where new files go',
|
|
1599
1751
|
},
|
|
1600
1752
|
{
|
|
1601
1753
|
id: 'blueprint-toggle',
|
|
1602
|
-
keys: ['Hide/Show
|
|
1603
|
-
label: '
|
|
1754
|
+
keys: ['Hide/Show'],
|
|
1755
|
+
label: 'Hide this color; other blueprint colors stay visible',
|
|
1604
1756
|
},
|
|
1605
1757
|
{
|
|
1606
1758
|
id: 'blueprint-clear',
|
|
1607
|
-
keys: ['Clear
|
|
1759
|
+
keys: ['Clear'],
|
|
1608
1760
|
label: 'Remove every planned file and folder',
|
|
1609
1761
|
},
|
|
1610
1762
|
{
|
|
1611
1763
|
id: 'blueprint-cleanup',
|
|
1612
|
-
keys: ['Cleanup
|
|
1764
|
+
keys: ['Cleanup'],
|
|
1613
1765
|
label: 'Drop blueprint files and folders that already exist',
|
|
1614
1766
|
},
|
|
1615
1767
|
{ id: 'toggle-map', keys: ['M'], label: 'Toggle map' },
|
|
@@ -1618,26 +1770,6 @@ function explorerInstructions({
|
|
|
1618
1770
|
keys: ['Double-click', 'Esc'],
|
|
1619
1771
|
label: 'Release mouse',
|
|
1620
1772
|
},
|
|
1621
|
-
...stop,
|
|
1622
|
-
],
|
|
1623
|
-
},
|
|
1624
|
-
{
|
|
1625
|
-
id: '3dview',
|
|
1626
|
-
title: '3D view',
|
|
1627
|
-
items: [
|
|
1628
|
-
{ id: 'scroll-zoom', keys: ['Scroll'], label: 'Zoom' },
|
|
1629
|
-
{ id: 'drag-pan', keys: ['Drag'], label: 'Pan' },
|
|
1630
|
-
{
|
|
1631
|
-
id: 'cmd-drag-rotate',
|
|
1632
|
-
keys: ['⌘', 'Ctrl', 'Drag'],
|
|
1633
|
-
label: 'Rotate',
|
|
1634
|
-
},
|
|
1635
|
-
{
|
|
1636
|
-
id: 'dblclick-reset',
|
|
1637
|
-
keys: ['Double-click'],
|
|
1638
|
-
label: 'Reset view',
|
|
1639
|
-
},
|
|
1640
|
-
thumbnail,
|
|
1641
1773
|
],
|
|
1642
1774
|
},
|
|
1643
1775
|
{
|
|
@@ -1646,26 +1778,26 @@ function explorerInstructions({
|
|
|
1646
1778
|
items: [
|
|
1647
1779
|
{ id: 'scroll-zoom', keys: ['Scroll'], label: 'Zoom' },
|
|
1648
1780
|
{ id: 'drag-pan', keys: ['Drag'], label: 'Pan' },
|
|
1649
|
-
{ id: 'click-block', keys: ['Click'], label: 'A
|
|
1781
|
+
{ id: 'click-block', keys: ['Click'], label: 'A file for info' },
|
|
1650
1782
|
{
|
|
1651
1783
|
id: 'click-island',
|
|
1652
1784
|
keys: ['Click'],
|
|
1653
|
-
label: '
|
|
1785
|
+
label: 'A folder for its files',
|
|
1654
1786
|
},
|
|
1655
1787
|
...(canPlace
|
|
1656
1788
|
? [
|
|
1657
|
-
{ id: 'select-island', keys: ['Click'], label: 'Select
|
|
1789
|
+
{ id: 'select-island', keys: ['Click'], label: 'Select a folder' },
|
|
1658
1790
|
{
|
|
1659
1791
|
id: 'add-file-folder',
|
|
1660
1792
|
keys: ['Right-click'],
|
|
1661
|
-
label: '
|
|
1793
|
+
label: 'Create a file or folder, or point to a folder',
|
|
1662
1794
|
},
|
|
1663
1795
|
]
|
|
1664
1796
|
: []),
|
|
1665
1797
|
{
|
|
1666
1798
|
id: 'option-click-walk',
|
|
1667
1799
|
keys: ['Option', 'Click'],
|
|
1668
|
-
label: '
|
|
1800
|
+
label: 'A folder to walk',
|
|
1669
1801
|
},
|
|
1670
1802
|
{
|
|
1671
1803
|
id: 'gold-pin',
|
|
@@ -1685,36 +1817,41 @@ function explorerInstructions({
|
|
|
1685
1817
|
: []),
|
|
1686
1818
|
...backspace,
|
|
1687
1819
|
...info,
|
|
1688
|
-
thumbnail,
|
|
1689
1820
|
imported,
|
|
1690
1821
|
...branch,
|
|
1691
1822
|
{
|
|
1692
1823
|
id: 'update-model',
|
|
1693
1824
|
keys: ['Update model'],
|
|
1694
|
-
label: 'Rescan and
|
|
1825
|
+
label: 'Rescan files and folders',
|
|
1695
1826
|
},
|
|
1696
1827
|
{
|
|
1697
|
-
id: '
|
|
1698
|
-
keys: ['
|
|
1699
|
-
label:
|
|
1828
|
+
id: 'cursor-chat',
|
|
1829
|
+
keys: ['Cursor chat'],
|
|
1830
|
+
label:
|
|
1831
|
+
'Connects to the next empty session, or /coral /amber /lime /orange /violet for that color; /explain for explain mode; 5 chats at once',
|
|
1832
|
+
},
|
|
1833
|
+
{
|
|
1834
|
+
id: 'blueprint-select',
|
|
1835
|
+
keys: ['Blueprint colors'],
|
|
1836
|
+
label:
|
|
1837
|
+
'All colors stay visible; the selected color is where new files go',
|
|
1700
1838
|
},
|
|
1701
1839
|
{
|
|
1702
1840
|
id: 'blueprint-toggle',
|
|
1703
|
-
keys: ['Hide/Show
|
|
1704
|
-
label: '
|
|
1841
|
+
keys: ['Hide/Show'],
|
|
1842
|
+
label: 'Hide this color; other blueprint colors stay visible',
|
|
1705
1843
|
},
|
|
1706
1844
|
{
|
|
1707
1845
|
id: 'blueprint-clear',
|
|
1708
|
-
keys: ['Clear
|
|
1846
|
+
keys: ['Clear'],
|
|
1709
1847
|
label: 'Remove every planned file and folder',
|
|
1710
1848
|
},
|
|
1711
1849
|
{
|
|
1712
1850
|
id: 'blueprint-cleanup',
|
|
1713
|
-
keys: ['Cleanup
|
|
1851
|
+
keys: ['Cleanup'],
|
|
1714
1852
|
label: 'Drop blueprint files and folders that already exist',
|
|
1715
1853
|
},
|
|
1716
1854
|
{ id: 'map-walk', keys: ['M'], label: 'Back to walk' },
|
|
1717
|
-
...stop,
|
|
1718
1855
|
],
|
|
1719
1856
|
},
|
|
1720
1857
|
]
|
|
@@ -1722,14 +1859,12 @@ function explorerInstructions({
|
|
|
1722
1859
|
|
|
1723
1860
|
type HUDProps = {
|
|
1724
1861
|
graph: CodebaseGraph
|
|
1725
|
-
layout: WorldLayout
|
|
1726
1862
|
mode: ViewMode
|
|
1727
1863
|
locked: boolean
|
|
1728
1864
|
selectedId: string | null
|
|
1729
1865
|
selectedTick?: number
|
|
1730
1866
|
inspectTick?: number
|
|
1731
1867
|
selectedFolder?: string | null
|
|
1732
|
-
landAt: [number, number]
|
|
1733
1868
|
aimedRelation: AimedRelation | null
|
|
1734
1869
|
aimedFileId?: string | null
|
|
1735
1870
|
intent: AgentIntent
|
|
@@ -1737,7 +1872,6 @@ type HUDProps = {
|
|
|
1737
1872
|
focusedSessionId?: string | null
|
|
1738
1873
|
nextAttachSessionId?: string | null
|
|
1739
1874
|
onFocusSession?: (sessionId: string) => void
|
|
1740
|
-
onSetupSession?: () => Promise<unknown>
|
|
1741
1875
|
onWorkflowAction: (
|
|
1742
1876
|
sessionId: string,
|
|
1743
1877
|
action: WorkflowAction,
|
|
@@ -1746,8 +1880,6 @@ type HUDProps = {
|
|
|
1746
1880
|
onNavigateDiff: (sessionId: string, diffId: string) => void
|
|
1747
1881
|
onOpenMap: () => void
|
|
1748
1882
|
onWalk: () => void
|
|
1749
|
-
followLook: boolean
|
|
1750
|
-
onToggleFollowLook: () => void
|
|
1751
1883
|
showBranchChanges?: boolean
|
|
1752
1884
|
branchChanges?: BranchChanges
|
|
1753
1885
|
canShowBranchChanges?: boolean
|
|
@@ -1772,6 +1904,9 @@ type HUDProps = {
|
|
|
1772
1904
|
onAddBlueprintFunction?: (fileId: string, name: string) => boolean
|
|
1773
1905
|
onAddBlueprintVariable?: (fileId: string, name: string) => boolean
|
|
1774
1906
|
onAddBlueprintImport?: (fileId: string, raw: string) => boolean
|
|
1907
|
+
importPickActive?: boolean
|
|
1908
|
+
onToggleImportPick?: () => void
|
|
1909
|
+
onCancelImportPick?: () => void
|
|
1775
1910
|
onRemoveBlueprintFunction?: (fileId: string, name: string) => void
|
|
1776
1911
|
onRemoveBlueprintVariable?: (fileId: string, name: string) => void
|
|
1777
1912
|
onRemoveBlueprintImport?: (
|
|
@@ -1789,32 +1924,40 @@ type HUDProps = {
|
|
|
1789
1924
|
kind: BlueprintPointerKind
|
|
1790
1925
|
path: string
|
|
1791
1926
|
name?: string
|
|
1927
|
+
color?: string
|
|
1792
1928
|
}) => void
|
|
1793
1929
|
onMapAddFile?: (folderPath: string) => void
|
|
1794
1930
|
onMapAddFolder?: (folderPath: string) => void
|
|
1931
|
+
onRenameCreatedFile?: (fileId: string, name: string) => string | null
|
|
1795
1932
|
onInspectFile?: (fileId: string) => void
|
|
1796
1933
|
onInspectBlock?: (fileId: string) => void
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1934
|
+
onExplainTarget?: (input: {
|
|
1935
|
+
kind: ExplainTargetKind
|
|
1936
|
+
path: string
|
|
1937
|
+
name?: string
|
|
1938
|
+
}) => void
|
|
1800
1939
|
blueprintHidden?: boolean
|
|
1801
1940
|
blueprintHasContent?: boolean
|
|
1802
1941
|
blueprintCanCleanup?: boolean
|
|
1942
|
+
blueprintColor?: string | null
|
|
1943
|
+
blueprintOptions?: BlueprintOption[]
|
|
1944
|
+
blueprintColorPointers?: BlueprintColorOption[]
|
|
1945
|
+
onSelectBlueprintColor?: (color: string) => void
|
|
1803
1946
|
onToggleBlueprintHidden?: () => void
|
|
1804
1947
|
onClearBlueprint?: () => void
|
|
1805
1948
|
onCleanupBlueprint?: () => void
|
|
1949
|
+
devTargets?: DevTargetsState
|
|
1950
|
+
onSelectDevTarget?: (id: string) => void
|
|
1806
1951
|
}
|
|
1807
1952
|
|
|
1808
1953
|
export function HUD({
|
|
1809
1954
|
graph,
|
|
1810
|
-
layout,
|
|
1811
1955
|
mode,
|
|
1812
1956
|
locked,
|
|
1813
1957
|
selectedId,
|
|
1814
1958
|
selectedTick = 0,
|
|
1815
1959
|
inspectTick = 0,
|
|
1816
1960
|
selectedFolder = null,
|
|
1817
|
-
landAt,
|
|
1818
1961
|
aimedRelation,
|
|
1819
1962
|
aimedFileId = null,
|
|
1820
1963
|
intent,
|
|
@@ -1822,13 +1965,10 @@ export function HUD({
|
|
|
1822
1965
|
focusedSessionId = null,
|
|
1823
1966
|
nextAttachSessionId = null,
|
|
1824
1967
|
onFocusSession,
|
|
1825
|
-
onSetupSession,
|
|
1826
1968
|
onWorkflowAction,
|
|
1827
1969
|
onNavigateDiff,
|
|
1828
1970
|
onOpenMap,
|
|
1829
1971
|
onWalk,
|
|
1830
|
-
followLook,
|
|
1831
|
-
onToggleFollowLook,
|
|
1832
1972
|
showBranchChanges = false,
|
|
1833
1973
|
branchChanges,
|
|
1834
1974
|
canShowBranchChanges = false,
|
|
@@ -1849,10 +1989,12 @@ export function HUD({
|
|
|
1849
1989
|
blueprintVariables = [],
|
|
1850
1990
|
blueprintImports = [],
|
|
1851
1991
|
blueprintNotes = [],
|
|
1852
|
-
blueprintPointers = [],
|
|
1853
1992
|
onAddBlueprintFunction,
|
|
1854
1993
|
onAddBlueprintVariable,
|
|
1855
1994
|
onAddBlueprintImport,
|
|
1995
|
+
importPickActive = false,
|
|
1996
|
+
onToggleImportPick,
|
|
1997
|
+
onCancelImportPick,
|
|
1856
1998
|
onRemoveBlueprintFunction,
|
|
1857
1999
|
onRemoveBlueprintVariable,
|
|
1858
2000
|
onRemoveBlueprintImport,
|
|
@@ -1860,17 +2002,22 @@ export function HUD({
|
|
|
1860
2002
|
onToggleBlueprintPointer,
|
|
1861
2003
|
onMapAddFile,
|
|
1862
2004
|
onMapAddFolder,
|
|
2005
|
+
onRenameCreatedFile,
|
|
1863
2006
|
onInspectFile,
|
|
1864
2007
|
onInspectBlock,
|
|
1865
|
-
|
|
1866
|
-
createdIds = [],
|
|
1867
|
-
deletedIds = [],
|
|
2008
|
+
onExplainTarget,
|
|
1868
2009
|
blueprintHidden = false,
|
|
1869
2010
|
blueprintHasContent = false,
|
|
1870
2011
|
blueprintCanCleanup = false,
|
|
2012
|
+
blueprintColor = null,
|
|
2013
|
+
blueprintOptions = [],
|
|
2014
|
+
blueprintColorPointers = [],
|
|
2015
|
+
onSelectBlueprintColor,
|
|
1871
2016
|
onToggleBlueprintHidden,
|
|
1872
2017
|
onClearBlueprint,
|
|
1873
2018
|
onCleanupBlueprint,
|
|
2019
|
+
devTargets,
|
|
2020
|
+
onSelectDevTarget,
|
|
1874
2021
|
}: HUDProps) {
|
|
1875
2022
|
const selected = graph.files.find((file) => file.id === selectedId)
|
|
1876
2023
|
const selectedFolderNode = graph.folders.find(
|
|
@@ -1891,7 +2038,6 @@ export function HUD({
|
|
|
1891
2038
|
const sessions = (intents ?? [intent]).filter(
|
|
1892
2039
|
(item) => item.sessionId && isReviewingIntent(item.status),
|
|
1893
2040
|
)
|
|
1894
|
-
const canStop = canStopSession(intent)
|
|
1895
2041
|
const nextAttachSession =
|
|
1896
2042
|
sessions.find((session) => session.sessionId === nextAttachSessionId) ??
|
|
1897
2043
|
[...sessions].reverse().find((session) => session.awaitingAttach) ??
|
|
@@ -1899,6 +2045,9 @@ export function HUD({
|
|
|
1899
2045
|
const [walkIntro, setWalkIntro] = useState(false)
|
|
1900
2046
|
const walkIntroSeen = useRef(false)
|
|
1901
2047
|
const [instructionsOpen, setInstructionsOpen] = useState(false)
|
|
2048
|
+
const [actionsMenuOpen, setActionsMenuOpen] = useState(false)
|
|
2049
|
+
const [actionsMenuPosition, setActionsMenuPosition] = useState<CSSProperties>()
|
|
2050
|
+
const actionsMenuRef = useRef<HTMLDivElement>(null)
|
|
1902
2051
|
const [noteEditor, setNoteEditor] = useState<{
|
|
1903
2052
|
file: string
|
|
1904
2053
|
kind: BlueprintNoteKind
|
|
@@ -1907,13 +2056,8 @@ export function HUD({
|
|
|
1907
2056
|
subtitle: string
|
|
1908
2057
|
placeholder: string
|
|
1909
2058
|
} | null>(null)
|
|
1910
|
-
const [setupError, setSetupError] = useState<string | null>(null)
|
|
1911
|
-
const [setupBusy, setSetupBusy] = useState(false)
|
|
1912
2059
|
const [infoVisible, setInfoVisible] = useState(false)
|
|
1913
2060
|
const [infoMinimized, setInfoMinimized] = useState(false)
|
|
1914
|
-
const [thumbnailVisible, setThumbnailVisible] = useState(true)
|
|
1915
|
-
const [thumbnailMinimized, setThumbnailMinimized] = useState(false)
|
|
1916
|
-
const [thumbnailMaximized, setThumbnailMaximized] = useState(false)
|
|
1917
2061
|
const infoPanelRef = useRef<HTMLDivElement>(null)
|
|
1918
2062
|
const canPlace = true
|
|
1919
2063
|
const overlay = showBranchChanges && branchChanges ? branchChanges : intent
|
|
@@ -1984,19 +2128,23 @@ export function HUD({
|
|
|
1984
2128
|
)
|
|
1985
2129
|
const canEditBlueprint =
|
|
1986
2130
|
canPlace && Boolean(selected) && !selected?.id.startsWith('draft:')
|
|
2131
|
+
const canRenameSelected =
|
|
2132
|
+
Boolean(onRenameCreatedFile) &&
|
|
2133
|
+
Boolean(selected?.userCreated) &&
|
|
2134
|
+
!selected?.id.startsWith('draft:')
|
|
2135
|
+
const renameSelectedFile = (nextName: string) => {
|
|
2136
|
+
if (!selected || !onRenameCreatedFile) return false
|
|
2137
|
+
const previousId = selected.id
|
|
2138
|
+
const nextId = onRenameCreatedFile(previousId, nextName)
|
|
2139
|
+
if (!nextId) return false
|
|
2140
|
+
setNoteEditor((current) =>
|
|
2141
|
+
current?.file === previousId ? { ...current, file: nextId } : current,
|
|
2142
|
+
)
|
|
2143
|
+
return true
|
|
2144
|
+
}
|
|
1987
2145
|
const selectedFileNote = selected
|
|
1988
2146
|
? findBlueprintNote(blueprintNotes, selected.id, 'file')
|
|
1989
2147
|
: ''
|
|
1990
|
-
const selectedFilePointed = selected
|
|
1991
|
-
? findBlueprintPointer(blueprintPointers, 'file', selected.id)
|
|
1992
|
-
: false
|
|
1993
|
-
const selectedFolderPointed = selectedFolderNode
|
|
1994
|
-
? findBlueprintPointer(
|
|
1995
|
-
blueprintPointers,
|
|
1996
|
-
'folder',
|
|
1997
|
-
selectedFolderNode.path,
|
|
1998
|
-
)
|
|
1999
|
-
: false
|
|
2000
2148
|
const openFileNote = () => {
|
|
2001
2149
|
if (!selected || !onSetBlueprintNote) return
|
|
2002
2150
|
setInstructionsOpen(false)
|
|
@@ -2074,6 +2222,11 @@ export function HUD({
|
|
|
2074
2222
|
setInfoMinimized(false)
|
|
2075
2223
|
}, [locked])
|
|
2076
2224
|
|
|
2225
|
+
useEffect(() => {
|
|
2226
|
+
if (infoVisible || !importPickActive) return
|
|
2227
|
+
onCancelImportPick?.()
|
|
2228
|
+
}, [importPickActive, infoVisible, onCancelImportPick])
|
|
2229
|
+
|
|
2077
2230
|
const infoOpen = infoVisible && Boolean(selected || selectedFolderNode)
|
|
2078
2231
|
|
|
2079
2232
|
useEffect(() => {
|
|
@@ -2102,26 +2255,6 @@ export function HUD({
|
|
|
2102
2255
|
return () => window.removeEventListener('keydown', onKey)
|
|
2103
2256
|
}, [aimedFileId, infoVisible, mode, onInspectBlock, selectedId])
|
|
2104
2257
|
|
|
2105
|
-
useEffect(() => {
|
|
2106
|
-
if (!mapping) return
|
|
2107
|
-
const onKey = (event: KeyboardEvent) => {
|
|
2108
|
-
if (event.repeat || event.code !== 'KeyT') return
|
|
2109
|
-
if (shouldIgnoreShortcut(event)) return
|
|
2110
|
-
event.preventDefault()
|
|
2111
|
-
setThumbnailVisible((visible) => {
|
|
2112
|
-
if (!visible) {
|
|
2113
|
-
setThumbnailMinimized(false)
|
|
2114
|
-
setThumbnailMaximized(false)
|
|
2115
|
-
} else {
|
|
2116
|
-
setThumbnailMaximized(false)
|
|
2117
|
-
}
|
|
2118
|
-
return !visible
|
|
2119
|
-
})
|
|
2120
|
-
}
|
|
2121
|
-
window.addEventListener('keydown', onKey)
|
|
2122
|
-
return () => window.removeEventListener('keydown', onKey)
|
|
2123
|
-
}, [mapping])
|
|
2124
|
-
|
|
2125
2258
|
useEffect(() => {
|
|
2126
2259
|
if (!infoVisible || (!selectedId && !selectedFolder)) return
|
|
2127
2260
|
const onKey = (event: KeyboardEvent) => {
|
|
@@ -2141,6 +2274,7 @@ export function HUD({
|
|
|
2141
2274
|
useEffect(() => {
|
|
2142
2275
|
if (!instructionsOpen) return
|
|
2143
2276
|
document.exitPointerLock()
|
|
2277
|
+
setActionsMenuOpen(false)
|
|
2144
2278
|
const onKey = (event: KeyboardEvent) => {
|
|
2145
2279
|
if (event.code !== 'Escape') return
|
|
2146
2280
|
if (shouldIgnoreShortcut(event)) return
|
|
@@ -2152,24 +2286,61 @@ export function HUD({
|
|
|
2152
2286
|
return () => window.removeEventListener('keydown', onKey, true)
|
|
2153
2287
|
}, [instructionsOpen])
|
|
2154
2288
|
|
|
2289
|
+
useLayoutEffect(() => {
|
|
2290
|
+
if (!actionsMenuOpen) return
|
|
2291
|
+
const updatePosition = () => {
|
|
2292
|
+
const trigger = actionsMenuRef.current
|
|
2293
|
+
if (!trigger) return
|
|
2294
|
+
const rect = trigger.getBoundingClientRect()
|
|
2295
|
+
setActionsMenuPosition({
|
|
2296
|
+
right: window.innerWidth - rect.right,
|
|
2297
|
+
bottom: window.innerHeight - rect.top + 8,
|
|
2298
|
+
})
|
|
2299
|
+
}
|
|
2300
|
+
updatePosition()
|
|
2301
|
+
window.addEventListener('resize', updatePosition)
|
|
2302
|
+
return () => window.removeEventListener('resize', updatePosition)
|
|
2303
|
+
}, [actionsMenuOpen])
|
|
2304
|
+
|
|
2305
|
+
useEffect(() => {
|
|
2306
|
+
if (!actionsMenuOpen) return
|
|
2307
|
+
const onKey = (event: KeyboardEvent) => {
|
|
2308
|
+
if (event.code !== 'Escape') return
|
|
2309
|
+
if (shouldIgnoreShortcut(event)) return
|
|
2310
|
+
event.preventDefault()
|
|
2311
|
+
event.stopPropagation()
|
|
2312
|
+
setActionsMenuOpen(false)
|
|
2313
|
+
}
|
|
2314
|
+
const onPointerDown = (event: PointerEvent) => {
|
|
2315
|
+
const target = event.target
|
|
2316
|
+
if (
|
|
2317
|
+
target instanceof Element &&
|
|
2318
|
+
(actionsMenuRef.current?.contains(target) ||
|
|
2319
|
+
target.closest('.hud-actions-menu-list'))
|
|
2320
|
+
) {
|
|
2321
|
+
return
|
|
2322
|
+
}
|
|
2323
|
+
setActionsMenuOpen(false)
|
|
2324
|
+
}
|
|
2325
|
+
window.addEventListener('keydown', onKey, true)
|
|
2326
|
+
window.addEventListener('pointerdown', onPointerDown, true)
|
|
2327
|
+
return () => {
|
|
2328
|
+
window.removeEventListener('keydown', onKey, true)
|
|
2329
|
+
window.removeEventListener('pointerdown', onPointerDown, true)
|
|
2330
|
+
}
|
|
2331
|
+
}, [actionsMenuOpen])
|
|
2332
|
+
|
|
2155
2333
|
const instructionSections = explorerInstructions({
|
|
2156
2334
|
canPlace,
|
|
2157
2335
|
hasChangeSet,
|
|
2158
2336
|
changePathsOnly,
|
|
2159
2337
|
selectedUserCreated: Boolean(selected?.userCreated),
|
|
2160
2338
|
infoVisible,
|
|
2161
|
-
thumbnailVisible,
|
|
2162
2339
|
importedBy,
|
|
2163
|
-
canStop,
|
|
2164
|
-
sessionCount: sessions.length,
|
|
2165
2340
|
showBranchChanges,
|
|
2166
2341
|
canShowBranchChanges,
|
|
2167
2342
|
})
|
|
2168
|
-
const currentInstructionView: InstructionView = mapping
|
|
2169
|
-
? thumbnailMaximized
|
|
2170
|
-
? '3dview'
|
|
2171
|
-
: 'map'
|
|
2172
|
-
: 'walk'
|
|
2343
|
+
const currentInstructionView: InstructionView = mapping ? 'map' : 'walk'
|
|
2173
2344
|
|
|
2174
2345
|
return (
|
|
2175
2346
|
<div className="hud">
|
|
@@ -2184,13 +2355,8 @@ export function HUD({
|
|
|
2184
2355
|
</p>
|
|
2185
2356
|
<p>
|
|
2186
2357
|
<kbd>W</kbd> <kbd>A</kbd> <kbd>S</kbd> <kbd>D</kbd> walk,{' '}
|
|
2187
|
-
<kbd>Shift</kbd> sprint
|
|
2188
|
-
|
|
2189
|
-
<>
|
|
2190
|
-
, <kbd>Space</kbd> place a file, <kbd>B</kbd> place an island
|
|
2191
|
-
</>
|
|
2192
|
-
) : null}
|
|
2193
|
-
, double-click a block or press <kbd>I</kbd> for info.
|
|
2358
|
+
<kbd>Shift</kbd> sprint, double-click a file or folder or press{' '}
|
|
2359
|
+
<kbd>I</kbd> for info.
|
|
2194
2360
|
</p>
|
|
2195
2361
|
</div>
|
|
2196
2362
|
</div>
|
|
@@ -2203,6 +2369,7 @@ export function HUD({
|
|
|
2203
2369
|
<div className="hud-name-gate">
|
|
2204
2370
|
<NameInput
|
|
2205
2371
|
placeholder="Folder name"
|
|
2372
|
+
fallbackName="New folder"
|
|
2206
2373
|
onCommit={onCommitIslandName}
|
|
2207
2374
|
onCancel={onCancelIslandName}
|
|
2208
2375
|
/>
|
|
@@ -2234,20 +2401,40 @@ export function HUD({
|
|
|
2234
2401
|
>
|
|
2235
2402
|
Walk
|
|
2236
2403
|
</button>
|
|
2237
|
-
{canStop && (
|
|
2238
|
-
<button
|
|
2239
|
-
className="hud-button hud-button-reject"
|
|
2240
|
-
type="button"
|
|
2241
|
-
aria-label="Stop LLM session"
|
|
2242
|
-
onClick={() =>
|
|
2243
|
-
intent.sessionId && onWorkflowAction(intent.sessionId, 'stop')
|
|
2244
|
-
}
|
|
2245
|
-
>
|
|
2246
|
-
Stop
|
|
2247
|
-
</button>
|
|
2248
|
-
)}
|
|
2249
2404
|
</div>
|
|
2250
|
-
{selected
|
|
2405
|
+
{(selected ||
|
|
2406
|
+
(devTargets?.enabled &&
|
|
2407
|
+
onSelectDevTarget &&
|
|
2408
|
+
devTargets.targets.length > 0)) && (
|
|
2409
|
+
<div className="hud-top-end">
|
|
2410
|
+
{selected && <div className="hud-chip">{selected.path}</div>}
|
|
2411
|
+
{devTargets?.enabled &&
|
|
2412
|
+
onSelectDevTarget &&
|
|
2413
|
+
devTargets.targets.length > 0 && (
|
|
2414
|
+
<label className="hud-target-select">
|
|
2415
|
+
<span>Look at</span>
|
|
2416
|
+
<select
|
|
2417
|
+
className="hud-button hud-target-select-control"
|
|
2418
|
+
aria-label="Look at"
|
|
2419
|
+
title="Choose which project the map scans. Only available while developing Inbase."
|
|
2420
|
+
value={devTargets.currentId ?? ''}
|
|
2421
|
+
disabled={updatingModel}
|
|
2422
|
+
onChange={(event) => {
|
|
2423
|
+
const next = event.target.value
|
|
2424
|
+
if (!next || next === devTargets.currentId) return
|
|
2425
|
+
onSelectDevTarget(next)
|
|
2426
|
+
}}
|
|
2427
|
+
>
|
|
2428
|
+
{devTargets.targets.map((target) => (
|
|
2429
|
+
<option key={target.id} value={target.id}>
|
|
2430
|
+
{target.label}
|
|
2431
|
+
</option>
|
|
2432
|
+
))}
|
|
2433
|
+
</select>
|
|
2434
|
+
</label>
|
|
2435
|
+
)}
|
|
2436
|
+
</div>
|
|
2437
|
+
)}
|
|
2251
2438
|
</div>
|
|
2252
2439
|
|
|
2253
2440
|
{(sessions.length > 0 || showBranchChanges) && (
|
|
@@ -2258,23 +2445,33 @@ export function HUD({
|
|
|
2258
2445
|
const active =
|
|
2259
2446
|
session.sessionId === (focusedSessionId ?? intent.sessionId)
|
|
2260
2447
|
const attached = session.awaitingAttach === false
|
|
2261
|
-
const label =
|
|
2448
|
+
const label = sessionDisplayName(session) || 'Session'
|
|
2262
2449
|
return (
|
|
2263
2450
|
<button
|
|
2264
2451
|
className="hud-button hud-session-tab"
|
|
2265
2452
|
type="button"
|
|
2266
2453
|
role="tab"
|
|
2267
2454
|
aria-selected={active}
|
|
2455
|
+
aria-label={
|
|
2456
|
+
attached ? `${label}, attached` : `${label}, waiting`
|
|
2457
|
+
}
|
|
2268
2458
|
data-active={active}
|
|
2269
2459
|
data-attached={attached}
|
|
2270
2460
|
key={session.sessionId}
|
|
2271
2461
|
title={attached ? `${label} · Attached` : `${label} · Waiting`}
|
|
2462
|
+
style={
|
|
2463
|
+
session.colorHex
|
|
2464
|
+
? ({
|
|
2465
|
+
'--session-color': session.colorHex,
|
|
2466
|
+
} as CSSProperties)
|
|
2467
|
+
: undefined
|
|
2468
|
+
}
|
|
2272
2469
|
onClick={() => {
|
|
2273
2470
|
if (session.sessionId) onFocusSession?.(session.sessionId)
|
|
2471
|
+
if (session.color) onSelectBlueprintColor?.(session.color)
|
|
2274
2472
|
}}
|
|
2275
2473
|
>
|
|
2276
|
-
<
|
|
2277
|
-
<span className="hud-session-tab-label">{label}</span>
|
|
2474
|
+
<SessionSwatch colorHex={session.colorHex} />
|
|
2278
2475
|
</button>
|
|
2279
2476
|
)
|
|
2280
2477
|
})}
|
|
@@ -2306,7 +2503,16 @@ export function HUD({
|
|
|
2306
2503
|
data-minimized={infoMinimized}
|
|
2307
2504
|
>
|
|
2308
2505
|
<PanelChrome
|
|
2309
|
-
title={
|
|
2506
|
+
title={
|
|
2507
|
+
canRenameSelected ? (
|
|
2508
|
+
<InfoNameField
|
|
2509
|
+
name={selected.name}
|
|
2510
|
+
onRename={renameSelectedFile}
|
|
2511
|
+
/>
|
|
2512
|
+
) : (
|
|
2513
|
+
selected.name
|
|
2514
|
+
)
|
|
2515
|
+
}
|
|
2310
2516
|
minimized={infoMinimized}
|
|
2311
2517
|
onMinimize={() => setInfoMinimized((current) => !current)}
|
|
2312
2518
|
onClose={() => {
|
|
@@ -2320,28 +2526,43 @@ export function HUD({
|
|
|
2320
2526
|
<p>
|
|
2321
2527
|
{selected.lines} lines · {selected.language}
|
|
2322
2528
|
</p>
|
|
2323
|
-
{
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2529
|
+
{(onExplainTarget ||
|
|
2530
|
+
canInspectFile(selected.id, selected.userCreated)) && (
|
|
2531
|
+
<div className="hud-file-actions">
|
|
2532
|
+
{onExplainTarget ? (
|
|
2533
|
+
<ExplainButton
|
|
2534
|
+
label={`Explain ${selected.name}`}
|
|
2535
|
+
onClick={() =>
|
|
2536
|
+
onExplainTarget({ kind: 'file', path: selected.id })
|
|
2537
|
+
}
|
|
2538
|
+
/>
|
|
2539
|
+
) : null}
|
|
2540
|
+
{canInspectFile(selected.id, selected.userCreated) && (
|
|
2541
|
+
<button
|
|
2542
|
+
className="hud-button hud-inspect"
|
|
2543
|
+
type="button"
|
|
2544
|
+
onClick={() => onInspectFile?.(selected.id)}
|
|
2545
|
+
>
|
|
2546
|
+
Inspect file
|
|
2547
|
+
</button>
|
|
2548
|
+
)}
|
|
2549
|
+
</div>
|
|
2331
2550
|
)}
|
|
2332
2551
|
{canEditBlueprint && onToggleBlueprintPointer && (
|
|
2333
|
-
<
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2552
|
+
<PointColorControl
|
|
2553
|
+
target={{ kind: 'file', path: selected.id }}
|
|
2554
|
+
colorPointers={blueprintColorPointers}
|
|
2555
|
+
currentColorId={blueprintColor}
|
|
2556
|
+
idleLabel="Point to file"
|
|
2557
|
+
pointedLabel="Stop pointing"
|
|
2558
|
+
onToggle={(color) =>
|
|
2559
|
+
onToggleBlueprintPointer({
|
|
2560
|
+
kind: 'file',
|
|
2561
|
+
path: selected.id,
|
|
2562
|
+
color,
|
|
2563
|
+
})
|
|
2340
2564
|
}
|
|
2341
|
-
|
|
2342
|
-
<EyeIcon size={15} />
|
|
2343
|
-
{selectedFilePointed ? 'Stop pointing' : 'Point to file'}
|
|
2344
|
-
</button>
|
|
2565
|
+
/>
|
|
2345
2566
|
)}
|
|
2346
2567
|
{canEditBlueprint && onSetBlueprintNote && (
|
|
2347
2568
|
<button
|
|
@@ -2399,6 +2620,21 @@ export function HUD({
|
|
|
2399
2620
|
>
|
|
2400
2621
|
{symbol.name}
|
|
2401
2622
|
</span>
|
|
2623
|
+
{onExplainTarget ? (
|
|
2624
|
+
<div className="hud-item-actions">
|
|
2625
|
+
<ExplainButton
|
|
2626
|
+
compact
|
|
2627
|
+
label={`Explain ${symbol.name}`}
|
|
2628
|
+
onClick={() =>
|
|
2629
|
+
onExplainTarget({
|
|
2630
|
+
kind: 'class',
|
|
2631
|
+
path: selected.id,
|
|
2632
|
+
name: symbol.name,
|
|
2633
|
+
})
|
|
2634
|
+
}
|
|
2635
|
+
/>
|
|
2636
|
+
</div>
|
|
2637
|
+
) : null}
|
|
2402
2638
|
</li>
|
|
2403
2639
|
))}
|
|
2404
2640
|
</ul>
|
|
@@ -2434,23 +2670,35 @@ export function HUD({
|
|
|
2434
2670
|
}
|
|
2435
2671
|
canEdit={Boolean(canEditBlueprint && onSetBlueprintNote)}
|
|
2436
2672
|
canRemove={Boolean(canEditBlueprint && symbol.intended)}
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2673
|
+
pointerTarget={{
|
|
2674
|
+
kind: 'function',
|
|
2675
|
+
path: selected.id,
|
|
2676
|
+
name: symbol.name,
|
|
2677
|
+
}}
|
|
2678
|
+
colorPointers={blueprintColorPointers}
|
|
2679
|
+
currentColorId={blueprintColor}
|
|
2443
2680
|
onRemove={() =>
|
|
2444
2681
|
onRemoveBlueprintFunction?.(selected.id, symbol.name)
|
|
2445
2682
|
}
|
|
2446
2683
|
onOpenNote={() => openSymbolNote('function', symbol.name)}
|
|
2684
|
+
onExplain={
|
|
2685
|
+
onExplainTarget
|
|
2686
|
+
? () =>
|
|
2687
|
+
onExplainTarget({
|
|
2688
|
+
kind: 'function',
|
|
2689
|
+
path: selected.id,
|
|
2690
|
+
name: symbol.name,
|
|
2691
|
+
})
|
|
2692
|
+
: undefined
|
|
2693
|
+
}
|
|
2447
2694
|
onTogglePoint={
|
|
2448
2695
|
onToggleBlueprintPointer
|
|
2449
|
-
? () =>
|
|
2696
|
+
? (color) =>
|
|
2450
2697
|
onToggleBlueprintPointer({
|
|
2451
2698
|
kind: 'function',
|
|
2452
2699
|
path: selected.id,
|
|
2453
2700
|
name: symbol.name,
|
|
2701
|
+
color,
|
|
2454
2702
|
})
|
|
2455
2703
|
: undefined
|
|
2456
2704
|
}
|
|
@@ -2475,20 +2723,32 @@ export function HUD({
|
|
|
2475
2723
|
noteEditor.name === item.name
|
|
2476
2724
|
}
|
|
2477
2725
|
canEdit={Boolean(canEditBlueprint && onSetBlueprintNote)}
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2726
|
+
pointerTarget={{
|
|
2727
|
+
kind: 'function',
|
|
2728
|
+
path: selected.id,
|
|
2729
|
+
name: item.name,
|
|
2730
|
+
}}
|
|
2731
|
+
colorPointers={blueprintColorPointers}
|
|
2732
|
+
currentColorId={blueprintColor}
|
|
2484
2733
|
onOpenNote={() => openSymbolNote('function', item.name)}
|
|
2734
|
+
onExplain={
|
|
2735
|
+
onExplainTarget
|
|
2736
|
+
? () =>
|
|
2737
|
+
onExplainTarget({
|
|
2738
|
+
kind: 'function',
|
|
2739
|
+
path: selected.id,
|
|
2740
|
+
name: item.name,
|
|
2741
|
+
})
|
|
2742
|
+
: undefined
|
|
2743
|
+
}
|
|
2485
2744
|
onTogglePoint={
|
|
2486
2745
|
onToggleBlueprintPointer
|
|
2487
|
-
? () =>
|
|
2746
|
+
? (color) =>
|
|
2488
2747
|
onToggleBlueprintPointer({
|
|
2489
2748
|
kind: 'function',
|
|
2490
2749
|
path: selected.id,
|
|
2491
2750
|
name: item.name,
|
|
2751
|
+
color,
|
|
2492
2752
|
})
|
|
2493
2753
|
: undefined
|
|
2494
2754
|
}
|
|
@@ -2532,23 +2792,35 @@ export function HUD({
|
|
|
2532
2792
|
}
|
|
2533
2793
|
canEdit={Boolean(canEditBlueprint && onSetBlueprintNote)}
|
|
2534
2794
|
canRemove={Boolean(canEditBlueprint && symbol.intended)}
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2795
|
+
pointerTarget={{
|
|
2796
|
+
kind: 'variable',
|
|
2797
|
+
path: selected.id,
|
|
2798
|
+
name: symbol.name,
|
|
2799
|
+
}}
|
|
2800
|
+
colorPointers={blueprintColorPointers}
|
|
2801
|
+
currentColorId={blueprintColor}
|
|
2541
2802
|
onRemove={() =>
|
|
2542
2803
|
onRemoveBlueprintVariable?.(selected.id, symbol.name)
|
|
2543
2804
|
}
|
|
2544
2805
|
onOpenNote={() => openSymbolNote('variable', symbol.name)}
|
|
2806
|
+
onExplain={
|
|
2807
|
+
onExplainTarget
|
|
2808
|
+
? () =>
|
|
2809
|
+
onExplainTarget({
|
|
2810
|
+
kind: 'variable',
|
|
2811
|
+
path: selected.id,
|
|
2812
|
+
name: symbol.name,
|
|
2813
|
+
})
|
|
2814
|
+
: undefined
|
|
2815
|
+
}
|
|
2545
2816
|
onTogglePoint={
|
|
2546
2817
|
onToggleBlueprintPointer
|
|
2547
|
-
? () =>
|
|
2818
|
+
? (color) =>
|
|
2548
2819
|
onToggleBlueprintPointer({
|
|
2549
2820
|
kind: 'variable',
|
|
2550
2821
|
path: selected.id,
|
|
2551
2822
|
name: symbol.name,
|
|
2823
|
+
color,
|
|
2552
2824
|
})
|
|
2553
2825
|
: undefined
|
|
2554
2826
|
}
|
|
@@ -2573,20 +2845,32 @@ export function HUD({
|
|
|
2573
2845
|
noteEditor.name === item.name
|
|
2574
2846
|
}
|
|
2575
2847
|
canEdit={Boolean(canEditBlueprint && onSetBlueprintNote)}
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2848
|
+
pointerTarget={{
|
|
2849
|
+
kind: 'variable',
|
|
2850
|
+
path: selected.id,
|
|
2851
|
+
name: item.name,
|
|
2852
|
+
}}
|
|
2853
|
+
colorPointers={blueprintColorPointers}
|
|
2854
|
+
currentColorId={blueprintColor}
|
|
2582
2855
|
onOpenNote={() => openSymbolNote('variable', item.name)}
|
|
2856
|
+
onExplain={
|
|
2857
|
+
onExplainTarget
|
|
2858
|
+
? () =>
|
|
2859
|
+
onExplainTarget({
|
|
2860
|
+
kind: 'variable',
|
|
2861
|
+
path: selected.id,
|
|
2862
|
+
name: item.name,
|
|
2863
|
+
})
|
|
2864
|
+
: undefined
|
|
2865
|
+
}
|
|
2583
2866
|
onTogglePoint={
|
|
2584
2867
|
onToggleBlueprintPointer
|
|
2585
|
-
? () =>
|
|
2868
|
+
? (color) =>
|
|
2586
2869
|
onToggleBlueprintPointer({
|
|
2587
2870
|
kind: 'variable',
|
|
2588
2871
|
path: selected.id,
|
|
2589
2872
|
name: item.name,
|
|
2873
|
+
color,
|
|
2590
2874
|
})
|
|
2591
2875
|
: undefined
|
|
2592
2876
|
}
|
|
@@ -2678,10 +2962,20 @@ export function HUD({
|
|
|
2678
2962
|
</ul>
|
|
2679
2963
|
)}
|
|
2680
2964
|
{canEditBlueprint && !importedBy && onAddBlueprintImport && (
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2965
|
+
<>
|
|
2966
|
+
<AddIntentRow
|
|
2967
|
+
placeholder="Clock from src/components/Clock.tsx"
|
|
2968
|
+
onAdd={(raw) => onAddBlueprintImport(selected.id, raw)}
|
|
2969
|
+
pickLabel={mapping ? 'Select a file' : undefined}
|
|
2970
|
+
pickActive={importPickActive}
|
|
2971
|
+
onTogglePick={mapping ? onToggleImportPick : undefined}
|
|
2972
|
+
/>
|
|
2973
|
+
{importPickActive && (
|
|
2974
|
+
<p className="hud-pick-hint">
|
|
2975
|
+
Click a file to import it. Esc to cancel.
|
|
2976
|
+
</p>
|
|
2977
|
+
)}
|
|
2978
|
+
</>
|
|
2685
2979
|
)}
|
|
2686
2980
|
</div>
|
|
2687
2981
|
)}
|
|
@@ -2701,6 +2995,16 @@ export function HUD({
|
|
|
2701
2995
|
setInfoVisible(false)
|
|
2702
2996
|
setInfoMinimized(false)
|
|
2703
2997
|
}}
|
|
2998
|
+
onExplain={
|
|
2999
|
+
onExplainTarget
|
|
3000
|
+
? () =>
|
|
3001
|
+
onExplainTarget({
|
|
3002
|
+
kind: 'folder',
|
|
3003
|
+
path: selectedFolderNode.path,
|
|
3004
|
+
})
|
|
3005
|
+
: undefined
|
|
3006
|
+
}
|
|
3007
|
+
explainLabel={`Explain ${selectedFolderNode.name}`}
|
|
2704
3008
|
/>
|
|
2705
3009
|
{!infoMinimized && (
|
|
2706
3010
|
<div ref={infoPanelRef} className="hud-panel-body">
|
|
@@ -2714,42 +3018,55 @@ export function HUD({
|
|
|
2714
3018
|
</p>
|
|
2715
3019
|
<div className="hud-section-title">Files</div>
|
|
2716
3020
|
{folderFiles.length === 0 ? (
|
|
2717
|
-
<p>No files
|
|
3021
|
+
<p>No files in this folder</p>
|
|
2718
3022
|
) : (
|
|
2719
3023
|
<ul>
|
|
2720
3024
|
{folderFiles.map((file) => (
|
|
2721
3025
|
<li key={file.id}>
|
|
2722
3026
|
<span>{file.name}</span>
|
|
2723
|
-
{
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
3027
|
+
{(onExplainTarget ||
|
|
3028
|
+
canInspectFile(file.id, file.userCreated)) && (
|
|
3029
|
+
<div className="hud-item-actions">
|
|
3030
|
+
{onExplainTarget ? (
|
|
3031
|
+
<ExplainButton
|
|
3032
|
+
compact
|
|
3033
|
+
label={`Explain ${file.name}`}
|
|
3034
|
+
onClick={() =>
|
|
3035
|
+
onExplainTarget({ kind: 'file', path: file.id })
|
|
3036
|
+
}
|
|
3037
|
+
/>
|
|
3038
|
+
) : null}
|
|
3039
|
+
{canInspectFile(file.id, file.userCreated) && (
|
|
3040
|
+
<button
|
|
3041
|
+
className="hud-item-inspect"
|
|
3042
|
+
type="button"
|
|
3043
|
+
onClick={() => onInspectFile?.(file.id)}
|
|
3044
|
+
>
|
|
3045
|
+
Inspect
|
|
3046
|
+
</button>
|
|
3047
|
+
)}
|
|
3048
|
+
</div>
|
|
2731
3049
|
)}
|
|
2732
3050
|
</li>
|
|
2733
3051
|
))}
|
|
2734
3052
|
</ul>
|
|
2735
3053
|
)}
|
|
2736
3054
|
{canPlace && onToggleBlueprintPointer && (
|
|
2737
|
-
<
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
3055
|
+
<PointColorControl
|
|
3056
|
+
target={{ kind: 'folder', path: selectedFolderNode.path }}
|
|
3057
|
+
colorPointers={blueprintColorPointers}
|
|
3058
|
+
currentColorId={blueprintColor}
|
|
3059
|
+
idleLabel="Point to folder"
|
|
3060
|
+
pointedLabel="Stop pointing"
|
|
2742
3061
|
disabled={naming || selectedFolderNode.path.startsWith('draft:')}
|
|
2743
|
-
|
|
3062
|
+
onToggle={(color) =>
|
|
2744
3063
|
onToggleBlueprintPointer({
|
|
2745
3064
|
kind: 'folder',
|
|
2746
3065
|
path: selectedFolderNode.path,
|
|
3066
|
+
color,
|
|
2747
3067
|
})
|
|
2748
3068
|
}
|
|
2749
|
-
|
|
2750
|
-
<EyeIcon size={15} />
|
|
2751
|
-
{selectedFolderPointed ? 'Stop pointing' : 'Point to folder'}
|
|
2752
|
-
</button>
|
|
3069
|
+
/>
|
|
2753
3070
|
)}
|
|
2754
3071
|
{canPlace && mapping && onMapAddFile && onMapAddFolder && (
|
|
2755
3072
|
<div className="hud-decide hud-map-blueprint">
|
|
@@ -2775,51 +3092,6 @@ export function HUD({
|
|
|
2775
3092
|
)}
|
|
2776
3093
|
</aside>
|
|
2777
3094
|
)}
|
|
2778
|
-
|
|
2779
|
-
{mapping && thumbnailVisible && (
|
|
2780
|
-
<CanvasErrorBoundary fallback={null}>
|
|
2781
|
-
<SelectionThumbnail
|
|
2782
|
-
graph={graph}
|
|
2783
|
-
layout={layout}
|
|
2784
|
-
selectedId={selectedId}
|
|
2785
|
-
selectedFolder={selectedFolder}
|
|
2786
|
-
landAt={landAt}
|
|
2787
|
-
importedBy={importedBy}
|
|
2788
|
-
minimized={thumbnailMinimized}
|
|
2789
|
-
maximized={thumbnailMaximized}
|
|
2790
|
-
plannedIds={plannedIds}
|
|
2791
|
-
createdIds={createdIds}
|
|
2792
|
-
deletedIds={deletedIds}
|
|
2793
|
-
pointedFileIds={
|
|
2794
|
-
blueprintHidden
|
|
2795
|
-
? []
|
|
2796
|
-
: blueprintPointers.flatMap((item) =>
|
|
2797
|
-
item.kind === 'folder' ? [] : [item.path],
|
|
2798
|
-
)
|
|
2799
|
-
}
|
|
2800
|
-
pointedFolderPaths={
|
|
2801
|
-
blueprintHidden
|
|
2802
|
-
? []
|
|
2803
|
-
: blueprintPointers.flatMap((item) =>
|
|
2804
|
-
item.kind === 'folder' ? [item.path] : [],
|
|
2805
|
-
)
|
|
2806
|
-
}
|
|
2807
|
-
onMinimize={() => {
|
|
2808
|
-
setThumbnailMaximized(false)
|
|
2809
|
-
setThumbnailMinimized((current) => !current)
|
|
2810
|
-
}}
|
|
2811
|
-
onMaximize={() => {
|
|
2812
|
-
setThumbnailMinimized(false)
|
|
2813
|
-
setThumbnailMaximized((current) => !current)
|
|
2814
|
-
}}
|
|
2815
|
-
onHide={() => {
|
|
2816
|
-
setThumbnailVisible(false)
|
|
2817
|
-
setThumbnailMinimized(false)
|
|
2818
|
-
setThumbnailMaximized(false)
|
|
2819
|
-
}}
|
|
2820
|
-
/>
|
|
2821
|
-
</CanvasErrorBoundary>
|
|
2822
|
-
)}
|
|
2823
3095
|
</div>
|
|
2824
3096
|
|
|
2825
3097
|
{noteEditor && onSetBlueprintNote && (
|
|
@@ -2892,52 +3164,54 @@ export function HUD({
|
|
|
2892
3164
|
|
|
2893
3165
|
<div className="hud-bottom">
|
|
2894
3166
|
<div className="hud-bottom-actions">
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
aria-expanded={instructionsOpen}
|
|
2901
|
-
onClick={() => {
|
|
2902
|
-
setNoteEditor(null)
|
|
2903
|
-
setInstructionsOpen((open) => !open)
|
|
2904
|
-
}}
|
|
2905
|
-
>
|
|
2906
|
-
Instructions
|
|
2907
|
-
</button>
|
|
2908
|
-
<button
|
|
2909
|
-
className="hud-button"
|
|
2910
|
-
type="button"
|
|
2911
|
-
aria-label="Rescan the project and rebuild the map"
|
|
2912
|
-
disabled={updatingModel}
|
|
2913
|
-
onClick={onUpdateModel}
|
|
2914
|
-
>
|
|
2915
|
-
{updatingModel ? 'Updating…' : 'Update model'}
|
|
2916
|
-
</button>
|
|
2917
|
-
{onSetupSession && (
|
|
2918
|
-
<button
|
|
2919
|
-
className="hud-button"
|
|
2920
|
-
type="button"
|
|
2921
|
-
aria-label="Start an LLM session without attaching a chat yet"
|
|
2922
|
-
title={setupError ?? 'Start an LLM session without attaching a chat yet'}
|
|
2923
|
-
disabled={setupBusy}
|
|
2924
|
-
onClick={() => {
|
|
2925
|
-
setInstructionsOpen(false)
|
|
2926
|
-
setSetupError(null)
|
|
2927
|
-
setSetupBusy(true)
|
|
2928
|
-
void onSetupSession()
|
|
2929
|
-
.catch((caught) => {
|
|
2930
|
-
setSetupError(
|
|
2931
|
-
caught instanceof Error
|
|
2932
|
-
? caught.message
|
|
2933
|
-
: 'Could not set up the session',
|
|
2934
|
-
)
|
|
2935
|
-
})
|
|
2936
|
-
.finally(() => setSetupBusy(false))
|
|
2937
|
-
}}
|
|
3167
|
+
{onSelectBlueprintColor && blueprintOptions.length > 0 && (
|
|
3168
|
+
<div
|
|
3169
|
+
className="hud-blueprint-select"
|
|
3170
|
+
role="radiogroup"
|
|
3171
|
+
aria-label="Blueprint"
|
|
2938
3172
|
>
|
|
2939
|
-
{
|
|
2940
|
-
|
|
3173
|
+
{blueprintOptions.map((option) => {
|
|
3174
|
+
const selected = (blueprintColor ?? 'global') === option.id
|
|
3175
|
+
return (
|
|
3176
|
+
<button
|
|
3177
|
+
key={option.id}
|
|
3178
|
+
className={
|
|
3179
|
+
option.kind === 'global'
|
|
3180
|
+
? 'hud-button hud-blueprint-option'
|
|
3181
|
+
: 'hud-button hud-blueprint-option hud-blueprint-option-swatch'
|
|
3182
|
+
}
|
|
3183
|
+
type="button"
|
|
3184
|
+
role="radio"
|
|
3185
|
+
aria-checked={selected}
|
|
3186
|
+
data-active={selected}
|
|
3187
|
+
aria-label={
|
|
3188
|
+
option.kind === 'global'
|
|
3189
|
+
? 'Global blueprint'
|
|
3190
|
+
: `${option.name} session blueprint`
|
|
3191
|
+
}
|
|
3192
|
+
title={
|
|
3193
|
+
option.kind === 'global'
|
|
3194
|
+
? 'Place on the global blueprint. All colors stay visible.'
|
|
3195
|
+
: `Place on the ${option.name} blueprint. All colors stay visible.`
|
|
3196
|
+
}
|
|
3197
|
+
style={
|
|
3198
|
+
{
|
|
3199
|
+
'--session-color': option.hex,
|
|
3200
|
+
} as CSSProperties
|
|
3201
|
+
}
|
|
3202
|
+
onClick={() => onSelectBlueprintColor(option.id)}
|
|
3203
|
+
>
|
|
3204
|
+
<SessionSwatch
|
|
3205
|
+
colorHex={option.hex}
|
|
3206
|
+
className="hud-session-swatch hud-blueprint-swatch"
|
|
3207
|
+
/>
|
|
3208
|
+
{option.kind === 'global' ? (
|
|
3209
|
+
<span className="hud-blueprint-select-label">Global</span>
|
|
3210
|
+
) : null}
|
|
3211
|
+
</button>
|
|
3212
|
+
)
|
|
3213
|
+
})}
|
|
3214
|
+
</div>
|
|
2941
3215
|
)}
|
|
2942
3216
|
{onToggleBlueprintHidden && (
|
|
2943
3217
|
<button
|
|
@@ -2947,12 +3221,12 @@ export function HUD({
|
|
|
2947
3221
|
aria-label={blueprintHidden ? 'Show blueprint' : 'Hide blueprint'}
|
|
2948
3222
|
title={
|
|
2949
3223
|
blueprintHidden
|
|
2950
|
-
? 'Show
|
|
2951
|
-
: 'Hide
|
|
3224
|
+
? 'Show this blueprint overlay'
|
|
3225
|
+
: 'Hide this blueprint overlay; other colors stay visible'
|
|
2952
3226
|
}
|
|
2953
3227
|
onClick={onToggleBlueprintHidden}
|
|
2954
3228
|
>
|
|
2955
|
-
{blueprintHidden ? 'Show
|
|
3229
|
+
{blueprintHidden ? 'Show' : 'Hide'}
|
|
2956
3230
|
</button>
|
|
2957
3231
|
)}
|
|
2958
3232
|
{onClearBlueprint && (
|
|
@@ -2964,7 +3238,7 @@ export function HUD({
|
|
|
2964
3238
|
disabled={!blueprintHasContent}
|
|
2965
3239
|
onClick={onClearBlueprint}
|
|
2966
3240
|
>
|
|
2967
|
-
Clear
|
|
3241
|
+
Clear
|
|
2968
3242
|
</button>
|
|
2969
3243
|
)}
|
|
2970
3244
|
{onCleanupBlueprint && (
|
|
@@ -2976,7 +3250,7 @@ export function HUD({
|
|
|
2976
3250
|
disabled={!blueprintCanCleanup}
|
|
2977
3251
|
onClick={onCleanupBlueprint}
|
|
2978
3252
|
>
|
|
2979
|
-
Cleanup
|
|
3253
|
+
Cleanup
|
|
2980
3254
|
</button>
|
|
2981
3255
|
)}
|
|
2982
3256
|
</div>
|
|
@@ -3020,48 +3294,6 @@ export function HUD({
|
|
|
3020
3294
|
</span>
|
|
3021
3295
|
</button>
|
|
3022
3296
|
)}
|
|
3023
|
-
{mapping && (
|
|
3024
|
-
<button
|
|
3025
|
-
className="hud-button hud-icon-button"
|
|
3026
|
-
data-active={thumbnailVisible}
|
|
3027
|
-
aria-label={
|
|
3028
|
-
thumbnailVisible ? 'Hide 3D view' : 'Show 3D view'
|
|
3029
|
-
}
|
|
3030
|
-
aria-keyshortcuts="T"
|
|
3031
|
-
aria-pressed={thumbnailVisible}
|
|
3032
|
-
type="button"
|
|
3033
|
-
onClick={() => {
|
|
3034
|
-
setThumbnailVisible((visible) => {
|
|
3035
|
-
if (!visible) {
|
|
3036
|
-
setThumbnailMinimized(false)
|
|
3037
|
-
setThumbnailMaximized(false)
|
|
3038
|
-
} else {
|
|
3039
|
-
setThumbnailMaximized(false)
|
|
3040
|
-
}
|
|
3041
|
-
return !visible
|
|
3042
|
-
})
|
|
3043
|
-
}}
|
|
3044
|
-
>
|
|
3045
|
-
<svg
|
|
3046
|
-
viewBox="0 0 24 24"
|
|
3047
|
-
width="18"
|
|
3048
|
-
height="18"
|
|
3049
|
-
fill="none"
|
|
3050
|
-
stroke="currentColor"
|
|
3051
|
-
strokeWidth="2"
|
|
3052
|
-
strokeLinecap="round"
|
|
3053
|
-
strokeLinejoin="round"
|
|
3054
|
-
aria-hidden="true"
|
|
3055
|
-
>
|
|
3056
|
-
<rect x="3" y="5" width="11" height="14" rx="1.5" />
|
|
3057
|
-
<path d="M16 8h5v11H9v-3" />
|
|
3058
|
-
<path d="M6.5 16.5 9 12l2 2.5 1.5-2L15 16.5" />
|
|
3059
|
-
</svg>
|
|
3060
|
-
<span className="hud-tooltip">
|
|
3061
|
-
{thumbnailVisible ? 'T hide 3D view' : 'T show 3D view'}
|
|
3062
|
-
</span>
|
|
3063
|
-
</button>
|
|
3064
|
-
)}
|
|
3065
3297
|
<button
|
|
3066
3298
|
className="hud-button hud-icon-button"
|
|
3067
3299
|
data-active={importedBy}
|
|
@@ -3139,30 +3371,70 @@ export function HUD({
|
|
|
3139
3371
|
: 'G show branch changes'}
|
|
3140
3372
|
</span>
|
|
3141
3373
|
</button>
|
|
3142
|
-
<
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
viewBox="0 0 24 24"
|
|
3152
|
-
width="18"
|
|
3153
|
-
height="18"
|
|
3154
|
-
fill="none"
|
|
3155
|
-
stroke="currentColor"
|
|
3156
|
-
strokeWidth="2"
|
|
3157
|
-
strokeLinecap="round"
|
|
3158
|
-
strokeLinejoin="round"
|
|
3159
|
-
aria-hidden="true"
|
|
3374
|
+
<div className="hud-actions-menu" ref={actionsMenuRef}>
|
|
3375
|
+
<button
|
|
3376
|
+
className="hud-button hud-icon-button"
|
|
3377
|
+
data-active={actionsMenuOpen}
|
|
3378
|
+
type="button"
|
|
3379
|
+
aria-label="More actions"
|
|
3380
|
+
aria-haspopup="menu"
|
|
3381
|
+
aria-expanded={actionsMenuOpen}
|
|
3382
|
+
onClick={() => setActionsMenuOpen((open) => !open)}
|
|
3160
3383
|
>
|
|
3161
|
-
<
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3384
|
+
<svg
|
|
3385
|
+
viewBox="0 0 24 24"
|
|
3386
|
+
width="18"
|
|
3387
|
+
height="18"
|
|
3388
|
+
fill="none"
|
|
3389
|
+
stroke="currentColor"
|
|
3390
|
+
strokeWidth="2"
|
|
3391
|
+
strokeLinecap="round"
|
|
3392
|
+
strokeLinejoin="round"
|
|
3393
|
+
aria-hidden="true"
|
|
3394
|
+
>
|
|
3395
|
+
<path d="M4 7h16" />
|
|
3396
|
+
<path d="M4 12h16" />
|
|
3397
|
+
<path d="M4 17h16" />
|
|
3398
|
+
</svg>
|
|
3399
|
+
<span className="hud-tooltip">More</span>
|
|
3400
|
+
</button>
|
|
3401
|
+
{actionsMenuOpen &&
|
|
3402
|
+
actionsMenuPosition &&
|
|
3403
|
+
createPortal(
|
|
3404
|
+
<div
|
|
3405
|
+
className="hud-actions-menu-list"
|
|
3406
|
+
role="menu"
|
|
3407
|
+
style={actionsMenuPosition}
|
|
3408
|
+
>
|
|
3409
|
+
<button
|
|
3410
|
+
type="button"
|
|
3411
|
+
role="menuitem"
|
|
3412
|
+
aria-haspopup="dialog"
|
|
3413
|
+
aria-expanded={instructionsOpen}
|
|
3414
|
+
onClick={() => {
|
|
3415
|
+
setNoteEditor(null)
|
|
3416
|
+
setActionsMenuOpen(false)
|
|
3417
|
+
setInstructionsOpen((open) => !open)
|
|
3418
|
+
}}
|
|
3419
|
+
>
|
|
3420
|
+
Instructions
|
|
3421
|
+
</button>
|
|
3422
|
+
<button
|
|
3423
|
+
type="button"
|
|
3424
|
+
role="menuitem"
|
|
3425
|
+
aria-label="Rescan files and folders"
|
|
3426
|
+
disabled={updatingModel}
|
|
3427
|
+
onClick={() => {
|
|
3428
|
+
setActionsMenuOpen(false)
|
|
3429
|
+
onUpdateModel()
|
|
3430
|
+
}}
|
|
3431
|
+
>
|
|
3432
|
+
{updatingModel ? 'Updating…' : 'Update model'}
|
|
3433
|
+
</button>
|
|
3434
|
+
</div>,
|
|
3435
|
+
document.body,
|
|
3436
|
+
)}
|
|
3437
|
+
</div>
|
|
3166
3438
|
</div>
|
|
3167
3439
|
</div>
|
|
3168
3440
|
</div>
|