@dudousxd/nestjs-agent-react 0.32.0 → 0.34.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 +7 -0
- package/dist/{client-C7eu9WMJ.d.cts → client-BdHUuqy-.d.cts} +36 -1
- package/dist/{client-C7eu9WMJ.d.ts → client-BdHUuqy-.d.ts} +36 -1
- package/dist/{generative-ui-VTZ1EADD.d.cts → generative-ui-CyHY8Lh1.d.cts} +32 -101
- package/dist/{generative-ui-VTZ1EADD.d.ts → generative-ui-DdUZPCXX.d.ts} +32 -101
- package/dist/genui-json-render.cjs +51 -8
- package/dist/genui-json-render.cjs.map +1 -1
- package/dist/genui-json-render.d.cts +2 -1
- package/dist/genui-json-render.d.ts +2 -1
- package/dist/genui-json-render.js +51 -8
- package/dist/genui-json-render.js.map +1 -1
- package/dist/genui-server-playwright.cjs +199 -0
- package/dist/genui-server-playwright.cjs.map +1 -0
- package/dist/genui-server-playwright.d.cts +18 -0
- package/dist/genui-server-playwright.d.ts +18 -0
- package/dist/genui-server-playwright.js +162 -0
- package/dist/genui-server-playwright.js.map +1 -0
- package/dist/genui-server.cjs +188 -0
- package/dist/genui-server.cjs.map +1 -0
- package/dist/genui-server.d.cts +36 -0
- package/dist/genui-server.d.ts +36 -0
- package/dist/genui-server.js +163 -0
- package/dist/genui-server.js.map +1 -0
- package/dist/genui.cjs +127 -8
- package/dist/genui.cjs.map +1 -1
- package/dist/genui.d.cts +5 -2
- package/dist/genui.d.ts +5 -2
- package/dist/genui.js +128 -8
- package/dist/genui.js.map +1 -1
- package/dist/index.cjs +559 -123
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -10
- package/dist/index.d.ts +35 -10
- package/dist/index.js +552 -120
- package/dist/index.js.map +1 -1
- package/dist/media.cjs.map +1 -1
- package/dist/media.d.cts +1 -1
- package/dist/media.d.ts +1 -1
- package/dist/media.js.map +1 -1
- package/dist/react-registry-Di4IVrw-.d.cts +25 -0
- package/dist/react-registry-z9GFbTm9.d.ts +25 -0
- package/dist/types-DZtFsWec.d.cts +99 -0
- package/dist/types-DZtFsWec.d.ts +99 -0
- package/package.json +35 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/genui/json-render.tsx","../src/genui/generative-ui.tsx","../src/components/ambient-ui.ts","../src/genui/use-generative-ui.ts","../src/genui/types.ts"],"sourcesContent":["/**\n * Optional json-render adapter for tree frames (`@dudousxd/nestjs-agent-react/genui/json-render`).\n * Needs the optional peer `@json-render/react` (>= 0.21). Use it when your app already renders\n * json-render specs and wants tree frames to go through the same `Renderer` (its state, actions and\n * visibility machinery); otherwise `<GenerativeUI>` renders trees natively.\n *\n * ```tsx\n * import { GenuiProvider } from '@dudousxd/nestjs-agent-react/genui/json-render';\n *\n * <GenuiProvider registry={registry} catalog={catalog} jsonRender>…</GenuiProvider>\n * ```\n */\nimport { type ComponentRegistry, JSONUIProvider, Renderer } from '@json-render/react';\nimport { type ReactNode, useMemo } from 'react';\nimport {\n GenuiProvider as BaseGenuiProvider,\n type GenuiProviderProps as BaseGenuiProviderProps,\n} from './generative-ui.js';\nimport type { GenerativeUIElement, GenuiRegistry, GenuiRenderer } from './types.js';\n\n/** A json-render flat spec: `{ root, elements: { [id]: { type, props, children } } }`. */\nexport interface JsonRenderFlatSpec {\n root: string;\n elements: Record<string, { type: string; props: Record<string, unknown>; children: string[] }>;\n}\n\n/** A tree frame's `root` as a json-render flat spec (ids `el-0`, `el-1`, … depth first). */\nexport function treeToJsonRenderSpec(root: GenerativeUIElement): JsonRenderFlatSpec {\n const elements: JsonRenderFlatSpec['elements'] = {};\n let next = 0;\n const visit = (node: GenerativeUIElement): string => {\n const id = `el-${next++}`;\n const entry = { type: node.type, props: node.props ?? {}, children: [] as string[] };\n elements[id] = entry;\n for (const child of node.children ?? []) entry.children.push(visit(child));\n return id;\n };\n return { root: visit(root), elements };\n}\n\n/**\n * Adapt a genui registry (renderers taking props + `children`) to a json-render registry (renderers\n * taking `{ element, children }`), so one set of components serves both.\n */\nexport function toJsonRenderRegistry(registry: GenuiRegistry): ComponentRegistry {\n return Object.fromEntries(\n Object.entries(registry).map(([name, Renderer]) => [\n name,\n ({ element, children }: { element: { props?: unknown }; children?: ReactNode }) => (\n <Renderer {...((element.props ?? {}) as Record<string, unknown>)}>{children}</Renderer>\n ),\n ]),\n );\n}\n\nexport interface JsonRenderTreeProps {\n root?: GenerativeUIElement;\n registry: ComponentRegistry;\n /** json-render state the spec's `$state` expressions read. */\n initialState?: Record<string, unknown>;\n}\n\n/** Render a tree frame's `root` with json-render. */\nexport function JsonRenderTree({ root, registry, initialState }: JsonRenderTreeProps) {\n const spec = useMemo(() => (root === undefined ? null : treeToJsonRenderSpec(root)), [root]);\n if (spec === null) return null;\n return (\n <JSONUIProvider registry={registry} {...(initialState !== undefined ? { initialState } : {})}>\n <Renderer spec={spec as never} registry={registry} />\n </JSONUIProvider>\n );\n}\n\n/** A registry entry for `genui:tree` that renders through json-render with `registry`. */\nexport function jsonRenderTree(registry: ComponentRegistry): GenuiRenderer<{\n root?: GenerativeUIElement;\n}> {\n return function JsonRenderTreeFrame({ root }: { root?: GenerativeUIElement }) {\n return <JsonRenderTree registry={registry} {...(root !== undefined ? { root } : {})} />;\n };\n}\n\nexport interface GenuiProviderProps extends BaseGenuiProviderProps {\n /**\n * Draw composed tree frames through json-render: pass your json-render registry, or `true` to\n * derive one from `registry` ({@link toJsonRenderRegistry}). Omitted → trees render natively.\n * An explicit `treeRenderer` wins over it.\n */\n jsonRender?: ComponentRegistry | true;\n}\n\n/** `GenuiProvider` from `/genui`, plus the {@link GenuiProviderProps.jsonRender} option. */\nexport function GenuiProvider({ jsonRender, treeRenderer, registry, ...rest }: GenuiProviderProps) {\n const derived = useMemo(() => {\n if (jsonRender === undefined) return undefined;\n return jsonRenderTree(jsonRender === true ? toJsonRenderRegistry(registry ?? {}) : jsonRender);\n }, [jsonRender, registry]);\n const tree = treeRenderer ?? derived;\n return (\n <BaseGenuiProvider\n {...rest}\n {...(registry !== undefined ? { registry } : {})}\n {...(tree !== undefined ? { treeRenderer: tree } : {})}\n />\n );\n}\n","import {\n Component,\n type Context,\n type ErrorInfo,\n type ReactNode,\n createContext,\n useCallback,\n useContext,\n useMemo,\n} from 'react';\nimport { AmbientRenderUiContext, sharedContext } from '../components/ambient-ui.js';\nimport type { TranscriptUiBlock } from '../transcript/model.js';\nimport type {\n GenerativeUIElement,\n GenerativeUIItem,\n GenerativeUIOptions,\n GenerativeUIProblem,\n GenerativeUIState,\n GenuiRegistry,\n GenuiRenderer,\n} from './types.js';\nimport {\n useGenerativeUIState,\n useResolvedComponent,\n useValidatedProps,\n} from './use-generative-ui.js';\n\n/** What to draw instead of a component that did not render. Default: nothing. */\nexport type GenerativeUIFallback = ReactNode | ((problem: GenerativeUIProblem) => ReactNode);\n\ninterface TreeScope {\n options: GenerativeUIOptions;\n fallback: GenerativeUIFallback | undefined;\n loading: ReactNode;\n onError: ((error: unknown, item: GenerativeUIItem) => void) | undefined;\n}\n\nconst TreeScopeContext = createContext<TreeScope | null>(null);\n\nfunction renderFallback(fallback: GenerativeUIFallback | undefined, problem: GenerativeUIProblem) {\n if (typeof fallback === 'function') return fallback(problem);\n return fallback ?? null;\n}\n\ninterface BoundaryProps {\n item: GenerativeUIItem;\n fallback: GenerativeUIFallback | undefined;\n onError: ((error: unknown, item: GenerativeUIItem) => void) | undefined;\n children: ReactNode;\n}\n\n/** One failing renderer takes down its own item, never the message around it. */\nclass ItemBoundary extends Component<BoundaryProps, { error: unknown; failed: boolean }> {\n override state = { error: undefined as unknown, failed: false };\n\n static getDerivedStateFromError(error: unknown) {\n return { error, failed: true };\n }\n\n override componentDidCatch(error: unknown, _info: ErrorInfo) {\n this.props.onError?.(error, this.props.item);\n }\n\n override componentDidUpdate(previous: BoundaryProps) {\n // New props for the same item (a streamed update) get a fresh chance to render.\n if (this.state.failed && previous.item !== this.props.item) {\n this.setState({ error: undefined, failed: false });\n }\n }\n\n override render() {\n if (this.state.failed) {\n return renderFallback(this.props.fallback, {\n reason: 'error',\n item: this.props.item,\n error: this.state.error,\n });\n }\n return this.props.children;\n }\n}\n\nfunction nodeItem(node: GenerativeUIElement, id: string): GenerativeUIItem {\n return { id, component: node.type, props: node.props ?? {}, version: null, toolCallId: null };\n}\n\n/** One tree node: resolved and validated like a top-level item, children rendered as its `children`. */\nfunction TreeNode({ node, id }: { node: GenerativeUIElement; id: string }) {\n const scope = useContext(TreeScopeContext);\n const item = useMemo(() => nodeItem(node, id), [node, id]);\n const resolution = useResolvedComponent(\n node.type,\n null,\n scope?.options.registry ?? {},\n scope?.options.resolveComponent,\n );\n const validation = useValidatedProps(scope?.options.catalog, node.type, item.props);\n if (scope === null) return null;\n if (resolution.status === 'loading' || validation.status === 'pending') return scope.loading;\n if (resolution.status === 'unknown') {\n return renderFallback(scope.fallback, { reason: 'unknown', item });\n }\n if (resolution.status === 'error') {\n return renderFallback(scope.fallback, { reason: 'error', item, error: resolution.error });\n }\n if (validation.status === 'invalid') {\n return renderFallback(scope.fallback, { reason: 'invalid', item, issues: validation.issues });\n }\n const Renderer = resolution.Component;\n const children = Array.isArray(node.children) ? node.children : [];\n return (\n <Renderer {...validation.props}>\n {children.length > 0\n ? children.map((child, index) => {\n const childId = `${id}.${index}`;\n return (\n <ItemBoundary\n key={childId}\n item={nodeItem(child, childId)}\n fallback={scope.fallback}\n onError={scope.onError}\n >\n <TreeNode node={child} id={childId} />\n </ItemBoundary>\n );\n })\n : undefined}\n </Renderer>\n );\n}\n\n/**\n * Renders a `genui:tree` frame's `{ root }` node by node through the same registry, catalog and\n * resolver as top-level components. Used automatically for tree frames unless a `treeRenderer`\n * (e.g. the json-render one) or a registry entry for `genui:tree` takes over.\n */\nexport function GenuiTree({ root }: { root?: GenerativeUIElement }) {\n if (root === undefined || root === null || typeof root.type !== 'string') return null;\n return <TreeNode node={root} id=\"root\" />;\n}\n\nexport interface GenerativeUIScopeProps extends GenerativeUIOptions {\n fallback?: GenerativeUIFallback;\n loading?: ReactNode;\n onError?: (error: unknown, item: GenerativeUIItem) => void;\n children?: ReactNode;\n}\n\n/**\n * The registry, catalog and fallbacks tree nodes render with. `<GenerativeUI>` provides it; wrap\n * your own chrome in it when you draw `useGenerativeUI`'s `Component` yourself and it may be a tree.\n */\nexport function GenerativeUIScope({\n registry,\n catalog,\n resolveComponent,\n fallback,\n loading = null,\n onError,\n children,\n}: GenerativeUIScopeProps) {\n const scope = useMemo<TreeScope>(\n () => ({\n options: {\n registry,\n ...(catalog !== undefined ? { catalog } : {}),\n ...(resolveComponent !== undefined ? { resolveComponent } : {}),\n },\n fallback,\n loading,\n onError,\n }),\n [registry, catalog, resolveComponent, fallback, loading, onError],\n );\n return <TreeScopeContext.Provider value={scope}>{children}</TreeScopeContext.Provider>;\n}\n\n/** What `<GenuiProvider>` hands every `<GenerativeUI>` / `useGenerativeUI` below it. */\nexport interface GenuiProviderValue extends Partial<GenerativeUIOptions> {\n fallback?: GenerativeUIFallback;\n loading?: ReactNode;\n onError?: (error: unknown, item: GenerativeUIItem) => void;\n}\n\n// Shared by key: `/genui` and `/genui/json-render` are separate bundles, and a provider from one\n// must reach a `<GenerativeUI>` from the other.\nconst GenuiContext: Context<GenuiProviderValue | null> = sharedContext<GenuiProviderValue>(\n '@dudousxd/nestjs-agent-react:genui-provider',\n);\n\n/** The enclosing `<GenuiProvider>`'s settings, or `null` outside one. */\nexport function useGenuiProvider(): GenuiProviderValue | null {\n return useContext(GenuiContext);\n}\n\nexport interface GenuiProviderProps extends GenuiProviderValue {\n children?: ReactNode;\n}\n\n/**\n * Set generative UI up once, at the app root: the registry of your renderers, the catalog to\n * validate against, a resolver for components the registry lacks, and what to draw when one does\n * not render. Every `<GenerativeUI>` below reads it (its own props still win), and `MessageItem` /\n * `MessageList` draw pushed components with it — no `renderUi` per message.\n *\n * ```tsx\n * <GenuiProvider registry={registry} catalog={catalog} fallback={({ item }) => <Unknown name={item.component} />}>\n * <App />\n * </GenuiProvider>\n * ```\n */\nexport function GenuiProvider({\n registry,\n catalog,\n resolveComponent,\n treeRenderer,\n fallback,\n loading,\n onError,\n children,\n}: GenuiProviderProps) {\n const value = useMemo<GenuiProviderValue>(\n () => ({\n ...(registry !== undefined ? { registry } : {}),\n ...(catalog !== undefined ? { catalog } : {}),\n ...(resolveComponent !== undefined ? { resolveComponent } : {}),\n ...(treeRenderer !== undefined ? { treeRenderer } : {}),\n ...(fallback !== undefined ? { fallback } : {}),\n ...(loading !== undefined ? { loading } : {}),\n ...(onError !== undefined ? { onError } : {}),\n }),\n [registry, catalog, resolveComponent, treeRenderer, fallback, loading, onError],\n );\n const renderUi = useCallback((block: TranscriptUiBlock) => <GenerativeUI part={block} />, []);\n return (\n <GenuiContext.Provider value={value}>\n <AmbientRenderUiContext.Provider value={renderUi}>{children}</AmbientRenderUiContext.Provider>\n </GenuiContext.Provider>\n );\n}\n\nconst NO_REGISTRY: GenuiRegistry = {};\n\n/** Provider settings overlaid with explicit ones (explicit wins), memoized on their parts. */\nfunction useMergedOptions(own: Partial<GenerativeUIOptions>): GenerativeUIOptions {\n const provided = useContext(GenuiContext);\n const registry = own.registry ?? provided?.registry ?? NO_REGISTRY;\n const catalog = own.catalog ?? provided?.catalog;\n const resolveComponent = own.resolveComponent ?? provided?.resolveComponent;\n const treeRenderer = own.treeRenderer ?? provided?.treeRenderer;\n return useMemo(\n () => ({\n registry,\n ...(catalog !== undefined ? { catalog } : {}),\n ...(resolveComponent !== undefined ? { resolveComponent } : {}),\n ...(treeRenderer !== undefined ? { treeRenderer } : {}),\n }),\n [registry, catalog, resolveComponent, treeRenderer],\n );\n}\n\n/**\n * The headless half of {@link GenerativeUI}: what to draw for one pushed component, or `null` when\n * `part` is not one. Options default to the enclosing `<GenuiProvider>`'s.\n */\nexport function useGenerativeUI(\n part: unknown,\n options: Partial<GenerativeUIOptions> = {},\n): GenerativeUIState | null {\n const merged = useMergedOptions(options);\n return useGenerativeUIState(part, merged, merged.treeRenderer ?? (GenuiTree as GenuiRenderer));\n}\n\nexport interface GenerativeUIProps extends Partial<GenerativeUIOptions> {\n /** A transcript `ui` block, a `data-ui` message part, or a stored `{ id, component, props, version? }`. */\n part: TranscriptUiBlock | GenerativeUIItem | unknown;\n /** Drawn for an unknown component, invalid props, or a renderer that threw. Default: the provider's, else nothing. */\n fallback?: GenerativeUIFallback;\n /** Drawn while a resolver or an async validation is pending. Default: the provider's, else nothing. */\n loading?: ReactNode;\n /** A renderer threw (the item shows `fallback`). */\n onError?: (error: unknown, item: GenerativeUIItem) => void;\n}\n\n/**\n * Draws one server-pushed component with the app's own renderer. Headless: it adds no element and\n * no style of its own — only what the registry's component renders (and whatever `fallback` /\n * `loading` you pass). Everything but `part` defaults to the enclosing `<GenuiProvider>`.\n *\n * ```tsx\n * <GenerativeUI part={block} />\n * ```\n */\nexport function GenerativeUI({\n part,\n registry,\n catalog,\n resolveComponent,\n treeRenderer,\n fallback: ownFallback,\n loading: ownLoading,\n onError: ownOnError,\n}: GenerativeUIProps) {\n const provided = useContext(GenuiContext);\n const fallback = ownFallback ?? provided?.fallback;\n const loading = ownLoading ?? provided?.loading ?? null;\n const onError = ownOnError ?? provided?.onError;\n const options = useMergedOptions({\n ...(registry !== undefined ? { registry } : {}),\n ...(catalog !== undefined ? { catalog } : {}),\n ...(resolveComponent !== undefined ? { resolveComponent } : {}),\n ...(treeRenderer !== undefined ? { treeRenderer } : {}),\n });\n const state = useGenerativeUIState(\n part,\n options,\n options.treeRenderer ?? (GenuiTree as GenuiRenderer),\n );\n if (state === null) return null;\n if (state.status === 'loading') return loading;\n if (state.status === 'problem') {\n const { status: _status, ...problem } = state;\n return renderFallback(fallback, problem);\n }\n const Renderer = state.Component;\n return (\n <GenerativeUIScope\n {...options}\n fallback={fallback}\n loading={loading}\n {...(onError !== undefined ? { onError } : {})}\n >\n <ItemBoundary item={state.item} fallback={fallback} onError={onError}>\n <Renderer {...state.props} />\n </ItemBoundary>\n </GenerativeUIScope>\n );\n}\n","import { type Context, type ReactNode, createContext, useContext } from 'react';\nimport type { TranscriptUiBlock } from '../transcript/model.js';\n\n/** Draws a server-pushed component block. */\nexport type AmbientRenderUi = (block: TranscriptUiBlock) => ReactNode;\n\n/**\n * A context stored on `globalThis` under a registered symbol. Each entry point (`.`, `/genui`,\n * `/genui/json-render`) is its own bundle with its own copy of every module, so a context one entry\n * provides and another reads has to be found by key, not by module identity.\n */\nexport function sharedContext<T>(key: string): Context<T | null> {\n const symbol = Symbol.for(key);\n const holder = globalThis as Record<symbol, Context<T | null> | undefined>;\n const existing = holder[symbol];\n if (existing !== undefined) return existing;\n const created = createContext<T | null>(null);\n holder[symbol] = created;\n return created;\n}\n\n/**\n * How pushed components are drawn when no `renderUi` is passed — set by `<GenuiProvider>` (the\n * `/genui` subpath) and read by `MessageItem`.\n */\nexport const AmbientRenderUiContext: Context<AmbientRenderUi | null> =\n sharedContext<AmbientRenderUi>('@dudousxd/nestjs-agent-react:ambient-render-ui');\n\n/**\n * The renderer `<GenuiProvider>` installed for pushed components, or `null` outside one. For a\n * custom transcript built on `useTranscriptItem`: draw a `ui` block with it and you get what\n * `MessageItem` draws.\n */\nexport function useAmbientRenderUi(): AmbientRenderUi | null {\n return useContext(AmbientRenderUiContext);\n}\n","import { useEffect, useMemo, useState } from 'react';\nimport {\n GENUI_TREE_COMPONENT,\n type GenerativeUIItem,\n type GenerativeUIOptions,\n type GenerativeUIState,\n type GenuiCatalogLike,\n type GenuiIssueLike,\n type GenuiRegistry,\n type GenuiRenderer,\n type ResolveComponent,\n} from './types.js';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * Normalize whatever carries a pushed component: a transcript `ui` block, a `data-ui` message part\n * (`{ type: 'data-ui', id, data }`), a stored `{ id, component, props, version? }` entry. `null` for\n * anything else.\n */\nexport function toGenerativeUIItem(part: unknown): GenerativeUIItem | null {\n if (!isRecord(part)) return null;\n const source = part.type === 'data-ui' && isRecord(part.data) ? part.data : part;\n if (typeof source.component !== 'string') return null;\n const id =\n typeof source.id === 'string' ? source.id : typeof part.id === 'string' ? part.id : null;\n if (id === null) return null;\n return {\n id,\n component: source.component,\n props: isRecord(source.props) ? source.props : {},\n version: typeof source.version === 'number' ? source.version : null,\n toolCallId: typeof source.toolCallId === 'string' ? source.toolCallId : null,\n };\n}\n\ntype Resolution =\n | { status: 'ready'; Component: GenuiRenderer }\n | { status: 'loading' }\n | { status: 'unknown' }\n | { status: 'error'; error: unknown };\n\ninterface CacheEntry {\n resolution: Resolution;\n settled: Promise<void> | null;\n}\n\nconst resolverCaches = new WeakMap<ResolveComponent, Map<string, CacheEntry>>();\n\nfunction resolveCached(\n resolve: ResolveComponent,\n name: string,\n version: number | null,\n): CacheEntry {\n let cache = resolverCaches.get(resolve);\n if (cache === undefined) {\n cache = new Map();\n resolverCaches.set(resolve, cache);\n }\n const key = `${name}@${version ?? ''}`;\n const hit = cache.get(key);\n if (hit !== undefined) return hit;\n let entry: CacheEntry;\n try {\n const result = resolve(name, version);\n if (result instanceof Promise) {\n entry = { resolution: { status: 'loading' }, settled: null };\n entry.settled = result.then(\n (Component) => {\n entry.resolution = Component ? { status: 'ready', Component } : { status: 'unknown' };\n },\n (error: unknown) => {\n entry.resolution = { status: 'error', error };\n },\n );\n } else {\n entry = {\n resolution: result ? { status: 'ready', Component: result } : { status: 'unknown' },\n settled: null,\n };\n }\n } catch (error) {\n entry = { resolution: { status: 'error', error }, settled: null };\n }\n cache.set(key, entry);\n return entry;\n}\n\n/**\n * Which renderer draws `name`: the registry first, then `resolveComponent` (cached; async results\n * re-render the caller when they land). `fallbackTree` answers the tree component when the\n * registry does not override it.\n */\nexport function useResolvedComponent(\n name: string,\n version: number | null,\n registry: GenuiRegistry,\n resolve: ResolveComponent | undefined,\n fallbackTree?: GenuiRenderer,\n): Resolution {\n const [, setTick] = useState(0);\n const own = registry[name];\n const entry =\n own !== undefined || resolve === undefined ? null : resolveCached(resolve, name, version);\n const pending = entry?.resolution.status === 'loading' ? entry.settled : null;\n useEffect(() => {\n if (pending === null) return;\n let live = true;\n void pending.then(() => {\n if (live) setTick((tick) => tick + 1);\n });\n return () => {\n live = false;\n };\n }, [pending]);\n if (own !== undefined) return { status: 'ready', Component: own };\n if (entry !== null && entry.resolution.status !== 'unknown') return entry.resolution;\n if (name === GENUI_TREE_COMPONENT && fallbackTree !== undefined) {\n return { status: 'ready', Component: fallbackTree };\n }\n return { status: 'unknown' };\n}\n\ntype Validation =\n | { status: 'ok'; props: Record<string, unknown> }\n | { status: 'pending' }\n | { status: 'invalid'; issues: GenuiIssueLike[] };\n\n/**\n * Props checked against the catalog: synchronously when the catalog can (`validateSync`), else\n * after the async check settles. A component the catalog does not know passes through unchecked.\n */\nexport function useValidatedProps(\n catalog: GenuiCatalogLike | undefined,\n name: string,\n props: Record<string, unknown>,\n): Validation {\n const checked = catalog?.has(name) === true;\n const immediate = useMemo<Validation | null>(() => {\n if (!checked || catalog === undefined) return { status: 'ok', props };\n const result = catalog.validateSync?.(name, props);\n if (result === undefined) return null;\n return result.ok\n ? { status: 'ok', props: result.value }\n : { status: 'invalid', issues: result.issues };\n }, [checked, catalog, name, props]);\n const [late, setLate] = useState<{ props: Record<string, unknown>; result: Validation } | null>(\n null,\n );\n useEffect(() => {\n if (immediate !== null || catalog === undefined) return;\n let live = true;\n catalog.validate(name, props).then(\n (result) => {\n if (!live) return;\n setLate({\n props,\n result: result.ok\n ? { status: 'ok', props: result.value }\n : { status: 'invalid', issues: result.issues },\n });\n },\n (error: unknown) => {\n if (!live) return;\n setLate({\n props,\n result: {\n status: 'invalid',\n issues: [{ path: [], message: error instanceof Error ? error.message : String(error) }],\n },\n });\n },\n );\n return () => {\n live = false;\n };\n }, [immediate, catalog, name, props]);\n if (immediate !== null) return immediate;\n return late !== null && late.props === props ? late.result : { status: 'pending' };\n}\n\n/** {@link import('./generative-ui.js').useGenerativeUI}, with the tree renderer handed in (avoids an import cycle). */\nexport function useGenerativeUIState(\n part: unknown,\n options: GenerativeUIOptions,\n fallbackTree: GenuiRenderer | undefined,\n): GenerativeUIState | null {\n const item = useMemo(() => toGenerativeUIItem(part), [part]);\n const name = item?.component ?? '';\n const resolution = useResolvedComponent(\n name,\n item?.version ?? null,\n options.registry,\n item === null ? undefined : options.resolveComponent,\n fallbackTree,\n );\n const empty = useMemo<Record<string, unknown>>(() => ({}), []);\n // A tree is validated node by node as it renders, not as one component.\n const validation = useValidatedProps(\n item === null || name === GENUI_TREE_COMPONENT ? undefined : options.catalog,\n name,\n item?.props ?? empty,\n );\n if (item === null) return null;\n if (resolution.status === 'loading') return { status: 'loading', item };\n if (resolution.status === 'unknown') return { status: 'problem', reason: 'unknown', item };\n if (resolution.status === 'error') {\n return { status: 'problem', reason: 'error', item, error: resolution.error };\n }\n if (validation.status === 'pending') return { status: 'loading', item };\n if (validation.status === 'invalid') {\n return { status: 'problem', reason: 'invalid', item, issues: validation.issues };\n }\n return { status: 'ready', item, Component: resolution.Component, props: validation.props };\n}\n","import type { ComponentType, ReactNode } from 'react';\n\n/** The `ui` frame component a composed tree is pushed under (`GENUI_TREE_COMPONENT` of `@dudousxd/nestjs-agent-core/genui`). */\nexport const GENUI_TREE_COMPONENT = 'genui:tree';\n\n/** One pushed component, normalized from whatever carried it (a transcript block, a `data-ui` part, a stored entry). */\nexport interface GenerativeUIItem {\n id: string;\n component: string;\n props: Record<string, unknown>;\n version: number | null;\n toolCallId: string | null;\n}\n\n/** A node of a composed tree (`genui:tree` frames): `{ type, props, children? }`. */\nexport interface GenerativeUIElement {\n type: string;\n props: Record<string, unknown>;\n children?: GenerativeUIElement[];\n}\n\n/**\n * An app's renderer for one component: it receives the component's props spread, plus `children`\n * when it is a layout node in a tree. Any React component — the library never styles anything.\n */\nexport type GenuiRenderer<P = any> = ComponentType<P & { children?: ReactNode }>;\n\n/** Component name → the app's renderer. */\nexport type GenuiRegistry = Record<string, GenuiRenderer>;\n\n/**\n * Resolve a component the registry does not have — typically a tenant's own component, fetched for\n * the exact `version` a message was rendered with. Return `null`/`undefined` for \"no such\n * component\". May be async; results are cached per resolver, name and version.\n */\nexport type ResolveComponent = (\n name: string,\n version: number | null,\n) => GenuiRenderer | null | undefined | Promise<GenuiRenderer | null | undefined>;\n\nexport interface GenuiIssueLike {\n path: (string | number)[];\n message: string;\n}\n\ntype ValidationLike =\n | { ok: true; value: Record<string, unknown> }\n | { ok: false; issues: GenuiIssueLike[] };\n\n/**\n * What the renderer needs from a catalog to validate props before drawing them. A `Catalog` from\n * `@dudousxd/nestjs-agent-core/genui` satisfies it; declared structurally so any catalog-shaped\n * object does too.\n */\nexport interface GenuiCatalogLike {\n has(name: string): boolean;\n validate(name: string, props: unknown): Promise<ValidationLike>;\n validateSync?(name: string, props: unknown): ValidationLike | undefined;\n}\n\nexport interface GenerativeUIOptions {\n registry: GenuiRegistry;\n /** Validate props against it before rendering. Components the catalog does not know render unvalidated. */\n catalog?: GenuiCatalogLike;\n resolveComponent?: ResolveComponent;\n /**\n * Draws a composed tree frame (`genui:tree`) whole — e.g. through json-render (see the\n * `/genui/json-render` subpath's `GenuiProvider`). Omitted → trees render node by node through\n * `registry`.\n */\n treeRenderer?: GenuiRenderer<{ root?: GenerativeUIElement }>;\n}\n\n/** Why an item did not render. */\nexport type GenerativeUIProblem =\n | { reason: 'unknown'; item: GenerativeUIItem }\n | { reason: 'invalid'; item: GenerativeUIItem; issues: GenuiIssueLike[] }\n | { reason: 'error'; item: GenerativeUIItem; error: unknown };\n\nexport type GenerativeUIState =\n | {\n status: 'ready';\n item: GenerativeUIItem;\n Component: GenuiRenderer;\n props: Record<string, unknown>;\n }\n | { status: 'loading'; item: GenerativeUIItem }\n | ({ status: 'problem' } & GenerativeUIProblem);\n"],"mappings":"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","names":["useMemo","createContext","useContext","useMemo","Component","createContext","useContext","useMemo","Renderer","useMemo","useContext","useContext","Renderer","jsx","Renderer","useMemo","GenuiProvider"]}
|
|
1
|
+
{"version":3,"sources":["../src/genui/json-render.tsx","../src/genui/generative-ui.tsx","../src/components/ambient-ui.ts","../src/genui/use-generative-ui.ts","../src/genui/types.ts"],"sourcesContent":["/**\n * Optional json-render adapter for tree frames (`@dudousxd/nestjs-agent-react/genui/json-render`).\n * Needs the optional peer `@json-render/react` (>= 0.21). Use it when your app already renders\n * json-render specs and wants tree frames to go through the same `Renderer` (its state, actions and\n * visibility machinery); otherwise `<GenerativeUI>` renders trees natively.\n *\n * ```tsx\n * import { GenuiProvider } from '@dudousxd/nestjs-agent-react/genui/json-render';\n *\n * <GenuiProvider registry={registry} catalog={catalog} jsonRender>…</GenuiProvider>\n * ```\n */\nimport { type ComponentRegistry, JSONUIProvider, Renderer } from '@json-render/react';\nimport { type ReactNode, useMemo } from 'react';\nimport {\n GenuiProvider as BaseGenuiProvider,\n type GenuiProviderProps as BaseGenuiProviderProps,\n} from './generative-ui.js';\nimport type { GenerativeUIElement, GenuiRegistry, GenuiRenderer } from './types.js';\n\n/** A json-render flat spec: `{ root, elements: { [id]: { type, props, children } } }`. */\nexport interface JsonRenderFlatSpec {\n root: string;\n elements: Record<string, { type: string; props: Record<string, unknown>; children: string[] }>;\n}\n\n/** A tree frame's `root` as a json-render flat spec (ids `el-0`, `el-1`, … depth first). */\nexport function treeToJsonRenderSpec(root: GenerativeUIElement): JsonRenderFlatSpec {\n const elements: JsonRenderFlatSpec['elements'] = {};\n let next = 0;\n const visit = (node: GenerativeUIElement): string => {\n const id = `el-${next++}`;\n const entry = { type: node.type, props: node.props ?? {}, children: [] as string[] };\n elements[id] = entry;\n for (const child of node.children ?? []) entry.children.push(visit(child));\n return id;\n };\n return { root: visit(root), elements };\n}\n\n/**\n * Adapt a genui registry (renderers taking props + `children`) to a json-render registry (renderers\n * taking `{ element, children }`), so one set of components serves both.\n */\nexport function toJsonRenderRegistry(registry: GenuiRegistry): ComponentRegistry {\n return Object.fromEntries(\n Object.entries(registry).map(([name, Renderer]) => [\n name,\n ({ element, children }: { element: { props?: unknown }; children?: ReactNode }) => (\n <Renderer {...((element.props ?? {}) as Record<string, unknown>)}>{children}</Renderer>\n ),\n ]),\n );\n}\n\nexport interface JsonRenderTreeProps {\n root?: GenerativeUIElement;\n registry: ComponentRegistry;\n /** json-render state the spec's `$state` expressions read. */\n initialState?: Record<string, unknown>;\n}\n\n/** Render a tree frame's `root` with json-render. */\nexport function JsonRenderTree({ root, registry, initialState }: JsonRenderTreeProps) {\n const spec = useMemo(() => (root === undefined ? null : treeToJsonRenderSpec(root)), [root]);\n if (spec === null) return null;\n return (\n <JSONUIProvider registry={registry} {...(initialState !== undefined ? { initialState } : {})}>\n <Renderer spec={spec as never} registry={registry} />\n </JSONUIProvider>\n );\n}\n\n/** A registry entry for `genui:tree` that renders through json-render with `registry`. */\nexport function jsonRenderTree(registry: ComponentRegistry): GenuiRenderer<{\n root?: GenerativeUIElement;\n}> {\n return function JsonRenderTreeFrame({ root }: { root?: GenerativeUIElement }) {\n return <JsonRenderTree registry={registry} {...(root !== undefined ? { root } : {})} />;\n };\n}\n\nexport interface GenuiProviderProps extends BaseGenuiProviderProps {\n /**\n * Draw composed tree frames through json-render: pass your json-render registry, or `true` to\n * derive one from `registry` ({@link toJsonRenderRegistry}). Omitted → trees render natively.\n * An explicit `treeRenderer` wins over it.\n */\n jsonRender?: ComponentRegistry | true;\n}\n\n/** `GenuiProvider` from `/genui`, plus the {@link GenuiProviderProps.jsonRender} option. */\nexport function GenuiProvider({ jsonRender, treeRenderer, registry, ...rest }: GenuiProviderProps) {\n const derived = useMemo(() => {\n if (jsonRender === undefined) return undefined;\n return jsonRenderTree(jsonRender === true ? toJsonRenderRegistry(registry ?? {}) : jsonRender);\n }, [jsonRender, registry]);\n const tree = treeRenderer ?? derived;\n return (\n <BaseGenuiProvider\n {...rest}\n {...(registry !== undefined ? { registry } : {})}\n {...(tree !== undefined ? { treeRenderer: tree } : {})}\n />\n );\n}\n","import {\n Component,\n type Context,\n type ErrorInfo,\n type ReactNode,\n createContext,\n useCallback,\n useContext,\n useMemo,\n} from 'react';\nimport { AmbientRenderUiContext, sharedContext } from '../components/ambient-ui.js';\nimport type { TranscriptUiBlock } from '../transcript/model.js';\nimport type {\n GenerativeUIElement,\n GenerativeUIItem,\n GenerativeUIOptions,\n GenerativeUIProblem,\n GenerativeUIState,\n GenuiRegistry,\n GenuiRenderer,\n} from './types.js';\nimport {\n useGenerativeUIState,\n useResolvedComponent,\n useValidatedProps,\n} from './use-generative-ui.js';\n\n/** What to draw instead of a component that did not render. Default: nothing. */\nexport type GenerativeUIFallback = ReactNode | ((problem: GenerativeUIProblem) => ReactNode);\n\ninterface TreeScope {\n fallbackText?: string;\n componentVersions?: Record<string, number>;\n options: GenerativeUIOptions;\n fallback: GenerativeUIFallback | undefined;\n loading: ReactNode;\n onError: ((error: unknown, item: GenerativeUIItem) => void) | undefined;\n}\n\nconst TreeScopeContext = createContext<TreeScope | null>(null);\n\nfunction renderFallback(fallback: GenerativeUIFallback | undefined, problem: GenerativeUIProblem) {\n if (typeof fallback === 'function') return fallback(problem);\n if (fallback !== undefined) return fallback;\n return problem.item.fallbackText !== undefined ? (\n <span style={{ whiteSpace: 'pre-wrap' }}>{problem.item.fallbackText}</span>\n ) : null;\n}\n\ninterface BoundaryProps {\n item: GenerativeUIItem;\n fallback: GenerativeUIFallback | undefined;\n onError: ((error: unknown, item: GenerativeUIItem) => void) | undefined;\n children: ReactNode;\n}\n\n/** One failing renderer takes down its own item, never the message around it. */\nclass ItemBoundary extends Component<BoundaryProps, { error: unknown; failed: boolean }> {\n override state = { error: undefined as unknown, failed: false };\n\n static getDerivedStateFromError(error: unknown) {\n return { error, failed: true };\n }\n\n override componentDidCatch(error: unknown, _info: ErrorInfo) {\n this.props.onError?.(error, this.props.item);\n }\n\n override componentDidUpdate(previous: BoundaryProps) {\n // New props for the same item (a streamed update) get a fresh chance to render.\n if (this.state.failed && previous.item !== this.props.item) {\n this.setState({ error: undefined, failed: false });\n }\n }\n\n override render() {\n if (this.state.failed) {\n return renderFallback(this.props.fallback, {\n reason: 'error',\n item: this.props.item,\n error: this.state.error,\n });\n }\n return this.props.children;\n }\n}\n\nfunction nodeItem(node: GenerativeUIElement, id: string): GenerativeUIItem {\n return { id, component: node.type, props: node.props ?? {}, version: null, toolCallId: null };\n}\n\n/** One tree node: resolved and validated like a top-level item, children rendered as its `children`. */\nfunction TreeNode({ node, id }: { node: GenerativeUIElement; id: string }) {\n const scope = useContext(TreeScopeContext);\n const item = useMemo(() => nodeItem(node, id), [node, id]);\n const storedVersion = scope?.componentVersions?.[node.type];\n const definition = scope?.options.catalog?.get?.(node.type);\n const incompatible =\n storedVersion !== undefined &&\n definition !== undefined &&\n storedVersion !== (definition.version ?? 1);\n const resolution = useResolvedComponent(\n node.type,\n storedVersion ?? null,\n incompatible ? {} : (scope?.options.registry ?? {}),\n scope?.options.resolveComponent,\n );\n const validation = useValidatedProps(\n incompatible ? undefined : scope?.options.catalog,\n node.type,\n item.props,\n );\n if (scope === null) return null;\n if (resolution.status === 'loading' || validation.status === 'pending') return scope.loading;\n if (resolution.status === 'unknown') {\n if (scope.fallback === undefined && scope.fallbackText !== undefined)\n throw new Error('Unknown tree component');\n return renderFallback(scope.fallback, { reason: 'unknown', item });\n }\n if (resolution.status === 'error') {\n if (scope.fallback === undefined && scope.fallbackText !== undefined) throw resolution.error;\n return renderFallback(scope.fallback, { reason: 'error', item, error: resolution.error });\n }\n if (validation.status === 'invalid') {\n if (scope.fallback === undefined && scope.fallbackText !== undefined)\n throw new Error('Invalid tree component');\n return renderFallback(scope.fallback, { reason: 'invalid', item, issues: validation.issues });\n }\n const Renderer = resolution.Component;\n const children = Array.isArray(node.children) ? node.children : [];\n return (\n <Renderer {...validation.props}>\n {children.length > 0\n ? children.map((child, index) => {\n const childId = `${id}.${index}`;\n if (scope.fallback === undefined && scope.fallbackText !== undefined)\n return <TreeNode key={childId} node={child} id={childId} />;\n return (\n <ItemBoundary\n key={childId}\n item={nodeItem(child, childId)}\n fallback={scope.fallback}\n onError={scope.onError}\n >\n <TreeNode node={child} id={childId} />\n </ItemBoundary>\n );\n })\n : undefined}\n </Renderer>\n );\n}\n\n/**\n * Renders a `genui:tree` frame's `{ root }` node by node through the same registry, catalog and\n * resolver as top-level components. Used automatically for tree frames unless a `treeRenderer`\n * (e.g. the json-render one) or a registry entry for `genui:tree` takes over.\n */\nexport function GenuiTree({ root }: { root?: GenerativeUIElement }) {\n if (root === undefined || root === null || typeof root.type !== 'string') return null;\n return <TreeNode node={root} id=\"root\" />;\n}\n\nexport interface GenerativeUIScopeProps extends GenerativeUIOptions {\n fallbackText?: string;\n componentVersions?: Record<string, number>;\n fallback?: GenerativeUIFallback;\n loading?: ReactNode;\n onError?: (error: unknown, item: GenerativeUIItem) => void;\n children?: ReactNode;\n}\n\n/**\n * The registry, catalog and fallbacks tree nodes render with. `<GenerativeUI>` provides it; wrap\n * your own chrome in it when you draw `useGenerativeUI`'s `Component` yourself and it may be a tree.\n */\nexport function GenerativeUIScope({\n registry,\n catalog,\n resolveComponent,\n fallback,\n fallbackText,\n componentVersions,\n loading = null,\n onError,\n children,\n}: GenerativeUIScopeProps) {\n const scope = useMemo<TreeScope>(\n () => ({\n options: {\n registry,\n ...(catalog !== undefined ? { catalog } : {}),\n ...(resolveComponent !== undefined ? { resolveComponent } : {}),\n },\n fallback,\n ...(componentVersions !== undefined ? { componentVersions } : {}),\n ...(fallbackText !== undefined ? { fallbackText } : {}),\n loading,\n onError,\n }),\n [\n registry,\n catalog,\n resolveComponent,\n fallback,\n fallbackText,\n componentVersions,\n loading,\n onError,\n ],\n );\n return <TreeScopeContext.Provider value={scope}>{children}</TreeScopeContext.Provider>;\n}\n\n/** What `<GenuiProvider>` hands every `<GenerativeUI>` / `useGenerativeUI` below it. */\nexport interface GenuiProviderValue extends Partial<GenerativeUIOptions> {\n fallback?: GenerativeUIFallback;\n loading?: ReactNode;\n onError?: (error: unknown, item: GenerativeUIItem) => void;\n}\n\n// Shared by key: `/genui` and `/genui/json-render` are separate bundles, and a provider from one\n// must reach a `<GenerativeUI>` from the other.\nconst GenuiContext: Context<GenuiProviderValue | null> = sharedContext<GenuiProviderValue>(\n '@dudousxd/nestjs-agent-react:genui-provider',\n);\n\n/** The enclosing `<GenuiProvider>`'s settings, or `null` outside one. */\nexport function useGenuiProvider(): GenuiProviderValue | null {\n return useContext(GenuiContext);\n}\n\nexport interface GenuiProviderProps extends GenuiProviderValue {\n children?: ReactNode;\n}\n\n/**\n * Set generative UI up once, at the app root: the registry of your renderers, the catalog to\n * validate against, a resolver for components the registry lacks, and what to draw when one does\n * not render. Every `<GenerativeUI>` below reads it (its own props still win), and `MessageItem` /\n * `MessageList` draw pushed components with it — no `renderUi` per message.\n *\n * ```tsx\n * <GenuiProvider registry={registry} catalog={catalog} fallback={({ item }) => <Unknown name={item.component} />}>\n * <App />\n * </GenuiProvider>\n * ```\n */\nexport function GenuiProvider({\n registry,\n catalog,\n resolveComponent,\n treeRenderer,\n fallback,\n loading,\n onError,\n children,\n}: GenuiProviderProps) {\n const value = useMemo<GenuiProviderValue>(\n () => ({\n ...(registry !== undefined ? { registry } : {}),\n ...(catalog !== undefined ? { catalog } : {}),\n ...(resolveComponent !== undefined ? { resolveComponent } : {}),\n ...(treeRenderer !== undefined ? { treeRenderer } : {}),\n ...(fallback !== undefined ? { fallback } : {}),\n ...(loading !== undefined ? { loading } : {}),\n ...(onError !== undefined ? { onError } : {}),\n }),\n [registry, catalog, resolveComponent, treeRenderer, fallback, loading, onError],\n );\n const renderUi = useCallback((block: TranscriptUiBlock) => <GenerativeUI part={block} />, []);\n return (\n <GenuiContext.Provider value={value}>\n <AmbientRenderUiContext.Provider value={renderUi}>{children}</AmbientRenderUiContext.Provider>\n </GenuiContext.Provider>\n );\n}\n\nconst NO_REGISTRY: GenuiRegistry = {};\n\n/** Provider settings overlaid with explicit ones (explicit wins), memoized on their parts. */\nfunction useMergedOptions(own: Partial<GenerativeUIOptions>): GenerativeUIOptions {\n const provided = useContext(GenuiContext);\n const registry = own.registry ?? provided?.registry ?? NO_REGISTRY;\n const catalog = own.catalog ?? provided?.catalog;\n const resolveComponent = own.resolveComponent ?? provided?.resolveComponent;\n const treeRenderer = own.treeRenderer ?? provided?.treeRenderer;\n return useMemo(\n () => ({\n registry,\n ...(catalog !== undefined ? { catalog } : {}),\n ...(resolveComponent !== undefined ? { resolveComponent } : {}),\n ...(treeRenderer !== undefined ? { treeRenderer } : {}),\n }),\n [registry, catalog, resolveComponent, treeRenderer],\n );\n}\n\n/**\n * The headless half of {@link GenerativeUI}: what to draw for one pushed component, or `null` when\n * `part` is not one. Options default to the enclosing `<GenuiProvider>`'s.\n */\nexport function useGenerativeUI(\n part: unknown,\n options: Partial<GenerativeUIOptions> = {},\n): GenerativeUIState | null {\n const merged = useMergedOptions(options);\n return useGenerativeUIState(part, merged, merged.treeRenderer ?? (GenuiTree as GenuiRenderer));\n}\n\nexport interface GenerativeUIProps extends Partial<GenerativeUIOptions> {\n /** A transcript `ui` block, a `data-ui` message part, or a stored `{ id, component, props, version? }`. */\n part: TranscriptUiBlock | GenerativeUIItem | unknown;\n /** Drawn for an unknown component, invalid props, or a renderer that threw. Default: the provider's, then the item's persisted text. */\n fallback?: GenerativeUIFallback;\n /** Drawn while a resolver or an async validation is pending. Default: the provider's, else nothing. */\n loading?: ReactNode;\n /** A renderer threw (the item shows `fallback`). */\n onError?: (error: unknown, item: GenerativeUIItem) => void;\n}\n\n/**\n * Draws one server-pushed component with the app's own renderer. Headless: it adds no element and\n * no style of its own — only what the registry's component renders (and whatever `fallback` /\n * `loading` you pass). Everything but `part` defaults to the enclosing `<GenuiProvider>`.\n *\n * ```tsx\n * <GenerativeUI part={block} />\n * ```\n */\nexport function GenerativeUI({\n part,\n registry,\n catalog,\n resolveComponent,\n treeRenderer,\n fallback: ownFallback,\n loading: ownLoading,\n onError: ownOnError,\n}: GenerativeUIProps) {\n const provided = useContext(GenuiContext);\n const fallback = ownFallback !== undefined ? ownFallback : provided?.fallback;\n const loading = ownLoading ?? provided?.loading ?? null;\n const onError = ownOnError ?? provided?.onError;\n const options = useMergedOptions({\n ...(registry !== undefined ? { registry } : {}),\n ...(catalog !== undefined ? { catalog } : {}),\n ...(resolveComponent !== undefined ? { resolveComponent } : {}),\n ...(treeRenderer !== undefined ? { treeRenderer } : {}),\n });\n const state = useGenerativeUIState(\n part,\n options,\n options.treeRenderer ?? (GenuiTree as GenuiRenderer),\n );\n if (state === null) return null;\n if (state.status === 'loading') return loading;\n if (state.status === 'problem') {\n const { status: _status, ...problem } = state;\n return renderFallback(fallback, problem);\n }\n const Renderer = state.Component;\n return (\n <GenerativeUIScope\n {...options}\n {...(state.item.componentVersions !== undefined\n ? { componentVersions: state.item.componentVersions }\n : {})}\n {...(state.item.fallbackText !== undefined ? { fallbackText: state.item.fallbackText } : {})}\n fallback={fallback}\n loading={loading}\n {...(onError !== undefined ? { onError } : {})}\n >\n <ItemBoundary item={state.item} fallback={fallback} onError={onError}>\n <Renderer {...state.props} />\n </ItemBoundary>\n </GenerativeUIScope>\n );\n}\n","import { type Context, type ReactNode, createContext, useContext } from 'react';\nimport type { TranscriptUiBlock } from '../transcript/model.js';\n\n/** Draws a server-pushed component block. */\nexport type AmbientRenderUi = (block: TranscriptUiBlock) => ReactNode;\n\n/**\n * A context stored on `globalThis` under a registered symbol. Each entry point (`.`, `/genui`,\n * `/genui/json-render`) is its own bundle with its own copy of every module, so a context one entry\n * provides and another reads has to be found by key, not by module identity.\n */\nexport function sharedContext<T>(key: string): Context<T | null> {\n const symbol = Symbol.for(key);\n const holder = globalThis as Record<symbol, Context<T | null> | undefined>;\n const existing = holder[symbol];\n if (existing !== undefined) return existing;\n const created = createContext<T | null>(null);\n holder[symbol] = created;\n return created;\n}\n\n/**\n * How pushed components are drawn when no `renderUi` is passed — set by `<GenuiProvider>` (the\n * `/genui` subpath) and read by `MessageItem`.\n */\nexport const AmbientRenderUiContext: Context<AmbientRenderUi | null> =\n sharedContext<AmbientRenderUi>('@dudousxd/nestjs-agent-react:ambient-render-ui');\n\n/**\n * The renderer `<GenuiProvider>` installed for pushed components, or `null` outside one. For a\n * custom transcript built on `useTranscriptItem`: draw a `ui` block with it and you get what\n * `MessageItem` draws.\n */\nexport function useAmbientRenderUi(): AmbientRenderUi | null {\n return useContext(AmbientRenderUiContext);\n}\n","import { useEffect, useMemo, useState } from 'react';\nimport {\n GENUI_TREE_COMPONENT,\n type GenerativeUIItem,\n type GenerativeUIOptions,\n type GenerativeUIState,\n type GenuiCatalogLike,\n type GenuiIssueLike,\n type GenuiRegistry,\n type GenuiRenderer,\n type ResolveComponent,\n} from './types.js';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * Normalize whatever carries a pushed component: a transcript `ui` block, a `data-ui` message part\n * (`{ type: 'data-ui', id, data }`), a stored `{ id, component, props, version? }` entry. `null` for\n * anything else.\n */\nexport function toGenerativeUIItem(part: unknown): GenerativeUIItem | null {\n if (!isRecord(part)) return null;\n const source = part.type === 'data-ui' && isRecord(part.data) ? part.data : part;\n if (typeof source.component !== 'string') return null;\n const id =\n typeof source.id === 'string' ? source.id : typeof part.id === 'string' ? part.id : null;\n if (id === null) return null;\n return {\n id,\n component: source.component,\n ...(isRecord(source.componentVersions) &&\n Object.values(source.componentVersions).every(\n (version) => typeof version === 'number' && Number.isSafeInteger(version) && version > 0,\n )\n ? { componentVersions: { ...source.componentVersions } as Record<string, number> }\n : {}),\n ...(typeof source.fallbackText === 'string' ? { fallbackText: source.fallbackText } : {}),\n props: isRecord(source.props) ? source.props : {},\n version: typeof source.version === 'number' ? source.version : null,\n toolCallId: typeof source.toolCallId === 'string' ? source.toolCallId : null,\n };\n}\n\ntype Resolution =\n | { status: 'ready'; Component: GenuiRenderer }\n | { status: 'loading' }\n | { status: 'unknown' }\n | { status: 'error'; error: unknown };\n\ninterface CacheEntry {\n resolution: Resolution;\n settled: Promise<void> | null;\n}\n\nconst resolverCaches = new WeakMap<ResolveComponent, Map<string, CacheEntry>>();\n\nfunction resolveCached(\n resolve: ResolveComponent,\n name: string,\n version: number | null,\n): CacheEntry {\n let cache = resolverCaches.get(resolve);\n if (cache === undefined) {\n cache = new Map();\n resolverCaches.set(resolve, cache);\n }\n const key = `${name}@${version ?? ''}`;\n const hit = cache.get(key);\n if (hit !== undefined) return hit;\n let entry: CacheEntry;\n try {\n const result = resolve(name, version);\n if (result instanceof Promise) {\n entry = { resolution: { status: 'loading' }, settled: null };\n entry.settled = result.then(\n (Component) => {\n entry.resolution = Component ? { status: 'ready', Component } : { status: 'unknown' };\n },\n (error: unknown) => {\n entry.resolution = { status: 'error', error };\n },\n );\n } else {\n entry = {\n resolution: result ? { status: 'ready', Component: result } : { status: 'unknown' },\n settled: null,\n };\n }\n } catch (error) {\n entry = { resolution: { status: 'error', error }, settled: null };\n }\n cache.set(key, entry);\n return entry;\n}\n\n/**\n * Which renderer draws `name`: the registry first, then `resolveComponent` (cached; async results\n * re-render the caller when they land). `fallbackTree` answers the tree component when the\n * registry does not override it.\n */\nexport function useResolvedComponent(\n name: string,\n version: number | null,\n registry: GenuiRegistry,\n resolve: ResolveComponent | undefined,\n fallbackTree?: GenuiRenderer,\n): Resolution {\n const [, setTick] = useState(0);\n const own = registry[name];\n const entry =\n own !== undefined || resolve === undefined ? null : resolveCached(resolve, name, version);\n const pending = entry?.resolution.status === 'loading' ? entry.settled : null;\n useEffect(() => {\n if (pending === null) return;\n let live = true;\n void pending.then(() => {\n if (live) setTick((tick) => tick + 1);\n });\n return () => {\n live = false;\n };\n }, [pending]);\n if (own !== undefined) return { status: 'ready', Component: own };\n if (entry !== null && entry.resolution.status !== 'unknown') return entry.resolution;\n if (name === GENUI_TREE_COMPONENT && fallbackTree !== undefined) {\n return { status: 'ready', Component: fallbackTree };\n }\n return { status: 'unknown' };\n}\n\ntype Validation =\n | { status: 'ok'; props: Record<string, unknown> }\n | { status: 'pending' }\n | { status: 'invalid'; issues: GenuiIssueLike[] };\n\n/**\n * Props checked against the catalog: synchronously when the catalog can (`validateSync`), else\n * after the async check settles. A component the catalog does not know passes through unchecked.\n */\nexport function useValidatedProps(\n catalog: GenuiCatalogLike | undefined,\n name: string,\n props: Record<string, unknown>,\n): Validation {\n const checked = catalog?.has(name) === true;\n const immediate = useMemo<Validation | null>(() => {\n if (!checked || catalog === undefined) return { status: 'ok', props };\n const result = catalog.validateSync?.(name, props);\n if (result === undefined) return null;\n return result.ok\n ? { status: 'ok', props: result.value }\n : { status: 'invalid', issues: result.issues };\n }, [checked, catalog, name, props]);\n const [late, setLate] = useState<{ props: Record<string, unknown>; result: Validation } | null>(\n null,\n );\n useEffect(() => {\n if (immediate !== null || catalog === undefined) return;\n let live = true;\n catalog.validate(name, props).then(\n (result) => {\n if (!live) return;\n setLate({\n props,\n result: result.ok\n ? { status: 'ok', props: result.value }\n : { status: 'invalid', issues: result.issues },\n });\n },\n (error: unknown) => {\n if (!live) return;\n setLate({\n props,\n result: {\n status: 'invalid',\n issues: [{ path: [], message: error instanceof Error ? error.message : String(error) }],\n },\n });\n },\n );\n return () => {\n live = false;\n };\n }, [immediate, catalog, name, props]);\n if (immediate !== null) return immediate;\n return late !== null && late.props === props ? late.result : { status: 'pending' };\n}\n\n/** {@link import('./generative-ui.js').useGenerativeUI}, with the tree renderer handed in (avoids an import cycle). */\nexport function useGenerativeUIState(\n part: unknown,\n options: GenerativeUIOptions,\n fallbackTree: GenuiRenderer | undefined,\n): GenerativeUIState | null {\n const item = useMemo(() => toGenerativeUIItem(part), [part]);\n const name = item?.component ?? '';\n const definition = options.catalog?.get?.(name);\n const incompatible =\n item?.fallbackText !== undefined &&\n item.version !== null &&\n definition !== undefined &&\n item.version !== (definition.version ?? 1);\n const resolution = useResolvedComponent(\n name,\n item?.version ?? null,\n incompatible ? {} : options.registry,\n item === null ? undefined : options.resolveComponent,\n fallbackTree,\n );\n const empty = useMemo<Record<string, unknown>>(() => ({}), []);\n // A tree is validated node by node as it renders, not as one component.\n const validation = useValidatedProps(\n item === null || name === GENUI_TREE_COMPONENT || incompatible ? undefined : options.catalog,\n name,\n item?.props ?? empty,\n );\n if (item === null) return null;\n if (\n name === GENUI_TREE_COMPONENT &&\n item.fallbackText !== undefined &&\n (!isRecord(item.props.root) || typeof item.props.root.type !== 'string')\n )\n return {\n status: 'problem',\n reason: 'invalid',\n item,\n issues: [{ path: ['root'], message: 'Invalid UI tree root' }],\n };\n if (resolution.status === 'loading') return { status: 'loading', item };\n if (resolution.status === 'unknown') return { status: 'problem', reason: 'unknown', item };\n if (resolution.status === 'error') {\n return { status: 'problem', reason: 'error', item, error: resolution.error };\n }\n if (validation.status === 'pending') return { status: 'loading', item };\n if (validation.status === 'invalid') {\n return { status: 'problem', reason: 'invalid', item, issues: validation.issues };\n }\n return { status: 'ready', item, Component: resolution.Component, props: validation.props };\n}\n","import type { ComponentType, ReactNode } from 'react';\n\n/** The `ui` frame component a composed tree is pushed under (`GENUI_TREE_COMPONENT` of `@dudousxd/nestjs-agent-core/genui`). */\nexport const GENUI_TREE_COMPONENT = 'genui:tree';\n\n/** One pushed component, normalized from whatever carried it (a transcript block, a `data-ui` part, a stored entry). */\nexport interface GenerativeUIItem {\n fallbackText?: string;\n componentVersions?: Record<string, number>;\n id: string;\n component: string;\n props: Record<string, unknown>;\n version: number | null;\n toolCallId: string | null;\n}\n\n/** A node of a composed tree (`genui:tree` frames): `{ type, props, children? }`. */\nexport interface GenerativeUIElement {\n type: string;\n props: Record<string, unknown>;\n children?: GenerativeUIElement[];\n}\n\n/**\n * An app's renderer for one component: it receives the component's props spread, plus `children`\n * when it is a layout node in a tree. Any React component — the library never styles anything.\n */\nexport type GenuiRenderer<P = any> = ComponentType<P & { children?: ReactNode }>;\n\n/** Component name → the app's renderer. */\nexport type GenuiRegistry = Record<string, GenuiRenderer>;\n\n/**\n * Resolve a component the registry does not have — typically a tenant's own component, fetched for\n * the exact `version` a message was rendered with. Return `null`/`undefined` for \"no such\n * component\". May be async; results are cached per resolver, name and version.\n */\nexport type ResolveComponent = (\n name: string,\n version: number | null,\n) => GenuiRenderer | null | undefined | Promise<GenuiRenderer | null | undefined>;\n\nexport interface GenuiIssueLike {\n path: (string | number)[];\n message: string;\n}\n\ntype ValidationLike =\n | { ok: true; value: Record<string, unknown> }\n | { ok: false; issues: GenuiIssueLike[] };\n\n/**\n * What the renderer needs from a catalog to validate props before drawing them. A `Catalog` from\n * `@dudousxd/nestjs-agent-core/genui` satisfies it; declared structurally so any catalog-shaped\n * object does too.\n */\nexport interface GenuiCatalogLike {\n get?(name: string): { version?: number } | undefined;\n has(name: string): boolean;\n validate(name: string, props: unknown): Promise<ValidationLike>;\n validateSync?(name: string, props: unknown): ValidationLike | undefined;\n}\n\nexport interface GenerativeUIOptions {\n registry: GenuiRegistry;\n /** Validate props against it before rendering. Components the catalog does not know render unvalidated. */\n catalog?: GenuiCatalogLike;\n resolveComponent?: ResolveComponent;\n /**\n * Draws a composed tree frame (`genui:tree`) whole — e.g. through json-render (see the\n * `/genui/json-render` subpath's `GenuiProvider`). Omitted → trees render node by node through\n * `registry`.\n */\n treeRenderer?: GenuiRenderer<{ root?: GenerativeUIElement }>;\n}\n\n/** Why an item did not render. */\nexport type GenerativeUIProblem =\n | { reason: 'unknown'; item: GenerativeUIItem }\n | { reason: 'invalid'; item: GenerativeUIItem; issues: GenuiIssueLike[] }\n | { reason: 'error'; item: GenerativeUIItem; error: unknown };\n\nexport type GenerativeUIState =\n | {\n status: 'ready';\n item: GenerativeUIItem;\n Component: GenuiRenderer;\n props: Record<string, unknown>;\n }\n | { status: 'loading'; item: GenerativeUIItem }\n | ({ status: 'problem' } & GenerativeUIProblem);\n"],"mappings":";AAYA,SAAiC,gBAAgB,gBAAgB;AACjE,SAAyB,WAAAA,gBAAe;;;ACbxC;AAAA,EACE;AAAA,EAIA,iBAAAC;AAAA,EACA;AAAA,EACA,cAAAC;AAAA,EACA,WAAAC;AAAA,OACK;;;ACTP,SAAuC,eAAe,kBAAkB;AAWjE,SAAS,cAAiB,KAAgC;AAC/D,QAAM,SAAS,OAAO,IAAI,GAAG;AAC7B,QAAM,SAAS;AACf,QAAM,WAAW,OAAO,MAAM;AAC9B,MAAI,aAAa,OAAW,QAAO;AACnC,QAAM,UAAU,cAAwB,IAAI;AAC5C,SAAO,MAAM,IAAI;AACjB,SAAO;AACT;AAMO,IAAM,yBACX,cAA+B,gDAAgD;;;AC1BjF,SAAS,WAAW,SAAS,gBAAgB;;;ACGtC,IAAM,uBAAuB;;;ADUpC,SAAS,SAAS,OAAkD;AAClE,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAOO,SAAS,mBAAmB,MAAwC;AACzE,MAAI,CAAC,SAAS,IAAI,EAAG,QAAO;AAC5B,QAAM,SAAS,KAAK,SAAS,aAAa,SAAS,KAAK,IAAI,IAAI,KAAK,OAAO;AAC5E,MAAI,OAAO,OAAO,cAAc,SAAU,QAAO;AACjD,QAAM,KACJ,OAAO,OAAO,OAAO,WAAW,OAAO,KAAK,OAAO,KAAK,OAAO,WAAW,KAAK,KAAK;AACtF,MAAI,OAAO,KAAM,QAAO;AACxB,SAAO;AAAA,IACL;AAAA,IACA,WAAW,OAAO;AAAA,IAClB,GAAI,SAAS,OAAO,iBAAiB,KACrC,OAAO,OAAO,OAAO,iBAAiB,EAAE;AAAA,MACtC,CAAC,YAAY,OAAO,YAAY,YAAY,OAAO,cAAc,OAAO,KAAK,UAAU;AAAA,IACzF,IACI,EAAE,mBAAmB,EAAE,GAAG,OAAO,kBAAkB,EAA4B,IAC/E,CAAC;AAAA,IACL,GAAI,OAAO,OAAO,iBAAiB,WAAW,EAAE,cAAc,OAAO,aAAa,IAAI,CAAC;AAAA,IACvF,OAAO,SAAS,OAAO,KAAK,IAAI,OAAO,QAAQ,CAAC;AAAA,IAChD,SAAS,OAAO,OAAO,YAAY,WAAW,OAAO,UAAU;AAAA,IAC/D,YAAY,OAAO,OAAO,eAAe,WAAW,OAAO,aAAa;AAAA,EAC1E;AACF;AAaA,IAAM,iBAAiB,oBAAI,QAAmD;AAE9E,SAAS,cACP,SACA,MACA,SACY;AACZ,MAAI,QAAQ,eAAe,IAAI,OAAO;AACtC,MAAI,UAAU,QAAW;AACvB,YAAQ,oBAAI,IAAI;AAChB,mBAAe,IAAI,SAAS,KAAK;AAAA,EACnC;AACA,QAAM,MAAM,GAAG,IAAI,IAAI,WAAW,EAAE;AACpC,QAAM,MAAM,MAAM,IAAI,GAAG;AACzB,MAAI,QAAQ,OAAW,QAAO;AAC9B,MAAI;AACJ,MAAI;AACF,UAAM,SAAS,QAAQ,MAAM,OAAO;AACpC,QAAI,kBAAkB,SAAS;AAC7B,cAAQ,EAAE,YAAY,EAAE,QAAQ,UAAU,GAAG,SAAS,KAAK;AAC3D,YAAM,UAAU,OAAO;AAAA,QACrB,CAACC,eAAc;AACb,gBAAM,aAAaA,aAAY,EAAE,QAAQ,SAAS,WAAAA,WAAU,IAAI,EAAE,QAAQ,UAAU;AAAA,QACtF;AAAA,QACA,CAAC,UAAmB;AAClB,gBAAM,aAAa,EAAE,QAAQ,SAAS,MAAM;AAAA,QAC9C;AAAA,MACF;AAAA,IACF,OAAO;AACL,cAAQ;AAAA,QACN,YAAY,SAAS,EAAE,QAAQ,SAAS,WAAW,OAAO,IAAI,EAAE,QAAQ,UAAU;AAAA,QAClF,SAAS;AAAA,MACX;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,YAAQ,EAAE,YAAY,EAAE,QAAQ,SAAS,MAAM,GAAG,SAAS,KAAK;AAAA,EAClE;AACA,QAAM,IAAI,KAAK,KAAK;AACpB,SAAO;AACT;AAOO,SAAS,qBACd,MACA,SACA,UACA,SACA,cACY;AACZ,QAAM,CAAC,EAAE,OAAO,IAAI,SAAS,CAAC;AAC9B,QAAM,MAAM,SAAS,IAAI;AACzB,QAAM,QACJ,QAAQ,UAAa,YAAY,SAAY,OAAO,cAAc,SAAS,MAAM,OAAO;AAC1F,QAAM,UAAU,OAAO,WAAW,WAAW,YAAY,MAAM,UAAU;AACzE,YAAU,MAAM;AACd,QAAI,YAAY,KAAM;AACtB,QAAI,OAAO;AACX,SAAK,QAAQ,KAAK,MAAM;AACtB,UAAI,KAAM,SAAQ,CAAC,SAAS,OAAO,CAAC;AAAA,IACtC,CAAC;AACD,WAAO,MAAM;AACX,aAAO;AAAA,IACT;AAAA,EACF,GAAG,CAAC,OAAO,CAAC;AACZ,MAAI,QAAQ,OAAW,QAAO,EAAE,QAAQ,SAAS,WAAW,IAAI;AAChE,MAAI,UAAU,QAAQ,MAAM,WAAW,WAAW,UAAW,QAAO,MAAM;AAC1E,MAAI,SAAS,wBAAwB,iBAAiB,QAAW;AAC/D,WAAO,EAAE,QAAQ,SAAS,WAAW,aAAa;AAAA,EACpD;AACA,SAAO,EAAE,QAAQ,UAAU;AAC7B;AAWO,SAAS,kBACd,SACA,MACA,OACY;AACZ,QAAM,UAAU,SAAS,IAAI,IAAI,MAAM;AACvC,QAAM,YAAY,QAA2B,MAAM;AACjD,QAAI,CAAC,WAAW,YAAY,OAAW,QAAO,EAAE,QAAQ,MAAM,MAAM;AACpE,UAAM,SAAS,QAAQ,eAAe,MAAM,KAAK;AACjD,QAAI,WAAW,OAAW,QAAO;AACjC,WAAO,OAAO,KACV,EAAE,QAAQ,MAAM,OAAO,OAAO,MAAM,IACpC,EAAE,QAAQ,WAAW,QAAQ,OAAO,OAAO;AAAA,EACjD,GAAG,CAAC,SAAS,SAAS,MAAM,KAAK,CAAC;AAClC,QAAM,CAAC,MAAM,OAAO,IAAI;AAAA,IACtB;AAAA,EACF;AACA,YAAU,MAAM;AACd,QAAI,cAAc,QAAQ,YAAY,OAAW;AACjD,QAAI,OAAO;AACX,YAAQ,SAAS,MAAM,KAAK,EAAE;AAAA,MAC5B,CAAC,WAAW;AACV,YAAI,CAAC,KAAM;AACX,gBAAQ;AAAA,UACN;AAAA,UACA,QAAQ,OAAO,KACX,EAAE,QAAQ,MAAM,OAAO,OAAO,MAAM,IACpC,EAAE,QAAQ,WAAW,QAAQ,OAAO,OAAO;AAAA,QACjD,CAAC;AAAA,MACH;AAAA,MACA,CAAC,UAAmB;AAClB,YAAI,CAAC,KAAM;AACX,gBAAQ;AAAA,UACN;AAAA,UACA,QAAQ;AAAA,YACN,QAAQ;AAAA,YACR,QAAQ,CAAC,EAAE,MAAM,CAAC,GAAG,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAE,CAAC;AAAA,UACxF;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF;AACA,WAAO,MAAM;AACX,aAAO;AAAA,IACT;AAAA,EACF,GAAG,CAAC,WAAW,SAAS,MAAM,KAAK,CAAC;AACpC,MAAI,cAAc,KAAM,QAAO;AAC/B,SAAO,SAAS,QAAQ,KAAK,UAAU,QAAQ,KAAK,SAAS,EAAE,QAAQ,UAAU;AACnF;AAGO,SAAS,qBACd,MACA,SACA,cAC0B;AAC1B,QAAM,OAAO,QAAQ,MAAM,mBAAmB,IAAI,GAAG,CAAC,IAAI,CAAC;AAC3D,QAAM,OAAO,MAAM,aAAa;AAChC,QAAM,aAAa,QAAQ,SAAS,MAAM,IAAI;AAC9C,QAAM,eACJ,MAAM,iBAAiB,UACvB,KAAK,YAAY,QACjB,eAAe,UACf,KAAK,aAAa,WAAW,WAAW;AAC1C,QAAM,aAAa;AAAA,IACjB;AAAA,IACA,MAAM,WAAW;AAAA,IACjB,eAAe,CAAC,IAAI,QAAQ;AAAA,IAC5B,SAAS,OAAO,SAAY,QAAQ;AAAA,IACpC;AAAA,EACF;AACA,QAAM,QAAQ,QAAiC,OAAO,CAAC,IAAI,CAAC,CAAC;AAE7D,QAAM,aAAa;AAAA,IACjB,SAAS,QAAQ,SAAS,wBAAwB,eAAe,SAAY,QAAQ;AAAA,IACrF;AAAA,IACA,MAAM,SAAS;AAAA,EACjB;AACA,MAAI,SAAS,KAAM,QAAO;AAC1B,MACE,SAAS,wBACT,KAAK,iBAAiB,WACrB,CAAC,SAAS,KAAK,MAAM,IAAI,KAAK,OAAO,KAAK,MAAM,KAAK,SAAS;AAE/D,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR;AAAA,MACA,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,GAAG,SAAS,uBAAuB,CAAC;AAAA,IAC9D;AACF,MAAI,WAAW,WAAW,UAAW,QAAO,EAAE,QAAQ,WAAW,KAAK;AACtE,MAAI,WAAW,WAAW,UAAW,QAAO,EAAE,QAAQ,WAAW,QAAQ,WAAW,KAAK;AACzF,MAAI,WAAW,WAAW,SAAS;AACjC,WAAO,EAAE,QAAQ,WAAW,QAAQ,SAAS,MAAM,OAAO,WAAW,MAAM;AAAA,EAC7E;AACA,MAAI,WAAW,WAAW,UAAW,QAAO,EAAE,QAAQ,WAAW,KAAK;AACtE,MAAI,WAAW,WAAW,WAAW;AACnC,WAAO,EAAE,QAAQ,WAAW,QAAQ,WAAW,MAAM,QAAQ,WAAW,OAAO;AAAA,EACjF;AACA,SAAO,EAAE,QAAQ,SAAS,MAAM,WAAW,WAAW,WAAW,OAAO,WAAW,MAAM;AAC3F;;;AFnMI;AANJ,IAAM,mBAAmBC,eAAgC,IAAI;AAE7D,SAAS,eAAe,UAA4C,SAA8B;AAChG,MAAI,OAAO,aAAa,WAAY,QAAO,SAAS,OAAO;AAC3D,MAAI,aAAa,OAAW,QAAO;AACnC,SAAO,QAAQ,KAAK,iBAAiB,SACnC,oBAAC,UAAK,OAAO,EAAE,YAAY,WAAW,GAAI,kBAAQ,KAAK,cAAa,IAClE;AACN;AAUA,IAAM,eAAN,cAA2B,UAA8D;AAAA,EAC9E,QAAQ,EAAE,OAAO,QAAsB,QAAQ,MAAM;AAAA,EAE9D,OAAO,yBAAyB,OAAgB;AAC9C,WAAO,EAAE,OAAO,QAAQ,KAAK;AAAA,EAC/B;AAAA,EAES,kBAAkB,OAAgB,OAAkB;AAC3D,SAAK,MAAM,UAAU,OAAO,KAAK,MAAM,IAAI;AAAA,EAC7C;AAAA,EAES,mBAAmB,UAAyB;AAEnD,QAAI,KAAK,MAAM,UAAU,SAAS,SAAS,KAAK,MAAM,MAAM;AAC1D,WAAK,SAAS,EAAE,OAAO,QAAW,QAAQ,MAAM,CAAC;AAAA,IACnD;AAAA,EACF;AAAA,EAES,SAAS;AAChB,QAAI,KAAK,MAAM,QAAQ;AACrB,aAAO,eAAe,KAAK,MAAM,UAAU;AAAA,QACzC,QAAQ;AAAA,QACR,MAAM,KAAK,MAAM;AAAA,QACjB,OAAO,KAAK,MAAM;AAAA,MACpB,CAAC;AAAA,IACH;AACA,WAAO,KAAK,MAAM;AAAA,EACpB;AACF;AAEA,SAAS,SAAS,MAA2B,IAA8B;AACzE,SAAO,EAAE,IAAI,WAAW,KAAK,MAAM,OAAO,KAAK,SAAS,CAAC,GAAG,SAAS,MAAM,YAAY,KAAK;AAC9F;AAGA,SAAS,SAAS,EAAE,MAAM,GAAG,GAA8C;AACzE,QAAM,QAAQC,YAAW,gBAAgB;AACzC,QAAM,OAAOC,SAAQ,MAAM,SAAS,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC;AACzD,QAAM,gBAAgB,OAAO,oBAAoB,KAAK,IAAI;AAC1D,QAAM,aAAa,OAAO,QAAQ,SAAS,MAAM,KAAK,IAAI;AAC1D,QAAM,eACJ,kBAAkB,UAClB,eAAe,UACf,mBAAmB,WAAW,WAAW;AAC3C,QAAM,aAAa;AAAA,IACjB,KAAK;AAAA,IACL,iBAAiB;AAAA,IACjB,eAAe,CAAC,IAAK,OAAO,QAAQ,YAAY,CAAC;AAAA,IACjD,OAAO,QAAQ;AAAA,EACjB;AACA,QAAM,aAAa;AAAA,IACjB,eAAe,SAAY,OAAO,QAAQ;AAAA,IAC1C,KAAK;AAAA,IACL,KAAK;AAAA,EACP;AACA,MAAI,UAAU,KAAM,QAAO;AAC3B,MAAI,WAAW,WAAW,aAAa,WAAW,WAAW,UAAW,QAAO,MAAM;AACrF,MAAI,WAAW,WAAW,WAAW;AACnC,QAAI,MAAM,aAAa,UAAa,MAAM,iBAAiB;AACzD,YAAM,IAAI,MAAM,wBAAwB;AAC1C,WAAO,eAAe,MAAM,UAAU,EAAE,QAAQ,WAAW,KAAK,CAAC;AAAA,EACnE;AACA,MAAI,WAAW,WAAW,SAAS;AACjC,QAAI,MAAM,aAAa,UAAa,MAAM,iBAAiB,OAAW,OAAM,WAAW;AACvF,WAAO,eAAe,MAAM,UAAU,EAAE,QAAQ,SAAS,MAAM,OAAO,WAAW,MAAM,CAAC;AAAA,EAC1F;AACA,MAAI,WAAW,WAAW,WAAW;AACnC,QAAI,MAAM,aAAa,UAAa,MAAM,iBAAiB;AACzD,YAAM,IAAI,MAAM,wBAAwB;AAC1C,WAAO,eAAe,MAAM,UAAU,EAAE,QAAQ,WAAW,MAAM,QAAQ,WAAW,OAAO,CAAC;AAAA,EAC9F;AACA,QAAMC,YAAW,WAAW;AAC5B,QAAM,WAAW,MAAM,QAAQ,KAAK,QAAQ,IAAI,KAAK,WAAW,CAAC;AACjE,SACE,oBAACA,WAAA,EAAU,GAAG,WAAW,OACtB,mBAAS,SAAS,IACf,SAAS,IAAI,CAAC,OAAO,UAAU;AAC7B,UAAM,UAAU,GAAG,EAAE,IAAI,KAAK;AAC9B,QAAI,MAAM,aAAa,UAAa,MAAM,iBAAiB;AACzD,aAAO,oBAAC,YAAuB,MAAM,OAAO,IAAI,WAA1B,OAAmC;AAC3D,WACE;AAAA,MAAC;AAAA;AAAA,QAEC,MAAM,SAAS,OAAO,OAAO;AAAA,QAC7B,UAAU,MAAM;AAAA,QAChB,SAAS,MAAM;AAAA,QAEf,8BAAC,YAAS,MAAM,OAAO,IAAI,SAAS;AAAA;AAAA,MAL/B;AAAA,IAMP;AAAA,EAEJ,CAAC,IACD,QACN;AAEJ;AAOO,SAAS,UAAU,EAAE,KAAK,GAAmC;AAClE,MAAI,SAAS,UAAa,SAAS,QAAQ,OAAO,KAAK,SAAS,SAAU,QAAO;AACjF,SAAO,oBAAC,YAAS,MAAM,MAAM,IAAG,QAAO;AACzC;AAeO,SAAS,kBAAkB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV;AAAA,EACA;AACF,GAA2B;AACzB,QAAM,QAAQD;AAAA,IACZ,OAAO;AAAA,MACL,SAAS;AAAA,QACP;AAAA,QACA,GAAI,YAAY,SAAY,EAAE,QAAQ,IAAI,CAAC;AAAA,QAC3C,GAAI,qBAAqB,SAAY,EAAE,iBAAiB,IAAI,CAAC;AAAA,MAC/D;AAAA,MACA;AAAA,MACA,GAAI,sBAAsB,SAAY,EAAE,kBAAkB,IAAI,CAAC;AAAA,MAC/D,GAAI,iBAAiB,SAAY,EAAE,aAAa,IAAI,CAAC;AAAA,MACrD;AAAA,MACA;AAAA,IACF;AAAA,IACA;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACA,SAAO,oBAAC,iBAAiB,UAAjB,EAA0B,OAAO,OAAQ,UAAS;AAC5D;AAWA,IAAM,eAAmD;AAAA,EACvD;AACF;AAuBO,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAuB;AACrB,QAAM,QAAQE;AAAA,IACZ,OAAO;AAAA,MACL,GAAI,aAAa,SAAY,EAAE,SAAS,IAAI,CAAC;AAAA,MAC7C,GAAI,YAAY,SAAY,EAAE,QAAQ,IAAI,CAAC;AAAA,MAC3C,GAAI,qBAAqB,SAAY,EAAE,iBAAiB,IAAI,CAAC;AAAA,MAC7D,GAAI,iBAAiB,SAAY,EAAE,aAAa,IAAI,CAAC;AAAA,MACrD,GAAI,aAAa,SAAY,EAAE,SAAS,IAAI,CAAC;AAAA,MAC7C,GAAI,YAAY,SAAY,EAAE,QAAQ,IAAI,CAAC;AAAA,MAC3C,GAAI,YAAY,SAAY,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC7C;AAAA,IACA,CAAC,UAAU,SAAS,kBAAkB,cAAc,UAAU,SAAS,OAAO;AAAA,EAChF;AACA,QAAM,WAAW,YAAY,CAAC,UAA6B,oBAAC,gBAAa,MAAM,OAAO,GAAI,CAAC,CAAC;AAC5F,SACE,oBAAC,aAAa,UAAb,EAAsB,OACrB,8BAAC,uBAAuB,UAAvB,EAAgC,OAAO,UAAW,UAAS,GAC9D;AAEJ;AAEA,IAAM,cAA6B,CAAC;AAGpC,SAAS,iBAAiB,KAAwD;AAChF,QAAM,WAAWC,YAAW,YAAY;AACxC,QAAM,WAAW,IAAI,YAAY,UAAU,YAAY;AACvD,QAAM,UAAU,IAAI,WAAW,UAAU;AACzC,QAAM,mBAAmB,IAAI,oBAAoB,UAAU;AAC3D,QAAM,eAAe,IAAI,gBAAgB,UAAU;AACnD,SAAOD;AAAA,IACL,OAAO;AAAA,MACL;AAAA,MACA,GAAI,YAAY,SAAY,EAAE,QAAQ,IAAI,CAAC;AAAA,MAC3C,GAAI,qBAAqB,SAAY,EAAE,iBAAiB,IAAI,CAAC;AAAA,MAC7D,GAAI,iBAAiB,SAAY,EAAE,aAAa,IAAI,CAAC;AAAA,IACvD;AAAA,IACA,CAAC,UAAU,SAAS,kBAAkB,YAAY;AAAA,EACpD;AACF;AAkCO,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,SAAS;AAAA,EACT,SAAS;AACX,GAAsB;AACpB,QAAM,WAAWE,YAAW,YAAY;AACxC,QAAM,WAAW,gBAAgB,SAAY,cAAc,UAAU;AACrE,QAAM,UAAU,cAAc,UAAU,WAAW;AACnD,QAAM,UAAU,cAAc,UAAU;AACxC,QAAM,UAAU,iBAAiB;AAAA,IAC/B,GAAI,aAAa,SAAY,EAAE,SAAS,IAAI,CAAC;AAAA,IAC7C,GAAI,YAAY,SAAY,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC3C,GAAI,qBAAqB,SAAY,EAAE,iBAAiB,IAAI,CAAC;AAAA,IAC7D,GAAI,iBAAiB,SAAY,EAAE,aAAa,IAAI,CAAC;AAAA,EACvD,CAAC;AACD,QAAM,QAAQ;AAAA,IACZ;AAAA,IACA;AAAA,IACA,QAAQ,gBAAiB;AAAA,EAC3B;AACA,MAAI,UAAU,KAAM,QAAO;AAC3B,MAAI,MAAM,WAAW,UAAW,QAAO;AACvC,MAAI,MAAM,WAAW,WAAW;AAC9B,UAAM,EAAE,QAAQ,SAAS,GAAG,QAAQ,IAAI;AACxC,WAAO,eAAe,UAAU,OAAO;AAAA,EACzC;AACA,QAAMC,YAAW,MAAM;AACvB,SACE;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACH,GAAI,MAAM,KAAK,sBAAsB,SAClC,EAAE,mBAAmB,MAAM,KAAK,kBAAkB,IAClD,CAAC;AAAA,MACJ,GAAI,MAAM,KAAK,iBAAiB,SAAY,EAAE,cAAc,MAAM,KAAK,aAAa,IAAI,CAAC;AAAA,MAC1F;AAAA,MACA;AAAA,MACC,GAAI,YAAY,SAAY,EAAE,QAAQ,IAAI,CAAC;AAAA,MAE5C,8BAAC,gBAAa,MAAM,MAAM,MAAM,UAAoB,SAClD,8BAACA,WAAA,EAAU,GAAG,MAAM,OAAO,GAC7B;AAAA;AAAA,EACF;AAEJ;;;ADzUQ,gBAAAC,YAAA;AAtBD,SAAS,qBAAqB,MAA+C;AAClF,QAAM,WAA2C,CAAC;AAClD,MAAI,OAAO;AACX,QAAM,QAAQ,CAAC,SAAsC;AACnD,UAAM,KAAK,MAAM,MAAM;AACvB,UAAM,QAAQ,EAAE,MAAM,KAAK,MAAM,OAAO,KAAK,SAAS,CAAC,GAAG,UAAU,CAAC,EAAc;AACnF,aAAS,EAAE,IAAI;AACf,eAAW,SAAS,KAAK,YAAY,CAAC,EAAG,OAAM,SAAS,KAAK,MAAM,KAAK,CAAC;AACzE,WAAO;AAAA,EACT;AACA,SAAO,EAAE,MAAM,MAAM,IAAI,GAAG,SAAS;AACvC;AAMO,SAAS,qBAAqB,UAA4C;AAC/E,SAAO,OAAO;AAAA,IACZ,OAAO,QAAQ,QAAQ,EAAE,IAAI,CAAC,CAAC,MAAMC,SAAQ,MAAM;AAAA,MACjD;AAAA,MACA,CAAC,EAAE,SAAS,SAAS,MACnB,gBAAAD,KAACC,WAAA,EAAU,GAAK,QAAQ,SAAS,CAAC,GAAiC,UAAS;AAAA,IAEhF,CAAC;AAAA,EACH;AACF;AAUO,SAAS,eAAe,EAAE,MAAM,UAAU,aAAa,GAAwB;AACpF,QAAM,OAAOC,SAAQ,MAAO,SAAS,SAAY,OAAO,qBAAqB,IAAI,GAAI,CAAC,IAAI,CAAC;AAC3F,MAAI,SAAS,KAAM,QAAO;AAC1B,SACE,gBAAAF,KAAC,kBAAe,UAAqB,GAAI,iBAAiB,SAAY,EAAE,aAAa,IAAI,CAAC,GACxF,0BAAAA,KAAC,YAAS,MAAqB,UAAoB,GACrD;AAEJ;AAGO,SAAS,eAAe,UAE5B;AACD,SAAO,SAAS,oBAAoB,EAAE,KAAK,GAAmC;AAC5E,WAAO,gBAAAA,KAAC,kBAAe,UAAqB,GAAI,SAAS,SAAY,EAAE,KAAK,IAAI,CAAC,GAAI;AAAA,EACvF;AACF;AAYO,SAASG,eAAc,EAAE,YAAY,cAAc,UAAU,GAAG,KAAK,GAAuB;AACjG,QAAM,UAAUD,SAAQ,MAAM;AAC5B,QAAI,eAAe,OAAW,QAAO;AACrC,WAAO,eAAe,eAAe,OAAO,qBAAqB,YAAY,CAAC,CAAC,IAAI,UAAU;AAAA,EAC/F,GAAG,CAAC,YAAY,QAAQ,CAAC;AACzB,QAAM,OAAO,gBAAgB;AAC7B,SACE,gBAAAF;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACH,GAAI,aAAa,SAAY,EAAE,SAAS,IAAI,CAAC;AAAA,MAC7C,GAAI,SAAS,SAAY,EAAE,cAAc,KAAK,IAAI,CAAC;AAAA;AAAA,EACtD;AAEJ;","names":["useMemo","createContext","useContext","useMemo","Component","createContext","useContext","useMemo","Renderer","useMemo","useContext","useContext","Renderer","jsx","Renderer","useMemo","GenuiProvider"]}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/genui/playwright.ts
|
|
31
|
+
var playwright_exports = {};
|
|
32
|
+
__export(playwright_exports, {
|
|
33
|
+
createPlaywrightCaptureAdapter: () => createPlaywrightCaptureAdapter
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(playwright_exports);
|
|
36
|
+
|
|
37
|
+
// src/genui/server.ts
|
|
38
|
+
var import_promises = require("fs/promises");
|
|
39
|
+
var import_react = require("react");
|
|
40
|
+
var import_server = require("react-dom/server");
|
|
41
|
+
function boundedCaptureOptions(options) {
|
|
42
|
+
const result = {
|
|
43
|
+
width: options.width ?? 1200,
|
|
44
|
+
height: options.height ?? 1600,
|
|
45
|
+
timeoutMs: options.timeoutMs ?? 15e3
|
|
46
|
+
};
|
|
47
|
+
for (const [key, value, min, max] of [
|
|
48
|
+
["width", result.width, 320, 4096],
|
|
49
|
+
["height", result.height, 200, 16384],
|
|
50
|
+
["timeoutMs", result.timeoutMs, 100, 6e4]
|
|
51
|
+
]) {
|
|
52
|
+
if (!Number.isInteger(value) || value < min || value > max)
|
|
53
|
+
throw new RangeError(`genui: ${key} must be ${min}\u2013${max}`);
|
|
54
|
+
}
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// src/genui/playwright.ts
|
|
59
|
+
function createPlaywrightCaptureAdapter(options = {}) {
|
|
60
|
+
async function withPage(settings, operation) {
|
|
61
|
+
const limits = boundedCaptureOptions(settings);
|
|
62
|
+
const browser = options.browser ?? await (await import("playwright-core")).chromium.launch(options.launchOptions);
|
|
63
|
+
try {
|
|
64
|
+
const context = await browser.newContext({
|
|
65
|
+
javaScriptEnabled: false,
|
|
66
|
+
serviceWorkers: "block",
|
|
67
|
+
viewport: { width: limits.width, height: limits.height }
|
|
68
|
+
});
|
|
69
|
+
try {
|
|
70
|
+
const page = await context.newPage();
|
|
71
|
+
try {
|
|
72
|
+
page.setDefaultTimeout(limits.timeoutMs);
|
|
73
|
+
await context.route("**/*", async (route) => {
|
|
74
|
+
try {
|
|
75
|
+
if (!options.allowAsset?.(route.request().url())) {
|
|
76
|
+
await route.abort();
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const response = await route.fetch({ maxRedirects: 0, timeout: limits.timeoutMs });
|
|
80
|
+
try {
|
|
81
|
+
if (response.status() >= 300 && response.status() < 400) {
|
|
82
|
+
await route.abort();
|
|
83
|
+
} else {
|
|
84
|
+
await route.fulfill({ response });
|
|
85
|
+
}
|
|
86
|
+
} finally {
|
|
87
|
+
await response.dispose();
|
|
88
|
+
}
|
|
89
|
+
} catch {
|
|
90
|
+
await route.abort();
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
let timer;
|
|
94
|
+
try {
|
|
95
|
+
return await Promise.race([
|
|
96
|
+
operation(page, limits),
|
|
97
|
+
new Promise((_, reject) => {
|
|
98
|
+
timer = setTimeout(
|
|
99
|
+
() => reject(new Error("genui: capture timed out")),
|
|
100
|
+
limits.timeoutMs
|
|
101
|
+
);
|
|
102
|
+
})
|
|
103
|
+
]);
|
|
104
|
+
} finally {
|
|
105
|
+
clearTimeout(timer);
|
|
106
|
+
}
|
|
107
|
+
} finally {
|
|
108
|
+
await page.close();
|
|
109
|
+
}
|
|
110
|
+
} finally {
|
|
111
|
+
await context.close();
|
|
112
|
+
}
|
|
113
|
+
} finally {
|
|
114
|
+
if (!options.browser) await browser.close();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function checkPages(html) {
|
|
118
|
+
if (!html.length || html.length > 100)
|
|
119
|
+
throw new RangeError("genui: capture requires 1\u2013100 pages");
|
|
120
|
+
}
|
|
121
|
+
async function ready(page, html, limits) {
|
|
122
|
+
await page.setContent(html, { waitUntil: "load", timeout: limits.timeoutMs });
|
|
123
|
+
await page.evaluate(async (timeoutMs) => {
|
|
124
|
+
let timer;
|
|
125
|
+
try {
|
|
126
|
+
await Promise.race([
|
|
127
|
+
Promise.all([
|
|
128
|
+
document.fonts.ready,
|
|
129
|
+
...Array.from(
|
|
130
|
+
document.images,
|
|
131
|
+
(image) => image.complete ? Promise.resolve() : new Promise((resolve) => {
|
|
132
|
+
image.addEventListener("load", () => resolve(), { once: true });
|
|
133
|
+
image.addEventListener("error", () => resolve(), { once: true });
|
|
134
|
+
})
|
|
135
|
+
)
|
|
136
|
+
]),
|
|
137
|
+
new Promise((_, reject) => {
|
|
138
|
+
timer = setTimeout(() => reject(new Error("genui: assets timed out")), timeoutMs);
|
|
139
|
+
})
|
|
140
|
+
]);
|
|
141
|
+
} finally {
|
|
142
|
+
clearTimeout(timer);
|
|
143
|
+
}
|
|
144
|
+
}, limits.timeoutMs);
|
|
145
|
+
const height = await page.evaluate(
|
|
146
|
+
() => Math.max(document.body.scrollHeight, document.documentElement.scrollHeight)
|
|
147
|
+
);
|
|
148
|
+
if (height > limits.height)
|
|
149
|
+
throw new RangeError(
|
|
150
|
+
"genui: content exceeds capture height; reduce rowsPerPage or paginate the component"
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
async images(html, settings) {
|
|
155
|
+
checkPages(html);
|
|
156
|
+
return withPage(settings, async (page, limits) => {
|
|
157
|
+
const images = [];
|
|
158
|
+
for (const content of html) {
|
|
159
|
+
await ready(page, content, limits);
|
|
160
|
+
images.push(
|
|
161
|
+
await page.screenshot({ type: "png", fullPage: true, timeout: limits.timeoutMs })
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
return images;
|
|
165
|
+
});
|
|
166
|
+
},
|
|
167
|
+
async pdf(html, settings) {
|
|
168
|
+
checkPages(html);
|
|
169
|
+
return withPage(settings, async (page, limits) => {
|
|
170
|
+
const escapeAttribute = (value) => value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
|
171
|
+
const pdfDocument = `<!doctype html><html><head><style>@page { size: ${limits.width}px ${limits.height}px; margin: 0 } body { margin: 0 } iframe { display:block; border:0; width:${limits.width}px; height:${limits.height}px; break-after:page } iframe:last-child { break-after:auto }</style></head><body>${html.map((content) => `<iframe sandbox="allow-same-origin" srcdoc="${escapeAttribute(content)}"></iframe>`).join("")}</body></html>`;
|
|
172
|
+
await page.setContent(pdfDocument, { waitUntil: "load", timeout: limits.timeoutMs });
|
|
173
|
+
for (const frame of page.frames().slice(1)) {
|
|
174
|
+
await frame.evaluate(async () => {
|
|
175
|
+
await document.fonts.ready;
|
|
176
|
+
await Promise.all(
|
|
177
|
+
Array.from(document.images, (image) => image.decode().catch(() => {
|
|
178
|
+
}))
|
|
179
|
+
);
|
|
180
|
+
});
|
|
181
|
+
const height = await frame.evaluate(() => document.documentElement.scrollHeight);
|
|
182
|
+
if (height > limits.height)
|
|
183
|
+
throw new RangeError("genui: content exceeds PDF page height; reduce rowsPerPage");
|
|
184
|
+
}
|
|
185
|
+
return page.pdf({
|
|
186
|
+
width: `${limits.width}px`,
|
|
187
|
+
height: `${limits.height}px`,
|
|
188
|
+
printBackground: true,
|
|
189
|
+
preferCSSPageSize: true
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
196
|
+
0 && (module.exports = {
|
|
197
|
+
createPlaywrightCaptureAdapter
|
|
198
|
+
});
|
|
199
|
+
//# sourceMappingURL=genui-server-playwright.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/genui/playwright.ts","../src/genui/server.ts"],"sourcesContent":["import type { Browser, LaunchOptions, Page } from 'playwright-core';\nimport { type CaptureAdapter, type CaptureOptions, boundedCaptureOptions } from './server.js';\n\nexport interface PlaywrightCaptureOptions {\n /** Caller-owned browser; the adapter closes only its own contexts and pages. */\n browser?: Browser;\n /** Used only without a supplied browser. Install playwright-core and supply executablePath. */\n launchOptions?: LaunchOptions;\n /** Explicit allowlist for trusted app images/fonts/styles. Redirects and other requests abort. */\n allowAsset?: (url: string) => boolean;\n}\nexport function createPlaywrightCaptureAdapter(\n options: PlaywrightCaptureOptions = {},\n): CaptureAdapter {\n async function withPage<T>(\n settings: CaptureOptions,\n operation: (page: Page, limits: Required<CaptureOptions>) => Promise<T>,\n ) {\n const limits = boundedCaptureOptions(settings);\n const browser =\n options.browser ??\n (await (await import('playwright-core')).chromium.launch(options.launchOptions));\n try {\n const context = await browser.newContext({\n javaScriptEnabled: false,\n serviceWorkers: 'block',\n viewport: { width: limits.width, height: limits.height },\n });\n try {\n const page = await context.newPage();\n try {\n page.setDefaultTimeout(limits.timeoutMs);\n await context.route('**/*', async (route) => {\n try {\n if (!options.allowAsset?.(route.request().url())) {\n await route.abort();\n return;\n }\n // Browser redirects bypass route handlers. Fetch one approved URL only,\n // then fulfill its response; never let an asset redirect escape the allowlist.\n const response = await route.fetch({ maxRedirects: 0, timeout: limits.timeoutMs });\n try {\n if (response.status() >= 300 && response.status() < 400) {\n await route.abort();\n } else {\n await route.fulfill({ response });\n }\n } finally {\n await response.dispose();\n }\n } catch {\n await route.abort();\n }\n });\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n operation(page, limits),\n new Promise<never>((_, reject) => {\n timer = setTimeout(\n () => reject(new Error('genui: capture timed out')),\n limits.timeoutMs,\n );\n }),\n ]);\n } finally {\n clearTimeout(timer);\n }\n } finally {\n await page.close();\n }\n } finally {\n await context.close();\n }\n } finally {\n if (!options.browser) await browser.close();\n }\n }\n function checkPages(html: readonly string[]) {\n if (!html.length || html.length > 100)\n throw new RangeError('genui: capture requires 1–100 pages');\n }\n async function ready(page: Page, html: string, limits: Required<CaptureOptions>) {\n await page.setContent(html, { waitUntil: 'load', timeout: limits.timeoutMs });\n // Host evaluation is available while page-origin JavaScript stays disabled.\n await page.evaluate(async (timeoutMs) => {\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n await Promise.race([\n Promise.all([\n document.fonts.ready,\n ...Array.from(document.images, (image) =>\n image.complete\n ? Promise.resolve()\n : new Promise<void>((resolve) => {\n image.addEventListener('load', () => resolve(), { once: true });\n image.addEventListener('error', () => resolve(), { once: true });\n }),\n ),\n ]),\n new Promise<never>((_, reject) => {\n timer = setTimeout(() => reject(new Error('genui: assets timed out')), timeoutMs);\n }),\n ]);\n } finally {\n clearTimeout(timer);\n }\n }, limits.timeoutMs);\n const height = await page.evaluate(() =>\n Math.max(document.body.scrollHeight, document.documentElement.scrollHeight),\n );\n if (height > limits.height)\n throw new RangeError(\n 'genui: content exceeds capture height; reduce rowsPerPage or paginate the component',\n );\n }\n return {\n async images(html, settings) {\n checkPages(html);\n return withPage(settings, async (page, limits) => {\n const images: Uint8Array[] = [];\n for (const content of html) {\n await ready(page, content, limits);\n images.push(\n await page.screenshot({ type: 'png', fullPage: true, timeout: limits.timeoutMs }),\n );\n }\n return images;\n });\n },\n async pdf(html, settings) {\n checkPages(html);\n return withPage(settings, async (page, limits) => {\n // Separate full HTML pages via srcdoc to preserve per-page CSS and theme.\n // Keep frames in the intercepted origin; opaque sandbox frames can bypass Chromium\n // request routing. Scripts remain disabled by both the context and the sandbox.\n const escapeAttribute = (value: string) =>\n value.replace(/&/g, '&').replace(/\"/g, '"').replace(/</g, '<');\n const pdfDocument = `<!doctype html><html><head><style>@page { size: ${limits.width}px ${limits.height}px; margin: 0 } body { margin: 0 } iframe { display:block; border:0; width:${limits.width}px; height:${limits.height}px; break-after:page } iframe:last-child { break-after:auto }</style></head><body>${html.map((content) => `<iframe sandbox=\"allow-same-origin\" srcdoc=\"${escapeAttribute(content)}\"></iframe>`).join('')}</body></html>`;\n await page.setContent(pdfDocument, { waitUntil: 'load', timeout: limits.timeoutMs });\n for (const frame of page.frames().slice(1)) {\n await frame.evaluate(async () => {\n await document.fonts.ready;\n await Promise.all(\n Array.from(document.images, (image) => image.decode().catch(() => {})),\n );\n });\n const height = await frame.evaluate(() => document.documentElement.scrollHeight);\n if (height > limits.height)\n throw new RangeError('genui: content exceeds PDF page height; reduce rowsPerPage');\n }\n return page.pdf({\n width: `${limits.width}px`,\n height: `${limits.height}px`,\n printBackground: true,\n preferCSSPageSize: true,\n });\n });\n },\n };\n}\n","import { readFile } from 'node:fs/promises';\nimport type { ComponentPresentation } from '@dudousxd/nestjs-agent-core/genui';\nimport { type ReactNode, createElement } from 'react';\nimport { renderToStaticMarkup } from 'react-dom/server';\nimport type { ReactComponentRegistry } from './react-registry.js';\nexport { createReactComponentRegistry, type ReactComponentRegistry } from './react-registry.js';\n\nexport interface CaptureOptions {\n width?: number;\n height?: number;\n timeoutMs?: number;\n}\nexport interface CaptureAdapter {\n images(html: readonly string[], options: CaptureOptions): Promise<Uint8Array[]>;\n pdf(html: readonly string[], options: CaptureOptions): Promise<Uint8Array>;\n}\nexport interface ServerRenderOptions extends CaptureOptions {\n rowsPerPage?: number;\n capture?: CaptureAdapter;\n}\nexport interface ReactServerRendererOptions {\n registry: ReactComponentRegistry;\n /** Trusted app CSS, or a trusted filesystem path; never supply user CSS. */\n stylesheet?: string | { path: string };\n theme?: string;\n capture?: CaptureAdapter;\n}\nexport function boundedCaptureOptions(options: CaptureOptions): Required<CaptureOptions> {\n const result = {\n width: options.width ?? 1200,\n height: options.height ?? 1600,\n timeoutMs: options.timeoutMs ?? 15000,\n };\n for (const [key, value, min, max] of [\n ['width', result.width, 320, 4096],\n ['height', result.height, 200, 16384],\n ['timeoutMs', result.timeoutMs, 100, 60000],\n ] as const) {\n if (!Number.isInteger(value) || value < min || value > max)\n throw new RangeError(`genui: ${key} must be ${min}–${max}`);\n }\n return result;\n}\nexport function createReactServerRenderer(options: ReactServerRendererOptions) {\n async function content(presentation: ComponentPresentation<object>) {\n const rendered = await options.registry.render(presentation, 'react');\n return renderToStaticMarkup(\n createElement(\n 'main',\n { 'data-component': presentation.component, 'data-theme': options.theme },\n rendered as ReactNode,\n ),\n );\n }\n async function stylesheet() {\n return typeof options.stylesheet === 'object'\n ? readFile(options.stylesheet.path, 'utf8')\n : (options.stylesheet ?? '');\n }\n async function html(presentation: ComponentPresentation<object>): Promise<string> {\n return document(await content(presentation));\n }\n async function document(markup: string) {\n const css = await stylesheet();\n // Closing tags in trusted CSS must still never break out of the style element.\n return `<!doctype html><html><head><meta charset=\"utf-8\"><style>${css.replace(/<\\/style/gi, '<\\\\/style')}</style></head><body>${markup}</body></html>`;\n }\n async function pages(presentation: ComponentPresentation<object>, settings: ServerRenderOptions) {\n const limits = boundedCaptureOptions(settings);\n const size = settings.rowsPerPage ?? 30;\n if (!Number.isInteger(size) || size < 1 || size > 500)\n throw new RangeError('genui: rowsPerPage must be 1–500');\n const capture = settings.capture ?? options.capture;\n if (!capture) throw new Error('genui: a capture adapter is required for PNG/PDF output');\n const rendered = await options.registry.renderPages(presentation, size);\n return {\n html: await Promise.all(\n rendered.map((node) =>\n document(\n renderToStaticMarkup(\n createElement(\n 'main',\n { 'data-component': presentation.component, 'data-theme': options.theme },\n node,\n ),\n ),\n ),\n ),\n ),\n capture,\n limits,\n };\n }\n return {\n html,\n async images(presentation: ComponentPresentation<object>, settings: ServerRenderOptions = {}) {\n const page = await pages(presentation, settings);\n return page.capture.images(page.html, page.limits);\n },\n async pdf(presentation: ComponentPresentation<object>, settings: ServerRenderOptions = {}) {\n const page = await pages(presentation, settings);\n return page.capture.pdf(page.html, page.limits);\n },\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Browser, LaunchOptions } from 'playwright-core';
|
|
2
|
+
import { CaptureAdapter } from './genui-server.cjs';
|
|
3
|
+
import '@dudousxd/nestjs-agent-core/genui';
|
|
4
|
+
import './react-registry-Di4IVrw-.cjs';
|
|
5
|
+
import 'react';
|
|
6
|
+
import './types-DZtFsWec.cjs';
|
|
7
|
+
|
|
8
|
+
interface PlaywrightCaptureOptions {
|
|
9
|
+
/** Caller-owned browser; the adapter closes only its own contexts and pages. */
|
|
10
|
+
browser?: Browser;
|
|
11
|
+
/** Used only without a supplied browser. Install playwright-core and supply executablePath. */
|
|
12
|
+
launchOptions?: LaunchOptions;
|
|
13
|
+
/** Explicit allowlist for trusted app images/fonts/styles. Redirects and other requests abort. */
|
|
14
|
+
allowAsset?: (url: string) => boolean;
|
|
15
|
+
}
|
|
16
|
+
declare function createPlaywrightCaptureAdapter(options?: PlaywrightCaptureOptions): CaptureAdapter;
|
|
17
|
+
|
|
18
|
+
export { type PlaywrightCaptureOptions, createPlaywrightCaptureAdapter };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Browser, LaunchOptions } from 'playwright-core';
|
|
2
|
+
import { CaptureAdapter } from './genui-server.js';
|
|
3
|
+
import '@dudousxd/nestjs-agent-core/genui';
|
|
4
|
+
import './react-registry-z9GFbTm9.js';
|
|
5
|
+
import 'react';
|
|
6
|
+
import './types-DZtFsWec.js';
|
|
7
|
+
|
|
8
|
+
interface PlaywrightCaptureOptions {
|
|
9
|
+
/** Caller-owned browser; the adapter closes only its own contexts and pages. */
|
|
10
|
+
browser?: Browser;
|
|
11
|
+
/** Used only without a supplied browser. Install playwright-core and supply executablePath. */
|
|
12
|
+
launchOptions?: LaunchOptions;
|
|
13
|
+
/** Explicit allowlist for trusted app images/fonts/styles. Redirects and other requests abort. */
|
|
14
|
+
allowAsset?: (url: string) => boolean;
|
|
15
|
+
}
|
|
16
|
+
declare function createPlaywrightCaptureAdapter(options?: PlaywrightCaptureOptions): CaptureAdapter;
|
|
17
|
+
|
|
18
|
+
export { type PlaywrightCaptureOptions, createPlaywrightCaptureAdapter };
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// src/genui/server.ts
|
|
2
|
+
import { readFile } from "fs/promises";
|
|
3
|
+
import { createElement } from "react";
|
|
4
|
+
import { renderToStaticMarkup } from "react-dom/server";
|
|
5
|
+
function boundedCaptureOptions(options) {
|
|
6
|
+
const result = {
|
|
7
|
+
width: options.width ?? 1200,
|
|
8
|
+
height: options.height ?? 1600,
|
|
9
|
+
timeoutMs: options.timeoutMs ?? 15e3
|
|
10
|
+
};
|
|
11
|
+
for (const [key, value, min, max] of [
|
|
12
|
+
["width", result.width, 320, 4096],
|
|
13
|
+
["height", result.height, 200, 16384],
|
|
14
|
+
["timeoutMs", result.timeoutMs, 100, 6e4]
|
|
15
|
+
]) {
|
|
16
|
+
if (!Number.isInteger(value) || value < min || value > max)
|
|
17
|
+
throw new RangeError(`genui: ${key} must be ${min}\u2013${max}`);
|
|
18
|
+
}
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// src/genui/playwright.ts
|
|
23
|
+
function createPlaywrightCaptureAdapter(options = {}) {
|
|
24
|
+
async function withPage(settings, operation) {
|
|
25
|
+
const limits = boundedCaptureOptions(settings);
|
|
26
|
+
const browser = options.browser ?? await (await import("playwright-core")).chromium.launch(options.launchOptions);
|
|
27
|
+
try {
|
|
28
|
+
const context = await browser.newContext({
|
|
29
|
+
javaScriptEnabled: false,
|
|
30
|
+
serviceWorkers: "block",
|
|
31
|
+
viewport: { width: limits.width, height: limits.height }
|
|
32
|
+
});
|
|
33
|
+
try {
|
|
34
|
+
const page = await context.newPage();
|
|
35
|
+
try {
|
|
36
|
+
page.setDefaultTimeout(limits.timeoutMs);
|
|
37
|
+
await context.route("**/*", async (route) => {
|
|
38
|
+
try {
|
|
39
|
+
if (!options.allowAsset?.(route.request().url())) {
|
|
40
|
+
await route.abort();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const response = await route.fetch({ maxRedirects: 0, timeout: limits.timeoutMs });
|
|
44
|
+
try {
|
|
45
|
+
if (response.status() >= 300 && response.status() < 400) {
|
|
46
|
+
await route.abort();
|
|
47
|
+
} else {
|
|
48
|
+
await route.fulfill({ response });
|
|
49
|
+
}
|
|
50
|
+
} finally {
|
|
51
|
+
await response.dispose();
|
|
52
|
+
}
|
|
53
|
+
} catch {
|
|
54
|
+
await route.abort();
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
let timer;
|
|
58
|
+
try {
|
|
59
|
+
return await Promise.race([
|
|
60
|
+
operation(page, limits),
|
|
61
|
+
new Promise((_, reject) => {
|
|
62
|
+
timer = setTimeout(
|
|
63
|
+
() => reject(new Error("genui: capture timed out")),
|
|
64
|
+
limits.timeoutMs
|
|
65
|
+
);
|
|
66
|
+
})
|
|
67
|
+
]);
|
|
68
|
+
} finally {
|
|
69
|
+
clearTimeout(timer);
|
|
70
|
+
}
|
|
71
|
+
} finally {
|
|
72
|
+
await page.close();
|
|
73
|
+
}
|
|
74
|
+
} finally {
|
|
75
|
+
await context.close();
|
|
76
|
+
}
|
|
77
|
+
} finally {
|
|
78
|
+
if (!options.browser) await browser.close();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function checkPages(html) {
|
|
82
|
+
if (!html.length || html.length > 100)
|
|
83
|
+
throw new RangeError("genui: capture requires 1\u2013100 pages");
|
|
84
|
+
}
|
|
85
|
+
async function ready(page, html, limits) {
|
|
86
|
+
await page.setContent(html, { waitUntil: "load", timeout: limits.timeoutMs });
|
|
87
|
+
await page.evaluate(async (timeoutMs) => {
|
|
88
|
+
let timer;
|
|
89
|
+
try {
|
|
90
|
+
await Promise.race([
|
|
91
|
+
Promise.all([
|
|
92
|
+
document.fonts.ready,
|
|
93
|
+
...Array.from(
|
|
94
|
+
document.images,
|
|
95
|
+
(image) => image.complete ? Promise.resolve() : new Promise((resolve) => {
|
|
96
|
+
image.addEventListener("load", () => resolve(), { once: true });
|
|
97
|
+
image.addEventListener("error", () => resolve(), { once: true });
|
|
98
|
+
})
|
|
99
|
+
)
|
|
100
|
+
]),
|
|
101
|
+
new Promise((_, reject) => {
|
|
102
|
+
timer = setTimeout(() => reject(new Error("genui: assets timed out")), timeoutMs);
|
|
103
|
+
})
|
|
104
|
+
]);
|
|
105
|
+
} finally {
|
|
106
|
+
clearTimeout(timer);
|
|
107
|
+
}
|
|
108
|
+
}, limits.timeoutMs);
|
|
109
|
+
const height = await page.evaluate(
|
|
110
|
+
() => Math.max(document.body.scrollHeight, document.documentElement.scrollHeight)
|
|
111
|
+
);
|
|
112
|
+
if (height > limits.height)
|
|
113
|
+
throw new RangeError(
|
|
114
|
+
"genui: content exceeds capture height; reduce rowsPerPage or paginate the component"
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
async images(html, settings) {
|
|
119
|
+
checkPages(html);
|
|
120
|
+
return withPage(settings, async (page, limits) => {
|
|
121
|
+
const images = [];
|
|
122
|
+
for (const content of html) {
|
|
123
|
+
await ready(page, content, limits);
|
|
124
|
+
images.push(
|
|
125
|
+
await page.screenshot({ type: "png", fullPage: true, timeout: limits.timeoutMs })
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
return images;
|
|
129
|
+
});
|
|
130
|
+
},
|
|
131
|
+
async pdf(html, settings) {
|
|
132
|
+
checkPages(html);
|
|
133
|
+
return withPage(settings, async (page, limits) => {
|
|
134
|
+
const escapeAttribute = (value) => value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
|
135
|
+
const pdfDocument = `<!doctype html><html><head><style>@page { size: ${limits.width}px ${limits.height}px; margin: 0 } body { margin: 0 } iframe { display:block; border:0; width:${limits.width}px; height:${limits.height}px; break-after:page } iframe:last-child { break-after:auto }</style></head><body>${html.map((content) => `<iframe sandbox="allow-same-origin" srcdoc="${escapeAttribute(content)}"></iframe>`).join("")}</body></html>`;
|
|
136
|
+
await page.setContent(pdfDocument, { waitUntil: "load", timeout: limits.timeoutMs });
|
|
137
|
+
for (const frame of page.frames().slice(1)) {
|
|
138
|
+
await frame.evaluate(async () => {
|
|
139
|
+
await document.fonts.ready;
|
|
140
|
+
await Promise.all(
|
|
141
|
+
Array.from(document.images, (image) => image.decode().catch(() => {
|
|
142
|
+
}))
|
|
143
|
+
);
|
|
144
|
+
});
|
|
145
|
+
const height = await frame.evaluate(() => document.documentElement.scrollHeight);
|
|
146
|
+
if (height > limits.height)
|
|
147
|
+
throw new RangeError("genui: content exceeds PDF page height; reduce rowsPerPage");
|
|
148
|
+
}
|
|
149
|
+
return page.pdf({
|
|
150
|
+
width: `${limits.width}px`,
|
|
151
|
+
height: `${limits.height}px`,
|
|
152
|
+
printBackground: true,
|
|
153
|
+
preferCSSPageSize: true
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
export {
|
|
160
|
+
createPlaywrightCaptureAdapter
|
|
161
|
+
};
|
|
162
|
+
//# sourceMappingURL=genui-server-playwright.js.map
|