@jkwd/inbase 0.1.20 → 0.1.22
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-ignore.mjs +9 -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 +1080 -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 +884 -223
- 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 +3 -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 -792
- 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 +1 -1
- 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 -1050
|
@@ -1,28 +1,29 @@
|
|
|
1
|
-
import { useEffect, useRef, useState, type ReactNode } from 'react'
|
|
2
|
-
import {
|
|
3
|
-
import { NameInput } from './NameInput'
|
|
4
|
-
import { SelectionThumbnail } from '../scene/SelectionThumbnail'
|
|
1
|
+
import { useEffect, useLayoutEffect, useRef, useState, type CSSProperties, type ReactNode } from 'react'
|
|
2
|
+
import { createPortal } from 'react-dom'
|
|
3
|
+
import { InfoNameField, NameInput } from './NameInput'
|
|
5
4
|
import {
|
|
6
|
-
canStopSession,
|
|
7
5
|
isReviewingIntent,
|
|
8
6
|
type AgentIntent,
|
|
9
7
|
type AgentIntentStatus,
|
|
10
8
|
type AimedRelation,
|
|
11
9
|
type BlueprintNote,
|
|
12
10
|
type BlueprintNoteKind,
|
|
11
|
+
GLOBAL_BLUEPRINT_COLOR,
|
|
12
|
+
type BlueprintOption,
|
|
13
13
|
type BlueprintPointer,
|
|
14
14
|
type BlueprintPointerKind,
|
|
15
15
|
type BranchChanges,
|
|
16
16
|
type CodebaseGraph,
|
|
17
|
+
type ExplainTargetKind,
|
|
17
18
|
type PatchImportAddition,
|
|
18
19
|
type PatchSymbolAddition,
|
|
19
20
|
type ViewMode,
|
|
20
|
-
type WorldLayout,
|
|
21
21
|
type WorkflowAction,
|
|
22
22
|
} from '../types'
|
|
23
23
|
import { findBlueprintNote, findBlueprintPointer } from '../userCreated'
|
|
24
24
|
import { EyeIcon } from './EyeIcon'
|
|
25
25
|
import { beginKeyboardIsolation, shouldIgnoreShortcut } from '../keyboard'
|
|
26
|
+
import type { DevTargetsState } from '../devTargets'
|
|
26
27
|
|
|
27
28
|
function reviewTitle(status: AgentIntentStatus) {
|
|
28
29
|
if (status === 'blueprint_ask') return 'Setup blueprint'
|
|
@@ -42,14 +43,270 @@ function sessionLabel(intent: AgentIntent) {
|
|
|
42
43
|
return intent.name?.trim() || intent.feature?.trim() || ''
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
function
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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>
|
|
49
309
|
)
|
|
50
|
-
if (same.length < 2) return title
|
|
51
|
-
const id = intent.sessionId ?? ''
|
|
52
|
-
return `${title} · ${id.slice(-4)}`
|
|
53
310
|
}
|
|
54
311
|
|
|
55
312
|
function fileBase(id: string) {
|
|
@@ -192,9 +449,15 @@ function MutationFold({
|
|
|
192
449
|
function AddIntentRow({
|
|
193
450
|
placeholder,
|
|
194
451
|
onAdd,
|
|
452
|
+
pickLabel,
|
|
453
|
+
pickActive = false,
|
|
454
|
+
onTogglePick,
|
|
195
455
|
}: {
|
|
196
456
|
placeholder: string
|
|
197
457
|
onAdd: (value: string) => boolean
|
|
458
|
+
pickLabel?: string
|
|
459
|
+
pickActive?: boolean
|
|
460
|
+
onTogglePick?: () => void
|
|
198
461
|
}) {
|
|
199
462
|
const [value, setValue] = useState('')
|
|
200
463
|
return (
|
|
@@ -217,6 +480,17 @@ function AddIntentRow({
|
|
|
217
480
|
<button className="hud-button" type="submit">
|
|
218
481
|
Add
|
|
219
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
|
+
)}
|
|
220
494
|
</form>
|
|
221
495
|
)
|
|
222
496
|
}
|
|
@@ -314,54 +588,71 @@ function BlueprintSymbolRow({
|
|
|
314
588
|
className,
|
|
315
589
|
hasNote,
|
|
316
590
|
noteOpen,
|
|
317
|
-
pointed,
|
|
318
591
|
canEdit,
|
|
319
592
|
canRemove,
|
|
320
593
|
onRemove,
|
|
321
594
|
onOpenNote,
|
|
595
|
+
onExplain,
|
|
596
|
+
pointerTarget,
|
|
597
|
+
colorPointers,
|
|
598
|
+
currentColorId,
|
|
322
599
|
onTogglePoint,
|
|
323
600
|
}: {
|
|
324
601
|
name: string
|
|
325
602
|
className?: string
|
|
326
603
|
hasNote: boolean
|
|
327
604
|
noteOpen?: boolean
|
|
328
|
-
pointed?: boolean
|
|
329
605
|
canEdit: boolean
|
|
330
606
|
canRemove?: boolean
|
|
331
607
|
onRemove?: () => void
|
|
332
608
|
onOpenNote: () => void
|
|
333
|
-
|
|
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
|
|
334
618
|
}) {
|
|
619
|
+
const showActions = Boolean(onExplain) || canEdit
|
|
335
620
|
return (
|
|
336
621
|
<li>
|
|
337
622
|
<span className={className}>{name}</span>
|
|
338
|
-
{
|
|
623
|
+
{showActions && (
|
|
339
624
|
<div className="hud-item-actions">
|
|
340
|
-
{
|
|
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 && (
|
|
341
644
|
<button
|
|
342
|
-
className="hud-item-
|
|
645
|
+
className="hud-item-note"
|
|
343
646
|
type="button"
|
|
344
|
-
data-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
}
|
|
348
|
-
aria-pressed={Boolean(pointed)}
|
|
349
|
-
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}
|
|
350
651
|
>
|
|
351
|
-
|
|
652
|
+
Note
|
|
352
653
|
</button>
|
|
353
654
|
)}
|
|
354
|
-
|
|
355
|
-
className="hud-item-note"
|
|
356
|
-
type="button"
|
|
357
|
-
data-has-note={hasNote ? 'true' : 'false'}
|
|
358
|
-
data-open={noteOpen ? 'true' : 'false'}
|
|
359
|
-
aria-label={`Edit note for ${name}`}
|
|
360
|
-
onClick={onOpenNote}
|
|
361
|
-
>
|
|
362
|
-
Note
|
|
363
|
-
</button>
|
|
364
|
-
{canRemove && (
|
|
655
|
+
{canEdit && canRemove && (
|
|
365
656
|
<button
|
|
366
657
|
className="hud-item-remove"
|
|
367
658
|
type="button"
|
|
@@ -390,6 +681,35 @@ function AttachStateBadge({ attached }: { attached: boolean }) {
|
|
|
390
681
|
)
|
|
391
682
|
}
|
|
392
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
|
+
|
|
393
713
|
function PanelChrome({
|
|
394
714
|
title,
|
|
395
715
|
subtitle,
|
|
@@ -397,8 +717,8 @@ function PanelChrome({
|
|
|
397
717
|
minimized = false,
|
|
398
718
|
onMinimize,
|
|
399
719
|
onClose,
|
|
400
|
-
|
|
401
|
-
|
|
720
|
+
onExplain,
|
|
721
|
+
explainLabel,
|
|
402
722
|
}: {
|
|
403
723
|
title: ReactNode
|
|
404
724
|
subtitle?: ReactNode
|
|
@@ -406,14 +726,22 @@ function PanelChrome({
|
|
|
406
726
|
minimized?: boolean
|
|
407
727
|
onMinimize?: () => void
|
|
408
728
|
onClose?: () => void
|
|
409
|
-
|
|
410
|
-
|
|
729
|
+
onExplain?: () => void
|
|
730
|
+
explainLabel?: string
|
|
411
731
|
}) {
|
|
412
732
|
return (
|
|
413
733
|
<div className="hud-panel-chrome">
|
|
414
734
|
<div className="hud-panel-chrome-heading">
|
|
415
735
|
<div className="hud-panel-chrome-title-row">
|
|
416
|
-
<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>
|
|
417
745
|
{badge}
|
|
418
746
|
</div>
|
|
419
747
|
{subtitle ? (
|
|
@@ -433,13 +761,9 @@ function PanelChrome({
|
|
|
433
761
|
)}
|
|
434
762
|
{onClose && (
|
|
435
763
|
<button
|
|
436
|
-
className=
|
|
437
|
-
closeReject
|
|
438
|
-
? 'hud-button hud-icon-button hud-panel-control hud-button-reject'
|
|
439
|
-
: 'hud-button hud-icon-button hud-panel-control'
|
|
440
|
-
}
|
|
764
|
+
className="hud-button hud-icon-button hud-panel-control"
|
|
441
765
|
type="button"
|
|
442
|
-
aria-label=
|
|
766
|
+
aria-label="Close"
|
|
443
767
|
onClick={onClose}
|
|
444
768
|
>
|
|
445
769
|
×
|
|
@@ -450,142 +774,9 @@ function PanelChrome({
|
|
|
450
774
|
)
|
|
451
775
|
}
|
|
452
776
|
|
|
453
|
-
function InitialInstructionField({
|
|
454
|
-
value,
|
|
455
|
-
onChange,
|
|
456
|
-
}: {
|
|
457
|
-
value: string
|
|
458
|
-
onChange: (value: string) => void
|
|
459
|
-
}) {
|
|
460
|
-
return (
|
|
461
|
-
<label className="hud-instruction">
|
|
462
|
-
<textarea
|
|
463
|
-
value={value}
|
|
464
|
-
maxLength={4000}
|
|
465
|
-
rows={4}
|
|
466
|
-
placeholder="What should the LLM build?"
|
|
467
|
-
onChange={(event) => onChange(event.target.value)}
|
|
468
|
-
onKeyDown={(event) => event.stopPropagation()}
|
|
469
|
-
/>
|
|
470
|
-
</label>
|
|
471
|
-
)
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
function formatFileSize(bytes: number) {
|
|
475
|
-
if (bytes < 1024) return `${bytes} B`
|
|
476
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
|
|
477
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
async function fileToBase64(file: File) {
|
|
481
|
-
const bytes = new Uint8Array(await file.arrayBuffer())
|
|
482
|
-
const chunk = 0x8000
|
|
483
|
-
let binary = ''
|
|
484
|
-
for (let offset = 0; offset < bytes.length; offset += chunk) {
|
|
485
|
-
binary += String.fromCharCode(...bytes.subarray(offset, offset + chunk))
|
|
486
|
-
}
|
|
487
|
-
return btoa(binary)
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
type ContextFileInfo = {
|
|
491
|
-
id: string
|
|
492
|
-
name: string
|
|
493
|
-
mimeType: string
|
|
494
|
-
size: number
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
function ContextFileDrop({
|
|
498
|
-
files,
|
|
499
|
-
busy,
|
|
500
|
-
error,
|
|
501
|
-
onAdd,
|
|
502
|
-
onRemove,
|
|
503
|
-
}: {
|
|
504
|
-
files: ContextFileInfo[]
|
|
505
|
-
busy: boolean
|
|
506
|
-
error: string | null
|
|
507
|
-
onAdd: (files: File[]) => void
|
|
508
|
-
onRemove: (fileId: string) => void
|
|
509
|
-
}) {
|
|
510
|
-
const inputRef = useRef<HTMLInputElement>(null)
|
|
511
|
-
const [over, setOver] = useState(false)
|
|
512
|
-
|
|
513
|
-
const takeFiles = (list: FileList | File[] | null) => {
|
|
514
|
-
if (!list || busy) return
|
|
515
|
-
const next = [...list].filter((file) => file.size > 0)
|
|
516
|
-
if (next.length > 0) onAdd(next)
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
return (
|
|
520
|
-
<div className="hud-context">
|
|
521
|
-
<button
|
|
522
|
-
className="hud-context-drop"
|
|
523
|
-
type="button"
|
|
524
|
-
data-over={over}
|
|
525
|
-
data-busy={busy}
|
|
526
|
-
disabled={busy}
|
|
527
|
-
aria-label="Attach files for the LLM"
|
|
528
|
-
onDragEnter={(event) => {
|
|
529
|
-
event.preventDefault()
|
|
530
|
-
if (event.dataTransfer.types.includes('Files')) setOver(true)
|
|
531
|
-
}}
|
|
532
|
-
onDragOver={(event) => {
|
|
533
|
-
event.preventDefault()
|
|
534
|
-
event.dataTransfer.dropEffect = 'copy'
|
|
535
|
-
}}
|
|
536
|
-
onDragLeave={(event) => {
|
|
537
|
-
if (event.currentTarget.contains(event.relatedTarget as Node)) return
|
|
538
|
-
setOver(false)
|
|
539
|
-
}}
|
|
540
|
-
onDrop={(event) => {
|
|
541
|
-
event.preventDefault()
|
|
542
|
-
event.stopPropagation()
|
|
543
|
-
setOver(false)
|
|
544
|
-
takeFiles(event.dataTransfer.files)
|
|
545
|
-
}}
|
|
546
|
-
onKeyDown={(event) => event.stopPropagation()}
|
|
547
|
-
onClick={() => inputRef.current?.click()}
|
|
548
|
-
>
|
|
549
|
-
<input
|
|
550
|
-
ref={inputRef}
|
|
551
|
-
type="file"
|
|
552
|
-
multiple
|
|
553
|
-
hidden
|
|
554
|
-
onChange={(event) => {
|
|
555
|
-
takeFiles(event.target.files)
|
|
556
|
-
event.target.value = ''
|
|
557
|
-
}}
|
|
558
|
-
/>
|
|
559
|
-
{busy ? 'Attaching…' : 'Drop files or click to attach'}
|
|
560
|
-
</button>
|
|
561
|
-
{files.length > 0 && (
|
|
562
|
-
<ul className="hud-context-list">
|
|
563
|
-
{files.map((file) => (
|
|
564
|
-
<li key={file.id}>
|
|
565
|
-
<span className="hud-context-name" title={file.name}>
|
|
566
|
-
{file.name}
|
|
567
|
-
</span>
|
|
568
|
-
<span className="hud-context-size">{formatFileSize(file.size)}</span>
|
|
569
|
-
<button
|
|
570
|
-
className="hud-item-remove"
|
|
571
|
-
type="button"
|
|
572
|
-
aria-label={`Remove ${file.name}`}
|
|
573
|
-
disabled={busy}
|
|
574
|
-
onClick={() => onRemove(file.id)}
|
|
575
|
-
>
|
|
576
|
-
×
|
|
577
|
-
</button>
|
|
578
|
-
</li>
|
|
579
|
-
))}
|
|
580
|
-
</ul>
|
|
581
|
-
)}
|
|
582
|
-
{error ? <p className="hud-context-error">{error}</p> : null}
|
|
583
|
-
</div>
|
|
584
|
-
)
|
|
585
|
-
}
|
|
586
|
-
|
|
587
777
|
function blueprintIsDefined(intent: AgentIntent) {
|
|
588
778
|
return (
|
|
779
|
+
Boolean(intent.localBlueprintEnabled) ||
|
|
589
780
|
(intent.userCreatedBlocks?.length ?? 0) > 0 ||
|
|
590
781
|
(intent.userCreatedIslands?.length ?? 0) > 0 ||
|
|
591
782
|
(intent.blueprintFunctions?.length ?? 0) > 0 ||
|
|
@@ -597,56 +788,18 @@ function blueprintIsDefined(intent: AgentIntent) {
|
|
|
597
788
|
}
|
|
598
789
|
|
|
599
790
|
function HandshakeSetup({
|
|
600
|
-
instruction,
|
|
601
|
-
onInstructionChange,
|
|
602
|
-
contextFiles,
|
|
603
|
-
contextBusy,
|
|
604
|
-
contextError,
|
|
605
|
-
onAddContextFiles,
|
|
606
|
-
onRemoveContextFile,
|
|
607
791
|
blueprintDefined,
|
|
608
792
|
awaitingAttach,
|
|
609
793
|
nextAttachLabel,
|
|
794
|
+
colorCommand,
|
|
610
795
|
}: {
|
|
611
|
-
instruction: string
|
|
612
|
-
onInstructionChange: (value: string) => void
|
|
613
|
-
contextFiles: ContextFileInfo[]
|
|
614
|
-
contextBusy: boolean
|
|
615
|
-
contextError: string | null
|
|
616
|
-
onAddContextFiles: (files: File[]) => void
|
|
617
|
-
onRemoveContextFile: (fileId: string) => void
|
|
618
796
|
blueprintDefined: boolean
|
|
619
797
|
awaitingAttach: boolean
|
|
620
798
|
nextAttachLabel: string | null
|
|
799
|
+
colorCommand?: string | null
|
|
621
800
|
}) {
|
|
622
801
|
return (
|
|
623
802
|
<div className="hud-setup">
|
|
624
|
-
<section
|
|
625
|
-
className="hud-setup-section"
|
|
626
|
-
onDragOver={(event) => {
|
|
627
|
-
if (event.dataTransfer.types.includes('Files')) event.preventDefault()
|
|
628
|
-
}}
|
|
629
|
-
onDrop={(event) => {
|
|
630
|
-
event.preventDefault()
|
|
631
|
-
const dropped = [...(event.dataTransfer.files ?? [])].filter(
|
|
632
|
-
(file) => file.size > 0,
|
|
633
|
-
)
|
|
634
|
-
if (dropped.length > 0) onAddContextFiles(dropped)
|
|
635
|
-
}}
|
|
636
|
-
>
|
|
637
|
-
<h2 className="hud-setup-heading">Instructions</h2>
|
|
638
|
-
<InitialInstructionField
|
|
639
|
-
value={instruction}
|
|
640
|
-
onChange={onInstructionChange}
|
|
641
|
-
/>
|
|
642
|
-
<ContextFileDrop
|
|
643
|
-
files={contextFiles}
|
|
644
|
-
busy={contextBusy}
|
|
645
|
-
error={contextError}
|
|
646
|
-
onAdd={onAddContextFiles}
|
|
647
|
-
onRemove={onRemoveContextFile}
|
|
648
|
-
/>
|
|
649
|
-
</section>
|
|
650
803
|
<section className="hud-setup-section">
|
|
651
804
|
<h2 className="hud-setup-heading">
|
|
652
805
|
Blueprint
|
|
@@ -658,27 +811,32 @@ function HandshakeSetup({
|
|
|
658
811
|
</span>
|
|
659
812
|
</h2>
|
|
660
813
|
<p>
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
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.
|
|
665
818
|
</p>
|
|
666
819
|
</section>
|
|
667
820
|
<section className="hud-setup-section">
|
|
668
821
|
<h2 className="hud-setup-heading">Start</h2>
|
|
669
822
|
{awaitingAttach && nextAttachLabel ? (
|
|
670
|
-
|
|
671
|
-
<
|
|
672
|
-
|
|
673
|
-
|
|
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
|
+
</>
|
|
674
830
|
) : awaitingAttach ? (
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
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
|
+
</>
|
|
678
838
|
) : (
|
|
679
|
-
<p>
|
|
680
|
-
This window is attached. Starting from <kbd>/inbase</kbd>…
|
|
681
|
-
</p>
|
|
839
|
+
<p>This window is attached. Starting from the Cursor chat…</p>
|
|
682
840
|
)}
|
|
683
841
|
</section>
|
|
684
842
|
</div>
|
|
@@ -700,7 +858,16 @@ function sessionLiveStatus(intent: AgentIntent) {
|
|
|
700
858
|
return { text: 'LLM wait timed out', busy: false }
|
|
701
859
|
}
|
|
702
860
|
if (intent.awaitingAttach) {
|
|
703
|
-
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 }
|
|
704
871
|
}
|
|
705
872
|
if (intent.status === 'pending') {
|
|
706
873
|
return intent.listening
|
|
@@ -742,9 +909,6 @@ function sessionLiveStatus(intent: AgentIntent) {
|
|
|
742
909
|
) {
|
|
743
910
|
return { text: 'LLM attached', busy: true }
|
|
744
911
|
}
|
|
745
|
-
if (intent.llmIdle) {
|
|
746
|
-
return { text: 'LLM is idle', busy: false }
|
|
747
|
-
}
|
|
748
912
|
return { text: 'LLM connected', busy: true }
|
|
749
913
|
}
|
|
750
914
|
|
|
@@ -752,12 +916,16 @@ function LiveStatus({
|
|
|
752
916
|
intent,
|
|
753
917
|
showStop = false,
|
|
754
918
|
onStop,
|
|
919
|
+
startingExplain = false,
|
|
755
920
|
}: {
|
|
756
921
|
intent: AgentIntent
|
|
757
922
|
showStop?: boolean
|
|
758
923
|
onStop?: () => void
|
|
924
|
+
startingExplain?: boolean
|
|
759
925
|
}) {
|
|
760
|
-
const status =
|
|
926
|
+
const status = startingExplain && !intent.pendingExplain
|
|
927
|
+
? { text: 'Starting an explanation…', busy: true }
|
|
928
|
+
: sessionLiveStatus(intent)
|
|
761
929
|
const [flash, setFlash] = useState(false)
|
|
762
930
|
const lastAt = intent.lastAck?.at
|
|
763
931
|
|
|
@@ -785,6 +953,12 @@ function LiveStatus({
|
|
|
785
953
|
)
|
|
786
954
|
}
|
|
787
955
|
|
|
956
|
+
function PlaceFilesHint() {
|
|
957
|
+
return (
|
|
958
|
+
<p className="hud-place-hint">Right-click to create files and folders.</p>
|
|
959
|
+
)
|
|
960
|
+
}
|
|
961
|
+
|
|
788
962
|
type SessionPanelProps = {
|
|
789
963
|
intent: AgentIntent
|
|
790
964
|
focused: boolean
|
|
@@ -810,14 +984,7 @@ function SessionPanel({
|
|
|
810
984
|
}: SessionPanelProps) {
|
|
811
985
|
const [minimized, setMinimized] = useState(false)
|
|
812
986
|
const [instruction, setInstruction] = useState('')
|
|
813
|
-
const [
|
|
814
|
-
() => intent.initialInstruction ?? '',
|
|
815
|
-
)
|
|
816
|
-
const [contextFiles, setContextFiles] = useState<ContextFileInfo[]>(
|
|
817
|
-
() => intent.contextFiles ?? [],
|
|
818
|
-
)
|
|
819
|
-
const [contextBusy, setContextBusy] = useState(false)
|
|
820
|
-
const [contextError, setContextError] = useState<string | null>(null)
|
|
987
|
+
const [startingExplain, setStartingExplain] = useState(false)
|
|
821
988
|
const sessionId = intent.sessionId
|
|
822
989
|
const latestEntry = intent.isActiveDiff ? intent.chain.at(-1) : null
|
|
823
990
|
const pending =
|
|
@@ -862,8 +1029,10 @@ function SessionPanel({
|
|
|
862
1029
|
planReady && !working && proposalStep === null
|
|
863
1030
|
? (intent.steps.find((step) => !acceptedSteps.has(step.index)) ?? null)
|
|
864
1031
|
: null
|
|
865
|
-
const
|
|
866
|
-
|
|
1032
|
+
const llmDisconnected =
|
|
1033
|
+
Boolean(intent.llmIdle) && intent.awaitingAttach === false
|
|
1034
|
+
const canRunNext = stepByStep && Boolean(invokeStep) && !llmDisconnected
|
|
1035
|
+
const canAcceptProposal = proposalStep !== null && !llmDisconnected
|
|
867
1036
|
const lastStep =
|
|
868
1037
|
typeof proposalStep === 'number' &&
|
|
869
1038
|
intent.steps.length > 0 &&
|
|
@@ -876,75 +1045,29 @@ function SessionPanel({
|
|
|
876
1045
|
|
|
877
1046
|
useEffect(() => {
|
|
878
1047
|
setInstruction('')
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
useEffect(() => {
|
|
882
|
-
setInitialInstruction(intent.initialInstruction ?? '')
|
|
883
|
-
setContextFiles(intent.contextFiles ?? [])
|
|
884
|
-
setContextError(null)
|
|
885
|
-
}, [sessionId])
|
|
1048
|
+
setStartingExplain(false)
|
|
1049
|
+
}, [intent.diffId, sessionId])
|
|
886
1050
|
|
|
887
1051
|
useEffect(() => {
|
|
888
|
-
if (
|
|
889
|
-
|
|
890
|
-
}, [contextBusy, intent.contextFiles])
|
|
1052
|
+
if (intent.pendingExplain) setStartingExplain(false)
|
|
1053
|
+
}, [intent.pendingExplain])
|
|
891
1054
|
|
|
892
1055
|
if (!sessionId || !isReviewingIntent(intent.status)) return null
|
|
893
1056
|
|
|
894
|
-
const
|
|
895
|
-
setInitialInstruction(value)
|
|
896
|
-
persistInitialInstruction(sessionId, value)
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
const addContextFiles = (files: File[]) => {
|
|
900
|
-
setContextBusy(true)
|
|
901
|
-
setContextError(null)
|
|
902
|
-
void Promise.all(
|
|
903
|
-
files.map(async (file) => ({
|
|
904
|
-
name: file.name,
|
|
905
|
-
mimeType: file.type || 'application/octet-stream',
|
|
906
|
-
contentBase64: await fileToBase64(file),
|
|
907
|
-
})),
|
|
908
|
-
)
|
|
909
|
-
.then((payload) => persistAddContextFiles(sessionId, payload))
|
|
910
|
-
.then((next) => {
|
|
911
|
-
setContextFiles(next.contextFiles ?? [])
|
|
912
|
-
})
|
|
913
|
-
.catch((caught) => {
|
|
914
|
-
setContextError(
|
|
915
|
-
caught instanceof Error ? caught.message : 'Could not attach files',
|
|
916
|
-
)
|
|
917
|
-
})
|
|
918
|
-
.finally(() => setContextBusy(false))
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
const removeContextFile = (fileId: string) => {
|
|
922
|
-
setContextBusy(true)
|
|
923
|
-
setContextError(null)
|
|
924
|
-
void persistRemoveContextFile(sessionId, fileId)
|
|
925
|
-
.then((next) => {
|
|
926
|
-
setContextFiles(next.contextFiles ?? [])
|
|
927
|
-
})
|
|
928
|
-
.catch((caught) => {
|
|
929
|
-
setContextError(
|
|
930
|
-
caught instanceof Error ? caught.message : 'Could not remove file',
|
|
931
|
-
)
|
|
932
|
-
})
|
|
933
|
-
.finally(() => setContextBusy(false))
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
const showInitialInstruction =
|
|
1057
|
+
const handshakeSetup =
|
|
937
1058
|
Boolean(intent.awaitingAttach) &&
|
|
938
1059
|
(askingBlueprint || sendingBlueprint || preparing)
|
|
939
|
-
const handshakeSetup = showInitialInstruction
|
|
940
1060
|
const llmConnected = intent.awaitingAttach === false
|
|
941
1061
|
const showConnectedProgress =
|
|
942
|
-
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
|
|
943
1066
|
const queuedBehind =
|
|
944
1067
|
intent.awaitingAttach &&
|
|
945
1068
|
nextAttachSession &&
|
|
946
1069
|
nextAttachSession.sessionId !== sessionId
|
|
947
|
-
?
|
|
1070
|
+
? sessionDisplayName(nextAttachSession) || 'an earlier session'
|
|
948
1071
|
: null
|
|
949
1072
|
|
|
950
1073
|
const act = (
|
|
@@ -961,50 +1084,64 @@ function SessionPanel({
|
|
|
961
1084
|
}
|
|
962
1085
|
data-minimized={minimized}
|
|
963
1086
|
data-focused={focused}
|
|
964
|
-
data-attached={llmConnected}
|
|
1087
|
+
data-attached={llmConnected && !llmDisconnected}
|
|
965
1088
|
onPointerDown={onFocus}
|
|
966
1089
|
>
|
|
967
1090
|
<PanelChrome
|
|
968
1091
|
title={
|
|
969
|
-
|
|
970
|
-
|
|
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
|
+
</>
|
|
971
1101
|
}
|
|
972
1102
|
subtitle={
|
|
973
1103
|
sessionLabel(intent)
|
|
974
1104
|
? showConnectedProgress
|
|
975
1105
|
? 'LLM connected'
|
|
976
1106
|
: reviewTitle(intent.status)
|
|
977
|
-
:
|
|
1107
|
+
: sessionColorName(intent)
|
|
1108
|
+
? showConnectedProgress
|
|
1109
|
+
? 'LLM connected'
|
|
1110
|
+
: reviewTitle(intent.status)
|
|
1111
|
+
: undefined
|
|
978
1112
|
}
|
|
979
|
-
badge={<AttachStateBadge attached={llmConnected} />}
|
|
1113
|
+
badge={<AttachStateBadge attached={llmConnected && !llmDisconnected} />}
|
|
980
1114
|
minimized={minimized}
|
|
981
1115
|
onMinimize={() => setMinimized((current) => !current)}
|
|
982
|
-
onClose={() => act('stop')}
|
|
983
|
-
closeLabel="Stop session"
|
|
984
|
-
closeReject
|
|
985
1116
|
/>
|
|
986
1117
|
{!minimized && (
|
|
987
1118
|
<>
|
|
988
1119
|
{!intent.awaitingAttach && (
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1120
|
+
<>
|
|
1121
|
+
{showPlaceHint && !planReady && !pending && <PlaceFilesHint />}
|
|
1122
|
+
<LiveStatus
|
|
1123
|
+
intent={intent}
|
|
1124
|
+
showStop={liveHasStop}
|
|
1125
|
+
startingExplain={startingExplain}
|
|
1126
|
+
onStop={() => act('stop')}
|
|
1127
|
+
/>
|
|
1128
|
+
</>
|
|
994
1129
|
)}
|
|
995
|
-
|
|
996
|
-
<
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
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 && (
|
|
1008
1145
|
<p className="hud-mode-hint">
|
|
1009
1146
|
LLM implements the full plan. You can still walk the diffs, then
|
|
1010
1147
|
Accept proposal.
|
|
@@ -1012,41 +1149,49 @@ function SessionPanel({
|
|
|
1012
1149
|
)}
|
|
1013
1150
|
{handshakeSetup ? (
|
|
1014
1151
|
<HandshakeSetup
|
|
1015
|
-
instruction={initialInstruction}
|
|
1016
|
-
onInstructionChange={updateInitialInstruction}
|
|
1017
|
-
contextFiles={contextFiles}
|
|
1018
|
-
contextBusy={contextBusy}
|
|
1019
|
-
contextError={contextError}
|
|
1020
|
-
onAddContextFiles={addContextFiles}
|
|
1021
|
-
onRemoveContextFile={removeContextFile}
|
|
1022
1152
|
blueprintDefined={blueprintIsDefined(intent)}
|
|
1023
1153
|
awaitingAttach={Boolean(intent.awaitingAttach)}
|
|
1024
1154
|
nextAttachLabel={queuedBehind}
|
|
1155
|
+
colorCommand={sessionSlashCommand(intent)}
|
|
1025
1156
|
/>
|
|
1026
1157
|
) : intent.awaitingAttach ? (
|
|
1027
1158
|
queuedBehind ? (
|
|
1028
|
-
<
|
|
1029
|
-
<
|
|
1030
|
-
|
|
1031
|
-
|
|
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>
|
|
1032
1169
|
) : (
|
|
1033
|
-
<
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
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>
|
|
1037
1177
|
)
|
|
1038
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}
|
|
1039
1184
|
{intent.feature &&
|
|
1040
1185
|
!handshakeSetup &&
|
|
1041
1186
|
intent.feature.trim() !== sessionLabel(intent) && (
|
|
1042
1187
|
<p className="hud-feature">{intent.feature}</p>
|
|
1043
1188
|
)}
|
|
1044
|
-
{askingBlueprint && !handshakeSetup && !showConnectedProgress ? (
|
|
1189
|
+
{askingBlueprint && !handshakeSetup && !showConnectedProgress && !llmDisconnected ? (
|
|
1045
1190
|
<>
|
|
1046
1191
|
<p>
|
|
1047
|
-
|
|
1048
|
-
session to the LLM, or skip and let it
|
|
1049
|
-
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.
|
|
1050
1195
|
</p>
|
|
1051
1196
|
<div className="hud-decide">
|
|
1052
1197
|
<button
|
|
@@ -1075,20 +1220,13 @@ function SessionPanel({
|
|
|
1075
1220
|
) : handshakeSetup ? null : intent.status === 'finished' ? (
|
|
1076
1221
|
<p>All plan steps were applied.</p>
|
|
1077
1222
|
) : showConnectedProgress || preparing ? null : (
|
|
1078
|
-
<p>
|
|
1223
|
+
<p className="hud-step-label">
|
|
1079
1224
|
{stepLabel}
|
|
1080
1225
|
{intent.reason ? ` · ${intent.reason}` : ''}
|
|
1081
1226
|
</p>
|
|
1082
1227
|
)}
|
|
1083
1228
|
{!askingBlueprint && !sendingBlueprint && (
|
|
1084
1229
|
<>
|
|
1085
|
-
{canPlace && !intent.working && (
|
|
1086
|
-
<p>
|
|
1087
|
-
On the map, right-click to add a file or folder.{' '}
|
|
1088
|
-
<kbd>Space</kbd> places a file, <kbd>B</kbd> an island while
|
|
1089
|
-
walking.
|
|
1090
|
-
</p>
|
|
1091
|
-
)}
|
|
1092
1230
|
{intent.steps?.length > 0 && (
|
|
1093
1231
|
<ol className="hud-steps">
|
|
1094
1232
|
{intent.steps.map((step) => {
|
|
@@ -1096,6 +1234,8 @@ function SessionPanel({
|
|
|
1096
1234
|
const processing = processingStep === step.index
|
|
1097
1235
|
const accepted = acceptedSteps.has(step.index) && !proposed
|
|
1098
1236
|
const creating = processing && !proposed
|
|
1237
|
+
const explaining =
|
|
1238
|
+
startingExplain || Boolean(intent.pendingExplain)
|
|
1099
1239
|
const showStepAction =
|
|
1100
1240
|
creating ||
|
|
1101
1241
|
(canRunNext && invokeStep?.index === step.index) ||
|
|
@@ -1110,29 +1250,54 @@ function SessionPanel({
|
|
|
1110
1250
|
<span className="hud-step-main">
|
|
1111
1251
|
<span className="hud-step-title">{step.title}</span>
|
|
1112
1252
|
{showStepAction && (
|
|
1113
|
-
<
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
?
|
|
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
|
+
})
|
|
1122
1266
|
: act('invoke', {
|
|
1123
|
-
step: step.index
|
|
1267
|
+
step: step.index,
|
|
1124
1268
|
})
|
|
1125
|
-
|
|
1126
|
-
|
|
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)
|
|
1127
1292
|
})
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
</
|
|
1293
|
+
}}
|
|
1294
|
+
>
|
|
1295
|
+
{explaining
|
|
1296
|
+
? 'Starting explanation…'
|
|
1297
|
+
: 'Explain proposal'}
|
|
1298
|
+
</button>
|
|
1299
|
+
)}
|
|
1300
|
+
</span>
|
|
1136
1301
|
)}
|
|
1137
1302
|
</span>
|
|
1138
1303
|
</li>
|
|
@@ -1199,7 +1364,7 @@ function SessionPanel({
|
|
|
1199
1364
|
{(intent.createFolders ?? []).length > 0 && (
|
|
1200
1365
|
<>
|
|
1201
1366
|
<div className="hud-section-title hud-section-title-add">
|
|
1202
|
-
Added
|
|
1367
|
+
Added folders
|
|
1203
1368
|
</div>
|
|
1204
1369
|
<ul>
|
|
1205
1370
|
{intent.createFolders.map((id) => (
|
|
@@ -1275,17 +1440,20 @@ function SessionPanel({
|
|
|
1275
1440
|
)}
|
|
1276
1441
|
</MutationFold>
|
|
1277
1442
|
{planReady && (
|
|
1278
|
-
<div className="hud-
|
|
1279
|
-
<
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
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>
|
|
1286
1454
|
</div>
|
|
1287
1455
|
)}
|
|
1288
|
-
{pending && (
|
|
1456
|
+
{pending && !llmDisconnected && (
|
|
1289
1457
|
<>
|
|
1290
1458
|
<label className="hud-instruction">
|
|
1291
1459
|
<span>Alternative instruction for the LLM</span>
|
|
@@ -1298,24 +1466,27 @@ function SessionPanel({
|
|
|
1298
1466
|
onKeyDown={(event) => event.stopPropagation()}
|
|
1299
1467
|
/>
|
|
1300
1468
|
</label>
|
|
1301
|
-
<div className="hud-
|
|
1302
|
-
<
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
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>
|
|
1319
1490
|
</div>
|
|
1320
1491
|
</>
|
|
1321
1492
|
)}
|
|
@@ -1390,7 +1561,7 @@ function BranchChangesPanel({
|
|
|
1390
1561
|
{(changes.createFolders ?? []).length > 0 && (
|
|
1391
1562
|
<>
|
|
1392
1563
|
<div className="hud-section-title hud-section-title-add">
|
|
1393
|
-
Added
|
|
1564
|
+
Added folders
|
|
1394
1565
|
</div>
|
|
1395
1566
|
<ul>
|
|
1396
1567
|
{changes.createFolders.map((id) => (
|
|
@@ -1458,7 +1629,7 @@ type ExplorerInstruction = {
|
|
|
1458
1629
|
label: string
|
|
1459
1630
|
}
|
|
1460
1631
|
|
|
1461
|
-
type InstructionView = 'walk' | '
|
|
1632
|
+
type InstructionView = 'walk' | 'map'
|
|
1462
1633
|
|
|
1463
1634
|
type ExplorerInstructionSection = {
|
|
1464
1635
|
id: InstructionView
|
|
@@ -1491,10 +1662,7 @@ function explorerInstructions({
|
|
|
1491
1662
|
changePathsOnly,
|
|
1492
1663
|
selectedUserCreated,
|
|
1493
1664
|
infoVisible,
|
|
1494
|
-
thumbnailVisible,
|
|
1495
1665
|
importedBy,
|
|
1496
|
-
canStop,
|
|
1497
|
-
sessionCount,
|
|
1498
1666
|
showBranchChanges,
|
|
1499
1667
|
canShowBranchChanges,
|
|
1500
1668
|
}: {
|
|
@@ -1503,10 +1671,7 @@ function explorerInstructions({
|
|
|
1503
1671
|
changePathsOnly: boolean
|
|
1504
1672
|
selectedUserCreated: boolean
|
|
1505
1673
|
infoVisible: boolean
|
|
1506
|
-
thumbnailVisible: boolean
|
|
1507
1674
|
importedBy: boolean
|
|
1508
|
-
canStop: boolean
|
|
1509
|
-
sessionCount: number
|
|
1510
1675
|
showBranchChanges: boolean
|
|
1511
1676
|
canShowBranchChanges: boolean
|
|
1512
1677
|
}): ExplorerInstructionSection[] {
|
|
@@ -1540,23 +1705,6 @@ function explorerInstructions({
|
|
|
1540
1705
|
},
|
|
1541
1706
|
]
|
|
1542
1707
|
: []
|
|
1543
|
-
const stop: ExplorerInstruction[] = canStop
|
|
1544
|
-
? [
|
|
1545
|
-
{
|
|
1546
|
-
id: 'stop',
|
|
1547
|
-
keys: ['Stop'],
|
|
1548
|
-
label:
|
|
1549
|
-
sessionCount > 1
|
|
1550
|
-
? 'Ends the focused LLM session'
|
|
1551
|
-
: 'Ends this LLM session',
|
|
1552
|
-
},
|
|
1553
|
-
]
|
|
1554
|
-
: []
|
|
1555
|
-
const thumbnail: ExplorerInstruction = {
|
|
1556
|
-
id: 'thumbnail',
|
|
1557
|
-
keys: ['T'],
|
|
1558
|
-
label: thumbnailVisible ? 'Hide 3D view' : 'Show 3D view',
|
|
1559
|
-
}
|
|
1560
1708
|
return [
|
|
1561
1709
|
{
|
|
1562
1710
|
id: 'walk',
|
|
@@ -1567,8 +1715,6 @@ function explorerInstructions({
|
|
|
1567
1715
|
{ id: 'shift', keys: ['Shift'], label: 'Sprint' },
|
|
1568
1716
|
...(canPlace
|
|
1569
1717
|
? [
|
|
1570
|
-
{ id: 'space', keys: ['Space'], label: 'Place file' },
|
|
1571
|
-
{ id: 'b-island', keys: ['B'], label: 'Place island' },
|
|
1572
1718
|
{
|
|
1573
1719
|
id: 'point-to',
|
|
1574
1720
|
keys: ['Point to'],
|
|
@@ -1580,7 +1726,7 @@ function explorerInstructions({
|
|
|
1580
1726
|
{
|
|
1581
1727
|
id: 'dblclick-info',
|
|
1582
1728
|
keys: ['Double-click'],
|
|
1583
|
-
label: 'A
|
|
1729
|
+
label: 'A file or folder for info',
|
|
1584
1730
|
},
|
|
1585
1731
|
{ id: 'aim-line', keys: ['Click'], label: 'Aim a line to fly' },
|
|
1586
1732
|
...info,
|
|
@@ -1589,26 +1735,33 @@ function explorerInstructions({
|
|
|
1589
1735
|
{
|
|
1590
1736
|
id: 'update-model',
|
|
1591
1737
|
keys: ['Update model'],
|
|
1592
|
-
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',
|
|
1593
1745
|
},
|
|
1594
1746
|
{
|
|
1595
|
-
id: '
|
|
1596
|
-
keys: ['
|
|
1597
|
-
label:
|
|
1747
|
+
id: 'blueprint-select',
|
|
1748
|
+
keys: ['Blueprint colors'],
|
|
1749
|
+
label:
|
|
1750
|
+
'All colors stay visible; the selected color is where new files go',
|
|
1598
1751
|
},
|
|
1599
1752
|
{
|
|
1600
1753
|
id: 'blueprint-toggle',
|
|
1601
|
-
keys: ['Hide/Show
|
|
1602
|
-
label: '
|
|
1754
|
+
keys: ['Hide/Show'],
|
|
1755
|
+
label: 'Hide this color; other blueprint colors stay visible',
|
|
1603
1756
|
},
|
|
1604
1757
|
{
|
|
1605
1758
|
id: 'blueprint-clear',
|
|
1606
|
-
keys: ['Clear
|
|
1759
|
+
keys: ['Clear'],
|
|
1607
1760
|
label: 'Remove every planned file and folder',
|
|
1608
1761
|
},
|
|
1609
1762
|
{
|
|
1610
1763
|
id: 'blueprint-cleanup',
|
|
1611
|
-
keys: ['Cleanup
|
|
1764
|
+
keys: ['Cleanup'],
|
|
1612
1765
|
label: 'Drop blueprint files and folders that already exist',
|
|
1613
1766
|
},
|
|
1614
1767
|
{ id: 'toggle-map', keys: ['M'], label: 'Toggle map' },
|
|
@@ -1617,26 +1770,6 @@ function explorerInstructions({
|
|
|
1617
1770
|
keys: ['Double-click', 'Esc'],
|
|
1618
1771
|
label: 'Release mouse',
|
|
1619
1772
|
},
|
|
1620
|
-
...stop,
|
|
1621
|
-
],
|
|
1622
|
-
},
|
|
1623
|
-
{
|
|
1624
|
-
id: '3dview',
|
|
1625
|
-
title: '3D view',
|
|
1626
|
-
items: [
|
|
1627
|
-
{ id: 'scroll-zoom', keys: ['Scroll'], label: 'Zoom' },
|
|
1628
|
-
{ id: 'drag-pan', keys: ['Drag'], label: 'Pan' },
|
|
1629
|
-
{
|
|
1630
|
-
id: 'cmd-drag-rotate',
|
|
1631
|
-
keys: ['⌘', 'Ctrl', 'Drag'],
|
|
1632
|
-
label: 'Rotate',
|
|
1633
|
-
},
|
|
1634
|
-
{
|
|
1635
|
-
id: 'dblclick-reset',
|
|
1636
|
-
keys: ['Double-click'],
|
|
1637
|
-
label: 'Reset view',
|
|
1638
|
-
},
|
|
1639
|
-
thumbnail,
|
|
1640
1773
|
],
|
|
1641
1774
|
},
|
|
1642
1775
|
{
|
|
@@ -1645,26 +1778,26 @@ function explorerInstructions({
|
|
|
1645
1778
|
items: [
|
|
1646
1779
|
{ id: 'scroll-zoom', keys: ['Scroll'], label: 'Zoom' },
|
|
1647
1780
|
{ id: 'drag-pan', keys: ['Drag'], label: 'Pan' },
|
|
1648
|
-
{ id: 'click-block', keys: ['Click'], label: 'A
|
|
1781
|
+
{ id: 'click-block', keys: ['Click'], label: 'A file for info' },
|
|
1649
1782
|
{
|
|
1650
1783
|
id: 'click-island',
|
|
1651
1784
|
keys: ['Click'],
|
|
1652
|
-
label: '
|
|
1785
|
+
label: 'A folder for its files',
|
|
1653
1786
|
},
|
|
1654
1787
|
...(canPlace
|
|
1655
1788
|
? [
|
|
1656
|
-
{ id: 'select-island', keys: ['Click'], label: 'Select
|
|
1789
|
+
{ id: 'select-island', keys: ['Click'], label: 'Select a folder' },
|
|
1657
1790
|
{
|
|
1658
1791
|
id: 'add-file-folder',
|
|
1659
1792
|
keys: ['Right-click'],
|
|
1660
|
-
label: '
|
|
1793
|
+
label: 'Create a file or folder, or point to a folder',
|
|
1661
1794
|
},
|
|
1662
1795
|
]
|
|
1663
1796
|
: []),
|
|
1664
1797
|
{
|
|
1665
1798
|
id: 'option-click-walk',
|
|
1666
1799
|
keys: ['Option', 'Click'],
|
|
1667
|
-
label: '
|
|
1800
|
+
label: 'A folder to walk',
|
|
1668
1801
|
},
|
|
1669
1802
|
{
|
|
1670
1803
|
id: 'gold-pin',
|
|
@@ -1684,36 +1817,41 @@ function explorerInstructions({
|
|
|
1684
1817
|
: []),
|
|
1685
1818
|
...backspace,
|
|
1686
1819
|
...info,
|
|
1687
|
-
thumbnail,
|
|
1688
1820
|
imported,
|
|
1689
1821
|
...branch,
|
|
1690
1822
|
{
|
|
1691
1823
|
id: 'update-model',
|
|
1692
1824
|
keys: ['Update model'],
|
|
1693
|
-
label: 'Rescan and
|
|
1825
|
+
label: 'Rescan files and folders',
|
|
1694
1826
|
},
|
|
1695
1827
|
{
|
|
1696
|
-
id: '
|
|
1697
|
-
keys: ['
|
|
1698
|
-
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',
|
|
1699
1838
|
},
|
|
1700
1839
|
{
|
|
1701
1840
|
id: 'blueprint-toggle',
|
|
1702
|
-
keys: ['Hide/Show
|
|
1703
|
-
label: '
|
|
1841
|
+
keys: ['Hide/Show'],
|
|
1842
|
+
label: 'Hide this color; other blueprint colors stay visible',
|
|
1704
1843
|
},
|
|
1705
1844
|
{
|
|
1706
1845
|
id: 'blueprint-clear',
|
|
1707
|
-
keys: ['Clear
|
|
1846
|
+
keys: ['Clear'],
|
|
1708
1847
|
label: 'Remove every planned file and folder',
|
|
1709
1848
|
},
|
|
1710
1849
|
{
|
|
1711
1850
|
id: 'blueprint-cleanup',
|
|
1712
|
-
keys: ['Cleanup
|
|
1851
|
+
keys: ['Cleanup'],
|
|
1713
1852
|
label: 'Drop blueprint files and folders that already exist',
|
|
1714
1853
|
},
|
|
1715
1854
|
{ id: 'map-walk', keys: ['M'], label: 'Back to walk' },
|
|
1716
|
-
...stop,
|
|
1717
1855
|
],
|
|
1718
1856
|
},
|
|
1719
1857
|
]
|
|
@@ -1721,14 +1859,12 @@ function explorerInstructions({
|
|
|
1721
1859
|
|
|
1722
1860
|
type HUDProps = {
|
|
1723
1861
|
graph: CodebaseGraph
|
|
1724
|
-
layout: WorldLayout
|
|
1725
1862
|
mode: ViewMode
|
|
1726
1863
|
locked: boolean
|
|
1727
1864
|
selectedId: string | null
|
|
1728
1865
|
selectedTick?: number
|
|
1729
1866
|
inspectTick?: number
|
|
1730
1867
|
selectedFolder?: string | null
|
|
1731
|
-
landAt: [number, number]
|
|
1732
1868
|
aimedRelation: AimedRelation | null
|
|
1733
1869
|
aimedFileId?: string | null
|
|
1734
1870
|
intent: AgentIntent
|
|
@@ -1736,7 +1872,6 @@ type HUDProps = {
|
|
|
1736
1872
|
focusedSessionId?: string | null
|
|
1737
1873
|
nextAttachSessionId?: string | null
|
|
1738
1874
|
onFocusSession?: (sessionId: string) => void
|
|
1739
|
-
onSetupSession?: () => Promise<unknown>
|
|
1740
1875
|
onWorkflowAction: (
|
|
1741
1876
|
sessionId: string,
|
|
1742
1877
|
action: WorkflowAction,
|
|
@@ -1745,8 +1880,6 @@ type HUDProps = {
|
|
|
1745
1880
|
onNavigateDiff: (sessionId: string, diffId: string) => void
|
|
1746
1881
|
onOpenMap: () => void
|
|
1747
1882
|
onWalk: () => void
|
|
1748
|
-
followLook: boolean
|
|
1749
|
-
onToggleFollowLook: () => void
|
|
1750
1883
|
showBranchChanges?: boolean
|
|
1751
1884
|
branchChanges?: BranchChanges
|
|
1752
1885
|
canShowBranchChanges?: boolean
|
|
@@ -1771,6 +1904,9 @@ type HUDProps = {
|
|
|
1771
1904
|
onAddBlueprintFunction?: (fileId: string, name: string) => boolean
|
|
1772
1905
|
onAddBlueprintVariable?: (fileId: string, name: string) => boolean
|
|
1773
1906
|
onAddBlueprintImport?: (fileId: string, raw: string) => boolean
|
|
1907
|
+
importPickActive?: boolean
|
|
1908
|
+
onToggleImportPick?: () => void
|
|
1909
|
+
onCancelImportPick?: () => void
|
|
1774
1910
|
onRemoveBlueprintFunction?: (fileId: string, name: string) => void
|
|
1775
1911
|
onRemoveBlueprintVariable?: (fileId: string, name: string) => void
|
|
1776
1912
|
onRemoveBlueprintImport?: (
|
|
@@ -1788,32 +1924,40 @@ type HUDProps = {
|
|
|
1788
1924
|
kind: BlueprintPointerKind
|
|
1789
1925
|
path: string
|
|
1790
1926
|
name?: string
|
|
1927
|
+
color?: string
|
|
1791
1928
|
}) => void
|
|
1792
1929
|
onMapAddFile?: (folderPath: string) => void
|
|
1793
1930
|
onMapAddFolder?: (folderPath: string) => void
|
|
1931
|
+
onRenameCreatedFile?: (fileId: string, name: string) => string | null
|
|
1794
1932
|
onInspectFile?: (fileId: string) => void
|
|
1795
1933
|
onInspectBlock?: (fileId: string) => void
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1934
|
+
onExplainTarget?: (input: {
|
|
1935
|
+
kind: ExplainTargetKind
|
|
1936
|
+
path: string
|
|
1937
|
+
name?: string
|
|
1938
|
+
}) => void
|
|
1799
1939
|
blueprintHidden?: boolean
|
|
1800
1940
|
blueprintHasContent?: boolean
|
|
1801
1941
|
blueprintCanCleanup?: boolean
|
|
1942
|
+
blueprintColor?: string | null
|
|
1943
|
+
blueprintOptions?: BlueprintOption[]
|
|
1944
|
+
blueprintColorPointers?: BlueprintColorOption[]
|
|
1945
|
+
onSelectBlueprintColor?: (color: string) => void
|
|
1802
1946
|
onToggleBlueprintHidden?: () => void
|
|
1803
1947
|
onClearBlueprint?: () => void
|
|
1804
1948
|
onCleanupBlueprint?: () => void
|
|
1949
|
+
devTargets?: DevTargetsState
|
|
1950
|
+
onSelectDevTarget?: (id: string) => void
|
|
1805
1951
|
}
|
|
1806
1952
|
|
|
1807
1953
|
export function HUD({
|
|
1808
1954
|
graph,
|
|
1809
|
-
layout,
|
|
1810
1955
|
mode,
|
|
1811
1956
|
locked,
|
|
1812
1957
|
selectedId,
|
|
1813
1958
|
selectedTick = 0,
|
|
1814
1959
|
inspectTick = 0,
|
|
1815
1960
|
selectedFolder = null,
|
|
1816
|
-
landAt,
|
|
1817
1961
|
aimedRelation,
|
|
1818
1962
|
aimedFileId = null,
|
|
1819
1963
|
intent,
|
|
@@ -1821,13 +1965,10 @@ export function HUD({
|
|
|
1821
1965
|
focusedSessionId = null,
|
|
1822
1966
|
nextAttachSessionId = null,
|
|
1823
1967
|
onFocusSession,
|
|
1824
|
-
onSetupSession,
|
|
1825
1968
|
onWorkflowAction,
|
|
1826
1969
|
onNavigateDiff,
|
|
1827
1970
|
onOpenMap,
|
|
1828
1971
|
onWalk,
|
|
1829
|
-
followLook,
|
|
1830
|
-
onToggleFollowLook,
|
|
1831
1972
|
showBranchChanges = false,
|
|
1832
1973
|
branchChanges,
|
|
1833
1974
|
canShowBranchChanges = false,
|
|
@@ -1848,10 +1989,12 @@ export function HUD({
|
|
|
1848
1989
|
blueprintVariables = [],
|
|
1849
1990
|
blueprintImports = [],
|
|
1850
1991
|
blueprintNotes = [],
|
|
1851
|
-
blueprintPointers = [],
|
|
1852
1992
|
onAddBlueprintFunction,
|
|
1853
1993
|
onAddBlueprintVariable,
|
|
1854
1994
|
onAddBlueprintImport,
|
|
1995
|
+
importPickActive = false,
|
|
1996
|
+
onToggleImportPick,
|
|
1997
|
+
onCancelImportPick,
|
|
1855
1998
|
onRemoveBlueprintFunction,
|
|
1856
1999
|
onRemoveBlueprintVariable,
|
|
1857
2000
|
onRemoveBlueprintImport,
|
|
@@ -1859,17 +2002,22 @@ export function HUD({
|
|
|
1859
2002
|
onToggleBlueprintPointer,
|
|
1860
2003
|
onMapAddFile,
|
|
1861
2004
|
onMapAddFolder,
|
|
2005
|
+
onRenameCreatedFile,
|
|
1862
2006
|
onInspectFile,
|
|
1863
2007
|
onInspectBlock,
|
|
1864
|
-
|
|
1865
|
-
createdIds = [],
|
|
1866
|
-
deletedIds = [],
|
|
2008
|
+
onExplainTarget,
|
|
1867
2009
|
blueprintHidden = false,
|
|
1868
2010
|
blueprintHasContent = false,
|
|
1869
2011
|
blueprintCanCleanup = false,
|
|
2012
|
+
blueprintColor = null,
|
|
2013
|
+
blueprintOptions = [],
|
|
2014
|
+
blueprintColorPointers = [],
|
|
2015
|
+
onSelectBlueprintColor,
|
|
1870
2016
|
onToggleBlueprintHidden,
|
|
1871
2017
|
onClearBlueprint,
|
|
1872
2018
|
onCleanupBlueprint,
|
|
2019
|
+
devTargets,
|
|
2020
|
+
onSelectDevTarget,
|
|
1873
2021
|
}: HUDProps) {
|
|
1874
2022
|
const selected = graph.files.find((file) => file.id === selectedId)
|
|
1875
2023
|
const selectedFolderNode = graph.folders.find(
|
|
@@ -1890,7 +2038,6 @@ export function HUD({
|
|
|
1890
2038
|
const sessions = (intents ?? [intent]).filter(
|
|
1891
2039
|
(item) => item.sessionId && isReviewingIntent(item.status),
|
|
1892
2040
|
)
|
|
1893
|
-
const canStop = canStopSession(intent)
|
|
1894
2041
|
const nextAttachSession =
|
|
1895
2042
|
sessions.find((session) => session.sessionId === nextAttachSessionId) ??
|
|
1896
2043
|
[...sessions].reverse().find((session) => session.awaitingAttach) ??
|
|
@@ -1898,6 +2045,9 @@ export function HUD({
|
|
|
1898
2045
|
const [walkIntro, setWalkIntro] = useState(false)
|
|
1899
2046
|
const walkIntroSeen = useRef(false)
|
|
1900
2047
|
const [instructionsOpen, setInstructionsOpen] = useState(false)
|
|
2048
|
+
const [actionsMenuOpen, setActionsMenuOpen] = useState(false)
|
|
2049
|
+
const [actionsMenuPosition, setActionsMenuPosition] = useState<CSSProperties>()
|
|
2050
|
+
const actionsMenuRef = useRef<HTMLDivElement>(null)
|
|
1901
2051
|
const [noteEditor, setNoteEditor] = useState<{
|
|
1902
2052
|
file: string
|
|
1903
2053
|
kind: BlueprintNoteKind
|
|
@@ -1906,13 +2056,8 @@ export function HUD({
|
|
|
1906
2056
|
subtitle: string
|
|
1907
2057
|
placeholder: string
|
|
1908
2058
|
} | null>(null)
|
|
1909
|
-
const [setupError, setSetupError] = useState<string | null>(null)
|
|
1910
|
-
const [setupBusy, setSetupBusy] = useState(false)
|
|
1911
2059
|
const [infoVisible, setInfoVisible] = useState(false)
|
|
1912
2060
|
const [infoMinimized, setInfoMinimized] = useState(false)
|
|
1913
|
-
const [thumbnailVisible, setThumbnailVisible] = useState(true)
|
|
1914
|
-
const [thumbnailMinimized, setThumbnailMinimized] = useState(false)
|
|
1915
|
-
const [thumbnailMaximized, setThumbnailMaximized] = useState(false)
|
|
1916
2061
|
const infoPanelRef = useRef<HTMLDivElement>(null)
|
|
1917
2062
|
const canPlace = true
|
|
1918
2063
|
const overlay = showBranchChanges && branchChanges ? branchChanges : intent
|
|
@@ -1983,19 +2128,23 @@ export function HUD({
|
|
|
1983
2128
|
)
|
|
1984
2129
|
const canEditBlueprint =
|
|
1985
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
|
+
}
|
|
1986
2145
|
const selectedFileNote = selected
|
|
1987
2146
|
? findBlueprintNote(blueprintNotes, selected.id, 'file')
|
|
1988
2147
|
: ''
|
|
1989
|
-
const selectedFilePointed = selected
|
|
1990
|
-
? findBlueprintPointer(blueprintPointers, 'file', selected.id)
|
|
1991
|
-
: false
|
|
1992
|
-
const selectedFolderPointed = selectedFolderNode
|
|
1993
|
-
? findBlueprintPointer(
|
|
1994
|
-
blueprintPointers,
|
|
1995
|
-
'folder',
|
|
1996
|
-
selectedFolderNode.path,
|
|
1997
|
-
)
|
|
1998
|
-
: false
|
|
1999
2148
|
const openFileNote = () => {
|
|
2000
2149
|
if (!selected || !onSetBlueprintNote) return
|
|
2001
2150
|
setInstructionsOpen(false)
|
|
@@ -2073,6 +2222,11 @@ export function HUD({
|
|
|
2073
2222
|
setInfoMinimized(false)
|
|
2074
2223
|
}, [locked])
|
|
2075
2224
|
|
|
2225
|
+
useEffect(() => {
|
|
2226
|
+
if (infoVisible || !importPickActive) return
|
|
2227
|
+
onCancelImportPick?.()
|
|
2228
|
+
}, [importPickActive, infoVisible, onCancelImportPick])
|
|
2229
|
+
|
|
2076
2230
|
const infoOpen = infoVisible && Boolean(selected || selectedFolderNode)
|
|
2077
2231
|
|
|
2078
2232
|
useEffect(() => {
|
|
@@ -2101,26 +2255,6 @@ export function HUD({
|
|
|
2101
2255
|
return () => window.removeEventListener('keydown', onKey)
|
|
2102
2256
|
}, [aimedFileId, infoVisible, mode, onInspectBlock, selectedId])
|
|
2103
2257
|
|
|
2104
|
-
useEffect(() => {
|
|
2105
|
-
if (!mapping) return
|
|
2106
|
-
const onKey = (event: KeyboardEvent) => {
|
|
2107
|
-
if (event.repeat || event.code !== 'KeyT') return
|
|
2108
|
-
if (shouldIgnoreShortcut(event)) return
|
|
2109
|
-
event.preventDefault()
|
|
2110
|
-
setThumbnailVisible((visible) => {
|
|
2111
|
-
if (!visible) {
|
|
2112
|
-
setThumbnailMinimized(false)
|
|
2113
|
-
setThumbnailMaximized(false)
|
|
2114
|
-
} else {
|
|
2115
|
-
setThumbnailMaximized(false)
|
|
2116
|
-
}
|
|
2117
|
-
return !visible
|
|
2118
|
-
})
|
|
2119
|
-
}
|
|
2120
|
-
window.addEventListener('keydown', onKey)
|
|
2121
|
-
return () => window.removeEventListener('keydown', onKey)
|
|
2122
|
-
}, [mapping])
|
|
2123
|
-
|
|
2124
2258
|
useEffect(() => {
|
|
2125
2259
|
if (!infoVisible || (!selectedId && !selectedFolder)) return
|
|
2126
2260
|
const onKey = (event: KeyboardEvent) => {
|
|
@@ -2140,6 +2274,7 @@ export function HUD({
|
|
|
2140
2274
|
useEffect(() => {
|
|
2141
2275
|
if (!instructionsOpen) return
|
|
2142
2276
|
document.exitPointerLock()
|
|
2277
|
+
setActionsMenuOpen(false)
|
|
2143
2278
|
const onKey = (event: KeyboardEvent) => {
|
|
2144
2279
|
if (event.code !== 'Escape') return
|
|
2145
2280
|
if (shouldIgnoreShortcut(event)) return
|
|
@@ -2151,24 +2286,61 @@ export function HUD({
|
|
|
2151
2286
|
return () => window.removeEventListener('keydown', onKey, true)
|
|
2152
2287
|
}, [instructionsOpen])
|
|
2153
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
|
+
|
|
2154
2333
|
const instructionSections = explorerInstructions({
|
|
2155
2334
|
canPlace,
|
|
2156
2335
|
hasChangeSet,
|
|
2157
2336
|
changePathsOnly,
|
|
2158
2337
|
selectedUserCreated: Boolean(selected?.userCreated),
|
|
2159
2338
|
infoVisible,
|
|
2160
|
-
thumbnailVisible,
|
|
2161
2339
|
importedBy,
|
|
2162
|
-
canStop,
|
|
2163
|
-
sessionCount: sessions.length,
|
|
2164
2340
|
showBranchChanges,
|
|
2165
2341
|
canShowBranchChanges,
|
|
2166
2342
|
})
|
|
2167
|
-
const currentInstructionView: InstructionView = mapping
|
|
2168
|
-
? thumbnailMaximized
|
|
2169
|
-
? '3dview'
|
|
2170
|
-
: 'map'
|
|
2171
|
-
: 'walk'
|
|
2343
|
+
const currentInstructionView: InstructionView = mapping ? 'map' : 'walk'
|
|
2172
2344
|
|
|
2173
2345
|
return (
|
|
2174
2346
|
<div className="hud">
|
|
@@ -2183,13 +2355,8 @@ export function HUD({
|
|
|
2183
2355
|
</p>
|
|
2184
2356
|
<p>
|
|
2185
2357
|
<kbd>W</kbd> <kbd>A</kbd> <kbd>S</kbd> <kbd>D</kbd> walk,{' '}
|
|
2186
|
-
<kbd>Shift</kbd> sprint
|
|
2187
|
-
|
|
2188
|
-
<>
|
|
2189
|
-
, <kbd>Space</kbd> place a file, <kbd>B</kbd> place an island
|
|
2190
|
-
</>
|
|
2191
|
-
) : null}
|
|
2192
|
-
, 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.
|
|
2193
2360
|
</p>
|
|
2194
2361
|
</div>
|
|
2195
2362
|
</div>
|
|
@@ -2202,6 +2369,7 @@ export function HUD({
|
|
|
2202
2369
|
<div className="hud-name-gate">
|
|
2203
2370
|
<NameInput
|
|
2204
2371
|
placeholder="Folder name"
|
|
2372
|
+
fallbackName="New folder"
|
|
2205
2373
|
onCommit={onCommitIslandName}
|
|
2206
2374
|
onCancel={onCancelIslandName}
|
|
2207
2375
|
/>
|
|
@@ -2233,20 +2401,40 @@ export function HUD({
|
|
|
2233
2401
|
>
|
|
2234
2402
|
Walk
|
|
2235
2403
|
</button>
|
|
2236
|
-
{canStop && (
|
|
2237
|
-
<button
|
|
2238
|
-
className="hud-button hud-button-reject"
|
|
2239
|
-
type="button"
|
|
2240
|
-
aria-label="Stop LLM session"
|
|
2241
|
-
onClick={() =>
|
|
2242
|
-
intent.sessionId && onWorkflowAction(intent.sessionId, 'stop')
|
|
2243
|
-
}
|
|
2244
|
-
>
|
|
2245
|
-
Stop
|
|
2246
|
-
</button>
|
|
2247
|
-
)}
|
|
2248
2404
|
</div>
|
|
2249
|
-
{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
|
+
)}
|
|
2250
2438
|
</div>
|
|
2251
2439
|
|
|
2252
2440
|
{(sessions.length > 0 || showBranchChanges) && (
|
|
@@ -2257,23 +2445,33 @@ export function HUD({
|
|
|
2257
2445
|
const active =
|
|
2258
2446
|
session.sessionId === (focusedSessionId ?? intent.sessionId)
|
|
2259
2447
|
const attached = session.awaitingAttach === false
|
|
2260
|
-
const label =
|
|
2448
|
+
const label = sessionDisplayName(session) || 'Session'
|
|
2261
2449
|
return (
|
|
2262
2450
|
<button
|
|
2263
2451
|
className="hud-button hud-session-tab"
|
|
2264
2452
|
type="button"
|
|
2265
2453
|
role="tab"
|
|
2266
2454
|
aria-selected={active}
|
|
2455
|
+
aria-label={
|
|
2456
|
+
attached ? `${label}, attached` : `${label}, waiting`
|
|
2457
|
+
}
|
|
2267
2458
|
data-active={active}
|
|
2268
2459
|
data-attached={attached}
|
|
2269
2460
|
key={session.sessionId}
|
|
2270
2461
|
title={attached ? `${label} · Attached` : `${label} · Waiting`}
|
|
2462
|
+
style={
|
|
2463
|
+
session.colorHex
|
|
2464
|
+
? ({
|
|
2465
|
+
'--session-color': session.colorHex,
|
|
2466
|
+
} as CSSProperties)
|
|
2467
|
+
: undefined
|
|
2468
|
+
}
|
|
2271
2469
|
onClick={() => {
|
|
2272
2470
|
if (session.sessionId) onFocusSession?.(session.sessionId)
|
|
2471
|
+
if (session.color) onSelectBlueprintColor?.(session.color)
|
|
2273
2472
|
}}
|
|
2274
2473
|
>
|
|
2275
|
-
<
|
|
2276
|
-
<span className="hud-session-tab-label">{label}</span>
|
|
2474
|
+
<SessionSwatch colorHex={session.colorHex} />
|
|
2277
2475
|
</button>
|
|
2278
2476
|
)
|
|
2279
2477
|
})}
|
|
@@ -2305,7 +2503,16 @@ export function HUD({
|
|
|
2305
2503
|
data-minimized={infoMinimized}
|
|
2306
2504
|
>
|
|
2307
2505
|
<PanelChrome
|
|
2308
|
-
title={
|
|
2506
|
+
title={
|
|
2507
|
+
canRenameSelected ? (
|
|
2508
|
+
<InfoNameField
|
|
2509
|
+
name={selected.name}
|
|
2510
|
+
onRename={renameSelectedFile}
|
|
2511
|
+
/>
|
|
2512
|
+
) : (
|
|
2513
|
+
selected.name
|
|
2514
|
+
)
|
|
2515
|
+
}
|
|
2309
2516
|
minimized={infoMinimized}
|
|
2310
2517
|
onMinimize={() => setInfoMinimized((current) => !current)}
|
|
2311
2518
|
onClose={() => {
|
|
@@ -2319,28 +2526,43 @@ export function HUD({
|
|
|
2319
2526
|
<p>
|
|
2320
2527
|
{selected.lines} lines · {selected.language}
|
|
2321
2528
|
</p>
|
|
2322
|
-
{
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
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>
|
|
2330
2550
|
)}
|
|
2331
2551
|
{canEditBlueprint && onToggleBlueprintPointer && (
|
|
2332
|
-
<
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
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
|
+
})
|
|
2339
2564
|
}
|
|
2340
|
-
|
|
2341
|
-
<EyeIcon size={15} />
|
|
2342
|
-
{selectedFilePointed ? 'Stop pointing' : 'Point to file'}
|
|
2343
|
-
</button>
|
|
2565
|
+
/>
|
|
2344
2566
|
)}
|
|
2345
2567
|
{canEditBlueprint && onSetBlueprintNote && (
|
|
2346
2568
|
<button
|
|
@@ -2398,6 +2620,21 @@ export function HUD({
|
|
|
2398
2620
|
>
|
|
2399
2621
|
{symbol.name}
|
|
2400
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}
|
|
2401
2638
|
</li>
|
|
2402
2639
|
))}
|
|
2403
2640
|
</ul>
|
|
@@ -2433,23 +2670,35 @@ export function HUD({
|
|
|
2433
2670
|
}
|
|
2434
2671
|
canEdit={Boolean(canEditBlueprint && onSetBlueprintNote)}
|
|
2435
2672
|
canRemove={Boolean(canEditBlueprint && symbol.intended)}
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2673
|
+
pointerTarget={{
|
|
2674
|
+
kind: 'function',
|
|
2675
|
+
path: selected.id,
|
|
2676
|
+
name: symbol.name,
|
|
2677
|
+
}}
|
|
2678
|
+
colorPointers={blueprintColorPointers}
|
|
2679
|
+
currentColorId={blueprintColor}
|
|
2442
2680
|
onRemove={() =>
|
|
2443
2681
|
onRemoveBlueprintFunction?.(selected.id, symbol.name)
|
|
2444
2682
|
}
|
|
2445
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
|
+
}
|
|
2446
2694
|
onTogglePoint={
|
|
2447
2695
|
onToggleBlueprintPointer
|
|
2448
|
-
? () =>
|
|
2696
|
+
? (color) =>
|
|
2449
2697
|
onToggleBlueprintPointer({
|
|
2450
2698
|
kind: 'function',
|
|
2451
2699
|
path: selected.id,
|
|
2452
2700
|
name: symbol.name,
|
|
2701
|
+
color,
|
|
2453
2702
|
})
|
|
2454
2703
|
: undefined
|
|
2455
2704
|
}
|
|
@@ -2474,20 +2723,32 @@ export function HUD({
|
|
|
2474
2723
|
noteEditor.name === item.name
|
|
2475
2724
|
}
|
|
2476
2725
|
canEdit={Boolean(canEditBlueprint && onSetBlueprintNote)}
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2726
|
+
pointerTarget={{
|
|
2727
|
+
kind: 'function',
|
|
2728
|
+
path: selected.id,
|
|
2729
|
+
name: item.name,
|
|
2730
|
+
}}
|
|
2731
|
+
colorPointers={blueprintColorPointers}
|
|
2732
|
+
currentColorId={blueprintColor}
|
|
2483
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
|
+
}
|
|
2484
2744
|
onTogglePoint={
|
|
2485
2745
|
onToggleBlueprintPointer
|
|
2486
|
-
? () =>
|
|
2746
|
+
? (color) =>
|
|
2487
2747
|
onToggleBlueprintPointer({
|
|
2488
2748
|
kind: 'function',
|
|
2489
2749
|
path: selected.id,
|
|
2490
2750
|
name: item.name,
|
|
2751
|
+
color,
|
|
2491
2752
|
})
|
|
2492
2753
|
: undefined
|
|
2493
2754
|
}
|
|
@@ -2531,23 +2792,35 @@ export function HUD({
|
|
|
2531
2792
|
}
|
|
2532
2793
|
canEdit={Boolean(canEditBlueprint && onSetBlueprintNote)}
|
|
2533
2794
|
canRemove={Boolean(canEditBlueprint && symbol.intended)}
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2795
|
+
pointerTarget={{
|
|
2796
|
+
kind: 'variable',
|
|
2797
|
+
path: selected.id,
|
|
2798
|
+
name: symbol.name,
|
|
2799
|
+
}}
|
|
2800
|
+
colorPointers={blueprintColorPointers}
|
|
2801
|
+
currentColorId={blueprintColor}
|
|
2540
2802
|
onRemove={() =>
|
|
2541
2803
|
onRemoveBlueprintVariable?.(selected.id, symbol.name)
|
|
2542
2804
|
}
|
|
2543
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
|
+
}
|
|
2544
2816
|
onTogglePoint={
|
|
2545
2817
|
onToggleBlueprintPointer
|
|
2546
|
-
? () =>
|
|
2818
|
+
? (color) =>
|
|
2547
2819
|
onToggleBlueprintPointer({
|
|
2548
2820
|
kind: 'variable',
|
|
2549
2821
|
path: selected.id,
|
|
2550
2822
|
name: symbol.name,
|
|
2823
|
+
color,
|
|
2551
2824
|
})
|
|
2552
2825
|
: undefined
|
|
2553
2826
|
}
|
|
@@ -2572,20 +2845,32 @@ export function HUD({
|
|
|
2572
2845
|
noteEditor.name === item.name
|
|
2573
2846
|
}
|
|
2574
2847
|
canEdit={Boolean(canEditBlueprint && onSetBlueprintNote)}
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2848
|
+
pointerTarget={{
|
|
2849
|
+
kind: 'variable',
|
|
2850
|
+
path: selected.id,
|
|
2851
|
+
name: item.name,
|
|
2852
|
+
}}
|
|
2853
|
+
colorPointers={blueprintColorPointers}
|
|
2854
|
+
currentColorId={blueprintColor}
|
|
2581
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
|
+
}
|
|
2582
2866
|
onTogglePoint={
|
|
2583
2867
|
onToggleBlueprintPointer
|
|
2584
|
-
? () =>
|
|
2868
|
+
? (color) =>
|
|
2585
2869
|
onToggleBlueprintPointer({
|
|
2586
2870
|
kind: 'variable',
|
|
2587
2871
|
path: selected.id,
|
|
2588
2872
|
name: item.name,
|
|
2873
|
+
color,
|
|
2589
2874
|
})
|
|
2590
2875
|
: undefined
|
|
2591
2876
|
}
|
|
@@ -2677,10 +2962,20 @@ export function HUD({
|
|
|
2677
2962
|
</ul>
|
|
2678
2963
|
)}
|
|
2679
2964
|
{canEditBlueprint && !importedBy && onAddBlueprintImport && (
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
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
|
+
</>
|
|
2684
2979
|
)}
|
|
2685
2980
|
</div>
|
|
2686
2981
|
)}
|
|
@@ -2700,6 +2995,16 @@ export function HUD({
|
|
|
2700
2995
|
setInfoVisible(false)
|
|
2701
2996
|
setInfoMinimized(false)
|
|
2702
2997
|
}}
|
|
2998
|
+
onExplain={
|
|
2999
|
+
onExplainTarget
|
|
3000
|
+
? () =>
|
|
3001
|
+
onExplainTarget({
|
|
3002
|
+
kind: 'folder',
|
|
3003
|
+
path: selectedFolderNode.path,
|
|
3004
|
+
})
|
|
3005
|
+
: undefined
|
|
3006
|
+
}
|
|
3007
|
+
explainLabel={`Explain ${selectedFolderNode.name}`}
|
|
2703
3008
|
/>
|
|
2704
3009
|
{!infoMinimized && (
|
|
2705
3010
|
<div ref={infoPanelRef} className="hud-panel-body">
|
|
@@ -2713,42 +3018,55 @@ export function HUD({
|
|
|
2713
3018
|
</p>
|
|
2714
3019
|
<div className="hud-section-title">Files</div>
|
|
2715
3020
|
{folderFiles.length === 0 ? (
|
|
2716
|
-
<p>No files
|
|
3021
|
+
<p>No files in this folder</p>
|
|
2717
3022
|
) : (
|
|
2718
3023
|
<ul>
|
|
2719
3024
|
{folderFiles.map((file) => (
|
|
2720
3025
|
<li key={file.id}>
|
|
2721
3026
|
<span>{file.name}</span>
|
|
2722
|
-
{
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
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>
|
|
2730
3049
|
)}
|
|
2731
3050
|
</li>
|
|
2732
3051
|
))}
|
|
2733
3052
|
</ul>
|
|
2734
3053
|
)}
|
|
2735
3054
|
{canPlace && onToggleBlueprintPointer && (
|
|
2736
|
-
<
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
3055
|
+
<PointColorControl
|
|
3056
|
+
target={{ kind: 'folder', path: selectedFolderNode.path }}
|
|
3057
|
+
colorPointers={blueprintColorPointers}
|
|
3058
|
+
currentColorId={blueprintColor}
|
|
3059
|
+
idleLabel="Point to folder"
|
|
3060
|
+
pointedLabel="Stop pointing"
|
|
2741
3061
|
disabled={naming || selectedFolderNode.path.startsWith('draft:')}
|
|
2742
|
-
|
|
3062
|
+
onToggle={(color) =>
|
|
2743
3063
|
onToggleBlueprintPointer({
|
|
2744
3064
|
kind: 'folder',
|
|
2745
3065
|
path: selectedFolderNode.path,
|
|
3066
|
+
color,
|
|
2746
3067
|
})
|
|
2747
3068
|
}
|
|
2748
|
-
|
|
2749
|
-
<EyeIcon size={15} />
|
|
2750
|
-
{selectedFolderPointed ? 'Stop pointing' : 'Point to folder'}
|
|
2751
|
-
</button>
|
|
3069
|
+
/>
|
|
2752
3070
|
)}
|
|
2753
3071
|
{canPlace && mapping && onMapAddFile && onMapAddFolder && (
|
|
2754
3072
|
<div className="hud-decide hud-map-blueprint">
|
|
@@ -2774,49 +3092,6 @@ export function HUD({
|
|
|
2774
3092
|
)}
|
|
2775
3093
|
</aside>
|
|
2776
3094
|
)}
|
|
2777
|
-
|
|
2778
|
-
{mapping && thumbnailVisible && (
|
|
2779
|
-
<SelectionThumbnail
|
|
2780
|
-
graph={graph}
|
|
2781
|
-
layout={layout}
|
|
2782
|
-
selectedId={selectedId}
|
|
2783
|
-
selectedFolder={selectedFolder}
|
|
2784
|
-
landAt={landAt}
|
|
2785
|
-
importedBy={importedBy}
|
|
2786
|
-
minimized={thumbnailMinimized}
|
|
2787
|
-
maximized={thumbnailMaximized}
|
|
2788
|
-
plannedIds={plannedIds}
|
|
2789
|
-
createdIds={createdIds}
|
|
2790
|
-
deletedIds={deletedIds}
|
|
2791
|
-
pointedFileIds={
|
|
2792
|
-
blueprintHidden
|
|
2793
|
-
? []
|
|
2794
|
-
: blueprintPointers.flatMap((item) =>
|
|
2795
|
-
item.kind === 'folder' ? [] : [item.path],
|
|
2796
|
-
)
|
|
2797
|
-
}
|
|
2798
|
-
pointedFolderPaths={
|
|
2799
|
-
blueprintHidden
|
|
2800
|
-
? []
|
|
2801
|
-
: blueprintPointers.flatMap((item) =>
|
|
2802
|
-
item.kind === 'folder' ? [item.path] : [],
|
|
2803
|
-
)
|
|
2804
|
-
}
|
|
2805
|
-
onMinimize={() => {
|
|
2806
|
-
setThumbnailMaximized(false)
|
|
2807
|
-
setThumbnailMinimized((current) => !current)
|
|
2808
|
-
}}
|
|
2809
|
-
onMaximize={() => {
|
|
2810
|
-
setThumbnailMinimized(false)
|
|
2811
|
-
setThumbnailMaximized((current) => !current)
|
|
2812
|
-
}}
|
|
2813
|
-
onHide={() => {
|
|
2814
|
-
setThumbnailVisible(false)
|
|
2815
|
-
setThumbnailMinimized(false)
|
|
2816
|
-
setThumbnailMaximized(false)
|
|
2817
|
-
}}
|
|
2818
|
-
/>
|
|
2819
|
-
)}
|
|
2820
3095
|
</div>
|
|
2821
3096
|
|
|
2822
3097
|
{noteEditor && onSetBlueprintNote && (
|
|
@@ -2889,52 +3164,54 @@ export function HUD({
|
|
|
2889
3164
|
|
|
2890
3165
|
<div className="hud-bottom">
|
|
2891
3166
|
<div className="hud-bottom-actions">
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
aria-expanded={instructionsOpen}
|
|
2898
|
-
onClick={() => {
|
|
2899
|
-
setNoteEditor(null)
|
|
2900
|
-
setInstructionsOpen((open) => !open)
|
|
2901
|
-
}}
|
|
2902
|
-
>
|
|
2903
|
-
Instructions
|
|
2904
|
-
</button>
|
|
2905
|
-
<button
|
|
2906
|
-
className="hud-button"
|
|
2907
|
-
type="button"
|
|
2908
|
-
aria-label="Rescan the project and rebuild the map"
|
|
2909
|
-
disabled={updatingModel}
|
|
2910
|
-
onClick={onUpdateModel}
|
|
2911
|
-
>
|
|
2912
|
-
{updatingModel ? 'Updating…' : 'Update model'}
|
|
2913
|
-
</button>
|
|
2914
|
-
{onSetupSession && (
|
|
2915
|
-
<button
|
|
2916
|
-
className="hud-button"
|
|
2917
|
-
type="button"
|
|
2918
|
-
aria-label="Start an LLM session without attaching a chat yet"
|
|
2919
|
-
title={setupError ?? 'Start an LLM session without attaching a chat yet'}
|
|
2920
|
-
disabled={setupBusy}
|
|
2921
|
-
onClick={() => {
|
|
2922
|
-
setInstructionsOpen(false)
|
|
2923
|
-
setSetupError(null)
|
|
2924
|
-
setSetupBusy(true)
|
|
2925
|
-
void onSetupSession()
|
|
2926
|
-
.catch((caught) => {
|
|
2927
|
-
setSetupError(
|
|
2928
|
-
caught instanceof Error
|
|
2929
|
-
? caught.message
|
|
2930
|
-
: 'Could not set up the session',
|
|
2931
|
-
)
|
|
2932
|
-
})
|
|
2933
|
-
.finally(() => setSetupBusy(false))
|
|
2934
|
-
}}
|
|
3167
|
+
{onSelectBlueprintColor && blueprintOptions.length > 0 && (
|
|
3168
|
+
<div
|
|
3169
|
+
className="hud-blueprint-select"
|
|
3170
|
+
role="radiogroup"
|
|
3171
|
+
aria-label="Blueprint"
|
|
2935
3172
|
>
|
|
2936
|
-
{
|
|
2937
|
-
|
|
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>
|
|
2938
3215
|
)}
|
|
2939
3216
|
{onToggleBlueprintHidden && (
|
|
2940
3217
|
<button
|
|
@@ -2944,12 +3221,12 @@ export function HUD({
|
|
|
2944
3221
|
aria-label={blueprintHidden ? 'Show blueprint' : 'Hide blueprint'}
|
|
2945
3222
|
title={
|
|
2946
3223
|
blueprintHidden
|
|
2947
|
-
? 'Show
|
|
2948
|
-
: 'Hide
|
|
3224
|
+
? 'Show this blueprint overlay'
|
|
3225
|
+
: 'Hide this blueprint overlay; other colors stay visible'
|
|
2949
3226
|
}
|
|
2950
3227
|
onClick={onToggleBlueprintHidden}
|
|
2951
3228
|
>
|
|
2952
|
-
{blueprintHidden ? 'Show
|
|
3229
|
+
{blueprintHidden ? 'Show' : 'Hide'}
|
|
2953
3230
|
</button>
|
|
2954
3231
|
)}
|
|
2955
3232
|
{onClearBlueprint && (
|
|
@@ -2961,7 +3238,7 @@ export function HUD({
|
|
|
2961
3238
|
disabled={!blueprintHasContent}
|
|
2962
3239
|
onClick={onClearBlueprint}
|
|
2963
3240
|
>
|
|
2964
|
-
Clear
|
|
3241
|
+
Clear
|
|
2965
3242
|
</button>
|
|
2966
3243
|
)}
|
|
2967
3244
|
{onCleanupBlueprint && (
|
|
@@ -2973,7 +3250,7 @@ export function HUD({
|
|
|
2973
3250
|
disabled={!blueprintCanCleanup}
|
|
2974
3251
|
onClick={onCleanupBlueprint}
|
|
2975
3252
|
>
|
|
2976
|
-
Cleanup
|
|
3253
|
+
Cleanup
|
|
2977
3254
|
</button>
|
|
2978
3255
|
)}
|
|
2979
3256
|
</div>
|
|
@@ -3017,48 +3294,6 @@ export function HUD({
|
|
|
3017
3294
|
</span>
|
|
3018
3295
|
</button>
|
|
3019
3296
|
)}
|
|
3020
|
-
{mapping && (
|
|
3021
|
-
<button
|
|
3022
|
-
className="hud-button hud-icon-button"
|
|
3023
|
-
data-active={thumbnailVisible}
|
|
3024
|
-
aria-label={
|
|
3025
|
-
thumbnailVisible ? 'Hide 3D view' : 'Show 3D view'
|
|
3026
|
-
}
|
|
3027
|
-
aria-keyshortcuts="T"
|
|
3028
|
-
aria-pressed={thumbnailVisible}
|
|
3029
|
-
type="button"
|
|
3030
|
-
onClick={() => {
|
|
3031
|
-
setThumbnailVisible((visible) => {
|
|
3032
|
-
if (!visible) {
|
|
3033
|
-
setThumbnailMinimized(false)
|
|
3034
|
-
setThumbnailMaximized(false)
|
|
3035
|
-
} else {
|
|
3036
|
-
setThumbnailMaximized(false)
|
|
3037
|
-
}
|
|
3038
|
-
return !visible
|
|
3039
|
-
})
|
|
3040
|
-
}}
|
|
3041
|
-
>
|
|
3042
|
-
<svg
|
|
3043
|
-
viewBox="0 0 24 24"
|
|
3044
|
-
width="18"
|
|
3045
|
-
height="18"
|
|
3046
|
-
fill="none"
|
|
3047
|
-
stroke="currentColor"
|
|
3048
|
-
strokeWidth="2"
|
|
3049
|
-
strokeLinecap="round"
|
|
3050
|
-
strokeLinejoin="round"
|
|
3051
|
-
aria-hidden="true"
|
|
3052
|
-
>
|
|
3053
|
-
<rect x="3" y="5" width="11" height="14" rx="1.5" />
|
|
3054
|
-
<path d="M16 8h5v11H9v-3" />
|
|
3055
|
-
<path d="M6.5 16.5 9 12l2 2.5 1.5-2L15 16.5" />
|
|
3056
|
-
</svg>
|
|
3057
|
-
<span className="hud-tooltip">
|
|
3058
|
-
{thumbnailVisible ? 'T hide 3D view' : 'T show 3D view'}
|
|
3059
|
-
</span>
|
|
3060
|
-
</button>
|
|
3061
|
-
)}
|
|
3062
3297
|
<button
|
|
3063
3298
|
className="hud-button hud-icon-button"
|
|
3064
3299
|
data-active={importedBy}
|
|
@@ -3136,30 +3371,70 @@ export function HUD({
|
|
|
3136
3371
|
: 'G show branch changes'}
|
|
3137
3372
|
</span>
|
|
3138
3373
|
</button>
|
|
3139
|
-
<
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
viewBox="0 0 24 24"
|
|
3149
|
-
width="18"
|
|
3150
|
-
height="18"
|
|
3151
|
-
fill="none"
|
|
3152
|
-
stroke="currentColor"
|
|
3153
|
-
strokeWidth="2"
|
|
3154
|
-
strokeLinecap="round"
|
|
3155
|
-
strokeLinejoin="round"
|
|
3156
|
-
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)}
|
|
3157
3383
|
>
|
|
3158
|
-
<
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
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>
|
|
3163
3438
|
</div>
|
|
3164
3439
|
</div>
|
|
3165
3440
|
</div>
|