Skip to content

Commit 29ac8d7

Browse files
committed
Improve notebook preview feedback and diagnostics
1 parent 9ad7dd5 commit 29ac8d7

14 files changed

Lines changed: 1052 additions & 75 deletions

scripts/notebook-ai-app-server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1680,7 +1680,7 @@ async function resolveNpmPackageVersion(name: string, signal: AbortSignal) {
16801680
`https://registry.npmjs.org/${encodeURIComponent(name)}/latest`,
16811681
{
16821682
headers: { Accept: 'application/json' },
1683-
redirect: 'error',
1683+
redirect: 'manual',
16841684
signal,
16851685
},
16861686
)

src/components/examples/ExampleWorkbench.client.tsx

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,14 +81,18 @@ import {
8181
type NotebookWorkbenchPane,
8282
type NotebookWorkbenchTab,
8383
} from '~/utils/notebook-workbench-tabs'
84+
import type { NotebookAiPromptLifecycle } from '~/utils/notebook-ai-prompt-queue'
8485
import {
8586
createExampleWorkspace,
8687
type ExampleDefinition,
8788
type ExampleWorkspace,
8889
} from '~/utils/example-workspace'
8990
import { CodeMirrorEditor } from './CodeMirrorEditor.client'
9091
import {
92+
MAX_SANDBOX_BROWSER_ANNOTATION_PROMPT_LENGTH,
9193
SandboxBrowser,
94+
formatSandboxBrowserAnnotations,
95+
type SandboxBrowserAnnotation,
9296
type SandboxBrowserAnnotationTarget,
9397
} from './SandboxBrowser.client'
9498

@@ -251,6 +255,10 @@ export function ExampleWorkbench({
251255
content: React.ReactNode
252256
label: string
253257
onActiveChange(active: boolean): void
258+
submitPrompt?(
259+
content: string,
260+
lifecycle?: NotebookAiPromptLifecycle,
261+
): boolean
254262
}
255263
autoRun?: boolean
256264
className?: string
@@ -382,12 +390,18 @@ export function ExampleWorkbench({
382390
const notebookPreviewAnnotationTargetsRef = React.useRef(
383391
new Map<string, SandboxBrowserAnnotationTarget>(),
384392
)
393+
const notebookPreviewAnnotationsRef = React.useRef(
394+
new Map<string, ReadonlyArray<SandboxBrowserAnnotation>>(),
395+
)
385396
const [, setNotebookPreviewRevision] = React.useState(0)
386397
const [previewNavigationError, setPreviewNavigationError] = React.useState('')
387398
const [previewAnnotationMode, setPreviewAnnotationModeActive] =
388399
React.useState(false)
389400
const [previewAnnotationTarget, setPreviewAnnotationTarget] =
390401
React.useState<SandboxBrowserAnnotationTarget>()
402+
const [previewAnnotations, setPreviewAnnotations] = React.useState<
403+
ReadonlyArray<SandboxBrowserAnnotation>
404+
>([])
391405
const [webContainerSession, setWebContainerSession] = React.useState<
392406
WebContainerExampleSession | undefined
393407
>()
@@ -803,6 +817,8 @@ export function ExampleWorkbench({
803817
if (notebookChanged) {
804818
setNotebookArrangeTabId(undefined)
805819
setNotebookWorkspaceVisible(true)
820+
notebookPreviewAnnotationsRef.current.clear()
821+
setPreviewAnnotations([])
806822
}
807823
const previousNotebookTabs = notebookTabsRef.current
808824
const availablePaths = Object.keys(nextWorkspace.files).filter(
@@ -1914,6 +1930,7 @@ export function ExampleWorkbench({
19141930
notebookPreviewNavigationErrorsRef.current.delete(tabId)
19151931
notebookPreviewAnnotationModesRef.current.delete(tabId)
19161932
notebookPreviewAnnotationTargetsRef.current.delete(tabId)
1933+
notebookPreviewAnnotationsRef.current.delete(tabId)
19171934
if (currentNotebookPreviewTabIdRef.current === tabId) {
19181935
currentNotebookPreviewTabIdRef.current = next.tabs.find(
19191936
(tab) => tab.kind === 'preview',
@@ -2244,6 +2261,83 @@ export function ExampleWorkbench({
22442261
sendPreviewBrowserCommand({ kind: 'annotation', enabled: true })
22452262
}
22462263

2264+
function addPreviewAnnotation(
2265+
annotation: SandboxBrowserAnnotation,
2266+
tabId?: string,
2267+
) {
2268+
if (tabId) {
2269+
const current = notebookPreviewAnnotationsRef.current.get(tabId) ?? []
2270+
notebookPreviewAnnotationsRef.current.set(tabId, [...current, annotation])
2271+
setNotebookPreviewRevision((revision) => revision + 1)
2272+
return
2273+
}
2274+
setPreviewAnnotations((current) => [...current, annotation])
2275+
}
2276+
2277+
function removePreviewAnnotation(annotationId: string, tabId?: string) {
2278+
if (tabId) {
2279+
const current = notebookPreviewAnnotationsRef.current.get(tabId) ?? []
2280+
const next = current.filter(
2281+
(annotation) => annotation.id !== annotationId,
2282+
)
2283+
if (next.length) {
2284+
notebookPreviewAnnotationsRef.current.set(tabId, next)
2285+
} else {
2286+
notebookPreviewAnnotationsRef.current.delete(tabId)
2287+
}
2288+
setNotebookPreviewRevision((revision) => revision + 1)
2289+
return
2290+
}
2291+
setPreviewAnnotations((current) =>
2292+
current.filter((annotation) => annotation.id !== annotationId),
2293+
)
2294+
}
2295+
2296+
function submitPreviewAnnotations(
2297+
annotations: ReadonlyArray<SandboxBrowserAnnotation>,
2298+
tabId: string,
2299+
) {
2300+
if (!alternateEditor?.submitPrompt || annotations.length === 0) return false
2301+
const content = formatSandboxBrowserAnnotations(annotations)
2302+
if (content.length > MAX_SANDBOX_BROWSER_ANNOTATION_PROMPT_LENGTH) {
2303+
return false
2304+
}
2305+
2306+
const definitionId = notebookDefinitionIdRef.current
2307+
const accepted = alternateEditor.submitPrompt(content, {
2308+
onDiscarded() {
2309+
if (
2310+
notebookDefinitionIdRef.current !== definitionId ||
2311+
!notebookTabsRef.current.tabs.some((tab) => tab.id === tabId)
2312+
) {
2313+
return
2314+
}
2315+
const current = notebookPreviewAnnotationsRef.current.get(tabId) ?? []
2316+
const currentIds = new Set(current.map((annotation) => annotation.id))
2317+
notebookPreviewAnnotationsRef.current.set(tabId, [
2318+
...annotations.filter((annotation) => !currentIds.has(annotation.id)),
2319+
...current,
2320+
])
2321+
setNotebookPreviewRevision((revision) => revision + 1)
2322+
},
2323+
})
2324+
alternateEditor.onActiveChange(true)
2325+
if (!accepted) return false
2326+
2327+
const submittedIds = new Set(annotations.map((annotation) => annotation.id))
2328+
const current = notebookPreviewAnnotationsRef.current.get(tabId) ?? []
2329+
const next = current.filter(
2330+
(annotation) => !submittedIds.has(annotation.id),
2331+
)
2332+
if (next.length) {
2333+
notebookPreviewAnnotationsRef.current.set(tabId, next)
2334+
} else {
2335+
notebookPreviewAnnotationsRef.current.delete(tabId)
2336+
}
2337+
setNotebookPreviewRevision((revision) => revision + 1)
2338+
return true
2339+
}
2340+
22472341
function capturePreview(tabId?: string) {
22482342
cancelPreviewCapture('A newer screenshot replaced this capture.')
22492343
const requestId = crypto.randomUUID()
@@ -2871,6 +2965,7 @@ export function ExampleWorkbench({
28712965
>
28722966
<SandboxBrowser
28732967
annotationAvailable={Boolean(previewUrl || sourceDocument)}
2968+
annotations={notebookPreviewAnnotationsRef.current.get(tab.id) ?? []}
28742969
annotationMode={notebookPreviewAnnotationModesRef.current.has(tab.id)}
28752970
annotationTarget={notebookPreviewAnnotationTargetsRef.current.get(
28762971
tab.id,
@@ -2889,15 +2984,26 @@ export function ExampleWorkbench({
28892984
onAnnotationModeChange={(active) =>
28902985
setPreviewAnnotationMode(active, tab.id)
28912986
}
2987+
onAddAnnotation={(annotation) =>
2988+
addPreviewAnnotation(annotation, tab.id)
2989+
}
28922990
onBack={() => navigateNotebookPreviewHistory(tab.id, -1)}
28932991
onClearAnnotationTarget={() => clearPreviewAnnotationTarget(tab.id)}
28942992
onForward={() => navigateNotebookPreviewHistory(tab.id, 1)}
28952993
onNavigate={(url) =>
28962994
sendPreviewBrowserCommand({ kind: 'navigate', url }, tab.id)
28972995
}
2996+
onRemoveAnnotation={(annotationId) =>
2997+
removePreviewAnnotation(annotationId, tab.id)
2998+
}
28982999
onReload={() => reloadPreview(tab.id)}
28993000
openExternalUrl={tabExternalPreviewUrl}
29003001
reloadDisabled={runActive || runDisabled}
3002+
onSubmitAnnotations={
3003+
alternateEditor?.submitPrompt
3004+
? (annotations) => submitPreviewAnnotations(annotations, tab.id)
3005+
: undefined
3006+
}
29013007
>
29023008
{previewUrl ? (
29033009
<iframe
@@ -4135,6 +4241,7 @@ export function ExampleWorkbench({
41354241
>
41364242
<SandboxBrowser
41374243
annotationAvailable={Boolean(previewUrl || sourceDocument)}
4244+
annotations={previewAnnotations}
41384245
annotationMode={previewAnnotationMode}
41394246
annotationTarget={previewAnnotationTarget}
41404247
canGoBack={canGoBackInExamplePreview(previewHistory)}
@@ -4147,12 +4254,16 @@ export function ExampleWorkbench({
41474254
history={[...new Set(previewHistory.entries)]}
41484255
navigationAvailable={Boolean(previewUrl || sourceDocument)}
41494256
onAnnotationModeChange={setPreviewAnnotationMode}
4257+
onAddAnnotation={(annotation) => addPreviewAnnotation(annotation)}
41504258
onBack={() => sendPreviewBrowserCommand({ kind: 'back' })}
41514259
onClearAnnotationTarget={clearPreviewAnnotationTarget}
41524260
onForward={() => sendPreviewBrowserCommand({ kind: 'forward' })}
41534261
onNavigate={(url) =>
41544262
sendPreviewBrowserCommand({ kind: 'navigate', url })
41554263
}
4264+
onRemoveAnnotation={(annotationId) =>
4265+
removePreviewAnnotation(annotationId)
4266+
}
41564267
onReload={reloadPreview}
41574268
openExternalUrl={externalPreviewUrl}
41584269
reloadDisabled={runActive || runDisabled}

0 commit comments

Comments
 (0)