@@ -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'
8485import {
8586 createExampleWorkspace ,
8687 type ExampleDefinition ,
8788 type ExampleWorkspace ,
8889} from '~/utils/example-workspace'
8990import { CodeMirrorEditor } from './CodeMirrorEditor.client'
9091import {
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