Skip to content

Commit

Permalink
fix(ai/rsc): New createStreamableUI implementation (#1825)
Browse files Browse the repository at this point in the history
Co-authored-by: Lars Grammel <[email protected]>
  • Loading branch information
shuding and lgrammel committed Jun 11, 2024
1 parent 8e5ea89 commit 3cabf07
Show file tree
Hide file tree
Showing 24 changed files with 448 additions and 1,154 deletions.
5 changes: 5 additions & 0 deletions .changeset/early-hounds-flow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'ai': patch
---

fix(ai/rsc): Refactor streamable UI internal implementation
91 changes: 0 additions & 91 deletions packages/core/rsc/__snapshots__/streamable.ui.test.tsx.snap

This file was deleted.

1 change: 1 addition & 0 deletions packages/core/rsc/rsc-shared.mts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ export {
useActions,
useSyncUIState,
InternalAIProvider,
InternalStreamableUIClient,
} from './shared-client';
56 changes: 56 additions & 0 deletions packages/core/rsc/shared-client/client-wrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
'use client';

import { useState, useEffect } from 'react';
import { StreamableValue } from '../types';
import { readStreamableValue } from './streamable';

export function InternalStreamableUIClient<T>({
s,
}: {
s: StreamableValue<T>;
}) {
// Set the value to the initial value of the streamable, if it has one.
const [value, setValue] = useState<T | undefined>(s.curr);

// Error state for the streamable. It might be errored initially and we want
// to error out as soon as possible.
const [error, setError] = useState<Error | undefined>(s.error);

useEffect(() => {
let canceled = false;
setError(undefined);

(async () => {
try {
// Read the streamable value and update the state with the new value.
for await (const v of readStreamableValue(s)) {
if (canceled) {
break;
}

setValue(v);
}
} catch (e) {
if (canceled) {
return;
}

setError(e as Error);
}
})();

return () => {
// If the component is unmounted, we want to cancel the stream.
canceled = true;
};
}, [s]);

// This ensures that errors from the streamable UI are thrown during the
// render phase, so that they can be caught by error boundary components.
// This is necessary for React's declarative model.
if (error) {
throw error;
}

return value;
}
1 change: 1 addition & 0 deletions packages/core/rsc/shared-client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export {
useSyncUIState,
InternalAIProvider,
} from './context';
export { InternalStreamableUIClient } from './client-wrapper';
15 changes: 14 additions & 1 deletion packages/core/rsc/shared-client/streamable.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { startTransition, useLayoutEffect, useState } from 'react';
import {
ReactElement,
startTransition,
useLayoutEffect,
useState,
} from 'react';
import { STREAMABLE_VALUE_TYPE } from '../constants';
import type { StreamableValue } from '../types';

Expand Down Expand Up @@ -97,6 +102,14 @@ export function readStreamableValue<T = unknown>(
(curr as string) = curr + row.diff[1];
}
break;
case 1:
(curr as ReactElement) = (
<>
{curr}
{row.diff[1]}
</>
);
break;
}
} else {
curr = row.curr;
Expand Down
157 changes: 0 additions & 157 deletions packages/core/rsc/stream-ui/__snapshots__/stream-ui.ui.test.tsx.snap

This file was deleted.

Loading

0 comments on commit 3cabf07

Please sign in to comment.