@jkwd/inbase 0.1.10 → 0.1.11
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/LICENSE +21 -0
- package/README.md +22 -9
- package/apps/explorer/package.json +1 -0
- package/apps/explorer/scripts/branch-changes.d.ts +24 -0
- package/apps/explorer/scripts/branch-changes.mjs +237 -0
- package/apps/explorer/scripts/js-source.mjs +12 -15
- package/apps/explorer/scripts/patch-lib.d.ts +5 -0
- package/apps/explorer/scripts/patch-lib.mjs +5 -0
- package/apps/explorer/scripts/scan-target.mjs +46 -13
- package/apps/explorer/scripts/session-store.d.ts +58 -1
- package/apps/explorer/scripts/session-store.mjs +276 -52
- package/apps/explorer/scripts/tree-diff.mjs +121 -0
- package/apps/explorer/src/App.tsx +132 -27
- package/apps/explorer/src/agentIntent.ts +63 -0
- package/apps/explorer/src/branchChanges.ts +54 -0
- package/apps/explorer/src/index.css +124 -0
- package/apps/explorer/src/scene/FileBlock.tsx +55 -5
- package/apps/explorer/src/types.ts +44 -0
- package/apps/explorer/src/ui/HUD.tsx +576 -92
- package/apps/explorer/src/userContext.ts +11 -0
- package/apps/explorer/vite.config.ts +61 -7
- package/bin/inbase.mjs +23 -4
- package/bin/project.mjs +62 -4
- package/bin/session.mjs +215 -125
- package/package.json +20 -2
- package/skill/commands/inbase.md +17 -0
- package/skill/commands/skipinbase.md +9 -0
- package/skill/inbase/SKILL.md +141 -112
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useEffect, useRef, useState, type ReactNode } from 'react'
|
|
2
|
+
import { persistInitialInstruction } from '../agentIntent'
|
|
2
3
|
import { NameInput } from './NameInput'
|
|
3
4
|
import { SelectionThumbnail } from '../scene/SelectionThumbnail'
|
|
4
5
|
import {
|
|
@@ -7,6 +8,7 @@ import {
|
|
|
7
8
|
type AgentIntent,
|
|
8
9
|
type AgentIntentStatus,
|
|
9
10
|
type AimedRelation,
|
|
11
|
+
type BranchChanges,
|
|
10
12
|
type CodebaseGraph,
|
|
11
13
|
type PatchImportAddition,
|
|
12
14
|
type PatchSymbolAddition,
|
|
@@ -29,10 +31,14 @@ function reviewTitle(status: AgentIntentStatus) {
|
|
|
29
31
|
return 'Visual workflow'
|
|
30
32
|
}
|
|
31
33
|
|
|
34
|
+
function sessionLabel(intent: AgentIntent) {
|
|
35
|
+
return intent.name?.trim() || intent.feature?.trim() || ''
|
|
36
|
+
}
|
|
37
|
+
|
|
32
38
|
function sessionTabLabel(intent: AgentIntent, sessions: AgentIntent[]) {
|
|
33
|
-
const title = intent
|
|
39
|
+
const title = sessionLabel(intent) || reviewTitle(intent.status)
|
|
34
40
|
const same = sessions.filter(
|
|
35
|
-
(item) => (item
|
|
41
|
+
(item) => (sessionLabel(item) || reviewTitle(item.status)) === title,
|
|
36
42
|
)
|
|
37
43
|
if (same.length < 2) return title
|
|
38
44
|
const id = intent.sessionId ?? ''
|
|
@@ -210,18 +216,29 @@ function AddIntentRow({
|
|
|
210
216
|
|
|
211
217
|
function PanelChrome({
|
|
212
218
|
title,
|
|
219
|
+
subtitle,
|
|
213
220
|
minimized = false,
|
|
214
221
|
onMinimize,
|
|
215
222
|
onClose,
|
|
223
|
+
closeLabel = 'Close',
|
|
224
|
+
closeReject = false,
|
|
216
225
|
}: {
|
|
217
226
|
title: ReactNode
|
|
227
|
+
subtitle?: ReactNode
|
|
218
228
|
minimized?: boolean
|
|
219
229
|
onMinimize?: () => void
|
|
220
230
|
onClose?: () => void
|
|
231
|
+
closeLabel?: string
|
|
232
|
+
closeReject?: boolean
|
|
221
233
|
}) {
|
|
222
234
|
return (
|
|
223
235
|
<div className="hud-panel-chrome">
|
|
224
|
-
<div className="hud-panel-chrome-
|
|
236
|
+
<div className="hud-panel-chrome-heading">
|
|
237
|
+
<div className="hud-panel-chrome-title">{title}</div>
|
|
238
|
+
{subtitle ? (
|
|
239
|
+
<div className="hud-panel-chrome-subtitle">{subtitle}</div>
|
|
240
|
+
) : null}
|
|
241
|
+
</div>
|
|
225
242
|
<div className="hud-panel-controls">
|
|
226
243
|
{onMinimize && (
|
|
227
244
|
<button
|
|
@@ -235,9 +252,13 @@ function PanelChrome({
|
|
|
235
252
|
)}
|
|
236
253
|
{onClose && (
|
|
237
254
|
<button
|
|
238
|
-
className=
|
|
255
|
+
className={
|
|
256
|
+
closeReject
|
|
257
|
+
? 'hud-button hud-icon-button hud-panel-control hud-button-reject'
|
|
258
|
+
: 'hud-button hud-icon-button hud-panel-control'
|
|
259
|
+
}
|
|
239
260
|
type="button"
|
|
240
|
-
aria-label=
|
|
261
|
+
aria-label={closeLabel}
|
|
241
262
|
onClick={onClose}
|
|
242
263
|
>
|
|
243
264
|
×
|
|
@@ -248,10 +269,200 @@ function PanelChrome({
|
|
|
248
269
|
)
|
|
249
270
|
}
|
|
250
271
|
|
|
272
|
+
function InitialInstructionField({
|
|
273
|
+
value,
|
|
274
|
+
onChange,
|
|
275
|
+
}: {
|
|
276
|
+
value: string
|
|
277
|
+
onChange: (value: string) => void
|
|
278
|
+
}) {
|
|
279
|
+
return (
|
|
280
|
+
<label className="hud-instruction">
|
|
281
|
+
<textarea
|
|
282
|
+
value={value}
|
|
283
|
+
maxLength={4000}
|
|
284
|
+
rows={4}
|
|
285
|
+
placeholder="What should the LLM build?"
|
|
286
|
+
onChange={(event) => onChange(event.target.value)}
|
|
287
|
+
onKeyDown={(event) => event.stopPropagation()}
|
|
288
|
+
/>
|
|
289
|
+
</label>
|
|
290
|
+
)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function blueprintIsDefined(intent: AgentIntent) {
|
|
294
|
+
return (
|
|
295
|
+
(intent.userCreatedBlocks?.length ?? 0) > 0 ||
|
|
296
|
+
(intent.userCreatedIslands?.length ?? 0) > 0 ||
|
|
297
|
+
(intent.blueprintFunctions?.length ?? 0) > 0 ||
|
|
298
|
+
(intent.blueprintVariables?.length ?? 0) > 0 ||
|
|
299
|
+
(intent.blueprintImports?.length ?? 0) > 0
|
|
300
|
+
)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function HandshakeSetup({
|
|
304
|
+
instruction,
|
|
305
|
+
onInstructionChange,
|
|
306
|
+
blueprintDefined,
|
|
307
|
+
awaitingAttach,
|
|
308
|
+
attachBlockedBy,
|
|
309
|
+
}: {
|
|
310
|
+
instruction: string
|
|
311
|
+
onInstructionChange: (value: string) => void
|
|
312
|
+
blueprintDefined: boolean
|
|
313
|
+
awaitingAttach: boolean
|
|
314
|
+
attachBlockedBy: string | null
|
|
315
|
+
}) {
|
|
316
|
+
return (
|
|
317
|
+
<div className="hud-setup">
|
|
318
|
+
<section className="hud-setup-section">
|
|
319
|
+
<h2 className="hud-setup-heading">Instructions</h2>
|
|
320
|
+
<InitialInstructionField
|
|
321
|
+
value={instruction}
|
|
322
|
+
onChange={onInstructionChange}
|
|
323
|
+
/>
|
|
324
|
+
</section>
|
|
325
|
+
<section className="hud-setup-section">
|
|
326
|
+
<h2 className="hud-setup-heading">
|
|
327
|
+
Blueprint
|
|
328
|
+
<span
|
|
329
|
+
className="hud-setup-tag"
|
|
330
|
+
data-ready={blueprintDefined ? 'true' : 'false'}
|
|
331
|
+
>
|
|
332
|
+
{blueprintDefined ? 'blueprint defined' : 'no blueprint'}
|
|
333
|
+
</span>
|
|
334
|
+
</h2>
|
|
335
|
+
<p>
|
|
336
|
+
Press <kbd>Space</kbd> for a file and <kbd>B</kbd> for an island.
|
|
337
|
+
Optional.
|
|
338
|
+
</p>
|
|
339
|
+
</section>
|
|
340
|
+
<section className="hud-setup-section">
|
|
341
|
+
<h2 className="hud-setup-heading">Start</h2>
|
|
342
|
+
{attachBlockedBy ? (
|
|
343
|
+
<p>
|
|
344
|
+
An LLM is already attached to {attachBlockedBy}. Stop that session
|
|
345
|
+
before running <kbd>/inbase</kbd>.
|
|
346
|
+
</p>
|
|
347
|
+
) : awaitingAttach ? (
|
|
348
|
+
<p>
|
|
349
|
+
Run <kbd>/inbase</kbd> in a Cursor chat to connect and start.
|
|
350
|
+
</p>
|
|
351
|
+
) : (
|
|
352
|
+
<p>
|
|
353
|
+
Starting from <kbd>/inbase</kbd>…
|
|
354
|
+
</p>
|
|
355
|
+
)}
|
|
356
|
+
</section>
|
|
357
|
+
</div>
|
|
358
|
+
)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function sessionLiveStatus(intent: AgentIntent) {
|
|
362
|
+
const ack = intent.lastAck
|
|
363
|
+
const kind = ack?.kind
|
|
364
|
+
const detail = ack?.detail?.trim() || ''
|
|
365
|
+
|
|
366
|
+
if (intent.status === 'finished' || kind === 'finished') {
|
|
367
|
+
return { text: 'Finished', busy: false }
|
|
368
|
+
}
|
|
369
|
+
if (kind === 'stopped' || intent.status === 'rejected') {
|
|
370
|
+
return { text: 'Stopped', busy: false }
|
|
371
|
+
}
|
|
372
|
+
if (kind === 'timeout') {
|
|
373
|
+
return { text: 'LLM wait timed out', busy: false }
|
|
374
|
+
}
|
|
375
|
+
if (intent.awaitingAttach) {
|
|
376
|
+
return { text: 'Waiting for /inbase in Cursor', busy: true }
|
|
377
|
+
}
|
|
378
|
+
if (kind === 'execute') {
|
|
379
|
+
return { text: `LLM received ${detail}`, busy: true }
|
|
380
|
+
}
|
|
381
|
+
if (kind === 'invoke') {
|
|
382
|
+
return { text: `Sent ${detail} — waiting for LLM`, busy: true }
|
|
383
|
+
}
|
|
384
|
+
if (kind === 'replan') {
|
|
385
|
+
return { text: 'LLM received a new instruction', busy: true }
|
|
386
|
+
}
|
|
387
|
+
if (intent.status === 'working') {
|
|
388
|
+
return {
|
|
389
|
+
text: intent.reason
|
|
390
|
+
? `LLM is working on ${intent.reason}`
|
|
391
|
+
: 'LLM is working',
|
|
392
|
+
busy: true,
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
if (intent.status === 'replanning') {
|
|
396
|
+
return { text: 'LLM is revising the plan', busy: true }
|
|
397
|
+
}
|
|
398
|
+
if (intent.status === 'preparing' || kind === 'blueprint') {
|
|
399
|
+
return { text: 'LLM is drafting the plan', busy: true }
|
|
400
|
+
}
|
|
401
|
+
if (kind === 'plan' || intent.status === 'planned') {
|
|
402
|
+
return intent.listening
|
|
403
|
+
? { text: 'LLM is listening — run the next step', busy: false }
|
|
404
|
+
: { text: 'Plan ready', busy: false }
|
|
405
|
+
}
|
|
406
|
+
if (intent.status === 'pending') {
|
|
407
|
+
return intent.listening
|
|
408
|
+
? { text: 'LLM is listening — accept or send an instruction', busy: false }
|
|
409
|
+
: { text: 'Review this step', busy: false }
|
|
410
|
+
}
|
|
411
|
+
if (
|
|
412
|
+
kind === 'attached' ||
|
|
413
|
+
intent.status === 'blueprint' ||
|
|
414
|
+
intent.status === 'blueprint_ask'
|
|
415
|
+
) {
|
|
416
|
+
return { text: 'LLM attached', busy: true }
|
|
417
|
+
}
|
|
418
|
+
if (intent.llmIdle) {
|
|
419
|
+
return { text: 'LLM is idle', busy: false }
|
|
420
|
+
}
|
|
421
|
+
return { text: 'LLM connected', busy: true }
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function LiveStatus({
|
|
425
|
+
intent,
|
|
426
|
+
showStop = false,
|
|
427
|
+
onStop,
|
|
428
|
+
}: {
|
|
429
|
+
intent: AgentIntent
|
|
430
|
+
showStop?: boolean
|
|
431
|
+
onStop?: () => void
|
|
432
|
+
}) {
|
|
433
|
+
const status = sessionLiveStatus(intent)
|
|
434
|
+
const [flash, setFlash] = useState(false)
|
|
435
|
+
const lastAt = intent.lastAck?.at
|
|
436
|
+
|
|
437
|
+
useEffect(() => {
|
|
438
|
+
if (!lastAt) return
|
|
439
|
+
setFlash(true)
|
|
440
|
+
const timer = window.setTimeout(() => setFlash(false), 700)
|
|
441
|
+
return () => window.clearTimeout(timer)
|
|
442
|
+
}, [lastAt])
|
|
443
|
+
|
|
444
|
+
return (
|
|
445
|
+
<div className="hud-live" data-busy={status.busy} data-flash={flash}>
|
|
446
|
+
{status.busy ? <span className="hud-spinner" aria-hidden="true" /> : null}
|
|
447
|
+
<span>{status.text}</span>
|
|
448
|
+
{showStop && onStop ? (
|
|
449
|
+
<button
|
|
450
|
+
className="hud-button hud-button-reject"
|
|
451
|
+
type="button"
|
|
452
|
+
onClick={onStop}
|
|
453
|
+
>
|
|
454
|
+
Stop
|
|
455
|
+
</button>
|
|
456
|
+
) : null}
|
|
457
|
+
</div>
|
|
458
|
+
)
|
|
459
|
+
}
|
|
460
|
+
|
|
251
461
|
type SessionPanelProps = {
|
|
252
462
|
intent: AgentIntent
|
|
253
463
|
focused: boolean
|
|
254
464
|
naming: boolean
|
|
465
|
+
attachedSession?: AgentIntent | null
|
|
255
466
|
onFocus: () => void
|
|
256
467
|
onWorkflowAction: (
|
|
257
468
|
sessionId: string,
|
|
@@ -265,12 +476,16 @@ function SessionPanel({
|
|
|
265
476
|
intent,
|
|
266
477
|
focused,
|
|
267
478
|
naming,
|
|
479
|
+
attachedSession = null,
|
|
268
480
|
onFocus,
|
|
269
481
|
onWorkflowAction,
|
|
270
482
|
onNavigateDiff,
|
|
271
483
|
}: SessionPanelProps) {
|
|
272
484
|
const [minimized, setMinimized] = useState(false)
|
|
273
485
|
const [instruction, setInstruction] = useState('')
|
|
486
|
+
const [initialInstruction, setInitialInstruction] = useState(
|
|
487
|
+
() => intent.initialInstruction ?? '',
|
|
488
|
+
)
|
|
274
489
|
const sessionId = intent.sessionId
|
|
275
490
|
const pending = intent.status === 'pending' && intent.isActiveDiff
|
|
276
491
|
const askingBlueprint = intent.status === 'blueprint_ask'
|
|
@@ -330,8 +545,31 @@ function SessionPanel({
|
|
|
330
545
|
setInstruction('')
|
|
331
546
|
}, [intent.diffId])
|
|
332
547
|
|
|
548
|
+
useEffect(() => {
|
|
549
|
+
setInitialInstruction(intent.initialInstruction ?? '')
|
|
550
|
+
}, [sessionId])
|
|
551
|
+
|
|
333
552
|
if (!sessionId || !isReviewingIntent(intent.status)) return null
|
|
334
553
|
|
|
554
|
+
const updateInitialInstruction = (value: string) => {
|
|
555
|
+
setInitialInstruction(value)
|
|
556
|
+
persistInitialInstruction(sessionId, value)
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const showInitialInstruction =
|
|
560
|
+
Boolean(intent.awaitingAttach) &&
|
|
561
|
+
(askingBlueprint || sendingBlueprint || preparing)
|
|
562
|
+
const handshakeSetup = showInitialInstruction
|
|
563
|
+
const llmConnected = intent.awaitingAttach === false
|
|
564
|
+
const showConnectedProgress =
|
|
565
|
+
llmConnected && (askingBlueprint || sendingBlueprint || preparing)
|
|
566
|
+
const attachBlockedBy =
|
|
567
|
+
intent.awaitingAttach &&
|
|
568
|
+
attachedSession &&
|
|
569
|
+
attachedSession.sessionId !== sessionId
|
|
570
|
+
? sessionLabel(attachedSession) || 'another session'
|
|
571
|
+
: null
|
|
572
|
+
|
|
335
573
|
const act = (
|
|
336
574
|
action: WorkflowAction,
|
|
337
575
|
options?: { instruction?: string; step?: number; stepByStep?: boolean },
|
|
@@ -349,12 +587,30 @@ function SessionPanel({
|
|
|
349
587
|
onPointerDown={onFocus}
|
|
350
588
|
>
|
|
351
589
|
<PanelChrome
|
|
352
|
-
title={
|
|
590
|
+
title={
|
|
591
|
+
sessionLabel(intent) ||
|
|
592
|
+
(showConnectedProgress ? 'LLM connected' : reviewTitle(intent.status))
|
|
593
|
+
}
|
|
594
|
+
subtitle={
|
|
595
|
+
sessionLabel(intent)
|
|
596
|
+
? showConnectedProgress
|
|
597
|
+
? 'LLM connected'
|
|
598
|
+
: reviewTitle(intent.status)
|
|
599
|
+
: undefined
|
|
600
|
+
}
|
|
353
601
|
minimized={minimized}
|
|
354
602
|
onMinimize={() => setMinimized((current) => !current)}
|
|
603
|
+
onClose={() => act('stop')}
|
|
604
|
+
closeLabel="Stop session"
|
|
605
|
+
closeReject
|
|
355
606
|
/>
|
|
356
607
|
{!minimized && (
|
|
357
608
|
<>
|
|
609
|
+
<LiveStatus
|
|
610
|
+
intent={intent}
|
|
611
|
+
showStop={showConnectedProgress || working}
|
|
612
|
+
onStop={() => act('stop')}
|
|
613
|
+
/>
|
|
358
614
|
<label className="hud-mode-switch">
|
|
359
615
|
<span>Step by step</span>
|
|
360
616
|
<button
|
|
@@ -373,13 +629,35 @@ function SessionPanel({
|
|
|
373
629
|
Accept proposal.
|
|
374
630
|
</p>
|
|
375
631
|
)}
|
|
376
|
-
{
|
|
377
|
-
<
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
632
|
+
{handshakeSetup ? (
|
|
633
|
+
<HandshakeSetup
|
|
634
|
+
instruction={initialInstruction}
|
|
635
|
+
onInstructionChange={updateInitialInstruction}
|
|
636
|
+
blueprintDefined={blueprintIsDefined(intent)}
|
|
637
|
+
awaitingAttach={Boolean(intent.awaitingAttach)}
|
|
638
|
+
attachBlockedBy={attachBlockedBy || null}
|
|
639
|
+
/>
|
|
640
|
+
) : intent.awaitingAttach ? (
|
|
641
|
+
attachedSession &&
|
|
642
|
+
attachedSession.sessionId !== sessionId ? (
|
|
643
|
+
<p className="hud-mode-hint">
|
|
644
|
+
An LLM is already attached to{' '}
|
|
645
|
+
{sessionLabel(attachedSession) || 'another session'}. Stop that
|
|
646
|
+
session before running <kbd>/inbase</kbd>.
|
|
647
|
+
</p>
|
|
648
|
+
) : (
|
|
649
|
+
<p className="hud-mode-hint">
|
|
650
|
+
No LLM is attached. Open a Cursor chat and run{' '}
|
|
651
|
+
<kbd>/inbase</kbd>. It connects to this focused session.
|
|
652
|
+
</p>
|
|
653
|
+
)
|
|
654
|
+
) : null}
|
|
655
|
+
{intent.feature &&
|
|
656
|
+
!handshakeSetup &&
|
|
657
|
+
intent.feature.trim() !== sessionLabel(intent) && (
|
|
658
|
+
<p className="hud-feature">{intent.feature}</p>
|
|
659
|
+
)}
|
|
660
|
+
{askingBlueprint && !handshakeSetup && !showConnectedProgress ? (
|
|
383
661
|
<>
|
|
384
662
|
<p>
|
|
385
663
|
Place files and folders for this chat, then send them as a
|
|
@@ -410,64 +688,9 @@ function SessionPanel({
|
|
|
410
688
|
</button>
|
|
411
689
|
</div>
|
|
412
690
|
</>
|
|
413
|
-
) :
|
|
414
|
-
<>
|
|
415
|
-
<p>
|
|
416
|
-
Walk the map, press <kbd>Space</kbd> for a file and{' '}
|
|
417
|
-
<kbd>B</kbd> for an island. Send the blueprint when the layout
|
|
418
|
-
is ready. You can keep placing files after this handshake.
|
|
419
|
-
</p>
|
|
420
|
-
<div className="hud-decide">
|
|
421
|
-
<button
|
|
422
|
-
className="hud-button hud-button-approve"
|
|
423
|
-
type="button"
|
|
424
|
-
disabled={naming}
|
|
425
|
-
onClick={() => act('blueprint_send')}
|
|
426
|
-
>
|
|
427
|
-
Send blueprint
|
|
428
|
-
</button>
|
|
429
|
-
<button
|
|
430
|
-
className="hud-button hud-button-reject"
|
|
431
|
-
type="button"
|
|
432
|
-
onClick={() => act('stop')}
|
|
433
|
-
>
|
|
434
|
-
Stop
|
|
435
|
-
</button>
|
|
436
|
-
</div>
|
|
437
|
-
</>
|
|
438
|
-
) : intent.status === 'finished' ? (
|
|
691
|
+
) : handshakeSetup ? null : intent.status === 'finished' ? (
|
|
439
692
|
<p>All plan steps were applied.</p>
|
|
440
|
-
) : preparing ? (
|
|
441
|
-
<div className="hud-working">
|
|
442
|
-
<span className="hud-spinner" aria-hidden="true" />
|
|
443
|
-
<span>LLM preparing…</span>
|
|
444
|
-
<button
|
|
445
|
-
className="hud-button hud-button-reject"
|
|
446
|
-
type="button"
|
|
447
|
-
onClick={() => act('stop')}
|
|
448
|
-
>
|
|
449
|
-
Stop
|
|
450
|
-
</button>
|
|
451
|
-
</div>
|
|
452
|
-
) : working ? (
|
|
453
|
-
<div className="hud-working">
|
|
454
|
-
<span className="hud-spinner" aria-hidden="true" />
|
|
455
|
-
<span>
|
|
456
|
-
{intent.status === 'replanning'
|
|
457
|
-
? 'Updating the remaining plan from your instruction…'
|
|
458
|
-
: intent.stalledWait
|
|
459
|
-
? 'The LLM is still waiting on this step…'
|
|
460
|
-
: `Implementing ${stepLabel.toLowerCase()}…`}
|
|
461
|
-
</span>
|
|
462
|
-
<button
|
|
463
|
-
className="hud-button hud-button-reject"
|
|
464
|
-
type="button"
|
|
465
|
-
onClick={() => act('stop')}
|
|
466
|
-
>
|
|
467
|
-
Stop
|
|
468
|
-
</button>
|
|
469
|
-
</div>
|
|
470
|
-
) : (
|
|
693
|
+
) : showConnectedProgress || preparing ? null : (
|
|
471
694
|
<p>
|
|
472
695
|
{stepLabel}
|
|
473
696
|
{intent.reason ? ` · ${intent.reason}` : ''}
|
|
@@ -475,7 +698,7 @@ function SessionPanel({
|
|
|
475
698
|
)}
|
|
476
699
|
{!askingBlueprint && !sendingBlueprint && (
|
|
477
700
|
<>
|
|
478
|
-
{canPlace && (
|
|
701
|
+
{canPlace && !intent.working && (
|
|
479
702
|
<p>
|
|
480
703
|
<kbd>Space</kbd> places a file, <kbd>B</kbd> an island for
|
|
481
704
|
this session.
|
|
@@ -487,6 +710,11 @@ function SessionPanel({
|
|
|
487
710
|
const proposed = proposalStep === step.index
|
|
488
711
|
const processing = processingStep === step.index
|
|
489
712
|
const accepted = acceptedSteps.has(step.index)
|
|
713
|
+
const creating = processing && !proposed
|
|
714
|
+
const showStepAction =
|
|
715
|
+
creating ||
|
|
716
|
+
(canRunNext && invokeStep?.index === step.index) ||
|
|
717
|
+
(canAcceptProposal && proposed)
|
|
490
718
|
return (
|
|
491
719
|
<li
|
|
492
720
|
key={step.index}
|
|
@@ -496,11 +724,12 @@ function SessionPanel({
|
|
|
496
724
|
<span className="hud-step-index">{step.index}.</span>
|
|
497
725
|
<span className="hud-step-main">
|
|
498
726
|
<span className="hud-step-title">{step.title}</span>
|
|
499
|
-
{
|
|
500
|
-
(canAcceptProposal && proposed)) && (
|
|
727
|
+
{showStepAction && (
|
|
501
728
|
<button
|
|
502
729
|
className="hud-button hud-button-approve hud-run-step"
|
|
503
730
|
type="button"
|
|
731
|
+
disabled={creating}
|
|
732
|
+
aria-busy={creating}
|
|
504
733
|
onClick={() =>
|
|
505
734
|
proposed
|
|
506
735
|
? lastStep
|
|
@@ -513,7 +742,11 @@ function SessionPanel({
|
|
|
513
742
|
})
|
|
514
743
|
}
|
|
515
744
|
>
|
|
516
|
-
{proposed
|
|
745
|
+
{proposed
|
|
746
|
+
? 'Accept proposal'
|
|
747
|
+
: creating
|
|
748
|
+
? 'Creating proposal…'
|
|
749
|
+
: 'Create proposal'}
|
|
517
750
|
</button>
|
|
518
751
|
)}
|
|
519
752
|
</span>
|
|
@@ -677,6 +910,7 @@ function SessionPanel({
|
|
|
677
910
|
rows={3}
|
|
678
911
|
placeholder="Describe what should change in the next diff…"
|
|
679
912
|
onChange={(event) => setInstruction(event.target.value)}
|
|
913
|
+
onKeyDown={(event) => event.stopPropagation()}
|
|
680
914
|
/>
|
|
681
915
|
</label>
|
|
682
916
|
<div className="hud-decide">
|
|
@@ -708,6 +942,131 @@ function SessionPanel({
|
|
|
708
942
|
)
|
|
709
943
|
}
|
|
710
944
|
|
|
945
|
+
function BranchChangesPanel({
|
|
946
|
+
changes,
|
|
947
|
+
}: {
|
|
948
|
+
changes: BranchChanges
|
|
949
|
+
}) {
|
|
950
|
+
const [minimized, setMinimized] = useState(false)
|
|
951
|
+
const addedFunctions = changes.addedFunctions ?? []
|
|
952
|
+
const addedVariables = changes.addedVariables ?? []
|
|
953
|
+
const addedImports = changes.addedImports ?? []
|
|
954
|
+
const changedFunctions = changes.changedFunctions ?? []
|
|
955
|
+
const changedVariables = changes.changedVariables ?? []
|
|
956
|
+
const subtitle =
|
|
957
|
+
changes.branch && changes.base
|
|
958
|
+
? `${changes.branch} vs ${changes.base}`
|
|
959
|
+
: changes.branch
|
|
960
|
+
? changes.branch
|
|
961
|
+
: null
|
|
962
|
+
const hasContent =
|
|
963
|
+
changes.files.length > 0 ||
|
|
964
|
+
(changes.createFolders ?? []).length > 0 ||
|
|
965
|
+
changes.creates.length > 0 ||
|
|
966
|
+
changes.deletes.length > 0 ||
|
|
967
|
+
addedFunctions.length > 0 ||
|
|
968
|
+
addedVariables.length > 0 ||
|
|
969
|
+
addedImports.length > 0 ||
|
|
970
|
+
changedFunctions.length > 0 ||
|
|
971
|
+
changedVariables.length > 0 ||
|
|
972
|
+
(changes.imports ?? []).length > 0
|
|
973
|
+
|
|
974
|
+
return (
|
|
975
|
+
<aside
|
|
976
|
+
className="hud-panel hud-panel-planned hud-panel-done"
|
|
977
|
+
data-minimized={minimized}
|
|
978
|
+
>
|
|
979
|
+
<PanelChrome
|
|
980
|
+
title="Branch changes"
|
|
981
|
+
minimized={minimized}
|
|
982
|
+
onMinimize={() => setMinimized((current) => !current)}
|
|
983
|
+
/>
|
|
984
|
+
{!minimized && (
|
|
985
|
+
<>
|
|
986
|
+
{subtitle && <p className="hud-feature">{subtitle}</p>}
|
|
987
|
+
{!hasContent ? (
|
|
988
|
+
<p>No file changes on this branch.</p>
|
|
989
|
+
) : (
|
|
990
|
+
<MutationFold hasContent>
|
|
991
|
+
{changes.files.length > 0 && (
|
|
992
|
+
<>
|
|
993
|
+
<div className="hud-section-title hud-section-title-edit">
|
|
994
|
+
Changed
|
|
995
|
+
</div>
|
|
996
|
+
<ul>
|
|
997
|
+
{changes.files.map((id) => (
|
|
998
|
+
<li className="hud-file-edit" key={id}>
|
|
999
|
+
{id}
|
|
1000
|
+
</li>
|
|
1001
|
+
))}
|
|
1002
|
+
</ul>
|
|
1003
|
+
</>
|
|
1004
|
+
)}
|
|
1005
|
+
{(changes.createFolders ?? []).length > 0 && (
|
|
1006
|
+
<>
|
|
1007
|
+
<div className="hud-section-title hud-section-title-add">
|
|
1008
|
+
Added islands
|
|
1009
|
+
</div>
|
|
1010
|
+
<ul>
|
|
1011
|
+
{changes.createFolders.map((id) => (
|
|
1012
|
+
<li className="hud-file-add" key={id}>
|
|
1013
|
+
{id}/
|
|
1014
|
+
</li>
|
|
1015
|
+
))}
|
|
1016
|
+
</ul>
|
|
1017
|
+
</>
|
|
1018
|
+
)}
|
|
1019
|
+
{changes.creates.length > 0 && (
|
|
1020
|
+
<>
|
|
1021
|
+
<div className="hud-section-title hud-section-title-add">
|
|
1022
|
+
Added
|
|
1023
|
+
</div>
|
|
1024
|
+
<ul>
|
|
1025
|
+
{changes.creates.map((id) => (
|
|
1026
|
+
<li className="hud-file-add" key={id}>
|
|
1027
|
+
{id}
|
|
1028
|
+
</li>
|
|
1029
|
+
))}
|
|
1030
|
+
</ul>
|
|
1031
|
+
</>
|
|
1032
|
+
)}
|
|
1033
|
+
{changes.deletes.length > 0 && (
|
|
1034
|
+
<>
|
|
1035
|
+
<div className="hud-section-title hud-section-title-remove">
|
|
1036
|
+
Removed
|
|
1037
|
+
</div>
|
|
1038
|
+
<ul>
|
|
1039
|
+
{changes.deletes.map((id) => (
|
|
1040
|
+
<li className="hud-file-remove" key={id}>
|
|
1041
|
+
{id}
|
|
1042
|
+
</li>
|
|
1043
|
+
))}
|
|
1044
|
+
</ul>
|
|
1045
|
+
</>
|
|
1046
|
+
)}
|
|
1047
|
+
<PatchSymbolChanges
|
|
1048
|
+
title="Functions"
|
|
1049
|
+
added={addedFunctions}
|
|
1050
|
+
changed={changedFunctions}
|
|
1051
|
+
/>
|
|
1052
|
+
<PatchSymbolChanges
|
|
1053
|
+
title="Vars"
|
|
1054
|
+
added={addedVariables}
|
|
1055
|
+
changed={changedVariables}
|
|
1056
|
+
/>
|
|
1057
|
+
<PanelList
|
|
1058
|
+
title="Imports"
|
|
1059
|
+
items={importLabels(addedImports)}
|
|
1060
|
+
tone="add"
|
|
1061
|
+
/>
|
|
1062
|
+
</MutationFold>
|
|
1063
|
+
)}
|
|
1064
|
+
</>
|
|
1065
|
+
)}
|
|
1066
|
+
</aside>
|
|
1067
|
+
)
|
|
1068
|
+
}
|
|
1069
|
+
|
|
711
1070
|
type ExplorerInstruction = {
|
|
712
1071
|
id: string
|
|
713
1072
|
keys: string[]
|
|
@@ -751,6 +1110,8 @@ function explorerInstructions({
|
|
|
751
1110
|
importedBy,
|
|
752
1111
|
canStop,
|
|
753
1112
|
sessionCount,
|
|
1113
|
+
showBranchChanges,
|
|
1114
|
+
canShowBranchChanges,
|
|
754
1115
|
}: {
|
|
755
1116
|
canPlace: boolean
|
|
756
1117
|
hasChangeSet: boolean
|
|
@@ -761,6 +1122,8 @@ function explorerInstructions({
|
|
|
761
1122
|
importedBy: boolean
|
|
762
1123
|
canStop: boolean
|
|
763
1124
|
sessionCount: number
|
|
1125
|
+
showBranchChanges: boolean
|
|
1126
|
+
canShowBranchChanges: boolean
|
|
764
1127
|
}): ExplorerInstructionSection[] {
|
|
765
1128
|
const backspace: ExplorerInstruction[] =
|
|
766
1129
|
selectedUserCreated && canPlace
|
|
@@ -781,6 +1144,17 @@ function explorerInstructions({
|
|
|
781
1144
|
keys: ['K'],
|
|
782
1145
|
label: importedBy ? 'Show imports' : 'Show imported by',
|
|
783
1146
|
}
|
|
1147
|
+
const branch: ExplorerInstruction[] = canShowBranchChanges
|
|
1148
|
+
? [
|
|
1149
|
+
{
|
|
1150
|
+
id: 'branch-changes',
|
|
1151
|
+
keys: ['G'],
|
|
1152
|
+
label: showBranchChanges
|
|
1153
|
+
? 'Hide branch changes'
|
|
1154
|
+
: 'Show branch changes',
|
|
1155
|
+
},
|
|
1156
|
+
]
|
|
1157
|
+
: []
|
|
784
1158
|
const stop: ExplorerInstruction[] = canStop
|
|
785
1159
|
? [
|
|
786
1160
|
{
|
|
@@ -821,11 +1195,17 @@ function explorerInstructions({
|
|
|
821
1195
|
{ id: 'aim-line', keys: ['Click'], label: 'Aim a line to fly' },
|
|
822
1196
|
...info,
|
|
823
1197
|
imported,
|
|
1198
|
+
...branch,
|
|
824
1199
|
{
|
|
825
1200
|
id: 'update-model',
|
|
826
1201
|
keys: ['Update model'],
|
|
827
1202
|
label: 'Rescan and rebuild the map',
|
|
828
1203
|
},
|
|
1204
|
+
{
|
|
1205
|
+
id: 'setup-session',
|
|
1206
|
+
keys: ['Setup LLM session'],
|
|
1207
|
+
label: 'Open a session; /inbase connects the focused one',
|
|
1208
|
+
},
|
|
829
1209
|
{ id: 'toggle-map', keys: ['M'], label: 'Toggle map' },
|
|
830
1210
|
{
|
|
831
1211
|
id: 'release',
|
|
@@ -902,11 +1282,17 @@ function explorerInstructions({
|
|
|
902
1282
|
...info,
|
|
903
1283
|
thumbnail,
|
|
904
1284
|
imported,
|
|
1285
|
+
...branch,
|
|
905
1286
|
{
|
|
906
1287
|
id: 'update-model',
|
|
907
1288
|
keys: ['Update model'],
|
|
908
1289
|
label: 'Rescan and rebuild the map',
|
|
909
1290
|
},
|
|
1291
|
+
{
|
|
1292
|
+
id: 'setup-session',
|
|
1293
|
+
keys: ['Setup LLM session'],
|
|
1294
|
+
label: 'Open a session; /inbase connects the focused one',
|
|
1295
|
+
},
|
|
910
1296
|
{ id: 'map-walk', keys: ['M'], label: 'Back to walk' },
|
|
911
1297
|
...stop,
|
|
912
1298
|
],
|
|
@@ -930,6 +1316,7 @@ type HUDProps = {
|
|
|
930
1316
|
intents?: AgentIntent[]
|
|
931
1317
|
focusedSessionId?: string | null
|
|
932
1318
|
onFocusSession?: (sessionId: string) => void
|
|
1319
|
+
onSetupSession?: () => Promise<unknown>
|
|
933
1320
|
onWorkflowAction: (
|
|
934
1321
|
sessionId: string,
|
|
935
1322
|
action: WorkflowAction,
|
|
@@ -940,6 +1327,11 @@ type HUDProps = {
|
|
|
940
1327
|
onWalk: () => void
|
|
941
1328
|
followLook: boolean
|
|
942
1329
|
onToggleFollowLook: () => void
|
|
1330
|
+
showBranchChanges?: boolean
|
|
1331
|
+
branchChanges?: BranchChanges
|
|
1332
|
+
canShowBranchChanges?: boolean
|
|
1333
|
+
llmMakingChanges?: boolean
|
|
1334
|
+
onToggleShowBranchChanges?: () => void
|
|
943
1335
|
onUpdateModel: () => void
|
|
944
1336
|
updatingModel?: boolean
|
|
945
1337
|
importedBy: boolean
|
|
@@ -989,12 +1381,18 @@ export function HUD({
|
|
|
989
1381
|
intents,
|
|
990
1382
|
focusedSessionId = null,
|
|
991
1383
|
onFocusSession,
|
|
1384
|
+
onSetupSession,
|
|
992
1385
|
onWorkflowAction,
|
|
993
1386
|
onNavigateDiff,
|
|
994
1387
|
onOpenMap,
|
|
995
1388
|
onWalk,
|
|
996
1389
|
followLook,
|
|
997
1390
|
onToggleFollowLook,
|
|
1391
|
+
showBranchChanges = false,
|
|
1392
|
+
branchChanges,
|
|
1393
|
+
canShowBranchChanges = false,
|
|
1394
|
+
llmMakingChanges = false,
|
|
1395
|
+
onToggleShowBranchChanges,
|
|
998
1396
|
onUpdateModel,
|
|
999
1397
|
updatingModel = false,
|
|
1000
1398
|
importedBy,
|
|
@@ -1043,9 +1441,13 @@ export function HUD({
|
|
|
1043
1441
|
(item) => item.sessionId && isReviewingIntent(item.status),
|
|
1044
1442
|
)
|
|
1045
1443
|
const canStop = canStopSession(intent)
|
|
1444
|
+
const attachedSession =
|
|
1445
|
+
sessions.find((session) => session.awaitingAttach === false) ?? null
|
|
1046
1446
|
const [walkIntro, setWalkIntro] = useState(false)
|
|
1047
1447
|
const walkIntroSeen = useRef(false)
|
|
1048
1448
|
const [instructionsOpen, setInstructionsOpen] = useState(false)
|
|
1449
|
+
const [setupError, setSetupError] = useState<string | null>(null)
|
|
1450
|
+
const [setupBusy, setSetupBusy] = useState(false)
|
|
1049
1451
|
const [infoVisible, setInfoVisible] = useState(false)
|
|
1050
1452
|
const [infoMinimized, setInfoMinimized] = useState(false)
|
|
1051
1453
|
const [thumbnailVisible, setThumbnailVisible] = useState(true)
|
|
@@ -1053,12 +1455,13 @@ export function HUD({
|
|
|
1053
1455
|
const [thumbnailMaximized, setThumbnailMaximized] = useState(false)
|
|
1054
1456
|
const infoPanelRef = useRef<HTMLDivElement>(null)
|
|
1055
1457
|
const canPlace = Boolean(intent.creationMode)
|
|
1056
|
-
const
|
|
1057
|
-
const
|
|
1058
|
-
const
|
|
1059
|
-
const
|
|
1060
|
-
const
|
|
1061
|
-
const
|
|
1458
|
+
const overlay = showBranchChanges && branchChanges ? branchChanges : intent
|
|
1459
|
+
const previewing = intent.preview || showBranchChanges
|
|
1460
|
+
const addedFunctions = overlay.addedFunctions ?? []
|
|
1461
|
+
const addedVariables = overlay.addedVariables ?? []
|
|
1462
|
+
const addedImports = overlay.addedImports ?? []
|
|
1463
|
+
const changedFunctions = overlay.changedFunctions ?? []
|
|
1464
|
+
const changedVariables = overlay.changedVariables ?? []
|
|
1062
1465
|
const selectedAddedFunctions = selected
|
|
1063
1466
|
? addedFunctions.filter((item) => item.file === selected.id)
|
|
1064
1467
|
: []
|
|
@@ -1285,6 +1688,8 @@ export function HUD({
|
|
|
1285
1688
|
importedBy,
|
|
1286
1689
|
canStop,
|
|
1287
1690
|
sessionCount: sessions.length,
|
|
1691
|
+
showBranchChanges,
|
|
1692
|
+
canShowBranchChanges,
|
|
1288
1693
|
})
|
|
1289
1694
|
const currentInstructionView: InstructionView = mapping
|
|
1290
1695
|
? thumbnailMaximized
|
|
@@ -1368,7 +1773,7 @@ export function HUD({
|
|
|
1368
1773
|
{selected && <div className="hud-chip">{selected.path}</div>}
|
|
1369
1774
|
</div>
|
|
1370
1775
|
|
|
1371
|
-
{sessions.length > 0 && (
|
|
1776
|
+
{(sessions.length > 0 || showBranchChanges) && (
|
|
1372
1777
|
<div className="hud-left-stack">
|
|
1373
1778
|
{sessions.length > 1 && (
|
|
1374
1779
|
<div className="hud-session-tabs" role="tablist" aria-label="LLM sessions">
|
|
@@ -1383,6 +1788,7 @@ export function HUD({
|
|
|
1383
1788
|
aria-selected={active}
|
|
1384
1789
|
data-active={active}
|
|
1385
1790
|
key={session.sessionId}
|
|
1791
|
+
title={sessionTabLabel(session, sessions)}
|
|
1386
1792
|
onClick={() => {
|
|
1387
1793
|
if (session.sessionId) onFocusSession?.(session.sessionId)
|
|
1388
1794
|
}}
|
|
@@ -1393,16 +1799,22 @@ export function HUD({
|
|
|
1393
1799
|
})}
|
|
1394
1800
|
</div>
|
|
1395
1801
|
)}
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1802
|
+
{sessions.length > 0 && (
|
|
1803
|
+
<SessionPanel
|
|
1804
|
+
intent={intent}
|
|
1805
|
+
focused
|
|
1806
|
+
naming={naming}
|
|
1807
|
+
attachedSession={attachedSession}
|
|
1808
|
+
onFocus={() => {
|
|
1809
|
+
if (intent.sessionId) onFocusSession?.(intent.sessionId)
|
|
1810
|
+
}}
|
|
1811
|
+
onWorkflowAction={onWorkflowAction}
|
|
1812
|
+
onNavigateDiff={onNavigateDiff}
|
|
1813
|
+
/>
|
|
1814
|
+
)}
|
|
1815
|
+
{showBranchChanges && branchChanges && (
|
|
1816
|
+
<BranchChangesPanel changes={branchChanges} />
|
|
1817
|
+
)}
|
|
1406
1818
|
</div>
|
|
1407
1819
|
)}
|
|
1408
1820
|
|
|
@@ -1444,7 +1856,7 @@ export function HUD({
|
|
|
1444
1856
|
selectedAddedImports.length > 0) && (
|
|
1445
1857
|
<>
|
|
1446
1858
|
<div className="hud-section-title hud-section-title-edit">
|
|
1447
|
-
LLM changes
|
|
1859
|
+
{showBranchChanges ? 'Branch changes' : 'LLM changes'}
|
|
1448
1860
|
</div>
|
|
1449
1861
|
<PatchSymbolChanges
|
|
1450
1862
|
title="Functions"
|
|
@@ -1823,6 +2235,31 @@ export function HUD({
|
|
|
1823
2235
|
>
|
|
1824
2236
|
{updatingModel ? 'Updating…' : 'Update model'}
|
|
1825
2237
|
</button>
|
|
2238
|
+
{onSetupSession && (
|
|
2239
|
+
<button
|
|
2240
|
+
className="hud-button"
|
|
2241
|
+
type="button"
|
|
2242
|
+
aria-label="Start an LLM session without attaching a chat yet"
|
|
2243
|
+
title={setupError ?? 'Start an LLM session without attaching a chat yet'}
|
|
2244
|
+
disabled={setupBusy}
|
|
2245
|
+
onClick={() => {
|
|
2246
|
+
setInstructionsOpen(false)
|
|
2247
|
+
setSetupError(null)
|
|
2248
|
+
setSetupBusy(true)
|
|
2249
|
+
void onSetupSession()
|
|
2250
|
+
.catch((caught) => {
|
|
2251
|
+
setSetupError(
|
|
2252
|
+
caught instanceof Error
|
|
2253
|
+
? caught.message
|
|
2254
|
+
: 'Could not set up the session',
|
|
2255
|
+
)
|
|
2256
|
+
})
|
|
2257
|
+
.finally(() => setSetupBusy(false))
|
|
2258
|
+
}}
|
|
2259
|
+
>
|
|
2260
|
+
{setupBusy ? 'Starting…' : 'Setup LLM session'}
|
|
2261
|
+
</button>
|
|
2262
|
+
)}
|
|
1826
2263
|
</div>
|
|
1827
2264
|
<div className="hud-icon-row">
|
|
1828
2265
|
{mapping && hasChangeSet && onToggleChangePathsOnly && (
|
|
@@ -1936,6 +2373,53 @@ export function HUD({
|
|
|
1936
2373
|
{importedBy ? 'K show imports' : 'K show imported by'}
|
|
1937
2374
|
</span>
|
|
1938
2375
|
</button>
|
|
2376
|
+
<button
|
|
2377
|
+
className="hud-button hud-icon-button"
|
|
2378
|
+
data-active={showBranchChanges}
|
|
2379
|
+
aria-label={
|
|
2380
|
+
llmMakingChanges
|
|
2381
|
+
? 'Show branch changes unavailable while the LLM is making changes'
|
|
2382
|
+
: showBranchChanges
|
|
2383
|
+
? 'Hide branch changes'
|
|
2384
|
+
: 'Show branch changes'
|
|
2385
|
+
}
|
|
2386
|
+
aria-keyshortcuts="G"
|
|
2387
|
+
aria-pressed={showBranchChanges}
|
|
2388
|
+
aria-disabled={!canShowBranchChanges}
|
|
2389
|
+
type="button"
|
|
2390
|
+
onClick={() => {
|
|
2391
|
+
if (!canShowBranchChanges) return
|
|
2392
|
+
onToggleShowBranchChanges?.()
|
|
2393
|
+
}}
|
|
2394
|
+
>
|
|
2395
|
+
<svg
|
|
2396
|
+
viewBox="0 0 24 24"
|
|
2397
|
+
width="18"
|
|
2398
|
+
height="18"
|
|
2399
|
+
fill="none"
|
|
2400
|
+
stroke="currentColor"
|
|
2401
|
+
strokeWidth="2"
|
|
2402
|
+
strokeLinecap="round"
|
|
2403
|
+
strokeLinejoin="round"
|
|
2404
|
+
aria-hidden="true"
|
|
2405
|
+
>
|
|
2406
|
+
<circle cx="6" cy="5" r="2.4" />
|
|
2407
|
+
<circle cx="6" cy="19" r="2.4" />
|
|
2408
|
+
<circle cx="18" cy="12" r="2.4" />
|
|
2409
|
+
<path d="M6 7.4v9.2" />
|
|
2410
|
+
<path d="M6 12h7.2" />
|
|
2411
|
+
<path d="M13.2 12c2.2 0 2.2-4.6 4.4-4.6" />
|
|
2412
|
+
</svg>
|
|
2413
|
+
<span className="hud-tooltip">
|
|
2414
|
+
{llmMakingChanges
|
|
2415
|
+
? 'Unavailable while the LLM is making changes'
|
|
2416
|
+
: !canShowBranchChanges
|
|
2417
|
+
? 'No git branch to show'
|
|
2418
|
+
: showBranchChanges
|
|
2419
|
+
? 'G hide branch changes'
|
|
2420
|
+
: 'G show branch changes'}
|
|
2421
|
+
</span>
|
|
2422
|
+
</button>
|
|
1939
2423
|
<button
|
|
1940
2424
|
className="hud-button hud-icon-button"
|
|
1941
2425
|
data-active={followLook}
|