feat(react): add initialValues option to useStream for cached thread display

Add initialValues parameter to UseStreamOptions to enable immediate display
of cached thread data while official history is being fetched from the server.

This addresses the common use case where applications cache thread data
locally (IndexedDB, localStorage, etc.) and want to show it instantly when
users navigate to existing threads, providing better UX with faster loading.

Key changes:
- Add initialValues?: Partial<StateType> | null to UseStreamOptions interface
- Update values precedence: streamValues > initialValues > historyValues
- Ensure optimisticValues properly override initialValues during submission
- Maintain full backward compatibility with existing API

Example usage:
```typescript
const stream = useStream({
  threadId,
  assistantId: 'my-assistant',
  initialValues: cachedThreadData?.values // Show cached data immediately
});
```

The values flow now follows this priority:
1. Initial load: shows initialValues while history loads
2. During submit: optimisticValues take precedence
3. After server response: official history replaces all
This commit is contained in:
MauritsBrinkman
2025-06-30 16:43:12 +02:00
committed by Tat Dat Duong
parent 8a763ad358
commit 141a6af4f7
+18 -6
View File
@@ -530,6 +530,17 @@ export interface UseStreamOptions<
/** Will reconnect the stream on mount */
reconnectOnMount?: boolean | (() => RunMetadataStorage);
/**
* Initial values to display immediately when loading a thread.
* Useful for displaying cached thread data while official history loads.
* These values will be replaced when official thread data is fetched.
*
* Note: UI components from initialValues will render immediately if they're
* predefined in LoadExternalComponent's components prop, providing instant
* cached UI display without server fetches.
*/
initialValues?: Partial<StateType> | null;
}
interface RunMetadataStorage {
@@ -925,7 +936,8 @@ export function useStream<
}
setStreamValues((streamValues) => {
const values = { ...historyValues, ...streamValues };
const baseValues = options.initialValues ? { ...historyValues, ...options.initialValues } : historyValues;
const values = { ...baseValues, ...streamValues };
// Assumption: we're concatenating the message
const messages = getMessages(values).slice();
@@ -1004,18 +1016,18 @@ export function useStream<
// Assumption: we're setting the initial value
// Used for instant feedback
setStreamValues(() => {
const values = { ...historyValues };
const baseValues = options.initialValues ? { ...historyValues, ...options.initialValues } : historyValues;
if (submitOptions?.optimisticValues != null) {
return {
...values,
...baseValues,
...(typeof submitOptions.optimisticValues === "function"
? submitOptions.optimisticValues(values)
? submitOptions.optimisticValues(baseValues)
: submitOptions.optimisticValues),
};
}
return values;
return baseValues;
});
let usableThreadId = threadId;
@@ -1116,7 +1128,7 @@ export function useStream<
}, [reconnectKey]);
const error = streamError ?? historyError;
const values = streamValues ?? historyValues;
const values = streamValues ?? (options.initialValues ? { ...historyValues, ...options.initialValues } : historyValues);
return {
get values() {