@jkwd/inbase 0.1.4 → 0.1.5
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 +3 -1
- package/apps/explorer/scripts/session-store.d.ts +20 -0
- package/apps/explorer/scripts/session-store.mjs +318 -32
- package/apps/explorer/src/App.tsx +154 -85
- package/apps/explorer/src/agentIntent.ts +33 -1
- package/apps/explorer/src/index.css +217 -2
- package/apps/explorer/src/layout.ts +46 -0
- package/apps/explorer/src/scene/FileBlock.tsx +110 -145
- package/apps/explorer/src/scene/FolderArea.tsx +17 -4
- package/apps/explorer/src/scene/MapSelectBorder.tsx +3 -1
- package/apps/explorer/src/scene/MapView.tsx +10 -12
- package/apps/explorer/src/scene/Player.tsx +27 -0
- package/apps/explorer/src/scene/RelationLines.tsx +36 -32
- package/apps/explorer/src/scene/SelectionController.tsx +21 -3
- package/apps/explorer/src/scene/SelectionThumbnail.tsx +451 -0
- package/apps/explorer/src/scene/World.tsx +16 -35
- package/apps/explorer/src/theme.ts +7 -1
- package/apps/explorer/src/types.ts +12 -0
- package/apps/explorer/src/ui/HUD.tsx +662 -358
- package/apps/explorer/vite.config.ts +33 -21
- package/bin/session.mjs +16 -2
- package/package.json +1 -1
- package/skill/inbase/SKILL.md +22 -14
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { useEffect, useRef, useState, type ReactNode } from 'react'
|
|
2
2
|
import { NameInput } from './NameInput'
|
|
3
|
+
import { SelectionThumbnail } from '../scene/SelectionThumbnail'
|
|
3
4
|
import {
|
|
5
|
+
canStopSession,
|
|
4
6
|
isReviewingIntent,
|
|
5
7
|
type AgentIntent,
|
|
6
8
|
type AgentIntentStatus,
|
|
@@ -9,6 +11,7 @@ import {
|
|
|
9
11
|
type PatchImportAddition,
|
|
10
12
|
type PatchSymbolAddition,
|
|
11
13
|
type ViewMode,
|
|
14
|
+
type WorldLayout,
|
|
12
15
|
type WorkflowAction,
|
|
13
16
|
} from '../types'
|
|
14
17
|
|
|
@@ -123,21 +126,488 @@ function AddIntentRow({
|
|
|
123
126
|
)
|
|
124
127
|
}
|
|
125
128
|
|
|
129
|
+
function PanelChrome({
|
|
130
|
+
title,
|
|
131
|
+
minimized = false,
|
|
132
|
+
onMinimize,
|
|
133
|
+
onClose,
|
|
134
|
+
}: {
|
|
135
|
+
title: ReactNode
|
|
136
|
+
minimized?: boolean
|
|
137
|
+
onMinimize?: () => void
|
|
138
|
+
onClose?: () => void
|
|
139
|
+
}) {
|
|
140
|
+
return (
|
|
141
|
+
<div className="hud-panel-chrome">
|
|
142
|
+
<div className="hud-panel-chrome-title">{title}</div>
|
|
143
|
+
<div className="hud-panel-controls">
|
|
144
|
+
{onMinimize && (
|
|
145
|
+
<button
|
|
146
|
+
className="hud-button hud-icon-button hud-panel-control"
|
|
147
|
+
type="button"
|
|
148
|
+
aria-label={minimized ? 'Restore' : 'Minimize'}
|
|
149
|
+
onClick={onMinimize}
|
|
150
|
+
>
|
|
151
|
+
{minimized ? '+' : '−'}
|
|
152
|
+
</button>
|
|
153
|
+
)}
|
|
154
|
+
{onClose && (
|
|
155
|
+
<button
|
|
156
|
+
className="hud-button hud-icon-button hud-panel-control"
|
|
157
|
+
type="button"
|
|
158
|
+
aria-label="Close"
|
|
159
|
+
onClick={onClose}
|
|
160
|
+
>
|
|
161
|
+
×
|
|
162
|
+
</button>
|
|
163
|
+
)}
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
type SessionPanelProps = {
|
|
170
|
+
intent: AgentIntent
|
|
171
|
+
focused: boolean
|
|
172
|
+
naming: boolean
|
|
173
|
+
onFocus: () => void
|
|
174
|
+
onWorkflowAction: (
|
|
175
|
+
sessionId: string,
|
|
176
|
+
action: WorkflowAction,
|
|
177
|
+
options?: { instruction?: string; step?: number },
|
|
178
|
+
) => void
|
|
179
|
+
onNavigateDiff: (sessionId: string, diffId: string) => void
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function SessionPanel({
|
|
183
|
+
intent,
|
|
184
|
+
focused,
|
|
185
|
+
naming,
|
|
186
|
+
onFocus,
|
|
187
|
+
onWorkflowAction,
|
|
188
|
+
onNavigateDiff,
|
|
189
|
+
}: SessionPanelProps) {
|
|
190
|
+
const [minimized, setMinimized] = useState(false)
|
|
191
|
+
const [instruction, setInstruction] = useState('')
|
|
192
|
+
const sessionId = intent.sessionId
|
|
193
|
+
const pending = intent.status === 'pending' && intent.isActiveDiff
|
|
194
|
+
const askingBlueprint = intent.status === 'blueprint_ask'
|
|
195
|
+
const creatingBlueprint = intent.creationMode || intent.status === 'blueprint'
|
|
196
|
+
const preparing = intent.status === 'preparing'
|
|
197
|
+
const planReady = intent.status === 'planned'
|
|
198
|
+
const working = intent.status === 'working' || intent.status === 'replanning'
|
|
199
|
+
const previewing = intent.preview
|
|
200
|
+
const chainIndex = intent.chainIndex ?? 0
|
|
201
|
+
const previousDiff = intent.chain[chainIndex - 1]
|
|
202
|
+
const nextDiff = intent.chain[chainIndex + 1]
|
|
203
|
+
const stepLabel =
|
|
204
|
+
intent.step && intent.steps?.length > 0
|
|
205
|
+
? `Step ${intent.step} of ${intent.steps.length}`
|
|
206
|
+
: 'Patch'
|
|
207
|
+
const lastStep =
|
|
208
|
+
typeof intent.step === 'number' &&
|
|
209
|
+
intent.steps.length > 0 &&
|
|
210
|
+
intent.step >= intent.steps.length
|
|
211
|
+
const addedFunctions = intent.addedFunctions ?? []
|
|
212
|
+
const addedVariables = intent.addedVariables ?? []
|
|
213
|
+
const addedImports = intent.addedImports ?? []
|
|
214
|
+
const doneSteps = new Set(
|
|
215
|
+
intent.status === 'finished'
|
|
216
|
+
? intent.steps.map((step) => step.index)
|
|
217
|
+
: intent.chain
|
|
218
|
+
.filter(
|
|
219
|
+
(entry) =>
|
|
220
|
+
entry.status === 'applied' || entry.status === 'extended',
|
|
221
|
+
)
|
|
222
|
+
.map((entry) => entry.step),
|
|
223
|
+
)
|
|
224
|
+
if (intent.status === 'approved' && typeof intent.step === 'number') {
|
|
225
|
+
doneSteps.add(intent.step)
|
|
226
|
+
}
|
|
227
|
+
if (pending && typeof intent.step === 'number' && !lastStep) {
|
|
228
|
+
doneSteps.add(intent.step)
|
|
229
|
+
}
|
|
230
|
+
const nextStep =
|
|
231
|
+
intent.status === 'finished'
|
|
232
|
+
? null
|
|
233
|
+
: (intent.steps.find((step) => !doneSteps.has(step.index)) ?? null)
|
|
234
|
+
const canRunNext =
|
|
235
|
+
Boolean(nextStep) && (planReady || pending) && !working
|
|
236
|
+
const canComplete = pending && lastStep
|
|
237
|
+
const panelDone =
|
|
238
|
+
intent.status === 'finished' ||
|
|
239
|
+
intent.status === 'approved' ||
|
|
240
|
+
doneSteps.size > 0
|
|
241
|
+
|
|
242
|
+
useEffect(() => {
|
|
243
|
+
setInstruction('')
|
|
244
|
+
}, [intent.diffId])
|
|
245
|
+
|
|
246
|
+
if (!sessionId || !isReviewingIntent(intent.status)) return null
|
|
247
|
+
|
|
248
|
+
const act = (
|
|
249
|
+
action: WorkflowAction,
|
|
250
|
+
options?: { instruction?: string; step?: number },
|
|
251
|
+
) => onWorkflowAction(sessionId, action, options)
|
|
252
|
+
|
|
253
|
+
return (
|
|
254
|
+
<aside
|
|
255
|
+
className={
|
|
256
|
+
panelDone
|
|
257
|
+
? 'hud-panel hud-panel-planned hud-panel-done'
|
|
258
|
+
: 'hud-panel hud-panel-planned'
|
|
259
|
+
}
|
|
260
|
+
data-minimized={minimized}
|
|
261
|
+
data-focused={focused}
|
|
262
|
+
onPointerDown={onFocus}
|
|
263
|
+
>
|
|
264
|
+
<PanelChrome
|
|
265
|
+
title={reviewTitle(intent.status)}
|
|
266
|
+
minimized={minimized}
|
|
267
|
+
onMinimize={() => setMinimized((current) => !current)}
|
|
268
|
+
/>
|
|
269
|
+
{!minimized && (
|
|
270
|
+
<>
|
|
271
|
+
{intent.feature && <p className="hud-feature">{intent.feature}</p>}
|
|
272
|
+
{askingBlueprint ? (
|
|
273
|
+
<>
|
|
274
|
+
<p>
|
|
275
|
+
{intent.canEnterBlueprint
|
|
276
|
+
? 'Place files and folders for this chat, then send them as a blueprint for the LLM?'
|
|
277
|
+
: `Blueprint edit mode is active in another chat (${intent.blueprintSessionId}). Finish or stop it before starting here.`}
|
|
278
|
+
</p>
|
|
279
|
+
<div className="hud-decide">
|
|
280
|
+
<button
|
|
281
|
+
className="hud-button hud-button-approve"
|
|
282
|
+
type="button"
|
|
283
|
+
disabled={!intent.canEnterBlueprint}
|
|
284
|
+
onClick={() => act('blueprint_yes')}
|
|
285
|
+
>
|
|
286
|
+
Create blueprint
|
|
287
|
+
</button>
|
|
288
|
+
<button
|
|
289
|
+
className="hud-button hud-button-approve"
|
|
290
|
+
type="button"
|
|
291
|
+
onClick={() => act('blueprint_no')}
|
|
292
|
+
>
|
|
293
|
+
Let LLM continue
|
|
294
|
+
</button>
|
|
295
|
+
<button
|
|
296
|
+
className="hud-button hud-button-reject"
|
|
297
|
+
type="button"
|
|
298
|
+
onClick={() => act('stop')}
|
|
299
|
+
>
|
|
300
|
+
Stop
|
|
301
|
+
</button>
|
|
302
|
+
</div>
|
|
303
|
+
</>
|
|
304
|
+
) : creatingBlueprint ? (
|
|
305
|
+
<>
|
|
306
|
+
<p>
|
|
307
|
+
Walk the map, press <kbd>Space</kbd> for a file and{' '}
|
|
308
|
+
<kbd>B</kbd> for an island. Send the blueprint when the layout
|
|
309
|
+
is ready.
|
|
310
|
+
</p>
|
|
311
|
+
<div className="hud-decide">
|
|
312
|
+
<button
|
|
313
|
+
className="hud-button hud-button-approve"
|
|
314
|
+
type="button"
|
|
315
|
+
disabled={naming}
|
|
316
|
+
onClick={() => act('blueprint_send')}
|
|
317
|
+
>
|
|
318
|
+
Send blueprint
|
|
319
|
+
</button>
|
|
320
|
+
<button
|
|
321
|
+
className="hud-button hud-button-reject"
|
|
322
|
+
type="button"
|
|
323
|
+
onClick={() => act('stop')}
|
|
324
|
+
>
|
|
325
|
+
Stop
|
|
326
|
+
</button>
|
|
327
|
+
</div>
|
|
328
|
+
</>
|
|
329
|
+
) : intent.status === 'finished' ? (
|
|
330
|
+
<p>All plan steps were applied.</p>
|
|
331
|
+
) : preparing ? (
|
|
332
|
+
<div className="hud-working">
|
|
333
|
+
<span className="hud-spinner" aria-hidden="true" />
|
|
334
|
+
<span>LLM preparing…</span>
|
|
335
|
+
<button
|
|
336
|
+
className="hud-button hud-button-reject"
|
|
337
|
+
type="button"
|
|
338
|
+
onClick={() => act('stop')}
|
|
339
|
+
>
|
|
340
|
+
Stop
|
|
341
|
+
</button>
|
|
342
|
+
</div>
|
|
343
|
+
) : working ? (
|
|
344
|
+
<div className="hud-working">
|
|
345
|
+
<span className="hud-spinner" aria-hidden="true" />
|
|
346
|
+
<span>
|
|
347
|
+
{intent.status === 'replanning'
|
|
348
|
+
? 'Updating the remaining plan from your instruction…'
|
|
349
|
+
: `Implementing ${stepLabel.toLowerCase()}…`}
|
|
350
|
+
</span>
|
|
351
|
+
<button
|
|
352
|
+
className="hud-button hud-button-reject"
|
|
353
|
+
type="button"
|
|
354
|
+
onClick={() => act('stop')}
|
|
355
|
+
>
|
|
356
|
+
Stop
|
|
357
|
+
</button>
|
|
358
|
+
</div>
|
|
359
|
+
) : (
|
|
360
|
+
<p>
|
|
361
|
+
{stepLabel}
|
|
362
|
+
{intent.reason ? ` · ${intent.reason}` : ''}
|
|
363
|
+
</p>
|
|
364
|
+
)}
|
|
365
|
+
{!askingBlueprint && !creatingBlueprint && (
|
|
366
|
+
<>
|
|
367
|
+
{intent.steps?.length > 0 && (
|
|
368
|
+
<ol className="hud-steps">
|
|
369
|
+
{intent.steps.map((step) => (
|
|
370
|
+
<li
|
|
371
|
+
key={step.index}
|
|
372
|
+
data-current={
|
|
373
|
+
intent.status !== 'finished' &&
|
|
374
|
+
step.index === intent.step &&
|
|
375
|
+
!doneSteps.has(step.index)
|
|
376
|
+
}
|
|
377
|
+
data-done={doneSteps.has(step.index)}
|
|
378
|
+
data-next={
|
|
379
|
+
nextStep?.index === step.index &&
|
|
380
|
+
!doneSteps.has(step.index)
|
|
381
|
+
}
|
|
382
|
+
>
|
|
383
|
+
<span className="hud-step-index">{step.index}.</span>
|
|
384
|
+
<span className="hud-step-main">
|
|
385
|
+
<span className="hud-step-title">{step.title}</span>
|
|
386
|
+
{((canRunNext && nextStep?.index === step.index) ||
|
|
387
|
+
(canComplete && step.index === intent.step)) && (
|
|
388
|
+
<button
|
|
389
|
+
className="hud-button hud-button-approve hud-run-step"
|
|
390
|
+
type="button"
|
|
391
|
+
onClick={() =>
|
|
392
|
+
canComplete && step.index === intent.step
|
|
393
|
+
? act('continue')
|
|
394
|
+
: act('invoke', {
|
|
395
|
+
step: step.index,
|
|
396
|
+
})
|
|
397
|
+
}
|
|
398
|
+
>
|
|
399
|
+
{canComplete && step.index === intent.step
|
|
400
|
+
? 'Complete'
|
|
401
|
+
: 'Run step'}
|
|
402
|
+
</button>
|
|
403
|
+
)}
|
|
404
|
+
</span>
|
|
405
|
+
</li>
|
|
406
|
+
))}
|
|
407
|
+
</ol>
|
|
408
|
+
)}
|
|
409
|
+
{intent.chain.length > 0 && (
|
|
410
|
+
<div className="hud-chain">
|
|
411
|
+
<button
|
|
412
|
+
className="hud-button"
|
|
413
|
+
type="button"
|
|
414
|
+
disabled={!previousDiff}
|
|
415
|
+
onClick={() =>
|
|
416
|
+
previousDiff && onNavigateDiff(sessionId, previousDiff.id)
|
|
417
|
+
}
|
|
418
|
+
>
|
|
419
|
+
Previous
|
|
420
|
+
</button>
|
|
421
|
+
<span>
|
|
422
|
+
Diff {chainIndex + 1} of {intent.chain.length}
|
|
423
|
+
</span>
|
|
424
|
+
<button
|
|
425
|
+
className="hud-button"
|
|
426
|
+
type="button"
|
|
427
|
+
disabled={!nextDiff}
|
|
428
|
+
onClick={() =>
|
|
429
|
+
nextDiff && onNavigateDiff(sessionId, nextDiff.id)
|
|
430
|
+
}
|
|
431
|
+
>
|
|
432
|
+
Next
|
|
433
|
+
</button>
|
|
434
|
+
</div>
|
|
435
|
+
)}
|
|
436
|
+
<MutationFold
|
|
437
|
+
hasContent={
|
|
438
|
+
previewing &&
|
|
439
|
+
(intent.files.length > 0 ||
|
|
440
|
+
(intent.createFolders ?? []).length > 0 ||
|
|
441
|
+
intent.creates.length > 0 ||
|
|
442
|
+
intent.deletes.length > 0 ||
|
|
443
|
+
addedFunctions.length > 0 ||
|
|
444
|
+
addedVariables.length > 0 ||
|
|
445
|
+
addedImports.length > 0 ||
|
|
446
|
+
(intent.imports ?? []).length > 0)
|
|
447
|
+
}
|
|
448
|
+
>
|
|
449
|
+
{intent.files.length > 0 && (
|
|
450
|
+
<>
|
|
451
|
+
<div className="hud-section-title hud-section-title-edit">
|
|
452
|
+
Changed
|
|
453
|
+
</div>
|
|
454
|
+
<ul>
|
|
455
|
+
{intent.files.map((id) => (
|
|
456
|
+
<li className="hud-file-edit" key={id}>
|
|
457
|
+
{id}
|
|
458
|
+
</li>
|
|
459
|
+
))}
|
|
460
|
+
</ul>
|
|
461
|
+
</>
|
|
462
|
+
)}
|
|
463
|
+
{(intent.createFolders ?? []).length > 0 && (
|
|
464
|
+
<>
|
|
465
|
+
<div className="hud-section-title hud-section-title-add">
|
|
466
|
+
Added islands
|
|
467
|
+
</div>
|
|
468
|
+
<ul>
|
|
469
|
+
{intent.createFolders.map((id) => (
|
|
470
|
+
<li className="hud-file-add" key={id}>
|
|
471
|
+
{id}/
|
|
472
|
+
</li>
|
|
473
|
+
))}
|
|
474
|
+
</ul>
|
|
475
|
+
</>
|
|
476
|
+
)}
|
|
477
|
+
{intent.creates.length > 0 && (
|
|
478
|
+
<>
|
|
479
|
+
<div className="hud-section-title hud-section-title-add">
|
|
480
|
+
Added
|
|
481
|
+
</div>
|
|
482
|
+
<ul>
|
|
483
|
+
{intent.creates.map((id) => (
|
|
484
|
+
<li className="hud-file-add" key={id}>
|
|
485
|
+
{id}
|
|
486
|
+
</li>
|
|
487
|
+
))}
|
|
488
|
+
</ul>
|
|
489
|
+
</>
|
|
490
|
+
)}
|
|
491
|
+
{intent.deletes.length > 0 && (
|
|
492
|
+
<>
|
|
493
|
+
<div className="hud-section-title hud-section-title-remove">
|
|
494
|
+
Removed
|
|
495
|
+
</div>
|
|
496
|
+
<ul>
|
|
497
|
+
{intent.deletes.map((id) => (
|
|
498
|
+
<li className="hud-file-remove" key={id}>
|
|
499
|
+
{id}
|
|
500
|
+
</li>
|
|
501
|
+
))}
|
|
502
|
+
</ul>
|
|
503
|
+
</>
|
|
504
|
+
)}
|
|
505
|
+
<PanelList
|
|
506
|
+
title="Functions"
|
|
507
|
+
items={symbolLabels(addedFunctions)}
|
|
508
|
+
/>
|
|
509
|
+
<PanelList
|
|
510
|
+
title="Variables"
|
|
511
|
+
items={symbolLabels(addedVariables)}
|
|
512
|
+
/>
|
|
513
|
+
{addedImports.length > 0 && (
|
|
514
|
+
<PanelList title="Imports" items={importLabels(addedImports)} />
|
|
515
|
+
)}
|
|
516
|
+
{addedImports.length === 0 && (intent.imports ?? []).length > 0 && (
|
|
517
|
+
<>
|
|
518
|
+
<div className="hud-section-title">Imports</div>
|
|
519
|
+
<ul>
|
|
520
|
+
{intent.imports.map((edge) => (
|
|
521
|
+
<li key={`${edge.from}->${edge.to}`}>
|
|
522
|
+
{edge.from.split('/').pop()} → {edge.to.split('/').pop()}
|
|
523
|
+
</li>
|
|
524
|
+
))}
|
|
525
|
+
</ul>
|
|
526
|
+
</>
|
|
527
|
+
)}
|
|
528
|
+
</MutationFold>
|
|
529
|
+
{planReady && (
|
|
530
|
+
<div className="hud-decide">
|
|
531
|
+
<button
|
|
532
|
+
className="hud-button hud-button-reject"
|
|
533
|
+
type="button"
|
|
534
|
+
onClick={() => act('stop')}
|
|
535
|
+
>
|
|
536
|
+
Stop
|
|
537
|
+
</button>
|
|
538
|
+
</div>
|
|
539
|
+
)}
|
|
540
|
+
{pending && (
|
|
541
|
+
<>
|
|
542
|
+
<label className="hud-instruction">
|
|
543
|
+
<span>Alternative instruction for the LLM</span>
|
|
544
|
+
<textarea
|
|
545
|
+
value={instruction}
|
|
546
|
+
maxLength={4000}
|
|
547
|
+
rows={3}
|
|
548
|
+
placeholder="Describe what should change in the next diff…"
|
|
549
|
+
onChange={(event) => setInstruction(event.target.value)}
|
|
550
|
+
/>
|
|
551
|
+
</label>
|
|
552
|
+
<div className="hud-decide">
|
|
553
|
+
{lastStep && (
|
|
554
|
+
<button
|
|
555
|
+
className="hud-button hud-button-approve"
|
|
556
|
+
type="button"
|
|
557
|
+
onClick={() => act('continue')}
|
|
558
|
+
>
|
|
559
|
+
Complete
|
|
560
|
+
</button>
|
|
561
|
+
)}
|
|
562
|
+
<button
|
|
563
|
+
className="hud-button hud-button-extend"
|
|
564
|
+
type="button"
|
|
565
|
+
disabled={!instruction.trim()}
|
|
566
|
+
onClick={() =>
|
|
567
|
+
act('instruct', { instruction })
|
|
568
|
+
}
|
|
569
|
+
>
|
|
570
|
+
Send instruction
|
|
571
|
+
</button>
|
|
572
|
+
<button
|
|
573
|
+
className="hud-button hud-button-reject"
|
|
574
|
+
type="button"
|
|
575
|
+
onClick={() => act('stop')}
|
|
576
|
+
>
|
|
577
|
+
Stop
|
|
578
|
+
</button>
|
|
579
|
+
</div>
|
|
580
|
+
</>
|
|
581
|
+
)}
|
|
582
|
+
</>
|
|
583
|
+
)}
|
|
584
|
+
</>
|
|
585
|
+
)}
|
|
586
|
+
</aside>
|
|
587
|
+
)
|
|
588
|
+
}
|
|
589
|
+
|
|
126
590
|
type HUDProps = {
|
|
127
591
|
graph: CodebaseGraph
|
|
592
|
+
layout: WorldLayout
|
|
128
593
|
mode: ViewMode
|
|
129
594
|
locked: boolean
|
|
130
595
|
selectedId: string | null
|
|
131
596
|
selectedTick?: number
|
|
132
597
|
selectedFolder?: string | null
|
|
598
|
+
landAt: [number, number]
|
|
133
599
|
aimedRelation: AimedRelation | null
|
|
134
600
|
currentFolder: string
|
|
135
601
|
intent: AgentIntent
|
|
602
|
+
intents?: AgentIntent[]
|
|
603
|
+
focusedSessionId?: string | null
|
|
604
|
+
onFocusSession?: (sessionId: string) => void
|
|
136
605
|
onWorkflowAction: (
|
|
606
|
+
sessionId: string,
|
|
137
607
|
action: WorkflowAction,
|
|
138
608
|
options?: { instruction?: string; step?: number },
|
|
139
609
|
) => void
|
|
140
|
-
onNavigateDiff: (diffId: string) => void
|
|
610
|
+
onNavigateDiff: (sessionId: string, diffId: string) => void
|
|
141
611
|
onOpenMap: () => void
|
|
142
612
|
onWalk: () => void
|
|
143
613
|
followLook: boolean
|
|
@@ -164,18 +634,26 @@ type HUDProps = {
|
|
|
164
634
|
onMapAddFile?: (folderPath: string) => void
|
|
165
635
|
onMapAddFolder?: (folderPath: string) => void
|
|
166
636
|
onInspectFile?: (fileId: string) => void
|
|
637
|
+
plannedIds?: string[]
|
|
638
|
+
createdIds?: string[]
|
|
639
|
+
deletedIds?: string[]
|
|
167
640
|
}
|
|
168
641
|
|
|
169
642
|
export function HUD({
|
|
170
643
|
graph,
|
|
644
|
+
layout,
|
|
171
645
|
mode,
|
|
172
646
|
locked,
|
|
173
647
|
selectedId,
|
|
174
648
|
selectedTick = 0,
|
|
175
649
|
selectedFolder = null,
|
|
650
|
+
landAt,
|
|
176
651
|
aimedRelation,
|
|
177
652
|
currentFolder,
|
|
178
653
|
intent,
|
|
654
|
+
intents,
|
|
655
|
+
focusedSessionId = null,
|
|
656
|
+
onFocusSession,
|
|
179
657
|
onWorkflowAction,
|
|
180
658
|
onNavigateDiff,
|
|
181
659
|
onOpenMap,
|
|
@@ -200,6 +678,9 @@ export function HUD({
|
|
|
200
678
|
onMapAddFile,
|
|
201
679
|
onMapAddFolder,
|
|
202
680
|
onInspectFile,
|
|
681
|
+
plannedIds = [],
|
|
682
|
+
createdIds = [],
|
|
683
|
+
deletedIds = [],
|
|
203
684
|
}: HUDProps) {
|
|
204
685
|
const selected = graph.files.find((file) => file.id === selectedId)
|
|
205
686
|
const selectedFolderNode = graph.folders.find(
|
|
@@ -217,31 +698,21 @@ export function HUD({
|
|
|
217
698
|
? graph.files.filter((file) => file.imports.includes(selected.id))
|
|
218
699
|
: []
|
|
219
700
|
const mapping = mode === 'map'
|
|
220
|
-
const
|
|
701
|
+
const sessions = (intents ?? [intent]).filter(
|
|
702
|
+
(item) => item.sessionId && isReviewingIntent(item.status),
|
|
703
|
+
)
|
|
704
|
+
const canStop = canStopSession(intent)
|
|
221
705
|
const [walkIntro, setWalkIntro] = useState(false)
|
|
222
706
|
const walkIntroSeen = useRef(false)
|
|
223
|
-
const [instruction, setInstruction] = useState('')
|
|
224
707
|
const [infoVisible, setInfoVisible] = useState(false)
|
|
225
|
-
const
|
|
226
|
-
const
|
|
227
|
-
const
|
|
228
|
-
const
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
708
|
+
const [infoMinimized, setInfoMinimized] = useState(false)
|
|
709
|
+
const [thumbnailVisible, setThumbnailVisible] = useState(true)
|
|
710
|
+
const [thumbnailMinimized, setThumbnailMinimized] = useState(false)
|
|
711
|
+
const infoPanelRef = useRef<HTMLDivElement>(null)
|
|
712
|
+
const creatingBlueprint = sessions.some(
|
|
713
|
+
(item) => item.creationMode || item.status === 'blueprint',
|
|
714
|
+
)
|
|
232
715
|
const previewing = intent.preview
|
|
233
|
-
const reviewing = isReviewingIntent(intent.status) || intent.chain.length > 0
|
|
234
|
-
const chainIndex = intent.chainIndex ?? 0
|
|
235
|
-
const previousDiff = intent.chain[chainIndex - 1]
|
|
236
|
-
const nextDiff = intent.chain[chainIndex + 1]
|
|
237
|
-
const stepLabel =
|
|
238
|
-
intent.step && intent.steps?.length > 0
|
|
239
|
-
? `Step ${intent.step} of ${intent.steps.length}`
|
|
240
|
-
: 'Patch'
|
|
241
|
-
const lastStep =
|
|
242
|
-
typeof intent.step === 'number' &&
|
|
243
|
-
intent.steps.length > 0 &&
|
|
244
|
-
intent.step >= intent.steps.length
|
|
245
716
|
const addedFunctions = intent.addedFunctions ?? []
|
|
246
717
|
const addedVariables = intent.addedVariables ?? []
|
|
247
718
|
const addedImports = intent.addedImports ?? []
|
|
@@ -280,38 +751,11 @@ export function HUD({
|
|
|
280
751
|
)
|
|
281
752
|
const canEditBlueprint =
|
|
282
753
|
creatingBlueprint && Boolean(selected) && !selected?.id.startsWith('draft:')
|
|
283
|
-
const doneSteps = new Set(
|
|
284
|
-
intent.status === 'finished'
|
|
285
|
-
? intent.steps.map((step) => step.index)
|
|
286
|
-
: intent.chain
|
|
287
|
-
.filter(
|
|
288
|
-
(entry) =>
|
|
289
|
-
entry.status === 'applied' || entry.status === 'extended',
|
|
290
|
-
)
|
|
291
|
-
.map((entry) => entry.step),
|
|
292
|
-
)
|
|
293
|
-
if (intent.status === 'approved' && typeof intent.step === 'number') {
|
|
294
|
-
doneSteps.add(intent.step)
|
|
295
|
-
}
|
|
296
|
-
if (pending && typeof intent.step === 'number' && !lastStep) {
|
|
297
|
-
doneSteps.add(intent.step)
|
|
298
|
-
}
|
|
299
|
-
const nextStep =
|
|
300
|
-
intent.status === 'finished'
|
|
301
|
-
? null
|
|
302
|
-
: (intent.steps.find((step) => !doneSteps.has(step.index)) ?? null)
|
|
303
|
-
const canRunNext =
|
|
304
|
-
Boolean(nextStep) && (planReady || pending) && !working
|
|
305
|
-
const canComplete = pending && lastStep
|
|
306
754
|
const canInspectFile = (fileId: string, userCreated = false) =>
|
|
307
755
|
Boolean(onInspectFile) &&
|
|
308
756
|
!fileId.startsWith('draft:') &&
|
|
309
757
|
!(previewing && (intent.deletes ?? []).includes(fileId)) &&
|
|
310
758
|
(!userCreated || (intent.creates ?? []).includes(fileId))
|
|
311
|
-
const panelDone =
|
|
312
|
-
intent.status === 'finished' ||
|
|
313
|
-
intent.status === 'approved' ||
|
|
314
|
-
doneSteps.size > 0
|
|
315
759
|
|
|
316
760
|
useEffect(() => {
|
|
317
761
|
if (mode !== 'walk') {
|
|
@@ -332,10 +776,6 @@ export function HUD({
|
|
|
332
776
|
return () => window.clearTimeout(timer)
|
|
333
777
|
}, [locked, mode, naming])
|
|
334
778
|
|
|
335
|
-
useEffect(() => {
|
|
336
|
-
setInstruction('')
|
|
337
|
-
}, [intent.diffId])
|
|
338
|
-
|
|
339
779
|
useEffect(() => {
|
|
340
780
|
infoPanelRef.current?.scrollTo({ top: 0 })
|
|
341
781
|
}, [selectedId, selectedFolder])
|
|
@@ -362,12 +802,39 @@ export function HUD({
|
|
|
362
802
|
return
|
|
363
803
|
}
|
|
364
804
|
event.preventDefault()
|
|
365
|
-
setInfoVisible((visible) =>
|
|
805
|
+
setInfoVisible((visible) => {
|
|
806
|
+
if (!visible) setInfoMinimized(false)
|
|
807
|
+
return !visible
|
|
808
|
+
})
|
|
366
809
|
}
|
|
367
810
|
window.addEventListener('keydown', onKey)
|
|
368
811
|
return () => window.removeEventListener('keydown', onKey)
|
|
369
812
|
}, [])
|
|
370
813
|
|
|
814
|
+
useEffect(() => {
|
|
815
|
+
if (!mapping) return
|
|
816
|
+
const onKey = (event: KeyboardEvent) => {
|
|
817
|
+
if (event.repeat || event.code !== 'KeyT') return
|
|
818
|
+
const target = event.target
|
|
819
|
+
if (
|
|
820
|
+
target instanceof HTMLElement &&
|
|
821
|
+
(target.tagName === 'TEXTAREA' ||
|
|
822
|
+
target.tagName === 'INPUT' ||
|
|
823
|
+
target.tagName === 'SELECT' ||
|
|
824
|
+
target.isContentEditable)
|
|
825
|
+
) {
|
|
826
|
+
return
|
|
827
|
+
}
|
|
828
|
+
event.preventDefault()
|
|
829
|
+
setThumbnailVisible((visible) => {
|
|
830
|
+
if (!visible) setThumbnailMinimized(false)
|
|
831
|
+
return !visible
|
|
832
|
+
})
|
|
833
|
+
}
|
|
834
|
+
window.addEventListener('keydown', onKey)
|
|
835
|
+
return () => window.removeEventListener('keydown', onKey)
|
|
836
|
+
}, [mapping])
|
|
837
|
+
|
|
371
838
|
useEffect(() => {
|
|
372
839
|
if (!infoVisible || (!selectedId && !selectedFolder)) return
|
|
373
840
|
const onKey = (event: KeyboardEvent) => {
|
|
@@ -400,8 +867,9 @@ export function HUD({
|
|
|
400
867
|
<div className="hud-gate-card">
|
|
401
868
|
<h1>Walk</h1>
|
|
402
869
|
<p>
|
|
403
|
-
Click to look around.
|
|
404
|
-
<kbd>M</kbd> again to return
|
|
870
|
+
Click to look around. Double-click to release the mouse. Press{' '}
|
|
871
|
+
<kbd>M</kbd> to open the map, press <kbd>M</kbd> again to return
|
|
872
|
+
here.
|
|
405
873
|
</p>
|
|
406
874
|
<p>
|
|
407
875
|
<kbd>W</kbd> <kbd>A</kbd> <kbd>S</kbd> <kbd>D</kbd> walk,{' '}
|
|
@@ -455,313 +923,57 @@ export function HUD({
|
|
|
455
923
|
>
|
|
456
924
|
Walk
|
|
457
925
|
</button>
|
|
926
|
+
{canStop && (
|
|
927
|
+
<button
|
|
928
|
+
className="hud-button hud-button-reject"
|
|
929
|
+
type="button"
|
|
930
|
+
aria-label="Stop LLM session"
|
|
931
|
+
onClick={() =>
|
|
932
|
+
intent.sessionId && onWorkflowAction(intent.sessionId, 'stop')
|
|
933
|
+
}
|
|
934
|
+
>
|
|
935
|
+
Stop
|
|
936
|
+
</button>
|
|
937
|
+
)}
|
|
458
938
|
</div>
|
|
459
939
|
{selected && <div className="hud-chip">{selected.path}</div>}
|
|
460
940
|
</div>
|
|
461
941
|
|
|
462
|
-
{
|
|
463
|
-
<
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
<>
|
|
476
|
-
<p>
|
|
477
|
-
{intent.canEnterBlueprint
|
|
478
|
-
? 'Place files and islands for this chat, then send them as a blueprint for the LLM?'
|
|
479
|
-
: `Blueprint edit mode is active in another chat (${intent.blueprintSessionId}). Finish or stop it before starting here.`}
|
|
480
|
-
</p>
|
|
481
|
-
<div className="hud-decide">
|
|
482
|
-
<button
|
|
483
|
-
className="hud-button hud-button-approve"
|
|
484
|
-
type="button"
|
|
485
|
-
disabled={!intent.canEnterBlueprint}
|
|
486
|
-
onClick={() => onWorkflowAction('blueprint_yes')}
|
|
487
|
-
>
|
|
488
|
-
Yes
|
|
489
|
-
</button>
|
|
490
|
-
<button
|
|
491
|
-
className="hud-button"
|
|
492
|
-
type="button"
|
|
493
|
-
onClick={() => onWorkflowAction('blueprint_no')}
|
|
494
|
-
>
|
|
495
|
-
No
|
|
496
|
-
</button>
|
|
497
|
-
<button
|
|
498
|
-
className="hud-button hud-button-reject"
|
|
499
|
-
type="button"
|
|
500
|
-
onClick={() => onWorkflowAction('stop')}
|
|
501
|
-
>
|
|
502
|
-
Stop
|
|
503
|
-
</button>
|
|
504
|
-
</div>
|
|
505
|
-
</>
|
|
506
|
-
) : creatingBlueprint ? (
|
|
507
|
-
<>
|
|
508
|
-
<p>
|
|
509
|
-
Walk the map, press <kbd>Space</kbd> for a file and{' '}
|
|
510
|
-
<kbd>B</kbd> for an island. Send the blueprint when the layout
|
|
511
|
-
is ready.
|
|
512
|
-
</p>
|
|
513
|
-
<div className="hud-decide">
|
|
514
|
-
<button
|
|
515
|
-
className="hud-button hud-button-approve"
|
|
516
|
-
type="button"
|
|
517
|
-
disabled={naming}
|
|
518
|
-
onClick={() => onWorkflowAction('blueprint_send')}
|
|
519
|
-
>
|
|
520
|
-
Send blueprint
|
|
521
|
-
</button>
|
|
522
|
-
<button
|
|
523
|
-
className="hud-button hud-button-reject"
|
|
524
|
-
type="button"
|
|
525
|
-
onClick={() => onWorkflowAction('stop')}
|
|
526
|
-
>
|
|
527
|
-
Stop
|
|
528
|
-
</button>
|
|
529
|
-
</div>
|
|
530
|
-
</>
|
|
531
|
-
) : intent.status === 'finished' ? (
|
|
532
|
-
<p>All plan steps were applied.</p>
|
|
533
|
-
) : preparing ? (
|
|
534
|
-
<div className="hud-working">
|
|
535
|
-
<span className="hud-spinner" aria-hidden="true" />
|
|
536
|
-
<span>LLM preparing…</span>
|
|
537
|
-
</div>
|
|
538
|
-
) : working ? (
|
|
539
|
-
<div className="hud-working">
|
|
540
|
-
<span className="hud-spinner" aria-hidden="true" />
|
|
541
|
-
<span>
|
|
542
|
-
{intent.status === 'replanning'
|
|
543
|
-
? 'Updating the remaining plan from your instruction…'
|
|
544
|
-
: `Implementing ${stepLabel.toLowerCase()}…`}
|
|
545
|
-
</span>
|
|
546
|
-
</div>
|
|
547
|
-
) : (
|
|
548
|
-
<p>
|
|
549
|
-
{stepLabel}
|
|
550
|
-
{intent.reason ? ` · ${intent.reason}` : ''}
|
|
551
|
-
</p>
|
|
552
|
-
)}
|
|
553
|
-
{!askingBlueprint && !creatingBlueprint && (
|
|
554
|
-
<>
|
|
555
|
-
{intent.steps?.length > 0 && (
|
|
556
|
-
<ol className="hud-steps">
|
|
557
|
-
{intent.steps.map((step) => (
|
|
558
|
-
<li
|
|
559
|
-
key={step.index}
|
|
560
|
-
data-current={
|
|
561
|
-
intent.status !== 'finished' &&
|
|
562
|
-
step.index === intent.step &&
|
|
563
|
-
!doneSteps.has(step.index)
|
|
564
|
-
}
|
|
565
|
-
data-done={doneSteps.has(step.index)}
|
|
566
|
-
data-next={
|
|
567
|
-
nextStep?.index === step.index && !doneSteps.has(step.index)
|
|
568
|
-
}
|
|
569
|
-
>
|
|
570
|
-
<span className="hud-step-index">{step.index}.</span>
|
|
571
|
-
<span className="hud-step-main">
|
|
572
|
-
<span className="hud-step-title">{step.title}</span>
|
|
573
|
-
{((canRunNext && nextStep?.index === step.index) ||
|
|
574
|
-
(canComplete && step.index === intent.step)) && (
|
|
575
|
-
<button
|
|
576
|
-
className="hud-button hud-button-approve hud-run-step"
|
|
577
|
-
type="button"
|
|
578
|
-
onClick={() =>
|
|
579
|
-
canComplete && step.index === intent.step
|
|
580
|
-
? onWorkflowAction('continue')
|
|
581
|
-
: onWorkflowAction('invoke', {
|
|
582
|
-
step: step.index,
|
|
583
|
-
})
|
|
584
|
-
}
|
|
585
|
-
>
|
|
586
|
-
{canComplete && step.index === intent.step
|
|
587
|
-
? 'Complete'
|
|
588
|
-
: 'Run step'}
|
|
589
|
-
</button>
|
|
590
|
-
)}
|
|
591
|
-
</span>
|
|
592
|
-
</li>
|
|
593
|
-
))}
|
|
594
|
-
</ol>
|
|
595
|
-
)}
|
|
596
|
-
{intent.chain.length > 0 && (
|
|
597
|
-
<div className="hud-chain">
|
|
598
|
-
<button
|
|
599
|
-
className="hud-button"
|
|
600
|
-
type="button"
|
|
601
|
-
disabled={!previousDiff}
|
|
602
|
-
onClick={() => previousDiff && onNavigateDiff(previousDiff.id)}
|
|
603
|
-
>
|
|
604
|
-
Previous
|
|
605
|
-
</button>
|
|
606
|
-
<span>
|
|
607
|
-
Diff {chainIndex + 1} of {intent.chain.length}
|
|
608
|
-
</span>
|
|
609
|
-
<button
|
|
610
|
-
className="hud-button"
|
|
611
|
-
type="button"
|
|
612
|
-
disabled={!nextDiff}
|
|
613
|
-
onClick={() => nextDiff && onNavigateDiff(nextDiff.id)}
|
|
614
|
-
>
|
|
615
|
-
Next
|
|
616
|
-
</button>
|
|
617
|
-
</div>
|
|
618
|
-
)}
|
|
619
|
-
<MutationFold
|
|
620
|
-
hasContent={
|
|
621
|
-
previewing &&
|
|
622
|
-
(intent.files.length > 0 ||
|
|
623
|
-
(intent.createFolders ?? []).length > 0 ||
|
|
624
|
-
intent.creates.length > 0 ||
|
|
625
|
-
intent.deletes.length > 0 ||
|
|
626
|
-
addedFunctions.length > 0 ||
|
|
627
|
-
addedVariables.length > 0 ||
|
|
628
|
-
addedImports.length > 0 ||
|
|
629
|
-
(intent.imports ?? []).length > 0)
|
|
630
|
-
}
|
|
631
|
-
>
|
|
632
|
-
{intent.files.length > 0 && (
|
|
633
|
-
<>
|
|
634
|
-
<div className="hud-section-title hud-section-title-edit">Changed</div>
|
|
635
|
-
<ul>
|
|
636
|
-
{intent.files.map((id) => (
|
|
637
|
-
<li className="hud-file-edit" key={id}>
|
|
638
|
-
{id}
|
|
639
|
-
</li>
|
|
640
|
-
))}
|
|
641
|
-
</ul>
|
|
642
|
-
</>
|
|
643
|
-
)}
|
|
644
|
-
{(intent.createFolders ?? []).length > 0 && (
|
|
645
|
-
<>
|
|
646
|
-
<div className="hud-section-title hud-section-title-add">Added islands</div>
|
|
647
|
-
<ul>
|
|
648
|
-
{intent.createFolders.map((id) => (
|
|
649
|
-
<li className="hud-file-add" key={id}>
|
|
650
|
-
{id}/
|
|
651
|
-
</li>
|
|
652
|
-
))}
|
|
653
|
-
</ul>
|
|
654
|
-
</>
|
|
655
|
-
)}
|
|
656
|
-
{intent.creates.length > 0 && (
|
|
657
|
-
<>
|
|
658
|
-
<div className="hud-section-title hud-section-title-add">Added</div>
|
|
659
|
-
<ul>
|
|
660
|
-
{intent.creates.map((id) => (
|
|
661
|
-
<li className="hud-file-add" key={id}>
|
|
662
|
-
{id}
|
|
663
|
-
</li>
|
|
664
|
-
))}
|
|
665
|
-
</ul>
|
|
666
|
-
</>
|
|
667
|
-
)}
|
|
668
|
-
{intent.deletes.length > 0 && (
|
|
669
|
-
<>
|
|
670
|
-
<div className="hud-section-title hud-section-title-remove">Removed</div>
|
|
671
|
-
<ul>
|
|
672
|
-
{intent.deletes.map((id) => (
|
|
673
|
-
<li className="hud-file-remove" key={id}>
|
|
674
|
-
{id}
|
|
675
|
-
</li>
|
|
676
|
-
))}
|
|
677
|
-
</ul>
|
|
678
|
-
</>
|
|
679
|
-
)}
|
|
680
|
-
<PanelList
|
|
681
|
-
title="Functions"
|
|
682
|
-
items={symbolLabels(addedFunctions)}
|
|
683
|
-
/>
|
|
684
|
-
<PanelList
|
|
685
|
-
title="Variables"
|
|
686
|
-
items={symbolLabels(addedVariables)}
|
|
942
|
+
{sessions.length > 0 && (
|
|
943
|
+
<div className="hud-left-stack">
|
|
944
|
+
{sessions.map((session) => (
|
|
945
|
+
<SessionPanel
|
|
946
|
+
key={session.sessionId}
|
|
947
|
+
intent={session}
|
|
948
|
+
focused={session.sessionId === (focusedSessionId ?? intent.sessionId)}
|
|
949
|
+
naming={naming}
|
|
950
|
+
onFocus={() => {
|
|
951
|
+
if (session.sessionId) onFocusSession?.(session.sessionId)
|
|
952
|
+
}}
|
|
953
|
+
onWorkflowAction={onWorkflowAction}
|
|
954
|
+
onNavigateDiff={onNavigateDiff}
|
|
687
955
|
/>
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
)}
|
|
691
|
-
{addedImports.length === 0 && (intent.imports ?? []).length > 0 && (
|
|
692
|
-
<>
|
|
693
|
-
<div className="hud-section-title">Imports</div>
|
|
694
|
-
<ul>
|
|
695
|
-
{intent.imports.map((edge) => (
|
|
696
|
-
<li key={`${edge.from}->${edge.to}`}>
|
|
697
|
-
{edge.from.split('/').pop()} → {edge.to.split('/').pop()}
|
|
698
|
-
</li>
|
|
699
|
-
))}
|
|
700
|
-
</ul>
|
|
701
|
-
</>
|
|
702
|
-
)}
|
|
703
|
-
</MutationFold>
|
|
704
|
-
{(planReady || preparing) && (
|
|
705
|
-
<div className="hud-decide">
|
|
706
|
-
<button
|
|
707
|
-
className="hud-button hud-button-reject"
|
|
708
|
-
type="button"
|
|
709
|
-
onClick={() => onWorkflowAction('stop')}
|
|
710
|
-
>
|
|
711
|
-
Stop
|
|
712
|
-
</button>
|
|
713
|
-
</div>
|
|
714
|
-
)}
|
|
715
|
-
{pending && (
|
|
716
|
-
<>
|
|
717
|
-
<label className="hud-instruction">
|
|
718
|
-
<span>Alternative instruction for the LLM</span>
|
|
719
|
-
<textarea
|
|
720
|
-
value={instruction}
|
|
721
|
-
maxLength={4000}
|
|
722
|
-
rows={3}
|
|
723
|
-
placeholder="Describe what should change in the next diff…"
|
|
724
|
-
onChange={(event) => setInstruction(event.target.value)}
|
|
725
|
-
/>
|
|
726
|
-
</label>
|
|
727
|
-
<div className="hud-decide">
|
|
728
|
-
{lastStep && (
|
|
729
|
-
<button
|
|
730
|
-
className="hud-button hud-button-approve"
|
|
731
|
-
type="button"
|
|
732
|
-
onClick={() => onWorkflowAction('continue')}
|
|
733
|
-
>
|
|
734
|
-
Complete
|
|
735
|
-
</button>
|
|
736
|
-
)}
|
|
737
|
-
<button
|
|
738
|
-
className="hud-button hud-button-extend"
|
|
739
|
-
type="button"
|
|
740
|
-
disabled={!instruction.trim()}
|
|
741
|
-
onClick={() =>
|
|
742
|
-
onWorkflowAction('instruct', { instruction })
|
|
743
|
-
}
|
|
744
|
-
>
|
|
745
|
-
Send instruction
|
|
746
|
-
</button>
|
|
747
|
-
<button
|
|
748
|
-
className="hud-button hud-button-reject"
|
|
749
|
-
type="button"
|
|
750
|
-
onClick={() => onWorkflowAction('stop')}
|
|
751
|
-
>
|
|
752
|
-
Stop
|
|
753
|
-
</button>
|
|
754
|
-
</div>
|
|
755
|
-
</>
|
|
756
|
-
)}
|
|
757
|
-
</>
|
|
758
|
-
)}
|
|
759
|
-
</aside>
|
|
956
|
+
))}
|
|
957
|
+
</div>
|
|
760
958
|
)}
|
|
761
959
|
|
|
960
|
+
<div className="hud-right-stack">
|
|
762
961
|
{selected && infoVisible && (
|
|
763
|
-
<aside
|
|
764
|
-
|
|
962
|
+
<aside
|
|
963
|
+
className="hud-panel hud-panel-info"
|
|
964
|
+
data-minimized={infoMinimized}
|
|
965
|
+
>
|
|
966
|
+
<PanelChrome
|
|
967
|
+
title={selected.name}
|
|
968
|
+
minimized={infoMinimized}
|
|
969
|
+
onMinimize={() => setInfoMinimized((current) => !current)}
|
|
970
|
+
onClose={() => {
|
|
971
|
+
setInfoVisible(false)
|
|
972
|
+
setInfoMinimized(false)
|
|
973
|
+
}}
|
|
974
|
+
/>
|
|
975
|
+
{!infoMinimized && (
|
|
976
|
+
<div ref={infoPanelRef} className="hud-panel-body">
|
|
765
977
|
<p className="path">{selected.path}</p>
|
|
766
978
|
<p>
|
|
767
979
|
{selected.lines} lines · {selected.language}
|
|
@@ -953,12 +1165,27 @@ export function HUD({
|
|
|
953
1165
|
onAdd={(raw) => onAddBlueprintImport(selected.id, raw)}
|
|
954
1166
|
/>
|
|
955
1167
|
)}
|
|
1168
|
+
</div>
|
|
1169
|
+
)}
|
|
956
1170
|
</aside>
|
|
957
1171
|
)}
|
|
958
1172
|
|
|
959
1173
|
{!selected && selectedFolderNode && infoVisible && (
|
|
960
|
-
<aside
|
|
961
|
-
|
|
1174
|
+
<aside
|
|
1175
|
+
className="hud-panel hud-panel-info"
|
|
1176
|
+
data-minimized={infoMinimized}
|
|
1177
|
+
>
|
|
1178
|
+
<PanelChrome
|
|
1179
|
+
title={selectedFolderNode.name}
|
|
1180
|
+
minimized={infoMinimized}
|
|
1181
|
+
onMinimize={() => setInfoMinimized((current) => !current)}
|
|
1182
|
+
onClose={() => {
|
|
1183
|
+
setInfoVisible(false)
|
|
1184
|
+
setInfoMinimized(false)
|
|
1185
|
+
}}
|
|
1186
|
+
/>
|
|
1187
|
+
{!infoMinimized && (
|
|
1188
|
+
<div ref={infoPanelRef} className="hud-panel-body">
|
|
962
1189
|
<p className="path">
|
|
963
1190
|
{selectedFolderNode.path === '.'
|
|
964
1191
|
? graph.targetName
|
|
@@ -1008,9 +1235,32 @@ export function HUD({
|
|
|
1008
1235
|
</button>
|
|
1009
1236
|
</div>
|
|
1010
1237
|
)}
|
|
1238
|
+
</div>
|
|
1239
|
+
)}
|
|
1011
1240
|
</aside>
|
|
1012
1241
|
)}
|
|
1013
1242
|
|
|
1243
|
+
{mapping && thumbnailVisible && (
|
|
1244
|
+
<SelectionThumbnail
|
|
1245
|
+
graph={graph}
|
|
1246
|
+
layout={layout}
|
|
1247
|
+
selectedId={selectedId}
|
|
1248
|
+
selectedFolder={selectedFolder}
|
|
1249
|
+
landAt={landAt}
|
|
1250
|
+
importedBy={importedBy}
|
|
1251
|
+
minimized={thumbnailMinimized}
|
|
1252
|
+
plannedIds={plannedIds}
|
|
1253
|
+
createdIds={createdIds}
|
|
1254
|
+
deletedIds={deletedIds}
|
|
1255
|
+
onMinimize={() => setThumbnailMinimized((current) => !current)}
|
|
1256
|
+
onHide={() => {
|
|
1257
|
+
setThumbnailVisible(false)
|
|
1258
|
+
setThumbnailMinimized(false)
|
|
1259
|
+
}}
|
|
1260
|
+
/>
|
|
1261
|
+
)}
|
|
1262
|
+
</div>
|
|
1263
|
+
|
|
1014
1264
|
<div className="hud-bottom">
|
|
1015
1265
|
<div className="hud-hints">
|
|
1016
1266
|
{mapping ? (
|
|
@@ -1032,10 +1282,20 @@ export function HUD({
|
|
|
1032
1282
|
)}
|
|
1033
1283
|
<span>{infoVisible ? 'I hide info' : 'I show info'}</span>
|
|
1034
1284
|
{infoVisible && <span>↑↓ scroll info</span>}
|
|
1285
|
+
<span>
|
|
1286
|
+
{thumbnailVisible ? 'T hide 3D view' : 'T show 3D view'}
|
|
1287
|
+
</span>
|
|
1035
1288
|
<span>
|
|
1036
1289
|
{importedBy ? 'K show imports' : 'K show imported by'}
|
|
1037
1290
|
</span>
|
|
1038
1291
|
<span>M back to walk</span>
|
|
1292
|
+
{canStop && (
|
|
1293
|
+
<span>
|
|
1294
|
+
{sessions.length > 1
|
|
1295
|
+
? 'Stop ends the focused LLM session'
|
|
1296
|
+
: 'Stop ends this LLM session'}
|
|
1297
|
+
</span>
|
|
1298
|
+
)}
|
|
1039
1299
|
</>
|
|
1040
1300
|
) : (
|
|
1041
1301
|
<>
|
|
@@ -1059,11 +1319,55 @@ export function HUD({
|
|
|
1059
1319
|
{importedBy ? 'K show imports' : 'K show imported by'}
|
|
1060
1320
|
</span>
|
|
1061
1321
|
<span>M toggle map</span>
|
|
1062
|
-
<span>Esc release mouse</span>
|
|
1322
|
+
<span>Double-click or Esc release mouse</span>
|
|
1323
|
+
{canStop && (
|
|
1324
|
+
<span>
|
|
1325
|
+
{sessions.length > 1
|
|
1326
|
+
? 'Stop ends the focused LLM session'
|
|
1327
|
+
: 'Stop ends this LLM session'}
|
|
1328
|
+
</span>
|
|
1329
|
+
)}
|
|
1063
1330
|
</>
|
|
1064
1331
|
)}
|
|
1065
1332
|
</div>
|
|
1066
1333
|
<div className="hud-icon-row">
|
|
1334
|
+
{mapping && (
|
|
1335
|
+
<button
|
|
1336
|
+
className="hud-button hud-icon-button"
|
|
1337
|
+
data-active={thumbnailVisible}
|
|
1338
|
+
aria-label={
|
|
1339
|
+
thumbnailVisible ? 'Hide 3D view' : 'Show 3D view'
|
|
1340
|
+
}
|
|
1341
|
+
aria-keyshortcuts="T"
|
|
1342
|
+
aria-pressed={thumbnailVisible}
|
|
1343
|
+
type="button"
|
|
1344
|
+
onClick={() => {
|
|
1345
|
+
setThumbnailVisible((visible) => {
|
|
1346
|
+
if (!visible) setThumbnailMinimized(false)
|
|
1347
|
+
return !visible
|
|
1348
|
+
})
|
|
1349
|
+
}}
|
|
1350
|
+
>
|
|
1351
|
+
<svg
|
|
1352
|
+
viewBox="0 0 24 24"
|
|
1353
|
+
width="18"
|
|
1354
|
+
height="18"
|
|
1355
|
+
fill="none"
|
|
1356
|
+
stroke="currentColor"
|
|
1357
|
+
strokeWidth="2"
|
|
1358
|
+
strokeLinecap="round"
|
|
1359
|
+
strokeLinejoin="round"
|
|
1360
|
+
aria-hidden="true"
|
|
1361
|
+
>
|
|
1362
|
+
<rect x="3" y="5" width="11" height="14" rx="1.5" />
|
|
1363
|
+
<path d="M16 8h5v11H9v-3" />
|
|
1364
|
+
<path d="M6.5 16.5 9 12l2 2.5 1.5-2L15 16.5" />
|
|
1365
|
+
</svg>
|
|
1366
|
+
<span className="hud-tooltip">
|
|
1367
|
+
{thumbnailVisible ? 'T hide 3D view' : 'T show 3D view'}
|
|
1368
|
+
</span>
|
|
1369
|
+
</button>
|
|
1370
|
+
)}
|
|
1067
1371
|
<button
|
|
1068
1372
|
className="hud-button hud-icon-button"
|
|
1069
1373
|
data-active={importedBy}
|