@jkwd/inbase 0.1.3 → 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/open-editor.d.ts +6 -0
- package/apps/explorer/scripts/open-editor.mjs +219 -0
- package/apps/explorer/scripts/session-store.d.ts +51 -1
- package/apps/explorer/scripts/session-store.mjs +461 -48
- package/apps/explorer/src/App.tsx +178 -78
- package/apps/explorer/src/agentIntent.ts +54 -1
- package/apps/explorer/src/index.css +238 -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 +41 -18
- 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 +19 -50
- package/apps/explorer/src/theme.ts +10 -1
- package/apps/explorer/src/types.ts +12 -0
- package/apps/explorer/src/ui/HUD.tsx +699 -390
- package/apps/explorer/vite.config.ts +72 -22
- package/bin/session.mjs +17 -3
- package/package.json +3 -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,20 +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
|
|
596
|
+
selectedTick?: number
|
|
131
597
|
selectedFolder?: string | null
|
|
598
|
+
landAt: [number, number]
|
|
132
599
|
aimedRelation: AimedRelation | null
|
|
133
600
|
currentFolder: string
|
|
134
601
|
intent: AgentIntent
|
|
602
|
+
intents?: AgentIntent[]
|
|
603
|
+
focusedSessionId?: string | null
|
|
604
|
+
onFocusSession?: (sessionId: string) => void
|
|
135
605
|
onWorkflowAction: (
|
|
606
|
+
sessionId: string,
|
|
136
607
|
action: WorkflowAction,
|
|
137
608
|
options?: { instruction?: string; step?: number },
|
|
138
609
|
) => void
|
|
139
|
-
onNavigateDiff: (diffId: string) => void
|
|
610
|
+
onNavigateDiff: (sessionId: string, diffId: string) => void
|
|
140
611
|
onOpenMap: () => void
|
|
141
612
|
onWalk: () => void
|
|
142
613
|
followLook: boolean
|
|
@@ -162,17 +633,27 @@ type HUDProps = {
|
|
|
162
633
|
) => void
|
|
163
634
|
onMapAddFile?: (folderPath: string) => void
|
|
164
635
|
onMapAddFolder?: (folderPath: string) => void
|
|
636
|
+
onInspectFile?: (fileId: string) => void
|
|
637
|
+
plannedIds?: string[]
|
|
638
|
+
createdIds?: string[]
|
|
639
|
+
deletedIds?: string[]
|
|
165
640
|
}
|
|
166
641
|
|
|
167
642
|
export function HUD({
|
|
168
643
|
graph,
|
|
644
|
+
layout,
|
|
169
645
|
mode,
|
|
170
646
|
locked,
|
|
171
647
|
selectedId,
|
|
648
|
+
selectedTick = 0,
|
|
172
649
|
selectedFolder = null,
|
|
650
|
+
landAt,
|
|
173
651
|
aimedRelation,
|
|
174
652
|
currentFolder,
|
|
175
653
|
intent,
|
|
654
|
+
intents,
|
|
655
|
+
focusedSessionId = null,
|
|
656
|
+
onFocusSession,
|
|
176
657
|
onWorkflowAction,
|
|
177
658
|
onNavigateDiff,
|
|
178
659
|
onOpenMap,
|
|
@@ -196,6 +677,10 @@ export function HUD({
|
|
|
196
677
|
onRemoveBlueprintImport,
|
|
197
678
|
onMapAddFile,
|
|
198
679
|
onMapAddFolder,
|
|
680
|
+
onInspectFile,
|
|
681
|
+
plannedIds = [],
|
|
682
|
+
createdIds = [],
|
|
683
|
+
deletedIds = [],
|
|
199
684
|
}: HUDProps) {
|
|
200
685
|
const selected = graph.files.find((file) => file.id === selectedId)
|
|
201
686
|
const selectedFolderNode = graph.folders.find(
|
|
@@ -213,31 +698,21 @@ export function HUD({
|
|
|
213
698
|
? graph.files.filter((file) => file.imports.includes(selected.id))
|
|
214
699
|
: []
|
|
215
700
|
const mapping = mode === 'map'
|
|
216
|
-
const
|
|
701
|
+
const sessions = (intents ?? [intent]).filter(
|
|
702
|
+
(item) => item.sessionId && isReviewingIntent(item.status),
|
|
703
|
+
)
|
|
704
|
+
const canStop = canStopSession(intent)
|
|
217
705
|
const [walkIntro, setWalkIntro] = useState(false)
|
|
218
706
|
const walkIntroSeen = useRef(false)
|
|
219
|
-
const [instruction, setInstruction] = useState('')
|
|
220
707
|
const [infoVisible, setInfoVisible] = useState(false)
|
|
221
|
-
const
|
|
222
|
-
const
|
|
223
|
-
const
|
|
224
|
-
const
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
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
|
+
)
|
|
228
715
|
const previewing = intent.preview
|
|
229
|
-
const reviewing = isReviewingIntent(intent.status) || intent.chain.length > 0
|
|
230
|
-
const chainIndex = intent.chainIndex ?? 0
|
|
231
|
-
const previousDiff = intent.chain[chainIndex - 1]
|
|
232
|
-
const nextDiff = intent.chain[chainIndex + 1]
|
|
233
|
-
const stepLabel =
|
|
234
|
-
intent.step && intent.steps?.length > 0
|
|
235
|
-
? `Step ${intent.step} of ${intent.steps.length}`
|
|
236
|
-
: 'Patch'
|
|
237
|
-
const lastStep =
|
|
238
|
-
typeof intent.step === 'number' &&
|
|
239
|
-
intent.steps.length > 0 &&
|
|
240
|
-
intent.step >= intent.steps.length
|
|
241
716
|
const addedFunctions = intent.addedFunctions ?? []
|
|
242
717
|
const addedVariables = intent.addedVariables ?? []
|
|
243
718
|
const addedImports = intent.addedImports ?? []
|
|
@@ -276,33 +751,11 @@ export function HUD({
|
|
|
276
751
|
)
|
|
277
752
|
const canEditBlueprint =
|
|
278
753
|
creatingBlueprint && Boolean(selected) && !selected?.id.startsWith('draft:')
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
(entry) =>
|
|
285
|
-
entry.status === 'applied' || entry.status === 'extended',
|
|
286
|
-
)
|
|
287
|
-
.map((entry) => entry.step),
|
|
288
|
-
)
|
|
289
|
-
if (intent.status === 'approved' && typeof intent.step === 'number') {
|
|
290
|
-
doneSteps.add(intent.step)
|
|
291
|
-
}
|
|
292
|
-
if (pending && typeof intent.step === 'number' && !lastStep) {
|
|
293
|
-
doneSteps.add(intent.step)
|
|
294
|
-
}
|
|
295
|
-
const nextStep =
|
|
296
|
-
intent.status === 'finished'
|
|
297
|
-
? null
|
|
298
|
-
: (intent.steps.find((step) => !doneSteps.has(step.index)) ?? null)
|
|
299
|
-
const canRunNext =
|
|
300
|
-
Boolean(nextStep) && (planReady || pending) && !working
|
|
301
|
-
const canComplete = pending && lastStep
|
|
302
|
-
const panelDone =
|
|
303
|
-
intent.status === 'finished' ||
|
|
304
|
-
intent.status === 'approved' ||
|
|
305
|
-
doneSteps.size > 0
|
|
754
|
+
const canInspectFile = (fileId: string, userCreated = false) =>
|
|
755
|
+
Boolean(onInspectFile) &&
|
|
756
|
+
!fileId.startsWith('draft:') &&
|
|
757
|
+
!(previewing && (intent.deletes ?? []).includes(fileId)) &&
|
|
758
|
+
(!userCreated || (intent.creates ?? []).includes(fileId))
|
|
306
759
|
|
|
307
760
|
useEffect(() => {
|
|
308
761
|
if (mode !== 'walk') {
|
|
@@ -323,17 +776,13 @@ export function HUD({
|
|
|
323
776
|
return () => window.clearTimeout(timer)
|
|
324
777
|
}, [locked, mode, naming])
|
|
325
778
|
|
|
326
|
-
useEffect(() => {
|
|
327
|
-
setInstruction('')
|
|
328
|
-
}, [intent.diffId])
|
|
329
|
-
|
|
330
779
|
useEffect(() => {
|
|
331
780
|
infoPanelRef.current?.scrollTo({ top: 0 })
|
|
332
781
|
}, [selectedId, selectedFolder])
|
|
333
782
|
|
|
334
783
|
useEffect(() => {
|
|
335
|
-
if (
|
|
336
|
-
}, [selectedId,
|
|
784
|
+
if (selectedId) setInfoVisible(true)
|
|
785
|
+
}, [selectedId, selectedTick])
|
|
337
786
|
|
|
338
787
|
useEffect(() => {
|
|
339
788
|
if (selectedFolder) setInfoVisible(true)
|
|
@@ -353,12 +802,39 @@ export function HUD({
|
|
|
353
802
|
return
|
|
354
803
|
}
|
|
355
804
|
event.preventDefault()
|
|
356
|
-
setInfoVisible((visible) =>
|
|
805
|
+
setInfoVisible((visible) => {
|
|
806
|
+
if (!visible) setInfoMinimized(false)
|
|
807
|
+
return !visible
|
|
808
|
+
})
|
|
357
809
|
}
|
|
358
810
|
window.addEventListener('keydown', onKey)
|
|
359
811
|
return () => window.removeEventListener('keydown', onKey)
|
|
360
812
|
}, [])
|
|
361
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
|
+
|
|
362
838
|
useEffect(() => {
|
|
363
839
|
if (!infoVisible || (!selectedId && !selectedFolder)) return
|
|
364
840
|
const onKey = (event: KeyboardEvent) => {
|
|
@@ -391,8 +867,9 @@ export function HUD({
|
|
|
391
867
|
<div className="hud-gate-card">
|
|
392
868
|
<h1>Walk</h1>
|
|
393
869
|
<p>
|
|
394
|
-
Click to look around.
|
|
395
|
-
<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.
|
|
396
873
|
</p>
|
|
397
874
|
<p>
|
|
398
875
|
<kbd>W</kbd> <kbd>A</kbd> <kbd>S</kbd> <kbd>D</kbd> walk,{' '}
|
|
@@ -402,7 +879,7 @@ export function HUD({
|
|
|
402
879
|
, <kbd>Space</kbd> place a file, <kbd>B</kbd> place an island
|
|
403
880
|
</>
|
|
404
881
|
) : null}
|
|
405
|
-
, click a block for
|
|
882
|
+
, click a block for info.
|
|
406
883
|
</p>
|
|
407
884
|
</div>
|
|
408
885
|
</div>
|
|
@@ -446,317 +923,70 @@ export function HUD({
|
|
|
446
923
|
>
|
|
447
924
|
Walk
|
|
448
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
|
+
)}
|
|
449
938
|
</div>
|
|
450
939
|
{selected && <div className="hud-chip">{selected.path}</div>}
|
|
451
940
|
</div>
|
|
452
941
|
|
|
453
|
-
{
|
|
454
|
-
<
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
<>
|
|
467
|
-
<p>
|
|
468
|
-
{intent.canEnterBlueprint
|
|
469
|
-
? 'Place files and islands for this chat, then send them as a blueprint for the LLM?'
|
|
470
|
-
: `Blueprint edit mode is active in another chat (${intent.blueprintSessionId}). Finish or stop it before starting here.`}
|
|
471
|
-
</p>
|
|
472
|
-
<div className="hud-decide">
|
|
473
|
-
<button
|
|
474
|
-
className="hud-button hud-button-approve"
|
|
475
|
-
type="button"
|
|
476
|
-
disabled={!intent.canEnterBlueprint}
|
|
477
|
-
onClick={() => onWorkflowAction('blueprint_yes')}
|
|
478
|
-
>
|
|
479
|
-
Yes
|
|
480
|
-
</button>
|
|
481
|
-
<button
|
|
482
|
-
className="hud-button"
|
|
483
|
-
type="button"
|
|
484
|
-
onClick={() => onWorkflowAction('blueprint_no')}
|
|
485
|
-
>
|
|
486
|
-
No
|
|
487
|
-
</button>
|
|
488
|
-
<button
|
|
489
|
-
className="hud-button hud-button-reject"
|
|
490
|
-
type="button"
|
|
491
|
-
onClick={() => onWorkflowAction('stop')}
|
|
492
|
-
>
|
|
493
|
-
Stop
|
|
494
|
-
</button>
|
|
495
|
-
</div>
|
|
496
|
-
</>
|
|
497
|
-
) : creatingBlueprint ? (
|
|
498
|
-
<>
|
|
499
|
-
<p>
|
|
500
|
-
Walk the map, press <kbd>Space</kbd> for a file and{' '}
|
|
501
|
-
<kbd>B</kbd> for an island. Send the blueprint when the layout
|
|
502
|
-
is ready.
|
|
503
|
-
</p>
|
|
504
|
-
<div className="hud-decide">
|
|
505
|
-
<button
|
|
506
|
-
className="hud-button hud-button-approve"
|
|
507
|
-
type="button"
|
|
508
|
-
disabled={naming}
|
|
509
|
-
onClick={() => onWorkflowAction('blueprint_send')}
|
|
510
|
-
>
|
|
511
|
-
Send blueprint
|
|
512
|
-
</button>
|
|
513
|
-
<button
|
|
514
|
-
className="hud-button hud-button-reject"
|
|
515
|
-
type="button"
|
|
516
|
-
onClick={() => onWorkflowAction('stop')}
|
|
517
|
-
>
|
|
518
|
-
Stop
|
|
519
|
-
</button>
|
|
520
|
-
</div>
|
|
521
|
-
</>
|
|
522
|
-
) : intent.status === 'finished' ? (
|
|
523
|
-
<p>All plan steps were applied.</p>
|
|
524
|
-
) : preparing ? (
|
|
525
|
-
<div className="hud-working">
|
|
526
|
-
<span className="hud-spinner" aria-hidden="true" />
|
|
527
|
-
<span>LLM preparing…</span>
|
|
528
|
-
</div>
|
|
529
|
-
) : working ? (
|
|
530
|
-
<div className="hud-working">
|
|
531
|
-
<span className="hud-spinner" aria-hidden="true" />
|
|
532
|
-
<span>
|
|
533
|
-
{intent.status === 'replanning'
|
|
534
|
-
? 'Updating the remaining plan from your instruction…'
|
|
535
|
-
: `Implementing ${stepLabel.toLowerCase()}…`}
|
|
536
|
-
</span>
|
|
537
|
-
</div>
|
|
538
|
-
) : (
|
|
539
|
-
<p>
|
|
540
|
-
{stepLabel}
|
|
541
|
-
{intent.reason ? ` · ${intent.reason}` : ''}
|
|
542
|
-
</p>
|
|
543
|
-
)}
|
|
544
|
-
{!askingBlueprint && !creatingBlueprint && (
|
|
545
|
-
<>
|
|
546
|
-
{intent.steps?.length > 0 && (
|
|
547
|
-
<ol className="hud-steps">
|
|
548
|
-
{intent.steps.map((step) => (
|
|
549
|
-
<li
|
|
550
|
-
key={step.index}
|
|
551
|
-
data-current={
|
|
552
|
-
intent.status !== 'finished' &&
|
|
553
|
-
step.index === intent.step &&
|
|
554
|
-
!doneSteps.has(step.index)
|
|
555
|
-
}
|
|
556
|
-
data-done={doneSteps.has(step.index)}
|
|
557
|
-
data-next={
|
|
558
|
-
nextStep?.index === step.index && !doneSteps.has(step.index)
|
|
559
|
-
}
|
|
560
|
-
>
|
|
561
|
-
<span className="hud-step-index">{step.index}.</span>
|
|
562
|
-
<span className="hud-step-main">
|
|
563
|
-
<span className="hud-step-title">{step.title}</span>
|
|
564
|
-
{((canRunNext && nextStep?.index === step.index) ||
|
|
565
|
-
(canComplete && step.index === intent.step)) && (
|
|
566
|
-
<button
|
|
567
|
-
className="hud-button hud-button-approve hud-run-step"
|
|
568
|
-
type="button"
|
|
569
|
-
onClick={() =>
|
|
570
|
-
canComplete && step.index === intent.step
|
|
571
|
-
? onWorkflowAction('continue')
|
|
572
|
-
: onWorkflowAction('invoke', {
|
|
573
|
-
step: step.index,
|
|
574
|
-
})
|
|
575
|
-
}
|
|
576
|
-
>
|
|
577
|
-
{canComplete && step.index === intent.step
|
|
578
|
-
? 'Complete'
|
|
579
|
-
: 'Run step'}
|
|
580
|
-
</button>
|
|
581
|
-
)}
|
|
582
|
-
</span>
|
|
583
|
-
</li>
|
|
584
|
-
))}
|
|
585
|
-
</ol>
|
|
586
|
-
)}
|
|
587
|
-
{intent.chain.length > 0 && (
|
|
588
|
-
<div className="hud-chain">
|
|
589
|
-
<button
|
|
590
|
-
className="hud-button"
|
|
591
|
-
type="button"
|
|
592
|
-
disabled={!previousDiff}
|
|
593
|
-
onClick={() => previousDiff && onNavigateDiff(previousDiff.id)}
|
|
594
|
-
>
|
|
595
|
-
Previous
|
|
596
|
-
</button>
|
|
597
|
-
<span>
|
|
598
|
-
Diff {chainIndex + 1} of {intent.chain.length}
|
|
599
|
-
</span>
|
|
600
|
-
<button
|
|
601
|
-
className="hud-button"
|
|
602
|
-
type="button"
|
|
603
|
-
disabled={!nextDiff}
|
|
604
|
-
onClick={() => nextDiff && onNavigateDiff(nextDiff.id)}
|
|
605
|
-
>
|
|
606
|
-
Next
|
|
607
|
-
</button>
|
|
608
|
-
</div>
|
|
609
|
-
)}
|
|
610
|
-
<MutationFold
|
|
611
|
-
hasContent={
|
|
612
|
-
previewing &&
|
|
613
|
-
(intent.files.length > 0 ||
|
|
614
|
-
(intent.createFolders ?? []).length > 0 ||
|
|
615
|
-
intent.creates.length > 0 ||
|
|
616
|
-
intent.deletes.length > 0 ||
|
|
617
|
-
addedFunctions.length > 0 ||
|
|
618
|
-
addedVariables.length > 0 ||
|
|
619
|
-
addedImports.length > 0 ||
|
|
620
|
-
(intent.imports ?? []).length > 0)
|
|
621
|
-
}
|
|
622
|
-
>
|
|
623
|
-
{intent.files.length > 0 && (
|
|
624
|
-
<>
|
|
625
|
-
<div className="hud-section-title hud-section-title-edit">Changed</div>
|
|
626
|
-
<ul>
|
|
627
|
-
{intent.files.map((id) => (
|
|
628
|
-
<li className="hud-file-edit" key={id}>
|
|
629
|
-
{id}
|
|
630
|
-
</li>
|
|
631
|
-
))}
|
|
632
|
-
</ul>
|
|
633
|
-
</>
|
|
634
|
-
)}
|
|
635
|
-
{(intent.createFolders ?? []).length > 0 && (
|
|
636
|
-
<>
|
|
637
|
-
<div className="hud-section-title hud-section-title-add">Added islands</div>
|
|
638
|
-
<ul>
|
|
639
|
-
{intent.createFolders.map((id) => (
|
|
640
|
-
<li className="hud-file-add" key={id}>
|
|
641
|
-
{id}/
|
|
642
|
-
</li>
|
|
643
|
-
))}
|
|
644
|
-
</ul>
|
|
645
|
-
</>
|
|
646
|
-
)}
|
|
647
|
-
{intent.creates.length > 0 && (
|
|
648
|
-
<>
|
|
649
|
-
<div className="hud-section-title hud-section-title-add">Added</div>
|
|
650
|
-
<ul>
|
|
651
|
-
{intent.creates.map((id) => (
|
|
652
|
-
<li className="hud-file-add" key={id}>
|
|
653
|
-
{id}
|
|
654
|
-
</li>
|
|
655
|
-
))}
|
|
656
|
-
</ul>
|
|
657
|
-
</>
|
|
658
|
-
)}
|
|
659
|
-
{intent.deletes.length > 0 && (
|
|
660
|
-
<>
|
|
661
|
-
<div className="hud-section-title hud-section-title-remove">Removed</div>
|
|
662
|
-
<ul>
|
|
663
|
-
{intent.deletes.map((id) => (
|
|
664
|
-
<li className="hud-file-remove" key={id}>
|
|
665
|
-
{id}
|
|
666
|
-
</li>
|
|
667
|
-
))}
|
|
668
|
-
</ul>
|
|
669
|
-
</>
|
|
670
|
-
)}
|
|
671
|
-
<PanelList
|
|
672
|
-
title="Functions"
|
|
673
|
-
items={symbolLabels(addedFunctions)}
|
|
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}
|
|
674
955
|
/>
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
items={symbolLabels(addedVariables)}
|
|
678
|
-
/>
|
|
679
|
-
{addedImports.length > 0 && (
|
|
680
|
-
<PanelList title="Imports" items={importLabels(addedImports)} />
|
|
681
|
-
)}
|
|
682
|
-
{addedImports.length === 0 && (intent.imports ?? []).length > 0 && (
|
|
683
|
-
<>
|
|
684
|
-
<div className="hud-section-title">Imports</div>
|
|
685
|
-
<ul>
|
|
686
|
-
{intent.imports.map((edge) => (
|
|
687
|
-
<li key={`${edge.from}->${edge.to}`}>
|
|
688
|
-
{edge.from.split('/').pop()} → {edge.to.split('/').pop()}
|
|
689
|
-
</li>
|
|
690
|
-
))}
|
|
691
|
-
</ul>
|
|
692
|
-
</>
|
|
693
|
-
)}
|
|
694
|
-
</MutationFold>
|
|
695
|
-
{(planReady || preparing) && (
|
|
696
|
-
<div className="hud-decide">
|
|
697
|
-
<button
|
|
698
|
-
className="hud-button hud-button-reject"
|
|
699
|
-
type="button"
|
|
700
|
-
onClick={() => onWorkflowAction('stop')}
|
|
701
|
-
>
|
|
702
|
-
Stop
|
|
703
|
-
</button>
|
|
704
|
-
</div>
|
|
705
|
-
)}
|
|
706
|
-
{pending && (
|
|
707
|
-
<>
|
|
708
|
-
<label className="hud-instruction">
|
|
709
|
-
<span>Alternative instruction for the LLM</span>
|
|
710
|
-
<textarea
|
|
711
|
-
value={instruction}
|
|
712
|
-
maxLength={4000}
|
|
713
|
-
rows={3}
|
|
714
|
-
placeholder="Describe what should change in the next diff…"
|
|
715
|
-
onChange={(event) => setInstruction(event.target.value)}
|
|
716
|
-
/>
|
|
717
|
-
</label>
|
|
718
|
-
<div className="hud-decide">
|
|
719
|
-
{lastStep && (
|
|
720
|
-
<button
|
|
721
|
-
className="hud-button hud-button-approve"
|
|
722
|
-
type="button"
|
|
723
|
-
onClick={() => onWorkflowAction('continue')}
|
|
724
|
-
>
|
|
725
|
-
Complete
|
|
726
|
-
</button>
|
|
727
|
-
)}
|
|
728
|
-
<button
|
|
729
|
-
className="hud-button hud-button-extend"
|
|
730
|
-
type="button"
|
|
731
|
-
disabled={!instruction.trim()}
|
|
732
|
-
onClick={() =>
|
|
733
|
-
onWorkflowAction('instruct', { instruction })
|
|
734
|
-
}
|
|
735
|
-
>
|
|
736
|
-
Send instruction
|
|
737
|
-
</button>
|
|
738
|
-
<button
|
|
739
|
-
className="hud-button hud-button-reject"
|
|
740
|
-
type="button"
|
|
741
|
-
onClick={() => onWorkflowAction('stop')}
|
|
742
|
-
>
|
|
743
|
-
Stop
|
|
744
|
-
</button>
|
|
745
|
-
</div>
|
|
746
|
-
</>
|
|
747
|
-
)}
|
|
748
|
-
</>
|
|
749
|
-
)}
|
|
750
|
-
</aside>
|
|
956
|
+
))}
|
|
957
|
+
</div>
|
|
751
958
|
)}
|
|
752
959
|
|
|
960
|
+
<div className="hud-right-stack">
|
|
753
961
|
{selected && infoVisible && (
|
|
754
|
-
<aside
|
|
755
|
-
|
|
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">
|
|
756
977
|
<p className="path">{selected.path}</p>
|
|
757
978
|
<p>
|
|
758
979
|
{selected.lines} lines · {selected.language}
|
|
759
980
|
</p>
|
|
981
|
+
{canInspectFile(selected.id, selected.userCreated) && (
|
|
982
|
+
<button
|
|
983
|
+
className="hud-button hud-inspect"
|
|
984
|
+
type="button"
|
|
985
|
+
onClick={() => onInspectFile?.(selected.id)}
|
|
986
|
+
>
|
|
987
|
+
Inspect file
|
|
988
|
+
</button>
|
|
989
|
+
)}
|
|
760
990
|
{selectedClasses.length > 0 && (
|
|
761
991
|
<>
|
|
762
992
|
<div className="hud-section-title">Classes</div>
|
|
@@ -935,12 +1165,27 @@ export function HUD({
|
|
|
935
1165
|
onAdd={(raw) => onAddBlueprintImport(selected.id, raw)}
|
|
936
1166
|
/>
|
|
937
1167
|
)}
|
|
1168
|
+
</div>
|
|
1169
|
+
)}
|
|
938
1170
|
</aside>
|
|
939
1171
|
)}
|
|
940
1172
|
|
|
941
1173
|
{!selected && selectedFolderNode && infoVisible && (
|
|
942
|
-
<aside
|
|
943
|
-
|
|
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">
|
|
944
1189
|
<p className="path">
|
|
945
1190
|
{selectedFolderNode.path === '.'
|
|
946
1191
|
? graph.targetName
|
|
@@ -955,7 +1200,18 @@ export function HUD({
|
|
|
955
1200
|
) : (
|
|
956
1201
|
<ul>
|
|
957
1202
|
{folderFiles.map((file) => (
|
|
958
|
-
<li key={file.id}>
|
|
1203
|
+
<li key={file.id}>
|
|
1204
|
+
<span>{file.path || file.id}</span>
|
|
1205
|
+
{canInspectFile(file.id, file.userCreated) && (
|
|
1206
|
+
<button
|
|
1207
|
+
className="hud-item-inspect"
|
|
1208
|
+
type="button"
|
|
1209
|
+
onClick={() => onInspectFile?.(file.id)}
|
|
1210
|
+
>
|
|
1211
|
+
Inspect
|
|
1212
|
+
</button>
|
|
1213
|
+
)}
|
|
1214
|
+
</li>
|
|
959
1215
|
))}
|
|
960
1216
|
</ul>
|
|
961
1217
|
)}
|
|
@@ -979,20 +1235,39 @@ export function HUD({
|
|
|
979
1235
|
</button>
|
|
980
1236
|
</div>
|
|
981
1237
|
)}
|
|
1238
|
+
</div>
|
|
1239
|
+
)}
|
|
982
1240
|
</aside>
|
|
983
1241
|
)}
|
|
984
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
|
+
|
|
985
1264
|
<div className="hud-bottom">
|
|
986
1265
|
<div className="hud-hints">
|
|
987
1266
|
{mapping ? (
|
|
988
1267
|
<>
|
|
989
1268
|
<span>Scroll zoom</span>
|
|
990
1269
|
<span>Drag pan</span>
|
|
991
|
-
<span>
|
|
992
|
-
{sessionMode
|
|
993
|
-
? 'Click a block for info'
|
|
994
|
-
: 'Click a block for relations'}
|
|
995
|
-
</span>
|
|
1270
|
+
<span>Click a block for info</span>
|
|
996
1271
|
<span>Click an island for its files</span>
|
|
997
1272
|
{creatingBlueprint && (
|
|
998
1273
|
<>
|
|
@@ -1001,24 +1276,26 @@ export function HUD({
|
|
|
1001
1276
|
</>
|
|
1002
1277
|
)}
|
|
1003
1278
|
<span>Click a line to fly there</span>
|
|
1004
|
-
<span>
|
|
1279
|
+
<span>Ctrl-click an island to walk</span>
|
|
1005
1280
|
{selected?.userCreated && creatingBlueprint && (
|
|
1006
1281
|
<span>Backspace delete</span>
|
|
1007
1282
|
)}
|
|
1283
|
+
<span>{infoVisible ? 'I hide info' : 'I show info'}</span>
|
|
1284
|
+
{infoVisible && <span>↑↓ scroll info</span>}
|
|
1008
1285
|
<span>
|
|
1009
|
-
{
|
|
1010
|
-
? infoVisible
|
|
1011
|
-
? 'I hide info'
|
|
1012
|
-
: 'Click a block for info'
|
|
1013
|
-
: infoVisible
|
|
1014
|
-
? 'I hide info'
|
|
1015
|
-
: 'I show info'}
|
|
1286
|
+
{thumbnailVisible ? 'T hide 3D view' : 'T show 3D view'}
|
|
1016
1287
|
</span>
|
|
1017
|
-
{infoVisible && <span>↑↓ scroll info</span>}
|
|
1018
1288
|
<span>
|
|
1019
1289
|
{importedBy ? 'K show imports' : 'K show imported by'}
|
|
1020
1290
|
</span>
|
|
1021
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
|
+
)}
|
|
1022
1299
|
</>
|
|
1023
1300
|
) : (
|
|
1024
1301
|
<>
|
|
@@ -1034,31 +1311,63 @@ export function HUD({
|
|
|
1034
1311
|
{selected?.userCreated && creatingBlueprint && (
|
|
1035
1312
|
<span>Backspace delete</span>
|
|
1036
1313
|
)}
|
|
1037
|
-
<span>
|
|
1038
|
-
{sessionMode
|
|
1039
|
-
? 'Click a block for info'
|
|
1040
|
-
: 'Click block for relations'}
|
|
1041
|
-
</span>
|
|
1314
|
+
<span>Click a block for info</span>
|
|
1042
1315
|
<span>Aim a line to fly</span>
|
|
1043
|
-
<span>
|
|
1044
|
-
{sessionMode
|
|
1045
|
-
? infoVisible
|
|
1046
|
-
? 'I hide info'
|
|
1047
|
-
: 'Click a block for info'
|
|
1048
|
-
: infoVisible
|
|
1049
|
-
? 'I hide info'
|
|
1050
|
-
: 'I show info'}
|
|
1051
|
-
</span>
|
|
1316
|
+
<span>{infoVisible ? 'I hide info' : 'I show info'}</span>
|
|
1052
1317
|
{infoVisible && <span>↑↓ scroll info</span>}
|
|
1053
1318
|
<span>
|
|
1054
1319
|
{importedBy ? 'K show imports' : 'K show imported by'}
|
|
1055
1320
|
</span>
|
|
1056
1321
|
<span>M toggle map</span>
|
|
1057
|
-
<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
|
+
)}
|
|
1058
1330
|
</>
|
|
1059
1331
|
)}
|
|
1060
1332
|
</div>
|
|
1061
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
|
+
)}
|
|
1062
1371
|
<button
|
|
1063
1372
|
className="hud-button hud-icon-button"
|
|
1064
1373
|
data-active={importedBy}
|