@jkwd/inbase 0.1.0
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 +76 -0
- package/apps/explorer/index.html +12 -0
- package/apps/explorer/package.json +28 -0
- package/apps/explorer/scripts/js-source.mjs +188 -0
- package/apps/explorer/scripts/patch-lib.d.ts +115 -0
- package/apps/explorer/scripts/patch-lib.mjs +472 -0
- package/apps/explorer/scripts/scan-target.mjs +188 -0
- package/apps/explorer/scripts/session-store.d.ts +156 -0
- package/apps/explorer/scripts/session-store.mjs +809 -0
- package/apps/explorer/scripts/target-config.d.ts +8 -0
- package/apps/explorer/scripts/target-config.mjs +42 -0
- package/apps/explorer/src/App.tsx +941 -0
- package/apps/explorer/src/agentIntent.ts +182 -0
- package/apps/explorer/src/codebase.ts +15 -0
- package/apps/explorer/src/index.css +632 -0
- package/apps/explorer/src/layout.ts +508 -0
- package/apps/explorer/src/main.tsx +16 -0
- package/apps/explorer/src/scene/BlockPlacer.tsx +87 -0
- package/apps/explorer/src/scene/Bridge.tsx +290 -0
- package/apps/explorer/src/scene/FileBlock.tsx +256 -0
- package/apps/explorer/src/scene/FolderArea.tsx +96 -0
- package/apps/explorer/src/scene/IslandPlacer.tsx +40 -0
- package/apps/explorer/src/scene/MapSelectBorder.tsx +57 -0
- package/apps/explorer/src/scene/MapView.tsx +247 -0
- package/apps/explorer/src/scene/Player.tsx +245 -0
- package/apps/explorer/src/scene/RelationLines.tsx +223 -0
- package/apps/explorer/src/scene/SelectionController.tsx +89 -0
- package/apps/explorer/src/scene/UserContextTracker.tsx +152 -0
- package/apps/explorer/src/scene/World.tsx +323 -0
- package/apps/explorer/src/theme.ts +111 -0
- package/apps/explorer/src/types.ts +245 -0
- package/apps/explorer/src/ui/CanvasErrorBoundary.tsx +38 -0
- package/apps/explorer/src/ui/HUD.tsx +1090 -0
- package/apps/explorer/src/ui/NameInput.tsx +45 -0
- package/apps/explorer/src/userContext.ts +73 -0
- package/apps/explorer/src/userCreated.ts +354 -0
- package/apps/explorer/src/vite-env.d.ts +1 -0
- package/apps/explorer/tsconfig.json +21 -0
- package/apps/explorer/vite.config.ts +295 -0
- package/bin/inbase.mjs +170 -0
- package/bin/project.mjs +94 -0
- package/bin/session.mjs +241 -0
- package/package.json +63 -0
- package/skill/inbase/SKILL.md +167 -0
|
@@ -0,0 +1,1090 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react'
|
|
2
|
+
import { NameInput } from './NameInput'
|
|
3
|
+
import {
|
|
4
|
+
isReviewingIntent,
|
|
5
|
+
type AgentIntent,
|
|
6
|
+
type AgentIntentStatus,
|
|
7
|
+
type AimedRelation,
|
|
8
|
+
type CodebaseGraph,
|
|
9
|
+
type PatchImportAddition,
|
|
10
|
+
type PatchSymbolAddition,
|
|
11
|
+
type ViewMode,
|
|
12
|
+
type WorkflowAction,
|
|
13
|
+
} from '../types'
|
|
14
|
+
|
|
15
|
+
function reviewTitle(status: AgentIntentStatus) {
|
|
16
|
+
if (status === 'blueprint_ask') return 'Setup blueprint'
|
|
17
|
+
if (status === 'blueprint') return 'Blueprint'
|
|
18
|
+
if (status === 'preparing') return 'LLM preparing'
|
|
19
|
+
if (status === 'planned') return 'Plan ready'
|
|
20
|
+
if (status === 'working') return 'LLM working'
|
|
21
|
+
if (status === 'replanning') return 'LLM revising plan'
|
|
22
|
+
if (status === 'pending') return 'Review this step'
|
|
23
|
+
if (status === 'extended') return 'Extended diff'
|
|
24
|
+
if (status === 'approved') return 'Completed step'
|
|
25
|
+
if (status === 'finished') return 'Finished'
|
|
26
|
+
return 'Visual workflow'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function fileBase(id: string) {
|
|
30
|
+
return id.split('/').pop() ?? id
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function symbolLabels(items: PatchSymbolAddition[]) {
|
|
34
|
+
const files = new Set(items.map((item) => item.file))
|
|
35
|
+
const showFile = files.size > 1
|
|
36
|
+
return items.map((item) => ({
|
|
37
|
+
key: `${item.file}:${item.name}`,
|
|
38
|
+
label: showFile ? `${item.name} · ${fileBase(item.file)}` : item.name,
|
|
39
|
+
}))
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function importLabels(items: PatchImportAddition[]) {
|
|
43
|
+
const files = new Set(items.map((item) => item.file))
|
|
44
|
+
const showFile = files.size > 1
|
|
45
|
+
return items.map((item) => {
|
|
46
|
+
const what =
|
|
47
|
+
item.name === item.from ? item.from : `${item.name} from ${item.from}`
|
|
48
|
+
return {
|
|
49
|
+
key: `${item.file}:${item.name}:${item.from}`,
|
|
50
|
+
label: showFile ? `${what} · ${fileBase(item.file)}` : what,
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function PanelList({
|
|
56
|
+
title,
|
|
57
|
+
items,
|
|
58
|
+
}: {
|
|
59
|
+
title: string
|
|
60
|
+
items: Array<{ key: string; label: string }>
|
|
61
|
+
}) {
|
|
62
|
+
if (items.length === 0) return null
|
|
63
|
+
return (
|
|
64
|
+
<>
|
|
65
|
+
<div className="hud-section-title">{title}</div>
|
|
66
|
+
<ul>
|
|
67
|
+
{items.map((item) => (
|
|
68
|
+
<li key={item.key}>{item.label}</li>
|
|
69
|
+
))}
|
|
70
|
+
</ul>
|
|
71
|
+
</>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function AddIntentRow({
|
|
76
|
+
placeholder,
|
|
77
|
+
onAdd,
|
|
78
|
+
}: {
|
|
79
|
+
placeholder: string
|
|
80
|
+
onAdd: (value: string) => boolean
|
|
81
|
+
}) {
|
|
82
|
+
const [value, setValue] = useState('')
|
|
83
|
+
return (
|
|
84
|
+
<form
|
|
85
|
+
className="hud-add-row"
|
|
86
|
+
onSubmit={(event) => {
|
|
87
|
+
event.preventDefault()
|
|
88
|
+
if (onAdd(value)) setValue('')
|
|
89
|
+
}}
|
|
90
|
+
>
|
|
91
|
+
<input
|
|
92
|
+
value={value}
|
|
93
|
+
onChange={(event) => setValue(event.target.value)}
|
|
94
|
+
placeholder={placeholder}
|
|
95
|
+
aria-label={placeholder}
|
|
96
|
+
autoComplete="off"
|
|
97
|
+
spellCheck={false}
|
|
98
|
+
onKeyDown={(event) => event.stopPropagation()}
|
|
99
|
+
/>
|
|
100
|
+
<button className="hud-button" type="submit">
|
|
101
|
+
Add
|
|
102
|
+
</button>
|
|
103
|
+
</form>
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
type HUDProps = {
|
|
108
|
+
graph: CodebaseGraph
|
|
109
|
+
mode: ViewMode
|
|
110
|
+
locked: boolean
|
|
111
|
+
selectedId: string | null
|
|
112
|
+
selectedFolder?: string | null
|
|
113
|
+
aimedRelation: AimedRelation | null
|
|
114
|
+
currentFolder: string
|
|
115
|
+
intent: AgentIntent
|
|
116
|
+
onWorkflowAction: (
|
|
117
|
+
action: WorkflowAction,
|
|
118
|
+
options?: { instruction?: string; step?: number },
|
|
119
|
+
) => void
|
|
120
|
+
onNavigateDiff: (diffId: string) => void
|
|
121
|
+
onOpenMap: () => void
|
|
122
|
+
onWalk: () => void
|
|
123
|
+
followLook: boolean
|
|
124
|
+
onToggleFollowLook: () => void
|
|
125
|
+
importedBy: boolean
|
|
126
|
+
onToggleImportedBy: () => void
|
|
127
|
+
naming?: boolean
|
|
128
|
+
namingIsland?: boolean
|
|
129
|
+
onCommitIslandName?: (name: string) => void
|
|
130
|
+
onCancelIslandName?: () => void
|
|
131
|
+
blueprintFunctions?: PatchSymbolAddition[]
|
|
132
|
+
blueprintVariables?: PatchSymbolAddition[]
|
|
133
|
+
blueprintImports?: PatchImportAddition[]
|
|
134
|
+
onAddBlueprintFunction?: (fileId: string, name: string) => boolean
|
|
135
|
+
onAddBlueprintVariable?: (fileId: string, name: string) => boolean
|
|
136
|
+
onAddBlueprintImport?: (fileId: string, raw: string) => boolean
|
|
137
|
+
onRemoveBlueprintFunction?: (fileId: string, name: string) => void
|
|
138
|
+
onRemoveBlueprintVariable?: (fileId: string, name: string) => void
|
|
139
|
+
onRemoveBlueprintImport?: (
|
|
140
|
+
fileId: string,
|
|
141
|
+
name: string,
|
|
142
|
+
from: string,
|
|
143
|
+
) => void
|
|
144
|
+
onMapAddFile?: (folderPath: string) => void
|
|
145
|
+
onMapAddFolder?: (folderPath: string) => void
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function HUD({
|
|
149
|
+
graph,
|
|
150
|
+
mode,
|
|
151
|
+
locked,
|
|
152
|
+
selectedId,
|
|
153
|
+
selectedFolder = null,
|
|
154
|
+
aimedRelation,
|
|
155
|
+
currentFolder,
|
|
156
|
+
intent,
|
|
157
|
+
onWorkflowAction,
|
|
158
|
+
onNavigateDiff,
|
|
159
|
+
onOpenMap,
|
|
160
|
+
onWalk,
|
|
161
|
+
followLook,
|
|
162
|
+
onToggleFollowLook,
|
|
163
|
+
importedBy,
|
|
164
|
+
onToggleImportedBy,
|
|
165
|
+
naming = false,
|
|
166
|
+
namingIsland = false,
|
|
167
|
+
onCommitIslandName,
|
|
168
|
+
onCancelIslandName,
|
|
169
|
+
blueprintFunctions = [],
|
|
170
|
+
blueprintVariables = [],
|
|
171
|
+
blueprintImports = [],
|
|
172
|
+
onAddBlueprintFunction,
|
|
173
|
+
onAddBlueprintVariable,
|
|
174
|
+
onAddBlueprintImport,
|
|
175
|
+
onRemoveBlueprintFunction,
|
|
176
|
+
onRemoveBlueprintVariable,
|
|
177
|
+
onRemoveBlueprintImport,
|
|
178
|
+
onMapAddFile,
|
|
179
|
+
onMapAddFolder,
|
|
180
|
+
}: HUDProps) {
|
|
181
|
+
const selected = graph.files.find((file) => file.id === selectedId)
|
|
182
|
+
const selectedFolderNode = graph.folders.find(
|
|
183
|
+
(folder) => folder.path === selectedFolder,
|
|
184
|
+
)
|
|
185
|
+
const folderFiles = selectedFolderNode
|
|
186
|
+
? graph.files.filter(
|
|
187
|
+
(file) =>
|
|
188
|
+
selectedFolderNode.files.includes(file.id) ||
|
|
189
|
+
file.folder === selectedFolderNode.path,
|
|
190
|
+
)
|
|
191
|
+
: []
|
|
192
|
+
const aimed = graph.files.find((file) => file.id === aimedRelation?.flyTo)
|
|
193
|
+
const importers = selected
|
|
194
|
+
? graph.files.filter((file) => file.imports.includes(selected.id))
|
|
195
|
+
: []
|
|
196
|
+
const mapping = mode === 'map'
|
|
197
|
+
const sessionMode = Boolean(intent.sessionId)
|
|
198
|
+
const [walkIntro, setWalkIntro] = useState(false)
|
|
199
|
+
const walkIntroSeen = useRef(false)
|
|
200
|
+
const [instruction, setInstruction] = useState('')
|
|
201
|
+
const [infoVisible, setInfoVisible] = useState(false)
|
|
202
|
+
const infoPanelRef = useRef<HTMLElement>(null)
|
|
203
|
+
const pending = intent.status === 'pending' && intent.isActiveDiff
|
|
204
|
+
const askingBlueprint = intent.status === 'blueprint_ask'
|
|
205
|
+
const creatingBlueprint = intent.creationMode || intent.status === 'blueprint'
|
|
206
|
+
const preparing = intent.status === 'preparing'
|
|
207
|
+
const planReady = intent.status === 'planned'
|
|
208
|
+
const working = intent.status === 'working' || intent.status === 'replanning'
|
|
209
|
+
const previewing = intent.preview
|
|
210
|
+
const reviewing = isReviewingIntent(intent.status) || intent.chain.length > 0
|
|
211
|
+
const chainIndex = intent.chainIndex ?? 0
|
|
212
|
+
const previousDiff = intent.chain[chainIndex - 1]
|
|
213
|
+
const nextDiff = intent.chain[chainIndex + 1]
|
|
214
|
+
const stepLabel =
|
|
215
|
+
intent.step && intent.steps?.length > 0
|
|
216
|
+
? `Step ${intent.step} of ${intent.steps.length}`
|
|
217
|
+
: 'Patch'
|
|
218
|
+
const lastStep =
|
|
219
|
+
typeof intent.step === 'number' &&
|
|
220
|
+
intent.steps.length > 0 &&
|
|
221
|
+
intent.step >= intent.steps.length
|
|
222
|
+
const addedFunctions = intent.addedFunctions ?? []
|
|
223
|
+
const addedVariables = intent.addedVariables ?? []
|
|
224
|
+
const addedImports = intent.addedImports ?? []
|
|
225
|
+
const selectedAddedFunctions = selected
|
|
226
|
+
? addedFunctions.filter((item) => item.file === selected.id)
|
|
227
|
+
: []
|
|
228
|
+
const selectedAddedVariables = selected
|
|
229
|
+
? addedVariables.filter((item) => item.file === selected.id)
|
|
230
|
+
: []
|
|
231
|
+
const selectedAddedImports = selected
|
|
232
|
+
? addedImports.filter((item) => item.file === selected.id)
|
|
233
|
+
: []
|
|
234
|
+
const selectedClasses = selected
|
|
235
|
+
? selected.symbols.filter((symbol) => symbol.kind === 'class')
|
|
236
|
+
: []
|
|
237
|
+
const selectedFunctions = selected
|
|
238
|
+
? selected.symbols.filter((symbol) => symbol.kind === 'function')
|
|
239
|
+
: []
|
|
240
|
+
const selectedVariables = selected
|
|
241
|
+
? selected.symbols.filter((symbol) => symbol.kind === 'variable')
|
|
242
|
+
: []
|
|
243
|
+
const selectedBlueprintFunctions = selected
|
|
244
|
+
? blueprintFunctions.filter((item) => item.file === selected.id)
|
|
245
|
+
: []
|
|
246
|
+
const selectedBlueprintVariables = selected
|
|
247
|
+
? blueprintVariables.filter((item) => item.file === selected.id)
|
|
248
|
+
: []
|
|
249
|
+
const selectedBlueprintImports = selected
|
|
250
|
+
? blueprintImports.filter((item) => item.file === selected.id)
|
|
251
|
+
: []
|
|
252
|
+
const intendedImportFrom = new Set(
|
|
253
|
+
selectedBlueprintImports.map((item) => item.from),
|
|
254
|
+
)
|
|
255
|
+
const extraBlueprintImports = selectedBlueprintImports.filter(
|
|
256
|
+
(item) => !selected?.imports.includes(item.from),
|
|
257
|
+
)
|
|
258
|
+
const canEditBlueprint =
|
|
259
|
+
creatingBlueprint && Boolean(selected) && !selected?.id.startsWith('draft:')
|
|
260
|
+
const doneSteps = new Set(
|
|
261
|
+
intent.status === 'finished'
|
|
262
|
+
? intent.steps.map((step) => step.index)
|
|
263
|
+
: intent.chain
|
|
264
|
+
.filter(
|
|
265
|
+
(entry) =>
|
|
266
|
+
entry.status === 'applied' || entry.status === 'extended',
|
|
267
|
+
)
|
|
268
|
+
.map((entry) => entry.step),
|
|
269
|
+
)
|
|
270
|
+
if (intent.status === 'approved' && typeof intent.step === 'number') {
|
|
271
|
+
doneSteps.add(intent.step)
|
|
272
|
+
}
|
|
273
|
+
if (pending && typeof intent.step === 'number' && !lastStep) {
|
|
274
|
+
doneSteps.add(intent.step)
|
|
275
|
+
}
|
|
276
|
+
const nextStep =
|
|
277
|
+
intent.status === 'finished'
|
|
278
|
+
? null
|
|
279
|
+
: (intent.steps.find((step) => !doneSteps.has(step.index)) ?? null)
|
|
280
|
+
const canRunNext =
|
|
281
|
+
Boolean(nextStep) && (planReady || pending) && !working
|
|
282
|
+
const canComplete = pending && lastStep
|
|
283
|
+
const panelDone =
|
|
284
|
+
intent.status === 'finished' ||
|
|
285
|
+
intent.status === 'approved' ||
|
|
286
|
+
doneSteps.size > 0
|
|
287
|
+
|
|
288
|
+
useEffect(() => {
|
|
289
|
+
if (mode !== 'walk') {
|
|
290
|
+
walkIntroSeen.current = false
|
|
291
|
+
setWalkIntro(false)
|
|
292
|
+
return
|
|
293
|
+
}
|
|
294
|
+
if (locked || naming) {
|
|
295
|
+
walkIntroSeen.current = true
|
|
296
|
+
setWalkIntro(false)
|
|
297
|
+
return
|
|
298
|
+
}
|
|
299
|
+
if (walkIntroSeen.current) {
|
|
300
|
+
setWalkIntro(false)
|
|
301
|
+
return
|
|
302
|
+
}
|
|
303
|
+
const timer = window.setTimeout(() => setWalkIntro(true), 160)
|
|
304
|
+
return () => window.clearTimeout(timer)
|
|
305
|
+
}, [locked, mode, naming])
|
|
306
|
+
|
|
307
|
+
useEffect(() => {
|
|
308
|
+
setInstruction('')
|
|
309
|
+
}, [intent.diffId])
|
|
310
|
+
|
|
311
|
+
useEffect(() => {
|
|
312
|
+
infoPanelRef.current?.scrollTo({ top: 0 })
|
|
313
|
+
}, [selectedId, selectedFolder])
|
|
314
|
+
|
|
315
|
+
useEffect(() => {
|
|
316
|
+
if (sessionMode && selectedId) setInfoVisible(true)
|
|
317
|
+
}, [selectedId, sessionMode])
|
|
318
|
+
|
|
319
|
+
useEffect(() => {
|
|
320
|
+
if (selectedFolder) setInfoVisible(true)
|
|
321
|
+
}, [selectedFolder])
|
|
322
|
+
|
|
323
|
+
useEffect(() => {
|
|
324
|
+
const onKey = (event: KeyboardEvent) => {
|
|
325
|
+
if (event.repeat || event.code !== 'KeyI') return
|
|
326
|
+
const target = event.target
|
|
327
|
+
if (
|
|
328
|
+
target instanceof HTMLElement &&
|
|
329
|
+
(target.tagName === 'TEXTAREA' ||
|
|
330
|
+
target.tagName === 'INPUT' ||
|
|
331
|
+
target.tagName === 'SELECT' ||
|
|
332
|
+
target.isContentEditable)
|
|
333
|
+
) {
|
|
334
|
+
return
|
|
335
|
+
}
|
|
336
|
+
event.preventDefault()
|
|
337
|
+
setInfoVisible((visible) => !visible)
|
|
338
|
+
}
|
|
339
|
+
window.addEventListener('keydown', onKey)
|
|
340
|
+
return () => window.removeEventListener('keydown', onKey)
|
|
341
|
+
}, [])
|
|
342
|
+
|
|
343
|
+
useEffect(() => {
|
|
344
|
+
if (!infoVisible || (!selectedId && !selectedFolder)) return
|
|
345
|
+
const onKey = (event: KeyboardEvent) => {
|
|
346
|
+
if (event.code !== 'ArrowUp' && event.code !== 'ArrowDown') return
|
|
347
|
+
const target = event.target
|
|
348
|
+
if (
|
|
349
|
+
target instanceof HTMLElement &&
|
|
350
|
+
(target.tagName === 'TEXTAREA' ||
|
|
351
|
+
target.tagName === 'INPUT' ||
|
|
352
|
+
target.tagName === 'SELECT' ||
|
|
353
|
+
target.isContentEditable)
|
|
354
|
+
) {
|
|
355
|
+
return
|
|
356
|
+
}
|
|
357
|
+
const panel = infoPanelRef.current
|
|
358
|
+
if (!panel || panel.scrollHeight <= panel.clientHeight + 1) return
|
|
359
|
+
event.preventDefault()
|
|
360
|
+
event.stopImmediatePropagation()
|
|
361
|
+
const step = Math.max(40, Math.round(panel.clientHeight * 0.2))
|
|
362
|
+
panel.scrollBy({ top: event.code === 'ArrowDown' ? step : -step })
|
|
363
|
+
}
|
|
364
|
+
window.addEventListener('keydown', onKey, true)
|
|
365
|
+
return () => window.removeEventListener('keydown', onKey, true)
|
|
366
|
+
}, [infoVisible, selectedId, selectedFolder])
|
|
367
|
+
|
|
368
|
+
return (
|
|
369
|
+
<div className="hud">
|
|
370
|
+
{mode === 'walk' && !locked && walkIntro && !naming && (
|
|
371
|
+
<div className="hud-gate">
|
|
372
|
+
<div className="hud-gate-card">
|
|
373
|
+
<h1>Walk</h1>
|
|
374
|
+
<p>
|
|
375
|
+
Click to look around. Press <kbd>M</kbd> to open the map, press{' '}
|
|
376
|
+
<kbd>M</kbd> again to return here.
|
|
377
|
+
</p>
|
|
378
|
+
<p>
|
|
379
|
+
<kbd>W</kbd> <kbd>A</kbd> <kbd>S</kbd> <kbd>D</kbd> walk,{' '}
|
|
380
|
+
<kbd>Shift</kbd> sprint
|
|
381
|
+
{creatingBlueprint ? (
|
|
382
|
+
<>
|
|
383
|
+
, <kbd>Space</kbd> place a file, <kbd>B</kbd> place an island
|
|
384
|
+
</>
|
|
385
|
+
) : null}
|
|
386
|
+
, click a block for imports.
|
|
387
|
+
</p>
|
|
388
|
+
</div>
|
|
389
|
+
</div>
|
|
390
|
+
)}
|
|
391
|
+
|
|
392
|
+
{namingIsland && onCommitIslandName && onCancelIslandName && (
|
|
393
|
+
<div className="hud-name-gate">
|
|
394
|
+
<NameInput
|
|
395
|
+
placeholder="Folder name"
|
|
396
|
+
onCommit={onCommitIslandName}
|
|
397
|
+
onCancel={onCancelIslandName}
|
|
398
|
+
/>
|
|
399
|
+
</div>
|
|
400
|
+
)}
|
|
401
|
+
|
|
402
|
+
{mode === 'walk' && locked && (
|
|
403
|
+
<div className="crosshair" data-aim={Boolean(aimed)} />
|
|
404
|
+
)}
|
|
405
|
+
{mode === 'walk' && locked && aimed && (
|
|
406
|
+
<div className="hud-aim">Click to fly to {aimed.name}</div>
|
|
407
|
+
)}
|
|
408
|
+
|
|
409
|
+
<div className="hud-top">
|
|
410
|
+
<div className="hud-chip">
|
|
411
|
+
{mapping ? `Map of ${graph.targetName}` : `You are in ${currentFolder}`}
|
|
412
|
+
</div>
|
|
413
|
+
<div className="hud-mode">
|
|
414
|
+
<button
|
|
415
|
+
className="hud-button"
|
|
416
|
+
data-active={mapping}
|
|
417
|
+
type="button"
|
|
418
|
+
onClick={onOpenMap}
|
|
419
|
+
>
|
|
420
|
+
Map
|
|
421
|
+
</button>
|
|
422
|
+
<button
|
|
423
|
+
className="hud-button"
|
|
424
|
+
data-active={!mapping}
|
|
425
|
+
type="button"
|
|
426
|
+
onClick={onWalk}
|
|
427
|
+
>
|
|
428
|
+
Walk
|
|
429
|
+
</button>
|
|
430
|
+
</div>
|
|
431
|
+
{selected && <div className="hud-chip">{selected.path}</div>}
|
|
432
|
+
</div>
|
|
433
|
+
|
|
434
|
+
{reviewing && (
|
|
435
|
+
<aside
|
|
436
|
+
className={
|
|
437
|
+
panelDone
|
|
438
|
+
? 'hud-panel hud-panel-planned hud-panel-done'
|
|
439
|
+
: 'hud-panel hud-panel-planned'
|
|
440
|
+
}
|
|
441
|
+
>
|
|
442
|
+
<div className="hud-section-title">
|
|
443
|
+
{reviewTitle(intent.status)}
|
|
444
|
+
</div>
|
|
445
|
+
{intent.feature && <p className="hud-feature">{intent.feature}</p>}
|
|
446
|
+
{askingBlueprint ? (
|
|
447
|
+
<>
|
|
448
|
+
<p>
|
|
449
|
+
{intent.canEnterBlueprint
|
|
450
|
+
? 'Place files and islands for this chat, then send them as a blueprint for the LLM?'
|
|
451
|
+
: `Blueprint edit mode is active in another chat (${intent.blueprintSessionId}). Finish or stop it before starting here.`}
|
|
452
|
+
</p>
|
|
453
|
+
<div className="hud-decide">
|
|
454
|
+
<button
|
|
455
|
+
className="hud-button hud-button-approve"
|
|
456
|
+
type="button"
|
|
457
|
+
disabled={!intent.canEnterBlueprint}
|
|
458
|
+
onClick={() => onWorkflowAction('blueprint_yes')}
|
|
459
|
+
>
|
|
460
|
+
Yes
|
|
461
|
+
</button>
|
|
462
|
+
<button
|
|
463
|
+
className="hud-button"
|
|
464
|
+
type="button"
|
|
465
|
+
onClick={() => onWorkflowAction('blueprint_no')}
|
|
466
|
+
>
|
|
467
|
+
No
|
|
468
|
+
</button>
|
|
469
|
+
<button
|
|
470
|
+
className="hud-button hud-button-reject"
|
|
471
|
+
type="button"
|
|
472
|
+
onClick={() => onWorkflowAction('stop')}
|
|
473
|
+
>
|
|
474
|
+
Stop
|
|
475
|
+
</button>
|
|
476
|
+
</div>
|
|
477
|
+
</>
|
|
478
|
+
) : creatingBlueprint ? (
|
|
479
|
+
<>
|
|
480
|
+
<p>
|
|
481
|
+
Walk the map, press <kbd>Space</kbd> for a file and{' '}
|
|
482
|
+
<kbd>B</kbd> for an island. Send the blueprint when the layout
|
|
483
|
+
is ready.
|
|
484
|
+
</p>
|
|
485
|
+
<div className="hud-decide">
|
|
486
|
+
<button
|
|
487
|
+
className="hud-button hud-button-approve"
|
|
488
|
+
type="button"
|
|
489
|
+
disabled={naming}
|
|
490
|
+
onClick={() => onWorkflowAction('blueprint_send')}
|
|
491
|
+
>
|
|
492
|
+
Send blueprint
|
|
493
|
+
</button>
|
|
494
|
+
<button
|
|
495
|
+
className="hud-button hud-button-reject"
|
|
496
|
+
type="button"
|
|
497
|
+
onClick={() => onWorkflowAction('stop')}
|
|
498
|
+
>
|
|
499
|
+
Stop
|
|
500
|
+
</button>
|
|
501
|
+
</div>
|
|
502
|
+
</>
|
|
503
|
+
) : intent.status === 'finished' ? (
|
|
504
|
+
<p>All plan steps were applied.</p>
|
|
505
|
+
) : preparing ? (
|
|
506
|
+
<div className="hud-working">
|
|
507
|
+
<span className="hud-spinner" aria-hidden="true" />
|
|
508
|
+
<span>LLM preparing…</span>
|
|
509
|
+
</div>
|
|
510
|
+
) : working ? (
|
|
511
|
+
<div className="hud-working">
|
|
512
|
+
<span className="hud-spinner" aria-hidden="true" />
|
|
513
|
+
<span>
|
|
514
|
+
{intent.status === 'replanning'
|
|
515
|
+
? 'Updating the remaining plan from your instruction…'
|
|
516
|
+
: `Implementing ${stepLabel.toLowerCase()}…`}
|
|
517
|
+
</span>
|
|
518
|
+
</div>
|
|
519
|
+
) : (
|
|
520
|
+
<p>
|
|
521
|
+
{stepLabel}
|
|
522
|
+
{intent.reason ? ` · ${intent.reason}` : ''}
|
|
523
|
+
</p>
|
|
524
|
+
)}
|
|
525
|
+
{!askingBlueprint && !creatingBlueprint && (
|
|
526
|
+
<>
|
|
527
|
+
{intent.steps?.length > 0 && (
|
|
528
|
+
<ol className="hud-steps">
|
|
529
|
+
{intent.steps.map((step) => (
|
|
530
|
+
<li
|
|
531
|
+
key={step.index}
|
|
532
|
+
data-current={
|
|
533
|
+
intent.status !== 'finished' &&
|
|
534
|
+
step.index === intent.step &&
|
|
535
|
+
!doneSteps.has(step.index)
|
|
536
|
+
}
|
|
537
|
+
data-done={doneSteps.has(step.index)}
|
|
538
|
+
data-next={
|
|
539
|
+
nextStep?.index === step.index && !doneSteps.has(step.index)
|
|
540
|
+
}
|
|
541
|
+
>
|
|
542
|
+
<span className="hud-step-main">
|
|
543
|
+
<span className="hud-step-title">{step.title}</span>
|
|
544
|
+
{((canRunNext && nextStep?.index === step.index) ||
|
|
545
|
+
(canComplete && step.index === intent.step)) && (
|
|
546
|
+
<button
|
|
547
|
+
className="hud-button hud-button-approve hud-run-step"
|
|
548
|
+
type="button"
|
|
549
|
+
onClick={() =>
|
|
550
|
+
canComplete && step.index === intent.step
|
|
551
|
+
? onWorkflowAction('continue')
|
|
552
|
+
: onWorkflowAction('invoke', {
|
|
553
|
+
step: step.index,
|
|
554
|
+
})
|
|
555
|
+
}
|
|
556
|
+
>
|
|
557
|
+
{canComplete && step.index === intent.step
|
|
558
|
+
? 'Complete'
|
|
559
|
+
: 'Run step'}
|
|
560
|
+
</button>
|
|
561
|
+
)}
|
|
562
|
+
</span>
|
|
563
|
+
</li>
|
|
564
|
+
))}
|
|
565
|
+
</ol>
|
|
566
|
+
)}
|
|
567
|
+
{intent.chain.length > 0 && (
|
|
568
|
+
<div className="hud-chain">
|
|
569
|
+
<button
|
|
570
|
+
className="hud-button"
|
|
571
|
+
type="button"
|
|
572
|
+
disabled={!previousDiff}
|
|
573
|
+
onClick={() => previousDiff && onNavigateDiff(previousDiff.id)}
|
|
574
|
+
>
|
|
575
|
+
Previous
|
|
576
|
+
</button>
|
|
577
|
+
<span>
|
|
578
|
+
Diff {chainIndex + 1} of {intent.chain.length}
|
|
579
|
+
</span>
|
|
580
|
+
<button
|
|
581
|
+
className="hud-button"
|
|
582
|
+
type="button"
|
|
583
|
+
disabled={!nextDiff}
|
|
584
|
+
onClick={() => nextDiff && onNavigateDiff(nextDiff.id)}
|
|
585
|
+
>
|
|
586
|
+
Next
|
|
587
|
+
</button>
|
|
588
|
+
</div>
|
|
589
|
+
)}
|
|
590
|
+
{previewing && intent.files.length > 0 && (
|
|
591
|
+
<>
|
|
592
|
+
<div className="hud-section-title hud-section-title-edit">Changed</div>
|
|
593
|
+
<ul>
|
|
594
|
+
{intent.files.map((id) => (
|
|
595
|
+
<li className="hud-file-edit" key={id}>
|
|
596
|
+
{id}
|
|
597
|
+
</li>
|
|
598
|
+
))}
|
|
599
|
+
</ul>
|
|
600
|
+
</>
|
|
601
|
+
)}
|
|
602
|
+
{previewing && (intent.createFolders ?? []).length > 0 && (
|
|
603
|
+
<>
|
|
604
|
+
<div className="hud-section-title hud-section-title-add">Added islands</div>
|
|
605
|
+
<ul>
|
|
606
|
+
{intent.createFolders.map((id) => (
|
|
607
|
+
<li className="hud-file-add" key={id}>
|
|
608
|
+
{id}/
|
|
609
|
+
</li>
|
|
610
|
+
))}
|
|
611
|
+
</ul>
|
|
612
|
+
</>
|
|
613
|
+
)}
|
|
614
|
+
{previewing && intent.creates.length > 0 && (
|
|
615
|
+
<>
|
|
616
|
+
<div className="hud-section-title hud-section-title-add">Added</div>
|
|
617
|
+
<ul>
|
|
618
|
+
{intent.creates.map((id) => (
|
|
619
|
+
<li className="hud-file-add" key={id}>
|
|
620
|
+
{id}
|
|
621
|
+
</li>
|
|
622
|
+
))}
|
|
623
|
+
</ul>
|
|
624
|
+
</>
|
|
625
|
+
)}
|
|
626
|
+
{previewing && intent.deletes.length > 0 && (
|
|
627
|
+
<>
|
|
628
|
+
<div className="hud-section-title hud-section-title-remove">Removed</div>
|
|
629
|
+
<ul>
|
|
630
|
+
{intent.deletes.map((id) => (
|
|
631
|
+
<li className="hud-file-remove" key={id}>
|
|
632
|
+
{id}
|
|
633
|
+
</li>
|
|
634
|
+
))}
|
|
635
|
+
</ul>
|
|
636
|
+
</>
|
|
637
|
+
)}
|
|
638
|
+
{previewing && (
|
|
639
|
+
<>
|
|
640
|
+
<PanelList
|
|
641
|
+
title="Functions"
|
|
642
|
+
items={symbolLabels(addedFunctions)}
|
|
643
|
+
/>
|
|
644
|
+
<PanelList
|
|
645
|
+
title="Variables"
|
|
646
|
+
items={symbolLabels(addedVariables)}
|
|
647
|
+
/>
|
|
648
|
+
</>
|
|
649
|
+
)}
|
|
650
|
+
{previewing && addedImports.length > 0 && (
|
|
651
|
+
<PanelList title="Imports" items={importLabels(addedImports)} />
|
|
652
|
+
)}
|
|
653
|
+
{previewing && addedImports.length === 0 && (intent.imports ?? []).length > 0 && (
|
|
654
|
+
<>
|
|
655
|
+
<div className="hud-section-title">Imports</div>
|
|
656
|
+
<ul>
|
|
657
|
+
{intent.imports.map((edge) => (
|
|
658
|
+
<li key={`${edge.from}->${edge.to}`}>
|
|
659
|
+
{edge.from.split('/').pop()} → {edge.to.split('/').pop()}
|
|
660
|
+
</li>
|
|
661
|
+
))}
|
|
662
|
+
</ul>
|
|
663
|
+
</>
|
|
664
|
+
)}
|
|
665
|
+
{(planReady || preparing) && (
|
|
666
|
+
<div className="hud-decide">
|
|
667
|
+
<button
|
|
668
|
+
className="hud-button hud-button-reject"
|
|
669
|
+
type="button"
|
|
670
|
+
onClick={() => onWorkflowAction('stop')}
|
|
671
|
+
>
|
|
672
|
+
Stop
|
|
673
|
+
</button>
|
|
674
|
+
</div>
|
|
675
|
+
)}
|
|
676
|
+
{pending && (
|
|
677
|
+
<>
|
|
678
|
+
<label className="hud-instruction">
|
|
679
|
+
<span>Alternative instruction for the LLM</span>
|
|
680
|
+
<textarea
|
|
681
|
+
value={instruction}
|
|
682
|
+
maxLength={4000}
|
|
683
|
+
rows={3}
|
|
684
|
+
placeholder="Describe what should change in the next diff…"
|
|
685
|
+
onChange={(event) => setInstruction(event.target.value)}
|
|
686
|
+
/>
|
|
687
|
+
</label>
|
|
688
|
+
<div className="hud-decide">
|
|
689
|
+
{lastStep && (
|
|
690
|
+
<button
|
|
691
|
+
className="hud-button hud-button-approve"
|
|
692
|
+
type="button"
|
|
693
|
+
onClick={() => onWorkflowAction('continue')}
|
|
694
|
+
>
|
|
695
|
+
Complete
|
|
696
|
+
</button>
|
|
697
|
+
)}
|
|
698
|
+
<button
|
|
699
|
+
className="hud-button hud-button-extend"
|
|
700
|
+
type="button"
|
|
701
|
+
disabled={!instruction.trim()}
|
|
702
|
+
onClick={() =>
|
|
703
|
+
onWorkflowAction('instruct', { instruction })
|
|
704
|
+
}
|
|
705
|
+
>
|
|
706
|
+
Send instruction
|
|
707
|
+
</button>
|
|
708
|
+
<button
|
|
709
|
+
className="hud-button hud-button-reject"
|
|
710
|
+
type="button"
|
|
711
|
+
onClick={() => onWorkflowAction('stop')}
|
|
712
|
+
>
|
|
713
|
+
Stop
|
|
714
|
+
</button>
|
|
715
|
+
</div>
|
|
716
|
+
</>
|
|
717
|
+
)}
|
|
718
|
+
</>
|
|
719
|
+
)}
|
|
720
|
+
</aside>
|
|
721
|
+
)}
|
|
722
|
+
|
|
723
|
+
{selected && infoVisible && (
|
|
724
|
+
<aside ref={infoPanelRef} className="hud-panel hud-panel-info">
|
|
725
|
+
<h2>{selected.name}</h2>
|
|
726
|
+
<p className="path">{selected.path}</p>
|
|
727
|
+
<p>
|
|
728
|
+
{selected.lines} lines · {selected.language}
|
|
729
|
+
</p>
|
|
730
|
+
{selectedClasses.length > 0 && (
|
|
731
|
+
<>
|
|
732
|
+
<div className="hud-section-title">Classes</div>
|
|
733
|
+
<ul>
|
|
734
|
+
{selectedClasses.map((symbol) => (
|
|
735
|
+
<li key={`class-${symbol.name}`}>
|
|
736
|
+
<span className={symbol.intended ? 'hud-intended' : undefined}>
|
|
737
|
+
{symbol.name}
|
|
738
|
+
</span>
|
|
739
|
+
</li>
|
|
740
|
+
))}
|
|
741
|
+
</ul>
|
|
742
|
+
</>
|
|
743
|
+
)}
|
|
744
|
+
<div className="hud-section-title">Functions</div>
|
|
745
|
+
{selectedFunctions.length === 0 &&
|
|
746
|
+
selectedBlueprintFunctions.length === 0 ? (
|
|
747
|
+
<p>No functions</p>
|
|
748
|
+
) : (
|
|
749
|
+
<ul>
|
|
750
|
+
{selectedFunctions.map((symbol) => (
|
|
751
|
+
<li key={`fn-${symbol.name}`}>
|
|
752
|
+
<span className={symbol.intended ? 'hud-intended' : undefined}>
|
|
753
|
+
{symbol.name}
|
|
754
|
+
</span>
|
|
755
|
+
{canEditBlueprint && symbol.intended && (
|
|
756
|
+
<button
|
|
757
|
+
className="hud-item-remove"
|
|
758
|
+
type="button"
|
|
759
|
+
aria-label={`Remove ${symbol.name}`}
|
|
760
|
+
onClick={() =>
|
|
761
|
+
onRemoveBlueprintFunction?.(selected.id, symbol.name)
|
|
762
|
+
}
|
|
763
|
+
>
|
|
764
|
+
×
|
|
765
|
+
</button>
|
|
766
|
+
)}
|
|
767
|
+
</li>
|
|
768
|
+
))}
|
|
769
|
+
</ul>
|
|
770
|
+
)}
|
|
771
|
+
{canEditBlueprint && onAddBlueprintFunction && (
|
|
772
|
+
<AddIntentRow
|
|
773
|
+
placeholder="Function name"
|
|
774
|
+
onAdd={(name) => onAddBlueprintFunction(selected.id, name)}
|
|
775
|
+
/>
|
|
776
|
+
)}
|
|
777
|
+
<div className="hud-section-title">Vars</div>
|
|
778
|
+
{selectedVariables.length === 0 &&
|
|
779
|
+
selectedBlueprintVariables.length === 0 ? (
|
|
780
|
+
<p>No vars</p>
|
|
781
|
+
) : (
|
|
782
|
+
<ul>
|
|
783
|
+
{selectedVariables.map((symbol) => (
|
|
784
|
+
<li key={`var-${symbol.name}`}>
|
|
785
|
+
<span className={symbol.intended ? 'hud-intended' : undefined}>
|
|
786
|
+
{symbol.name}
|
|
787
|
+
</span>
|
|
788
|
+
{canEditBlueprint && symbol.intended && (
|
|
789
|
+
<button
|
|
790
|
+
className="hud-item-remove"
|
|
791
|
+
type="button"
|
|
792
|
+
aria-label={`Remove ${symbol.name}`}
|
|
793
|
+
onClick={() =>
|
|
794
|
+
onRemoveBlueprintVariable?.(selected.id, symbol.name)
|
|
795
|
+
}
|
|
796
|
+
>
|
|
797
|
+
×
|
|
798
|
+
</button>
|
|
799
|
+
)}
|
|
800
|
+
</li>
|
|
801
|
+
))}
|
|
802
|
+
</ul>
|
|
803
|
+
)}
|
|
804
|
+
{canEditBlueprint && onAddBlueprintVariable && (
|
|
805
|
+
<AddIntentRow
|
|
806
|
+
placeholder="Variable name"
|
|
807
|
+
onAdd={(name) => onAddBlueprintVariable(selected.id, name)}
|
|
808
|
+
/>
|
|
809
|
+
)}
|
|
810
|
+
{previewing && (
|
|
811
|
+
<>
|
|
812
|
+
<PanelList
|
|
813
|
+
title="Added functions"
|
|
814
|
+
items={symbolLabels(selectedAddedFunctions)}
|
|
815
|
+
/>
|
|
816
|
+
<PanelList
|
|
817
|
+
title="Added variables"
|
|
818
|
+
items={symbolLabels(selectedAddedVariables)}
|
|
819
|
+
/>
|
|
820
|
+
<PanelList
|
|
821
|
+
title="Added imports"
|
|
822
|
+
items={importLabels(selectedAddedImports)}
|
|
823
|
+
/>
|
|
824
|
+
</>
|
|
825
|
+
)}
|
|
826
|
+
<div className="hud-section-title">
|
|
827
|
+
{importedBy ? 'Imported by' : 'Imports'}
|
|
828
|
+
</div>
|
|
829
|
+
{importedBy ? (
|
|
830
|
+
importers.length === 0 ? (
|
|
831
|
+
<p>Nothing local imports this</p>
|
|
832
|
+
) : (
|
|
833
|
+
<ul>
|
|
834
|
+
{importers.map((file) => (
|
|
835
|
+
<li key={file.id}>{file.id}</li>
|
|
836
|
+
))}
|
|
837
|
+
</ul>
|
|
838
|
+
)
|
|
839
|
+
) : selected.imports.length === 0 && extraBlueprintImports.length === 0 ? (
|
|
840
|
+
<p>No local imports</p>
|
|
841
|
+
) : (
|
|
842
|
+
<ul>
|
|
843
|
+
{selected.imports.map((id) => (
|
|
844
|
+
<li key={id}>
|
|
845
|
+
<span
|
|
846
|
+
className={
|
|
847
|
+
intendedImportFrom.has(id) ? 'hud-intended' : undefined
|
|
848
|
+
}
|
|
849
|
+
>
|
|
850
|
+
{id}
|
|
851
|
+
</span>
|
|
852
|
+
{canEditBlueprint && intendedImportFrom.has(id) && (
|
|
853
|
+
<button
|
|
854
|
+
className="hud-item-remove"
|
|
855
|
+
type="button"
|
|
856
|
+
aria-label={`Remove import ${id}`}
|
|
857
|
+
onClick={() => {
|
|
858
|
+
const item = selectedBlueprintImports.find(
|
|
859
|
+
(entry) => entry.from === id,
|
|
860
|
+
)
|
|
861
|
+
if (item) {
|
|
862
|
+
onRemoveBlueprintImport?.(
|
|
863
|
+
selected.id,
|
|
864
|
+
item.name,
|
|
865
|
+
item.from,
|
|
866
|
+
)
|
|
867
|
+
}
|
|
868
|
+
}}
|
|
869
|
+
>
|
|
870
|
+
×
|
|
871
|
+
</button>
|
|
872
|
+
)}
|
|
873
|
+
</li>
|
|
874
|
+
))}
|
|
875
|
+
{extraBlueprintImports.map((item) => (
|
|
876
|
+
<li key={`bp-${item.name}-${item.from}`}>
|
|
877
|
+
<span className="hud-intended">
|
|
878
|
+
{item.name === item.from
|
|
879
|
+
? item.from
|
|
880
|
+
: `${item.name} from ${item.from}`}
|
|
881
|
+
</span>
|
|
882
|
+
{canEditBlueprint && (
|
|
883
|
+
<button
|
|
884
|
+
className="hud-item-remove"
|
|
885
|
+
type="button"
|
|
886
|
+
aria-label={`Remove import ${item.name}`}
|
|
887
|
+
onClick={() =>
|
|
888
|
+
onRemoveBlueprintImport?.(
|
|
889
|
+
selected.id,
|
|
890
|
+
item.name,
|
|
891
|
+
item.from,
|
|
892
|
+
)
|
|
893
|
+
}
|
|
894
|
+
>
|
|
895
|
+
×
|
|
896
|
+
</button>
|
|
897
|
+
)}
|
|
898
|
+
</li>
|
|
899
|
+
))}
|
|
900
|
+
</ul>
|
|
901
|
+
)}
|
|
902
|
+
{canEditBlueprint && !importedBy && onAddBlueprintImport && (
|
|
903
|
+
<AddIntentRow
|
|
904
|
+
placeholder="Clock from src/components/Clock.tsx"
|
|
905
|
+
onAdd={(raw) => onAddBlueprintImport(selected.id, raw)}
|
|
906
|
+
/>
|
|
907
|
+
)}
|
|
908
|
+
</aside>
|
|
909
|
+
)}
|
|
910
|
+
|
|
911
|
+
{!selected && selectedFolderNode && infoVisible && (
|
|
912
|
+
<aside ref={infoPanelRef} className="hud-panel hud-panel-info">
|
|
913
|
+
<h2>{selectedFolderNode.name}</h2>
|
|
914
|
+
<p className="path">
|
|
915
|
+
{selectedFolderNode.path === '.'
|
|
916
|
+
? graph.targetName
|
|
917
|
+
: selectedFolderNode.path}
|
|
918
|
+
</p>
|
|
919
|
+
<p>
|
|
920
|
+
{folderFiles.length} {folderFiles.length === 1 ? 'file' : 'files'}
|
|
921
|
+
</p>
|
|
922
|
+
<div className="hud-section-title">Files</div>
|
|
923
|
+
{folderFiles.length === 0 ? (
|
|
924
|
+
<p>No files on this island</p>
|
|
925
|
+
) : (
|
|
926
|
+
<ul>
|
|
927
|
+
{folderFiles.map((file) => (
|
|
928
|
+
<li key={file.id}>{file.path || file.id}</li>
|
|
929
|
+
))}
|
|
930
|
+
</ul>
|
|
931
|
+
)}
|
|
932
|
+
{creatingBlueprint && mapping && onMapAddFile && onMapAddFolder && (
|
|
933
|
+
<div className="hud-decide hud-map-blueprint">
|
|
934
|
+
<button
|
|
935
|
+
className="hud-button hud-button-approve"
|
|
936
|
+
type="button"
|
|
937
|
+
disabled={naming}
|
|
938
|
+
onClick={() => onMapAddFile(selectedFolderNode.path)}
|
|
939
|
+
>
|
|
940
|
+
Add file
|
|
941
|
+
</button>
|
|
942
|
+
<button
|
|
943
|
+
className="hud-button"
|
|
944
|
+
type="button"
|
|
945
|
+
disabled={naming}
|
|
946
|
+
onClick={() => onMapAddFolder(selectedFolderNode.path)}
|
|
947
|
+
>
|
|
948
|
+
Add folder
|
|
949
|
+
</button>
|
|
950
|
+
</div>
|
|
951
|
+
)}
|
|
952
|
+
</aside>
|
|
953
|
+
)}
|
|
954
|
+
|
|
955
|
+
<div className="hud-bottom">
|
|
956
|
+
<div className="hud-hints">
|
|
957
|
+
{mapping ? (
|
|
958
|
+
<>
|
|
959
|
+
<span>Scroll zoom</span>
|
|
960
|
+
<span>Drag pan</span>
|
|
961
|
+
<span>
|
|
962
|
+
{sessionMode
|
|
963
|
+
? 'Click a block for info'
|
|
964
|
+
: 'Click a block for relations'}
|
|
965
|
+
</span>
|
|
966
|
+
<span>Click an island for its files</span>
|
|
967
|
+
{creatingBlueprint && (
|
|
968
|
+
<>
|
|
969
|
+
<span>Select an island</span>
|
|
970
|
+
<span>Add file / Add folder in panel</span>
|
|
971
|
+
</>
|
|
972
|
+
)}
|
|
973
|
+
<span>Click a line to fly there</span>
|
|
974
|
+
<span>Double-click ground to walk</span>
|
|
975
|
+
{selected?.userCreated && creatingBlueprint && (
|
|
976
|
+
<span>Backspace delete</span>
|
|
977
|
+
)}
|
|
978
|
+
<span>
|
|
979
|
+
{sessionMode
|
|
980
|
+
? infoVisible
|
|
981
|
+
? 'I hide info'
|
|
982
|
+
: 'Click a block for info'
|
|
983
|
+
: infoVisible
|
|
984
|
+
? 'I hide info'
|
|
985
|
+
: 'I show info'}
|
|
986
|
+
</span>
|
|
987
|
+
{infoVisible && <span>↑↓ scroll info</span>}
|
|
988
|
+
<span>
|
|
989
|
+
{importedBy ? 'K show imports' : 'K show imported by'}
|
|
990
|
+
</span>
|
|
991
|
+
<span>M back to walk</span>
|
|
992
|
+
</>
|
|
993
|
+
) : (
|
|
994
|
+
<>
|
|
995
|
+
<span>WASD walk</span>
|
|
996
|
+
<span>Mouse look</span>
|
|
997
|
+
<span>Shift sprint</span>
|
|
998
|
+
{creatingBlueprint && (
|
|
999
|
+
<>
|
|
1000
|
+
<span>Space place file</span>
|
|
1001
|
+
<span>B place island</span>
|
|
1002
|
+
</>
|
|
1003
|
+
)}
|
|
1004
|
+
{selected?.userCreated && creatingBlueprint && (
|
|
1005
|
+
<span>Backspace delete</span>
|
|
1006
|
+
)}
|
|
1007
|
+
<span>
|
|
1008
|
+
{sessionMode
|
|
1009
|
+
? 'Click a block for info'
|
|
1010
|
+
: 'Click block for relations'}
|
|
1011
|
+
</span>
|
|
1012
|
+
<span>Aim a line to fly</span>
|
|
1013
|
+
<span>
|
|
1014
|
+
{sessionMode
|
|
1015
|
+
? infoVisible
|
|
1016
|
+
? 'I hide info'
|
|
1017
|
+
: 'Click a block for info'
|
|
1018
|
+
: infoVisible
|
|
1019
|
+
? 'I hide info'
|
|
1020
|
+
: 'I show info'}
|
|
1021
|
+
</span>
|
|
1022
|
+
{infoVisible && <span>↑↓ scroll info</span>}
|
|
1023
|
+
<span>
|
|
1024
|
+
{importedBy ? 'K show imports' : 'K show imported by'}
|
|
1025
|
+
</span>
|
|
1026
|
+
<span>M toggle map</span>
|
|
1027
|
+
<span>Esc release mouse</span>
|
|
1028
|
+
</>
|
|
1029
|
+
)}
|
|
1030
|
+
</div>
|
|
1031
|
+
<div className="hud-icon-row">
|
|
1032
|
+
<button
|
|
1033
|
+
className="hud-button hud-icon-button"
|
|
1034
|
+
data-active={importedBy}
|
|
1035
|
+
aria-label={
|
|
1036
|
+
importedBy ? 'Show imports' : 'Show imported by'
|
|
1037
|
+
}
|
|
1038
|
+
aria-keyshortcuts="K"
|
|
1039
|
+
aria-pressed={importedBy}
|
|
1040
|
+
type="button"
|
|
1041
|
+
onClick={onToggleImportedBy}
|
|
1042
|
+
>
|
|
1043
|
+
<svg
|
|
1044
|
+
viewBox="0 0 24 24"
|
|
1045
|
+
width="18"
|
|
1046
|
+
height="18"
|
|
1047
|
+
fill="none"
|
|
1048
|
+
stroke="currentColor"
|
|
1049
|
+
strokeWidth="2"
|
|
1050
|
+
strokeLinecap="round"
|
|
1051
|
+
strokeLinejoin="round"
|
|
1052
|
+
aria-hidden="true"
|
|
1053
|
+
>
|
|
1054
|
+
<rect x="13" y="6" width="8" height="12" rx="1.5" />
|
|
1055
|
+
<path d="M3 12h8" />
|
|
1056
|
+
<path d="M8 8l4 4-4 4" />
|
|
1057
|
+
</svg>
|
|
1058
|
+
<span className="hud-tooltip">
|
|
1059
|
+
{importedBy ? 'K show imports' : 'K show imported by'}
|
|
1060
|
+
</span>
|
|
1061
|
+
</button>
|
|
1062
|
+
<button
|
|
1063
|
+
className="hud-button hud-icon-button"
|
|
1064
|
+
data-active={followLook}
|
|
1065
|
+
aria-label="Make LLM look where I look"
|
|
1066
|
+
aria-pressed={followLook}
|
|
1067
|
+
type="button"
|
|
1068
|
+
onClick={onToggleFollowLook}
|
|
1069
|
+
>
|
|
1070
|
+
<svg
|
|
1071
|
+
viewBox="0 0 24 24"
|
|
1072
|
+
width="18"
|
|
1073
|
+
height="18"
|
|
1074
|
+
fill="none"
|
|
1075
|
+
stroke="currentColor"
|
|
1076
|
+
strokeWidth="2"
|
|
1077
|
+
strokeLinecap="round"
|
|
1078
|
+
strokeLinejoin="round"
|
|
1079
|
+
aria-hidden="true"
|
|
1080
|
+
>
|
|
1081
|
+
<path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7Z" />
|
|
1082
|
+
<circle cx="12" cy="12" r="3" />
|
|
1083
|
+
</svg>
|
|
1084
|
+
<span className="hud-tooltip">Make LLM look where I look</span>
|
|
1085
|
+
</button>
|
|
1086
|
+
</div>
|
|
1087
|
+
</div>
|
|
1088
|
+
</div>
|
|
1089
|
+
)
|
|
1090
|
+
}
|