@inferagraph/core
Advanced tools
| import { L as LLMProvider, C as CacheProvider, a as AIEngineConfig, A as AIEngine, G as GraphStore, b as ChatOptions, c as ChatEvent, d as GraphData, N as NodeId, e as NodeData, P as PaginationOptions, f as PaginatedResult, D as DataFilter, g as ContentData, E as EdgeData } from './AIEngine-C0Rd5U-b.cjs'; | ||
| /** | ||
| * Abstract chat transport. The same shape on either side: pass a message | ||
| * (and optional cancel signal / emit-tool-calls flag), get an async iterable | ||
| * of {@link ChatEvent}s back. | ||
| * | ||
| * Two built-in implementations: | ||
| * - {@link inProcessTransport} — runs the AIEngine in the current JS | ||
| * context. Suitable for tests, demos, and any host where the API key | ||
| * can live in the same process as the renderer. | ||
| * - {@link httpTransport} — POSTs the message to a server endpoint and | ||
| * parses an SSE response into ChatEvents. Used by Next.js / browser | ||
| * hosts that must keep the LLM + cache server-side (because the redis | ||
| * client can't run in the browser, and API keys must not be exposed | ||
| * to it). | ||
| * | ||
| * Hosts may also implement a custom transport if they need bespoke routing | ||
| * (queueing, fanout, retry, etc.). The contract is intentionally tiny. | ||
| */ | ||
| interface Transport { | ||
| chat(message: string, opts?: ChatOptions): AsyncIterable<ChatEvent>; | ||
| } | ||
| /** | ||
| * Configuration for {@link inProcessTransport}. Either pass an existing | ||
| * {@link AIEngine} (advanced — when you've already constructed one) or | ||
| * the provider/cache/config triple (typical — the transport builds the | ||
| * engine for you). | ||
| */ | ||
| interface InProcessTransportConfig { | ||
| /** LLM provider. Required when no `engine` is supplied. */ | ||
| provider?: LLMProvider; | ||
| /** Optional response cache. */ | ||
| cache?: CacheProvider; | ||
| /** Optional AIEngine config (e.g. `schemaSampleSize`). */ | ||
| aiConfig?: AIEngineConfig; | ||
| /** Optional pre-built {@link AIEngine}. Wins over the other fields. */ | ||
| engine?: AIEngine; | ||
| /** Optional pre-built {@link GraphStore} (used when building a fresh engine). */ | ||
| store?: GraphStore; | ||
| } | ||
| /** | ||
| * In-process transport. Constructs (or reuses) an {@link AIEngine} and | ||
| * forwards `chat` calls directly to it. The transport does NOT own the | ||
| * graph data — when no `engine` is supplied, the caller is responsible | ||
| * for loading nodes into the supplied store before invoking `chat`. | ||
| * | ||
| * In typical app usage the React layer reuses the AIEngine that | ||
| * `GraphProvider` already constructed; the transport function is exposed | ||
| * for non-React consumers and tests. | ||
| */ | ||
| declare function inProcessTransport(config: InProcessTransportConfig): Transport; | ||
| /** | ||
| * Configuration for {@link httpTransport}. | ||
| */ | ||
| interface HttpTransportConfig { | ||
| /** Endpoint URL. e.g. `'/api/chat'`. */ | ||
| url: string; | ||
| /** Override `fetch` (testing / non-DOM environments). Defaults to global fetch. */ | ||
| fetch?: typeof fetch; | ||
| /** Extra headers. The transport always sets `Content-Type: application/json`. */ | ||
| headers?: HeadersInit; | ||
| } | ||
| /** | ||
| * HTTP transport. POSTs `{ message, emitToolCalls }` to the configured | ||
| * endpoint and parses the response as Server-Sent Events into | ||
| * {@link ChatEvent}s. | ||
| * | ||
| * SSE format (mirrors what biblegraph's `/api/chat` route emits in Phase | ||
| * 2C): | ||
| * ``` | ||
| * data: {"type":"text","delta":"hello"} | ||
| * | ||
| * data: {"type":"done","reason":"stop"} | ||
| * ``` | ||
| * Each `data:` line is a JSON-encoded {@link ChatEvent}. Blank lines | ||
| * delimit events. The transport silently drops malformed lines so a | ||
| * single bad line doesn't break the rest of the stream. | ||
| * | ||
| * Tool-call events that carry a function predicate (`apply_filter`) are | ||
| * round-tripped via the `spec` field — the transport rebuilds the | ||
| * predicate on the client side using the same `buildPredicateFromSpec` | ||
| * helper as the live path. This keeps the wire format JSON-clean. | ||
| */ | ||
| declare function httpTransport(config: HttpTransportConfig): Transport; | ||
| /** Configuration passed to the adapter during initialization */ | ||
| interface DataAdapterConfig { | ||
| [key: string]: unknown; | ||
| } | ||
| /** Interface for fetching graph data from any source */ | ||
| interface DataAdapter { | ||
| /** Called once when InferaGraph initializes. Return the starting subgraph. */ | ||
| getInitialView(config?: DataAdapterConfig): Promise<GraphData>; | ||
| /** Get a single node by ID */ | ||
| getNode(id: NodeId): Promise<NodeData | undefined>; | ||
| /** Get direct neighbors of a node (1-hop by default). Used when user clicks/expands a node. */ | ||
| getNeighbors(nodeId: NodeId, depth?: number): Promise<GraphData>; | ||
| /** Find a path between two nodes. Used for lineage/connection queries. */ | ||
| findPath(fromId: NodeId, toId: NodeId): Promise<GraphData>; | ||
| /** Search nodes by text query. Used by search UI. */ | ||
| search(query: string, pagination?: PaginationOptions): Promise<PaginatedResult<NodeData>>; | ||
| /** Filter nodes by criteria. Used by filter panel. */ | ||
| filter(filter: DataFilter, pagination?: PaginationOptions): Promise<PaginatedResult<NodeData>>; | ||
| /** Get content/details for a node. Used when detail panel opens. */ | ||
| getContent(nodeId: NodeId): Promise<ContentData | undefined>; | ||
| } | ||
| /** Minimal store interface required by DataManager */ | ||
| interface MergeableStore { | ||
| merge(data: { | ||
| nodes: NodeData[]; | ||
| edges: unknown[]; | ||
| }): void; | ||
| } | ||
| /** | ||
| * Bridges async DataAdapter to sync GraphStore. | ||
| * Manages caching of which nodes have been fetched and | ||
| * merges adapter results into the store. | ||
| */ | ||
| declare class DataManager { | ||
| private readonly _adapter; | ||
| private readonly store; | ||
| private readonly fetchedNodes; | ||
| private initialized; | ||
| constructor(store: MergeableStore, adapter: DataAdapter); | ||
| get isInitialized(): boolean; | ||
| /** | ||
| * The underlying {@link DataAdapter} this manager was constructed with. | ||
| * Exposed so callers (notably `GraphProvider`) can compare adapter | ||
| * identity across renders to decide whether to swap the manager out. | ||
| */ | ||
| get adapter(): DataAdapter; | ||
| /** Initialize: call adapter.getInitialView(), merge into store */ | ||
| initialize(config?: DataAdapterConfig): Promise<void>; | ||
| /** Expand a node: fetch neighbors and merge into store */ | ||
| expandNode(nodeId: NodeId, depth?: number): Promise<GraphData>; | ||
| /** Find path: fetch path subgraph, merge into store, return path nodes */ | ||
| findPath(fromId: NodeId, toId: NodeId): Promise<NodeData[]>; | ||
| /** Search: delegates to adapter, returns results (doesn't auto-add to store) */ | ||
| search(query: string): Promise<PaginatedResult<NodeData>>; | ||
| /** Get content for detail panel */ | ||
| getContent(nodeId: NodeId): Promise<ContentData | undefined>; | ||
| /** Check if a node has been fetched */ | ||
| hasFetched(nodeId: NodeId): boolean; | ||
| } | ||
| /** | ||
| * Minimal slice of the {@link GraphStore} surface that {@link MemoryManager} | ||
| * needs. Kept narrow so tests can stub a fake store with the four methods. | ||
| */ | ||
| interface MemoryManagedStore { | ||
| removeNode(id: NodeId): void; | ||
| hasNode(id: NodeId): boolean; | ||
| readonly nodeCount: number; | ||
| /** | ||
| * Enumerate every NodeId currently in the store. Used when picking the | ||
| * eviction victim — untouched nodes (loaded via `getInitialView` but | ||
| * never interacted with) need to surface in the LRU candidate pool with | ||
| * an implicit "oldest of all" timestamp, otherwise they'd live forever. | ||
| */ | ||
| getAllNodes(): Array<{ | ||
| id: NodeId; | ||
| } | { | ||
| readonly id: NodeId; | ||
| }>; | ||
| } | ||
| /** | ||
| * Optional AI-side cleanup hooks. {@link AIEngine} implements both; tests can | ||
| * pass `undefined` (or a partial object) when no AI surface is active. | ||
| */ | ||
| interface MemoryManagedAIEngine { | ||
| /** Remove the embedding record(s) for `nodeId`. No-op when none cached. */ | ||
| dropEmbedding?: (nodeId: NodeId) => void | Promise<void>; | ||
| /** Remove every inferred edge incident to `nodeId`. No-op when none stored. */ | ||
| dropInferredEdgesFor?: (nodeId: NodeId) => void | Promise<void>; | ||
| } | ||
| /** | ||
| * LRU eviction coordinator. Keeps the in-memory graph footprint bounded so | ||
| * long drilldown sessions don't grow the store unboundedly. | ||
| * | ||
| * Responsibilities: | ||
| * - Track when each node was last "touched" (hover, focus, expand, | ||
| * getContent). The most-recent timestamp wins so freshly-interacted | ||
| * nodes survive eviction. | ||
| * - When the store exceeds its configured cap, evict the oldest | ||
| * non-protected nodes until either the store is under cap or every | ||
| * remaining node is protected. | ||
| * - Coordinate cleanup across the {@link GraphStore} (node + edge | ||
| * removal) AND the {@link AIEngine} (embedding cache + inferred-edge | ||
| * overlay) so a victim doesn't leak memory in a sibling subsystem. | ||
| * | ||
| * Design note (per Phase 6 plan): this is a SEPARATE class, not bolted into | ||
| * {@link GraphStore}. The store is domain-agnostic and concerns itself only | ||
| * with structural integrity; eviction policy is a host-controlled tuning | ||
| * decision pushed in via `<InferaGraph maxNodes>`. | ||
| */ | ||
| declare class MemoryManager { | ||
| private readonly store; | ||
| private readonly aiEngine; | ||
| private readonly maxNodes; | ||
| private readonly touchTimestamps; | ||
| /** | ||
| * Monotonic counter used as the timestamp source. We avoid `Date.now()` so | ||
| * tests don't have to fake the clock — every `touch()` advances by one, | ||
| * which is enough to define a strict ordering for LRU eviction. | ||
| */ | ||
| private clock; | ||
| /** | ||
| * @param store Mergeable store whose `removeNode` is invoked on eviction. | ||
| * @param aiEngine Optional AI engine; when supplied, the manager calls | ||
| * `dropEmbedding` + `dropInferredEdgesFor` for each victim | ||
| * so embeddings + inferred-edge overlay don't leak. | ||
| * @param maxNodes Soft cap. `undefined` disables eviction entirely | ||
| * (every `enforceCap` call becomes a no-op). `0` is treated | ||
| * the same as `undefined` so passing `<InferaGraph maxNodes={0}>` | ||
| * doesn't accidentally evict everything. | ||
| */ | ||
| constructor(store: MemoryManagedStore, aiEngine?: MemoryManagedAIEngine, maxNodes?: number); | ||
| /** | ||
| * Mark `nodeId` as freshly used. Subsequent calls overwrite the prior | ||
| * timestamp — most-recent touch wins, so the node moves to the front of | ||
| * the LRU order. | ||
| */ | ||
| touch(nodeId: NodeId): void; | ||
| /** | ||
| * Drop the LRU bookkeeping for `nodeId`. Called automatically during | ||
| * eviction; hosts that remove nodes through other paths (manual store | ||
| * surgery, data reset) can call this directly to keep the LRU map in | ||
| * sync with the store. | ||
| */ | ||
| forget(nodeId: NodeId): void; | ||
| /** | ||
| * Read-only snapshot of the LRU bookkeeping. Exposed for tests + diagnostics. | ||
| * Consumers MUST treat the returned map as immutable — mutations corrupt | ||
| * the eviction order. | ||
| */ | ||
| get timestamps(): ReadonlyMap<NodeId, number>; | ||
| /** Configured cap, or `undefined` when disabled. */ | ||
| get cap(): number | undefined; | ||
| /** | ||
| * Enforce the configured cap by evicting oldest non-protected nodes. | ||
| * | ||
| * Algorithm: | ||
| * 1. If no cap is set, return immediately. | ||
| * 2. Build the candidate list (nodes currently in the store, not in the | ||
| * protected set). | ||
| * 3. Sort candidates ascending by last-touched timestamp. Nodes that have | ||
| * never been touched sort first (timestamp `-Infinity`) so they're | ||
| * evicted before any interacted node. | ||
| * 4. Pop the oldest candidate, remove it from the store + the AI engine, | ||
| * and forget its timestamp. Repeat until either the store is under | ||
| * cap OR no non-protected candidates remain. | ||
| * | ||
| * Returns the list of evicted node ids (oldest first) so callers can sync | ||
| * downstream state (renderer mesh rebuild, etc.). When the store is already | ||
| * under cap, returns `[]`. | ||
| * | ||
| * Special case: if `protectedIds.size >= maxNodes`, the function logs a | ||
| * developer-facing console warning AND skips eviction. This protects against | ||
| * the scenario where `expand(nodeId, depth=2)` returns more neighbors than | ||
| * the cap allows — the just-expanded set would be evicted on the same call, | ||
| * which is almost certainly a misconfiguration. | ||
| */ | ||
| enforceCap(protectedIds?: ReadonlySet<NodeId>): NodeId[]; | ||
| /** | ||
| * Choose the next eviction victim — the oldest non-protected node currently | ||
| * in the store. Returns `undefined` when no candidate exists. | ||
| * | ||
| * Iterates the LRU map in insertion order (which matches touch order for a | ||
| * correctly-maintained map) and falls back to "any non-protected node not | ||
| * in the LRU map at all" for nodes that were merged in but never touched. | ||
| */ | ||
| private pickVictim; | ||
| /** Remove `victim` from the store + the AI side-stores. */ | ||
| private evictOne; | ||
| } | ||
| /** | ||
| * Label value used by edge label maps. A plain `string` is used as-is | ||
| * regardless of how many targets share the bucket. An object of the form | ||
| * `{ one, other }` selects the CLDR-style plural form: `one` when the | ||
| * bucket resolves to a single target name, `other` for two or more. | ||
| * | ||
| * @example | ||
| * // Verb phrase — invariant across cardinality: | ||
| * dwelt_in: 'Was home to' | ||
| * | ||
| * @example | ||
| * // Noun phrase — pluralizes when multiple targets aggregate: | ||
| * participant_in: { one: 'Has participant', other: 'Has participants' } | ||
| */ | ||
| type EdgeLabelValue = string | { | ||
| one: string; | ||
| other: string; | ||
| }; | ||
| /** | ||
| * Configuration for how edge types map to human-readable descriptions. | ||
| * The key is the edge type (e.g., 'father_of'), the value is either the | ||
| * label string used for any cardinality, or an `{ one, other }` pair to | ||
| * select the singular vs. plural form based on the number of aggregated | ||
| * target names. | ||
| * | ||
| * Edge types that share the same resolved label will be grouped together. | ||
| */ | ||
| type EdgeLabelMap = Record<string, EdgeLabelValue>; | ||
| /** | ||
| * Resolve an {@link EdgeLabelValue} to a concrete string given the number | ||
| * of aggregated target names. Plain strings pass through unchanged; the | ||
| * `{ one, other }` form picks `one` for `count === 1` and `other` otherwise. | ||
| * | ||
| * Exported for callers that need the same resolution rule outside of | ||
| * {@link aggregateEdges} (e.g. when composing labels for a custom UI). | ||
| */ | ||
| declare function resolveEdgeLabel(value: EdgeLabelValue, count: number): string; | ||
| interface AggregatedEdge { | ||
| /** The human-readable label (e.g., "Son of") */ | ||
| label: string; | ||
| /** The names of the related nodes, joined naturally */ | ||
| names: string[]; | ||
| /** The formatted description (e.g., "Son of Abraham and Sarah") */ | ||
| description: string; | ||
| } | ||
| declare function aggregateEdges(nodeId: NodeId, edges: EdgeData[], getNodeName: (id: NodeId) => string, incomingLabels?: EdgeLabelMap, outgoingLabels?: EdgeLabelMap): AggregatedEdge[]; | ||
| export { type AggregatedEdge as A, type DataAdapter as D, type EdgeLabelMap as E, type HttpTransportConfig as H, type InProcessTransportConfig as I, type MemoryManagedAIEngine as M, type Transport as T, type DataAdapterConfig as a, DataManager as b, type EdgeLabelValue as c, type MemoryManagedStore as d, MemoryManager as e, aggregateEdges as f, httpTransport as h, inProcessTransport as i, resolveEdgeLabel as r }; |
| import { L as LLMProvider, C as CacheProvider, a as AIEngineConfig, A as AIEngine, G as GraphStore, b as ChatOptions, c as ChatEvent, d as GraphData, N as NodeId, e as NodeData, P as PaginationOptions, f as PaginatedResult, D as DataFilter, g as ContentData, E as EdgeData } from './AIEngine-C0Rd5U-b.js'; | ||
| /** | ||
| * Abstract chat transport. The same shape on either side: pass a message | ||
| * (and optional cancel signal / emit-tool-calls flag), get an async iterable | ||
| * of {@link ChatEvent}s back. | ||
| * | ||
| * Two built-in implementations: | ||
| * - {@link inProcessTransport} — runs the AIEngine in the current JS | ||
| * context. Suitable for tests, demos, and any host where the API key | ||
| * can live in the same process as the renderer. | ||
| * - {@link httpTransport} — POSTs the message to a server endpoint and | ||
| * parses an SSE response into ChatEvents. Used by Next.js / browser | ||
| * hosts that must keep the LLM + cache server-side (because the redis | ||
| * client can't run in the browser, and API keys must not be exposed | ||
| * to it). | ||
| * | ||
| * Hosts may also implement a custom transport if they need bespoke routing | ||
| * (queueing, fanout, retry, etc.). The contract is intentionally tiny. | ||
| */ | ||
| interface Transport { | ||
| chat(message: string, opts?: ChatOptions): AsyncIterable<ChatEvent>; | ||
| } | ||
| /** | ||
| * Configuration for {@link inProcessTransport}. Either pass an existing | ||
| * {@link AIEngine} (advanced — when you've already constructed one) or | ||
| * the provider/cache/config triple (typical — the transport builds the | ||
| * engine for you). | ||
| */ | ||
| interface InProcessTransportConfig { | ||
| /** LLM provider. Required when no `engine` is supplied. */ | ||
| provider?: LLMProvider; | ||
| /** Optional response cache. */ | ||
| cache?: CacheProvider; | ||
| /** Optional AIEngine config (e.g. `schemaSampleSize`). */ | ||
| aiConfig?: AIEngineConfig; | ||
| /** Optional pre-built {@link AIEngine}. Wins over the other fields. */ | ||
| engine?: AIEngine; | ||
| /** Optional pre-built {@link GraphStore} (used when building a fresh engine). */ | ||
| store?: GraphStore; | ||
| } | ||
| /** | ||
| * In-process transport. Constructs (or reuses) an {@link AIEngine} and | ||
| * forwards `chat` calls directly to it. The transport does NOT own the | ||
| * graph data — when no `engine` is supplied, the caller is responsible | ||
| * for loading nodes into the supplied store before invoking `chat`. | ||
| * | ||
| * In typical app usage the React layer reuses the AIEngine that | ||
| * `GraphProvider` already constructed; the transport function is exposed | ||
| * for non-React consumers and tests. | ||
| */ | ||
| declare function inProcessTransport(config: InProcessTransportConfig): Transport; | ||
| /** | ||
| * Configuration for {@link httpTransport}. | ||
| */ | ||
| interface HttpTransportConfig { | ||
| /** Endpoint URL. e.g. `'/api/chat'`. */ | ||
| url: string; | ||
| /** Override `fetch` (testing / non-DOM environments). Defaults to global fetch. */ | ||
| fetch?: typeof fetch; | ||
| /** Extra headers. The transport always sets `Content-Type: application/json`. */ | ||
| headers?: HeadersInit; | ||
| } | ||
| /** | ||
| * HTTP transport. POSTs `{ message, emitToolCalls }` to the configured | ||
| * endpoint and parses the response as Server-Sent Events into | ||
| * {@link ChatEvent}s. | ||
| * | ||
| * SSE format (mirrors what biblegraph's `/api/chat` route emits in Phase | ||
| * 2C): | ||
| * ``` | ||
| * data: {"type":"text","delta":"hello"} | ||
| * | ||
| * data: {"type":"done","reason":"stop"} | ||
| * ``` | ||
| * Each `data:` line is a JSON-encoded {@link ChatEvent}. Blank lines | ||
| * delimit events. The transport silently drops malformed lines so a | ||
| * single bad line doesn't break the rest of the stream. | ||
| * | ||
| * Tool-call events that carry a function predicate (`apply_filter`) are | ||
| * round-tripped via the `spec` field — the transport rebuilds the | ||
| * predicate on the client side using the same `buildPredicateFromSpec` | ||
| * helper as the live path. This keeps the wire format JSON-clean. | ||
| */ | ||
| declare function httpTransport(config: HttpTransportConfig): Transport; | ||
| /** Configuration passed to the adapter during initialization */ | ||
| interface DataAdapterConfig { | ||
| [key: string]: unknown; | ||
| } | ||
| /** Interface for fetching graph data from any source */ | ||
| interface DataAdapter { | ||
| /** Called once when InferaGraph initializes. Return the starting subgraph. */ | ||
| getInitialView(config?: DataAdapterConfig): Promise<GraphData>; | ||
| /** Get a single node by ID */ | ||
| getNode(id: NodeId): Promise<NodeData | undefined>; | ||
| /** Get direct neighbors of a node (1-hop by default). Used when user clicks/expands a node. */ | ||
| getNeighbors(nodeId: NodeId, depth?: number): Promise<GraphData>; | ||
| /** Find a path between two nodes. Used for lineage/connection queries. */ | ||
| findPath(fromId: NodeId, toId: NodeId): Promise<GraphData>; | ||
| /** Search nodes by text query. Used by search UI. */ | ||
| search(query: string, pagination?: PaginationOptions): Promise<PaginatedResult<NodeData>>; | ||
| /** Filter nodes by criteria. Used by filter panel. */ | ||
| filter(filter: DataFilter, pagination?: PaginationOptions): Promise<PaginatedResult<NodeData>>; | ||
| /** Get content/details for a node. Used when detail panel opens. */ | ||
| getContent(nodeId: NodeId): Promise<ContentData | undefined>; | ||
| } | ||
| /** Minimal store interface required by DataManager */ | ||
| interface MergeableStore { | ||
| merge(data: { | ||
| nodes: NodeData[]; | ||
| edges: unknown[]; | ||
| }): void; | ||
| } | ||
| /** | ||
| * Bridges async DataAdapter to sync GraphStore. | ||
| * Manages caching of which nodes have been fetched and | ||
| * merges adapter results into the store. | ||
| */ | ||
| declare class DataManager { | ||
| private readonly _adapter; | ||
| private readonly store; | ||
| private readonly fetchedNodes; | ||
| private initialized; | ||
| constructor(store: MergeableStore, adapter: DataAdapter); | ||
| get isInitialized(): boolean; | ||
| /** | ||
| * The underlying {@link DataAdapter} this manager was constructed with. | ||
| * Exposed so callers (notably `GraphProvider`) can compare adapter | ||
| * identity across renders to decide whether to swap the manager out. | ||
| */ | ||
| get adapter(): DataAdapter; | ||
| /** Initialize: call adapter.getInitialView(), merge into store */ | ||
| initialize(config?: DataAdapterConfig): Promise<void>; | ||
| /** Expand a node: fetch neighbors and merge into store */ | ||
| expandNode(nodeId: NodeId, depth?: number): Promise<GraphData>; | ||
| /** Find path: fetch path subgraph, merge into store, return path nodes */ | ||
| findPath(fromId: NodeId, toId: NodeId): Promise<NodeData[]>; | ||
| /** Search: delegates to adapter, returns results (doesn't auto-add to store) */ | ||
| search(query: string): Promise<PaginatedResult<NodeData>>; | ||
| /** Get content for detail panel */ | ||
| getContent(nodeId: NodeId): Promise<ContentData | undefined>; | ||
| /** Check if a node has been fetched */ | ||
| hasFetched(nodeId: NodeId): boolean; | ||
| } | ||
| /** | ||
| * Minimal slice of the {@link GraphStore} surface that {@link MemoryManager} | ||
| * needs. Kept narrow so tests can stub a fake store with the four methods. | ||
| */ | ||
| interface MemoryManagedStore { | ||
| removeNode(id: NodeId): void; | ||
| hasNode(id: NodeId): boolean; | ||
| readonly nodeCount: number; | ||
| /** | ||
| * Enumerate every NodeId currently in the store. Used when picking the | ||
| * eviction victim — untouched nodes (loaded via `getInitialView` but | ||
| * never interacted with) need to surface in the LRU candidate pool with | ||
| * an implicit "oldest of all" timestamp, otherwise they'd live forever. | ||
| */ | ||
| getAllNodes(): Array<{ | ||
| id: NodeId; | ||
| } | { | ||
| readonly id: NodeId; | ||
| }>; | ||
| } | ||
| /** | ||
| * Optional AI-side cleanup hooks. {@link AIEngine} implements both; tests can | ||
| * pass `undefined` (or a partial object) when no AI surface is active. | ||
| */ | ||
| interface MemoryManagedAIEngine { | ||
| /** Remove the embedding record(s) for `nodeId`. No-op when none cached. */ | ||
| dropEmbedding?: (nodeId: NodeId) => void | Promise<void>; | ||
| /** Remove every inferred edge incident to `nodeId`. No-op when none stored. */ | ||
| dropInferredEdgesFor?: (nodeId: NodeId) => void | Promise<void>; | ||
| } | ||
| /** | ||
| * LRU eviction coordinator. Keeps the in-memory graph footprint bounded so | ||
| * long drilldown sessions don't grow the store unboundedly. | ||
| * | ||
| * Responsibilities: | ||
| * - Track when each node was last "touched" (hover, focus, expand, | ||
| * getContent). The most-recent timestamp wins so freshly-interacted | ||
| * nodes survive eviction. | ||
| * - When the store exceeds its configured cap, evict the oldest | ||
| * non-protected nodes until either the store is under cap or every | ||
| * remaining node is protected. | ||
| * - Coordinate cleanup across the {@link GraphStore} (node + edge | ||
| * removal) AND the {@link AIEngine} (embedding cache + inferred-edge | ||
| * overlay) so a victim doesn't leak memory in a sibling subsystem. | ||
| * | ||
| * Design note (per Phase 6 plan): this is a SEPARATE class, not bolted into | ||
| * {@link GraphStore}. The store is domain-agnostic and concerns itself only | ||
| * with structural integrity; eviction policy is a host-controlled tuning | ||
| * decision pushed in via `<InferaGraph maxNodes>`. | ||
| */ | ||
| declare class MemoryManager { | ||
| private readonly store; | ||
| private readonly aiEngine; | ||
| private readonly maxNodes; | ||
| private readonly touchTimestamps; | ||
| /** | ||
| * Monotonic counter used as the timestamp source. We avoid `Date.now()` so | ||
| * tests don't have to fake the clock — every `touch()` advances by one, | ||
| * which is enough to define a strict ordering for LRU eviction. | ||
| */ | ||
| private clock; | ||
| /** | ||
| * @param store Mergeable store whose `removeNode` is invoked on eviction. | ||
| * @param aiEngine Optional AI engine; when supplied, the manager calls | ||
| * `dropEmbedding` + `dropInferredEdgesFor` for each victim | ||
| * so embeddings + inferred-edge overlay don't leak. | ||
| * @param maxNodes Soft cap. `undefined` disables eviction entirely | ||
| * (every `enforceCap` call becomes a no-op). `0` is treated | ||
| * the same as `undefined` so passing `<InferaGraph maxNodes={0}>` | ||
| * doesn't accidentally evict everything. | ||
| */ | ||
| constructor(store: MemoryManagedStore, aiEngine?: MemoryManagedAIEngine, maxNodes?: number); | ||
| /** | ||
| * Mark `nodeId` as freshly used. Subsequent calls overwrite the prior | ||
| * timestamp — most-recent touch wins, so the node moves to the front of | ||
| * the LRU order. | ||
| */ | ||
| touch(nodeId: NodeId): void; | ||
| /** | ||
| * Drop the LRU bookkeeping for `nodeId`. Called automatically during | ||
| * eviction; hosts that remove nodes through other paths (manual store | ||
| * surgery, data reset) can call this directly to keep the LRU map in | ||
| * sync with the store. | ||
| */ | ||
| forget(nodeId: NodeId): void; | ||
| /** | ||
| * Read-only snapshot of the LRU bookkeeping. Exposed for tests + diagnostics. | ||
| * Consumers MUST treat the returned map as immutable — mutations corrupt | ||
| * the eviction order. | ||
| */ | ||
| get timestamps(): ReadonlyMap<NodeId, number>; | ||
| /** Configured cap, or `undefined` when disabled. */ | ||
| get cap(): number | undefined; | ||
| /** | ||
| * Enforce the configured cap by evicting oldest non-protected nodes. | ||
| * | ||
| * Algorithm: | ||
| * 1. If no cap is set, return immediately. | ||
| * 2. Build the candidate list (nodes currently in the store, not in the | ||
| * protected set). | ||
| * 3. Sort candidates ascending by last-touched timestamp. Nodes that have | ||
| * never been touched sort first (timestamp `-Infinity`) so they're | ||
| * evicted before any interacted node. | ||
| * 4. Pop the oldest candidate, remove it from the store + the AI engine, | ||
| * and forget its timestamp. Repeat until either the store is under | ||
| * cap OR no non-protected candidates remain. | ||
| * | ||
| * Returns the list of evicted node ids (oldest first) so callers can sync | ||
| * downstream state (renderer mesh rebuild, etc.). When the store is already | ||
| * under cap, returns `[]`. | ||
| * | ||
| * Special case: if `protectedIds.size >= maxNodes`, the function logs a | ||
| * developer-facing console warning AND skips eviction. This protects against | ||
| * the scenario where `expand(nodeId, depth=2)` returns more neighbors than | ||
| * the cap allows — the just-expanded set would be evicted on the same call, | ||
| * which is almost certainly a misconfiguration. | ||
| */ | ||
| enforceCap(protectedIds?: ReadonlySet<NodeId>): NodeId[]; | ||
| /** | ||
| * Choose the next eviction victim — the oldest non-protected node currently | ||
| * in the store. Returns `undefined` when no candidate exists. | ||
| * | ||
| * Iterates the LRU map in insertion order (which matches touch order for a | ||
| * correctly-maintained map) and falls back to "any non-protected node not | ||
| * in the LRU map at all" for nodes that were merged in but never touched. | ||
| */ | ||
| private pickVictim; | ||
| /** Remove `victim` from the store + the AI side-stores. */ | ||
| private evictOne; | ||
| } | ||
| /** | ||
| * Label value used by edge label maps. A plain `string` is used as-is | ||
| * regardless of how many targets share the bucket. An object of the form | ||
| * `{ one, other }` selects the CLDR-style plural form: `one` when the | ||
| * bucket resolves to a single target name, `other` for two or more. | ||
| * | ||
| * @example | ||
| * // Verb phrase — invariant across cardinality: | ||
| * dwelt_in: 'Was home to' | ||
| * | ||
| * @example | ||
| * // Noun phrase — pluralizes when multiple targets aggregate: | ||
| * participant_in: { one: 'Has participant', other: 'Has participants' } | ||
| */ | ||
| type EdgeLabelValue = string | { | ||
| one: string; | ||
| other: string; | ||
| }; | ||
| /** | ||
| * Configuration for how edge types map to human-readable descriptions. | ||
| * The key is the edge type (e.g., 'father_of'), the value is either the | ||
| * label string used for any cardinality, or an `{ one, other }` pair to | ||
| * select the singular vs. plural form based on the number of aggregated | ||
| * target names. | ||
| * | ||
| * Edge types that share the same resolved label will be grouped together. | ||
| */ | ||
| type EdgeLabelMap = Record<string, EdgeLabelValue>; | ||
| /** | ||
| * Resolve an {@link EdgeLabelValue} to a concrete string given the number | ||
| * of aggregated target names. Plain strings pass through unchanged; the | ||
| * `{ one, other }` form picks `one` for `count === 1` and `other` otherwise. | ||
| * | ||
| * Exported for callers that need the same resolution rule outside of | ||
| * {@link aggregateEdges} (e.g. when composing labels for a custom UI). | ||
| */ | ||
| declare function resolveEdgeLabel(value: EdgeLabelValue, count: number): string; | ||
| interface AggregatedEdge { | ||
| /** The human-readable label (e.g., "Son of") */ | ||
| label: string; | ||
| /** The names of the related nodes, joined naturally */ | ||
| names: string[]; | ||
| /** The formatted description (e.g., "Son of Abraham and Sarah") */ | ||
| description: string; | ||
| } | ||
| declare function aggregateEdges(nodeId: NodeId, edges: EdgeData[], getNodeName: (id: NodeId) => string, incomingLabels?: EdgeLabelMap, outgoingLabels?: EdgeLabelMap): AggregatedEdge[]; | ||
| export { type AggregatedEdge as A, type DataAdapter as D, type EdgeLabelMap as E, type HttpTransportConfig as H, type InProcessTransportConfig as I, type MemoryManagedAIEngine as M, type Transport as T, type DataAdapterConfig as a, DataManager as b, type EdgeLabelValue as c, type MemoryManagedStore as d, MemoryManager as e, aggregateEdges as f, httpTransport as h, inProcessTransport as i, resolveEdgeLabel as r }; |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
| import * as React$1 from 'react'; | ||
| import React__default, { ReactNode } from 'react'; | ||
| import { G as GraphStore, $ as QueryEngine, A as AIEngine, N as NodeId, d as GraphData, K as InferredEdgeStore, e as NodeData, E as EdgeData, l as LayoutMode, h as NodeRenderConfig, T as TooltipConfig, L as LLMProvider, C as CacheProvider, y as EmbeddingStore, c as ChatEvent, f as PaginatedResult, g as ContentData, B as FilterSpec, a0 as SearchResult, Y as NodeComponentProps, j as NodeRenderFn, a7 as TooltipComponentProps, a9 as TooltipRenderFn, b as ChatOptions } from './AIEngine-C0Rd5U-b.js'; | ||
| import { b as DataManager, e as MemoryManager, D as DataAdapter, a as DataAdapterConfig, E as EdgeLabelMap, T as Transport } from './aggregateEdges-DSr58z_h.js'; | ||
| /** | ||
| * Slug → node-id resolver. Hosts that route detail pages by human-readable | ||
| * slug (e.g. `/person/adam`) supply this so the library can translate the | ||
| * slug into the canonical node id used by the graph store + adapter. | ||
| * | ||
| * The sync escape hatch (`NodeId` instead of `Promise<NodeId>`) is | ||
| * deliberate: biblegraph's slug→UUID v5 helper is purely deterministic and | ||
| * doesn't need a microtask. Hooks always `await` the return so async | ||
| * resolvers (database lookups, manifest fetches) work too. | ||
| */ | ||
| type SlugResolver = (slug: string) => NodeId | Promise<NodeId>; | ||
| interface GraphContextValue { | ||
| store: GraphStore; | ||
| queryEngine: QueryEngine; | ||
| aiEngine: AIEngine; | ||
| dataManager: DataManager | null; | ||
| /** | ||
| * LRU eviction coordinator. Always present, even when no `maxNodes` cap | ||
| * is configured — `enforceCap` becomes a no-op in that case. | ||
| */ | ||
| memoryManager: MemoryManager; | ||
| /** | ||
| * Active {@link SlugResolver}, or `undefined` when the host opted out. | ||
| * Hooks check this before resolving — when undefined, they treat the | ||
| * input as a raw NodeId (no slug→id translation). | ||
| */ | ||
| slugResolver: SlugResolver | undefined; | ||
| /** | ||
| * Library-internal slug→nodeId cache. Slugs are immutable, so there's no | ||
| * TTL — entries live for the GraphProvider's lifetime. Exposed on the | ||
| * context value (rather than encapsulated) so the two hooks | ||
| * (`useInferaGraphContent` + `useInferaGraphNeighbors`) share one cache. | ||
| */ | ||
| slugCache: Map<string, NodeId>; | ||
| } | ||
| interface GraphProviderProps { | ||
| children: ReactNode; | ||
| /** Static data (existing behavior — wraps in StaticDataAdapter) */ | ||
| data?: GraphData; | ||
| /** DataAdapter for dynamic data fetching */ | ||
| adapter?: DataAdapter; | ||
| /** Config passed to adapter.getInitialView() */ | ||
| initialViewConfig?: DataAdapterConfig; | ||
| /** Slug → nodeId resolver. See {@link SlugResolver}. */ | ||
| slugResolver?: SlugResolver; | ||
| /** | ||
| * Soft cap on the number of nodes retained in the {@link GraphStore}. | ||
| * When the count exceeds the cap the {@link MemoryManager} evicts the | ||
| * oldest non-protected entries. `undefined` (the default) disables the | ||
| * cap so memory grows with the dataset. | ||
| */ | ||
| maxNodes?: number; | ||
| /** | ||
| * Pluggable {@link InferredEdgeStore} consumed by the engine for | ||
| * inferred-relationship persistence. Pass `RemoteInferredEdgeStore` | ||
| * in a browser host to read server-computed edges via HTTP; pass | ||
| * `inMemoryInferredEdgeStore()` for self-contained demos. Omitting | ||
| * leaves the engine without a store — `getInferredEdges()` returns | ||
| * `[]` and compute is a no-op. | ||
| */ | ||
| inferredEdgeStore?: InferredEdgeStore; | ||
| /** Called when initial data is loaded */ | ||
| onReady?: () => void; | ||
| } | ||
| declare function GraphProvider({ children, data, adapter, initialViewConfig, slugResolver, maxNodes, inferredEdgeStore, onReady, }: GraphProviderProps): React__default.JSX.Element; | ||
| /** | ||
| * Fallback color used only when the configured palette is empty. The CSS | ||
| * variable `--ig-node-color` should override this in themed apps; this | ||
| * constant is the absolute floor. | ||
| */ | ||
| declare const DEFAULT_NODE_COLOR = "#3b82f6"; | ||
| /** Function form: read the node, return a CSS hex/rgb color. */ | ||
| type NodeColorFn = (node: NodeData) => string | undefined; | ||
| interface NodeColorResolverOptions { | ||
| /** Override resolver — wins over both the type-keyed map and the palette. */ | ||
| colorFn?: NodeColorFn; | ||
| /** Explicit type → color map. Wins over auto-assignment. */ | ||
| nodeColors?: Record<string, string>; | ||
| /** | ||
| * Pool of colors used to deterministically assign a color to types that | ||
| * are not present in `nodeColors`. Defaults to {@link DEFAULT_PALETTE_32}. | ||
| */ | ||
| palette?: readonly string[]; | ||
| /** Default color used when palette is empty AND nothing else matches. */ | ||
| defaultColor?: string; | ||
| /** | ||
| * Multiplier (0..1) used by {@link NodeColorResolver.resolveHover} to lift | ||
| * the resolved color toward white for hover state. Defaults to 0.25. | ||
| */ | ||
| hoverBrightness?: number; | ||
| } | ||
| /** | ||
| * Resolves resting + hover colors for a node based on its attributes. | ||
| * | ||
| * Resolution order (resting): | ||
| * 1. `colorFn(node)` if it returns a non-undefined string | ||
| * 2. `nodeColors[node.attributes.type]` if it's a string lookup | ||
| * 3. `node.attributes.color` if the consumer set one explicitly | ||
| * 4. `palette[hashStringToIndex(type, palette.length)]` (deterministic auto) | ||
| * 5. `defaultColor` (only reached when palette is empty) | ||
| * | ||
| * Hover: same color, brightened by `hoverBrightness` (default 25 % toward white). | ||
| * | ||
| * Domain knowledge note: this resolver ships ZERO domain-specific defaults | ||
| * — the consumer (e.g. Bible Graph) supplies its own `nodeColors` map. | ||
| */ | ||
| declare class NodeColorResolver { | ||
| private readonly colorFn?; | ||
| private readonly nodeColors; | ||
| private readonly palette; | ||
| private readonly defaultColor; | ||
| private readonly hoverBrightness; | ||
| constructor(options?: NodeColorResolverOptions); | ||
| /** Resting color for `node`. */ | ||
| resolve(node: NodeData): string; | ||
| /** Hover color for `node` — resting color brightened toward white. */ | ||
| resolveHover(node: NodeData): string; | ||
| /** The active palette (for tests + introspection). */ | ||
| getPalette(): readonly string[]; | ||
| } | ||
| /** Fallback edge color when the palette is empty AND no override matches. */ | ||
| declare const DEFAULT_EDGE_COLOR = "#6366f1"; | ||
| /** | ||
| * Per-call context handed to {@link EdgeColorFn}. Carries the resolved | ||
| * resting colors of the edge's source + target nodes (the same hex values | ||
| * {@link NodeColorResolver.resolve} would return for those nodes) so the | ||
| * function can derive a color from its endpoints — for example by | ||
| * blending them, picking one side, or treating same-vs-different as a | ||
| * boolean. | ||
| * | ||
| * Both fields are guaranteed to be valid CSS color strings; callers fall | ||
| * back to {@link DEFAULT_EDGE_COLOR} (or the consumer's chosen | ||
| * fallback) for either side that cannot be resolved against the live | ||
| * graph store. | ||
| */ | ||
| interface EdgeColorContext { | ||
| /** Resolved resting color of the source node. */ | ||
| sourceColor: string; | ||
| /** Resolved resting color of the target node. */ | ||
| targetColor: string; | ||
| } | ||
| /** | ||
| * Function form: read the edge + its endpoint colors, return a CSS | ||
| * hex/rgb color (or `undefined` to fall through to the type-keyed map / | ||
| * palette). | ||
| * | ||
| * The second argument is optional at call sites that pre-date the context | ||
| * — older `(edge) => string` consumers continue to work because they | ||
| * simply ignore the extra parameter. | ||
| */ | ||
| type EdgeColorFn = (edge: EdgeData, ctx: EdgeColorContext) => string | undefined; | ||
| interface EdgeColorMapOptions { | ||
| /** Override resolver — wins over both the type-keyed map and the palette. */ | ||
| colorFn?: EdgeColorFn; | ||
| /** Explicit type → color map. Wins over auto-assignment. */ | ||
| edgeColors?: Record<string, string>; | ||
| /** | ||
| * Pool of colors used to deterministically assign a color to relationship | ||
| * types not present in `edgeColors`. Defaults to {@link DEFAULT_PALETTE_32}. | ||
| */ | ||
| palette?: readonly string[]; | ||
| /** Default color used when palette is empty AND nothing else matches. */ | ||
| defaultColor?: string; | ||
| /** | ||
| * Multiplier (0..1) used by {@link EdgeColorMap.resolveHover} to lift the | ||
| * resolved color toward white for hover state. Defaults to 0.25. | ||
| */ | ||
| hoverBrightness?: number; | ||
| } | ||
| /** | ||
| * Resolves resting + hover colors for an edge based on its `attributes.type` | ||
| * (the relationship type, e.g. `father_of`). | ||
| * | ||
| * Resolution order (resting): | ||
| * 1. `colorFn(edge, ctx)` if it returns a non-undefined string | ||
| * 2. `edgeColors[edge.attributes.type]` | ||
| * 3. `palette[hashStringToIndex(type, palette.length)]` (deterministic auto) | ||
| * 4. `defaultColor` (only reached when palette is empty) | ||
| * | ||
| * Hover: same color, brightened by `hoverBrightness` (default 25 % toward white). | ||
| * | ||
| * Domain knowledge note: this map ships ZERO domain-specific defaults — | ||
| * `father_of`, `married_to`, etc. mean nothing to InferaGraph. The consumer | ||
| * (e.g. Bible Graph) supplies its own `edgeColors` map. | ||
| */ | ||
| declare class EdgeColorMap { | ||
| private readonly colorFn?; | ||
| private readonly edgeColors; | ||
| private readonly palette; | ||
| private readonly defaultColor; | ||
| private readonly hoverBrightness; | ||
| constructor(options?: EdgeColorMapOptions); | ||
| /** | ||
| * Resting color for `edge`. | ||
| * | ||
| * `ctx` carries the resolved endpoint colors so a {@link EdgeColorFn} | ||
| * can derive the edge color from its endpoints (see | ||
| * {@link blendEdgeColors}). The argument is defaulted to the fallback | ||
| * color on both sides so legacy call sites that don't yet plumb | ||
| * endpoint colors through still produce a sensible result. | ||
| */ | ||
| resolve(edge: EdgeData, ctx?: EdgeColorContext): string; | ||
| /** Hover color for `edge` — resting color brightened toward white. */ | ||
| resolveHover(edge: EdgeData, ctx?: EdgeColorContext): string; | ||
| /** The active palette (for tests + introspection). */ | ||
| getPalette(): readonly string[]; | ||
| } | ||
| interface InferaGraphProps { | ||
| data?: GraphData; | ||
| layout?: LayoutMode; | ||
| nodeRender?: NodeRenderConfig; | ||
| tooltip?: TooltipConfig; | ||
| /** Pool of colors for deterministic auto-assignment. */ | ||
| palette?: readonly string[]; | ||
| /** Explicit type → color map for nodes. */ | ||
| nodeColors?: Record<string, string>; | ||
| /** Function override for nodes. */ | ||
| nodeColorFn?: NodeColorFn; | ||
| /** Explicit relationship-type → color map for edges. */ | ||
| edgeColors?: Record<string, string>; | ||
| /** Function override for edges. */ | ||
| edgeColorFn?: EdgeColorFn; | ||
| /** | ||
| * Incoming-edge label map for the default tooltip's natural-language | ||
| * description (e.g. `{ father_of: 'Son of', mother_of: 'Son of' }`). | ||
| * Ignored when `tooltip.renderTooltip` / `tooltip.component` is supplied. | ||
| */ | ||
| incomingEdgeLabels?: EdgeLabelMap; | ||
| /** | ||
| * Outgoing-edge label map for the default tooltip's natural-language | ||
| * description (e.g. `{ father_of: 'Father of' }`). | ||
| */ | ||
| outgoingEdgeLabels?: EdgeLabelMap; | ||
| /** | ||
| * Domain-agnostic visibility predicate. When supplied, only nodes for | ||
| * which the predicate returns `true` are rendered; edges whose source | ||
| * OR target node is filtered out are hidden too. | ||
| * | ||
| * The same predicate applies in **every** visualization mode — graph, | ||
| * tree, and any future mode (geospatial / timeline / chord / etc.). | ||
| * Filter changes are applied as in-place visibility toggles on the | ||
| * existing GPU buffers — there's no mesh teardown, no rebuild, and | ||
| * no layout recompute. Hidden nodes keep their layout positions, so | ||
| * unhiding restores the prior frame instantly. | ||
| * | ||
| * Default: no filter (every node visible). | ||
| */ | ||
| filter?: (node: NodeData) => boolean; | ||
| /** | ||
| * Tree-mode option: edge types that the hierarchical tidy-tree layout | ||
| * treats as parent -> child. Default `['parent_of']` — set to your | ||
| * domain's vocabulary (`['manages']` for org charts, `['supplies']` | ||
| * for supply chains, `['is_a']` for taxonomies, `['cites']` for | ||
| * citation graphs, etc.). Has no effect in graph mode. | ||
| */ | ||
| parentEdgeTypes?: string[]; | ||
| /** | ||
| * Tree-mode option: edge types that pair two nodes at the same depth | ||
| * so they appear adjacent and share children. Default `[]` (no | ||
| * pairing). Has no effect in graph mode. | ||
| */ | ||
| pairedEdgeTypes?: string[]; | ||
| /** | ||
| * LLM provider for AI features (NLQ filtering today; chat / search / highlight | ||
| * land in later phases). The host imports a provider package and passes a | ||
| * configured INSTANCE here. The host never invokes the LLM directly — | ||
| * InferaGraph owns the entire LLM lifecycle. | ||
| * | ||
| * Omitted = AI features are unavailable; the explicit `filter` predicate | ||
| * still works. | ||
| */ | ||
| llm?: LLMProvider; | ||
| /** | ||
| * Cache provider for LLM responses. Defaults to NO CACHING when omitted, so | ||
| * tests + small demos don't accidentally retain old responses. Pass | ||
| * `lruCache()` (built-in) or `@inferagraph/redis-cache-provider` for | ||
| * production. The cache is wiped automatically when the LLM provider | ||
| * instance changes. | ||
| * | ||
| * Also acts as the **Tier 2** embedding storage when `embeddingStore` is | ||
| * not supplied AND the configured `llm` provider implements `embed()` — | ||
| * embeddings are persisted in the cache and similarity is computed | ||
| * in-memory at query time. | ||
| */ | ||
| cache?: CacheProvider; | ||
| /** | ||
| * Optional dedicated {@link EmbeddingStore} (Tier 3 of the embedding | ||
| * progression). When supplied, AIEngine uses the store's vector-native | ||
| * `similar()` for semantic search. The default in-process implementation | ||
| * ships as `inMemoryEmbeddingStore()`; persistent stores live in their | ||
| * own packages. | ||
| * | ||
| * Tier mapping at runtime: | ||
| * - omit both → keyword search only. | ||
| * - `cache` only + provider with `embed()` → Tier 2. | ||
| * - `embeddingStore` + provider with `embed()` → Tier 3. | ||
| */ | ||
| embeddingStore?: EmbeddingStore; | ||
| /** | ||
| * Natural-language query that the LLM compiles into a filter predicate. | ||
| * Combined with the explicit `filter` prop via AND: the developer-set | ||
| * `filter` runs first (proactive scope), then `query` narrows within that | ||
| * scope. Requires `llm`. An empty / undefined `query` is a no-op. | ||
| */ | ||
| query?: string; | ||
| /** | ||
| * Optional explicit chat {@link Transport}. Wins over the implicit | ||
| * "build an in-process transport from `llm`" path. Use this to wire | ||
| * an HTTP transport (Next.js `/api/chat` proxy) so the LLM + cache | ||
| * stay server-side. | ||
| */ | ||
| transport?: Transport; | ||
| /** | ||
| * Toggle visibility of the Phase 5 inferred-edge overlay (dashed | ||
| * lines between nodes the AI inference pipeline thinks are related). | ||
| * Defaults to `false` (overlay hidden) so the explicit graph reads | ||
| * cleanly out of the box. Hosts opt in either by setting this to | ||
| * `true` or by handing the LLM a `set_inferred_visibility` chat | ||
| * tool call. | ||
| * | ||
| * The actual inferred edges are computed by | ||
| * `aiEngine.computeInferredEdges()` and pushed to the renderer | ||
| * separately — toggling this prop only shows/hides whatever has | ||
| * already been computed. | ||
| */ | ||
| showInferredEdges?: boolean; | ||
| /** | ||
| * Pluggable {@link InferredEdgeStore}. When the host toggles | ||
| * `showInferredEdges` to `true`, the library calls | ||
| * `aiEngine.getInferredEdges()` (which delegates to this store) and | ||
| * pipes the result into the renderer. Omitting the store leaves | ||
| * the engine without a persistence layer — the overlay stays empty | ||
| * until the host pushes edges via another route. | ||
| */ | ||
| inferredEdgeStore?: InferredEdgeStore; | ||
| /** | ||
| * Fires `true` when the auto-fetch effect kicks off (the | ||
| * `showInferredEdges` prop just flipped to `true`) and `false` when | ||
| * the fetch resolves, errors, or is cancelled by unmount. Hosts use | ||
| * this to render a brief spinner near the toggle while the inferred | ||
| * edges are loading from the server. | ||
| */ | ||
| onInferredEdgesLoadingChange?: (loading: boolean) => void; | ||
| /** | ||
| * Host-facing chat callback. Called with `text` and `done` events | ||
| * only — tool calls (`apply_filter` / `highlight` / `focus` / | ||
| * `annotate`) are dispatched silently to the renderer. | ||
| * | ||
| * The host writes its own chat UI on top of this callback (input | ||
| * box, message log, conversation pane). InferaGraph owns the | ||
| * streaming / tool-routing logistics. | ||
| */ | ||
| onChat?: (event: ChatEvent) => void; | ||
| /** | ||
| * Phase 1 (RAG architecture, 0.8.0) — fired when the engine emits a | ||
| * `debug` ChatEvent (vector-search, rerank, retrieval-empty, etc.). | ||
| * Hosts use this to render diagnostic badges underneath assistant | ||
| * bubbles for ops visibility. Debug events are NEVER yielded to the | ||
| * iterator — only this callback receives them. | ||
| */ | ||
| onDiagnostic?: (event: Extract<ChatEvent, { | ||
| type: 'debug'; | ||
| }>) => void; | ||
| /** | ||
| * Phase 1 (RAG architecture, 0.8.0) — fired after a tool-call event | ||
| * has been dispatched to the renderer. Lets host UIs surface | ||
| * "N applied, M unknown" badges. The library reports the requested | ||
| * ids as `appliedIds`; future enhancements will reconcile against | ||
| * the store's known-ids set to populate `unknownIds`. | ||
| */ | ||
| onToolCallOutcome?: (outcome: { | ||
| tool: string; | ||
| appliedIds?: string[]; | ||
| unknownIds?: string[]; | ||
| }) => void; | ||
| /** | ||
| * Phase 6 — slug → nodeId resolver. When supplied, hooks like | ||
| * {@link useInferaGraphContent} translate the input through this | ||
| * resolver before calling into the {@link DataAdapter}. Hosts that | ||
| * route by raw UUIDs can omit it. | ||
| */ | ||
| slugResolver?: SlugResolver; | ||
| /** | ||
| * Phase 6 — soft cap on the number of nodes retained in the store. | ||
| * When exceeded, the {@link MemoryManager} evicts the oldest | ||
| * non-protected nodes. `undefined` (default) disables the cap. | ||
| */ | ||
| maxNodes?: number; | ||
| /** | ||
| * Phase 6 — fired when the user clicks a node body. The host typically | ||
| * opens a detail dialog. Receives the canonical NodeId (post-slug | ||
| * resolution) and the node's data snapshot. | ||
| * | ||
| * NOT fired for clicks on the "+" affordance — those go through | ||
| * {@link onExpandRequest} instead. | ||
| */ | ||
| onNodeClick?: (nodeId: NodeId, node: NodeData) => void; | ||
| /** | ||
| * Phase 6 — fired when the user clicks the "+" hover affordance on a | ||
| * node. Receives the canonical NodeId. The host typically calls | ||
| * `useInferaGraphNeighbors().expand(nodeId)`. When omitted, the | ||
| * library installs a default handler that fires the built-in | ||
| * `expand(nodeId)` dispatch. | ||
| */ | ||
| onExpandRequest?: (nodeId: NodeId) => void; | ||
| className?: string; | ||
| style?: React__default.CSSProperties; | ||
| /** | ||
| * Optional children rendered inside the InferaGraph subtree. Useful | ||
| * for hosts that want to embed components that consume | ||
| * {@link useInferaGraphChat} or other hooks that depend on the | ||
| * InferaGraph context. Children render OUTSIDE the WebGL canvas | ||
| * (after the `.ig-container` div) — they're for host-owned chrome / | ||
| * chat UI, not for in-canvas rendering. | ||
| */ | ||
| children?: React__default.ReactNode; | ||
| } | ||
| declare function InferaGraph(props: InferaGraphProps): React__default.JSX.Element; | ||
| interface UseInferaGraphReturn { | ||
| loadData: (data: GraphData) => void; | ||
| nodeCount: number; | ||
| edgeCount: number; | ||
| expandNode: (nodeId: NodeId, depth?: number) => Promise<void>; | ||
| findPath: (fromId: NodeId, toId: NodeId) => Promise<NodeData[]>; | ||
| search: (query: string) => Promise<PaginatedResult<NodeData>>; | ||
| getContent: (nodeId: NodeId) => Promise<ContentData | undefined>; | ||
| isReady: boolean; | ||
| } | ||
| declare function useInferaGraph(): UseInferaGraphReturn; | ||
| /** | ||
| * Public return shape of {@link useInferaGraphChat}. The hook returns a | ||
| * single function that streams a chat with the configured transport. | ||
| * | ||
| * The returned `AsyncIterable` yields ONLY `text` and `done` events — | ||
| * tool calls (`apply_filter`, `highlight`, `focus`, `annotate`) are | ||
| * dispatched silently to the renderer, exactly mirroring the contract | ||
| * of `<InferaGraph onChat>`. | ||
| */ | ||
| interface InferaGraphChatHook { | ||
| /** | ||
| * Send `message` through the active chat transport. Returns an async | ||
| * iterable that yields {@link ChatEvent}s — text + done only by | ||
| * default. Tool-call events are dispatched to the renderer's | ||
| * `setHighlight` / `setFilter` / `focusOn` / `annotate` and not | ||
| * surfaced to the iterator. | ||
| * | ||
| * Pass `signal` to cancel mid-stream. | ||
| * | ||
| * Throws synchronously if no transport (or no `llm` prop) is | ||
| * configured on the host `<InferaGraph>` element. | ||
| */ | ||
| chat: (message: string, opts?: { | ||
| signal?: AbortSignal; | ||
| /** | ||
| * Optional conversation id, forwarded to the active transport. When | ||
| * the HTTP transport is in use the id flows into the request body | ||
| * so the server route can thread it into its `ConversationStore`; | ||
| * with the in-process transport the id is forwarded directly to | ||
| * `AIEngine.chat`. Omit to opt out of conversation memory for this | ||
| * call. | ||
| */ | ||
| conversationId?: string; | ||
| }) => AsyncIterable<ChatEvent>; | ||
| } | ||
| /** | ||
| * React hook that surfaces InferaGraph's chat API to the host. | ||
| * | ||
| * Internally: | ||
| * 1. The hook resolves the active transport via React context | ||
| * (`<InferaGraph>` populates this). | ||
| * 2. When `chat(message)` is called, the hook iterates the transport's | ||
| * full event stream: | ||
| * - tool-call events are dispatched to the SceneController's | ||
| * highlight / focus / annotate / filter sinks. | ||
| * - text + done events are re-yielded to the host. | ||
| * 3. The host iterates the returned async iterable to render text | ||
| * bubbles. The renderer never appears in the host's iteration. | ||
| * | ||
| * MUST be called inside an `<InferaGraph>` subtree. | ||
| */ | ||
| declare function useInferaGraphChat(): InferaGraphChatHook; | ||
| /** | ||
| * Public return shape of {@link useInferaGraphChatContext}. The hook | ||
| * surfaces the renderer's dispatch sink so a host can fire ad-hoc | ||
| * {@link ChatEvent}s without going through `chat()`. | ||
| * | ||
| * The same dispatch path receives chat-driven tool-call events under | ||
| * the hood — calling `dispatch(...)` here goes through the exact same | ||
| * SceneController surface, which means highlights, filters, focus | ||
| * moves, annotations, and the new visual-reset commands all behave | ||
| * identically whether the model emits them or the host fires them. | ||
| */ | ||
| interface InferaGraphChatContextHook { | ||
| /** | ||
| * Dispatch a {@link ChatEvent} into the renderer. Mostly useful for | ||
| * host UI affordances ("Clear conversation" → reset highlight, | ||
| * "Camera home" button → `reset_view`, etc.). | ||
| * | ||
| * Prefer {@link InferaGraphCommands} (`useInferaGraphCommands`) for | ||
| * the typical cases — it's a thin semantic facade on top of this | ||
| * sink. Reach for this hook when you need to dispatch an event | ||
| * variant the facade doesn't surface (e.g. emitting a `debug` | ||
| * ChatEvent for ops visibility, or a synthetic `text` event for | ||
| * a UI-only "Welcome" bubble). | ||
| */ | ||
| dispatch: (event: ChatEvent) => void; | ||
| } | ||
| /** | ||
| * 0.10.0 — public escape-hatch hook returning the renderer's dispatch | ||
| * function. Most hosts should reach for {@link useInferaGraphCommands} | ||
| * instead; this hook exists for host code that needs to dispatch a | ||
| * {@link ChatEvent} variant the semantic facade doesn't expose. | ||
| * | ||
| * MUST be called inside an `<InferaGraph>` subtree. | ||
| */ | ||
| declare function useInferaGraphChatContext(): InferaGraphChatContextHook; | ||
| /** | ||
| * 0.10.0 — semantic facade for host-initiated graph commands. Most hosts | ||
| * use this hook instead of the lower-level | ||
| * {@link useInferaGraphChatContext}. | ||
| * | ||
| * Each method dispatches the equivalent {@link ChatEvent} to the | ||
| * renderer's dispatch sink — exactly the same path the library uses for | ||
| * chat-driven (model-emitted) events. There's no separate "host API" | ||
| * that diverges from the chat contract. | ||
| */ | ||
| interface InferaGraphCommands { | ||
| /** | ||
| * Replace the active highlight set. Pass an empty Set to clear (every | ||
| * node returns to full opacity). | ||
| */ | ||
| setHighlight(ids: ReadonlySet<string>): void; | ||
| /** | ||
| * Move the camera to the supplied node. The library does not have | ||
| * an "unfocus" affordance — hosts that want to recenter should call | ||
| * {@link resetView} instead. | ||
| */ | ||
| focusOn(nodeId: string): void; | ||
| /** | ||
| * Apply a domain-agnostic filter spec to the visible graph. The spec | ||
| * shape mirrors the LLM's `apply_filter` tool-call output: keys are | ||
| * node attribute names, values are arrays of allowed string values. | ||
| * Pass an empty object (`{}`) to clear the filter. | ||
| */ | ||
| applyFilter(spec: FilterSpec): void; | ||
| /** Toggle the inferred-edge overlay. */ | ||
| setInferredVisibility(visible: boolean): void; | ||
| /** Attach a callout to a node. */ | ||
| annotate(nodeId: string, text: string): void; | ||
| /** Drop every annotation currently mounted. */ | ||
| clearAnnotations(): void; | ||
| /** Snap the camera back to its captured initial orientation. */ | ||
| resetView(): void; | ||
| /** | ||
| * Comprehensive reset — highlights + annotations + filter + camera. | ||
| * The "fresh canvas" command. Hosts wire this to "Clear | ||
| * conversation" / "New session" UX. | ||
| */ | ||
| clearVisualState(): void; | ||
| } | ||
| /** | ||
| * 0.10.0 — public, semantic hook for dispatching host-initiated graph | ||
| * commands. Built on top of {@link useInferaGraphChatContext}, but with | ||
| * a flat, named surface so callers don't have to construct | ||
| * {@link ChatEvent} objects by hand. | ||
| * | ||
| * The returned object is memoized for the lifetime of the hook so | ||
| * downstream `useEffect` / `useMemo` deps remain stable across renders. | ||
| * | ||
| * MUST be called inside an `<InferaGraph>` subtree. | ||
| */ | ||
| declare function useInferaGraphCommands(): InferaGraphCommands; | ||
| /** | ||
| * Public return shape of {@link useInferaGraphSearch}. The hook returns a | ||
| * single `search(query)` function that delegates to the AIEngine's | ||
| * auto-detect router (keyword vs semantic). | ||
| * | ||
| * Pair with `<InferaGraph>` props: | ||
| * - `llm` — required for semantic routing. | ||
| * - `cache` — opts the engine into Tier 2 (cache-as-vector-store). | ||
| * - `embeddingStore` — opts the engine into Tier 3 (vector-native store). | ||
| * Without `llm`, every query falls back to the data-layer keyword search. | ||
| */ | ||
| interface InferaGraphSearchHook { | ||
| /** | ||
| * Run an auto-routed search. Short token-only queries hit the data-layer | ||
| * keyword index; sentence-shaped or NLQ inputs go through embeddings. | ||
| * | ||
| * Returns at most `opts.k ?? 25` hits, sorted by descending score. | ||
| */ | ||
| search: (query: string, opts?: { | ||
| k?: number; | ||
| signal?: AbortSignal; | ||
| }) => Promise<SearchResult[]>; | ||
| } | ||
| /** | ||
| * React hook surfacing InferaGraph's Phase 4 search API. | ||
| * | ||
| * Internally: | ||
| * 1. Pulls the {@link AIEngine} out of the GraphContext (populated by | ||
| * `<InferaGraph>` / `<GraphProvider>`). | ||
| * 2. Wraps `engine.search` in a stable `useCallback` so consumers can | ||
| * memoize / debounce without identity churn. | ||
| * 3. Holds the engine behind a ref so prop changes on the host don't | ||
| * invalidate the callback. | ||
| * | ||
| * MUST be called inside an `<InferaGraph>` (or standalone `<GraphProvider>`) | ||
| * subtree. | ||
| */ | ||
| declare function useInferaGraphSearch(): InferaGraphSearchHook; | ||
| /** | ||
| * Return shape of {@link useInferaGraphContent}. | ||
| * | ||
| * Mirrors the host-friendly fetch-state quartet (data + loading + error + | ||
| * refetch). All four fields are always populated — `loading` is `false` | ||
| * when `idOrSlug` is `undefined`, so hosts don't need conditional rendering | ||
| * gymnastics. | ||
| */ | ||
| interface UseInferaGraphContentReturn { | ||
| /** Fetched content, or `undefined` while loading / on error / for invalid input. */ | ||
| data: ContentData | undefined; | ||
| /** `true` while a fetch is in flight (or queued for the current id). */ | ||
| loading: boolean; | ||
| /** Last error encountered, or `undefined` on success / before first fetch. */ | ||
| error: Error | undefined; | ||
| /** Imperatively re-run the fetch for the current id. No-op when id is undefined. */ | ||
| refetch: () => void; | ||
| } | ||
| /** | ||
| * Tier-1 cache for getContent results. Keyed by canonical NodeId (post slug | ||
| * resolution). Populated on every successful fetch; consulted before issuing | ||
| * a network call so re-mounted DetailModals don't re-fetch. | ||
| * | ||
| * Lives at module scope intentionally: the GraphProvider context value is | ||
| * recreated on adapter swap, but that swap also wipes the store and would | ||
| * make any cached content stale. We mirror that lifecycle by clearing this | ||
| * Map whenever the dataManager identity flips (see effect below). | ||
| */ | ||
| /** | ||
| * React hook that fetches detail content for one node. | ||
| * | ||
| * Workflow: | ||
| * 1. If `idOrSlug` is `undefined`, return a disabled-state object — no | ||
| * fetch fires, no spinner. | ||
| * 2. Pass the input through the configured {@link SlugResolver}. When the | ||
| * host opted out (no resolver), the input is treated as a raw NodeId. | ||
| * 3. Hit the per-DataManager cache. On hit, return immediately; on miss, | ||
| * call `dataManager.getContent(uuid)` and cache the result. | ||
| * 4. Touch the {@link MemoryManager} so the node moves to the MRU end | ||
| * and survives any subsequent cap-enforcement pass. | ||
| * 5. Cancel via the standard "captured-token" pattern when the input | ||
| * changes mid-flight; `setData` only fires for the latest call. | ||
| * | ||
| * MUST be called inside an `<InferaGraph>` (or standalone `<GraphProvider>`) | ||
| * subtree. | ||
| */ | ||
| declare function useInferaGraphContent(idOrSlug: string | undefined): UseInferaGraphContentReturn; | ||
| /** Lifecycle status of a single expand call, tracked per-nodeId. */ | ||
| type NeighborStatus = 'loading' | 'loaded' | 'error'; | ||
| /** | ||
| * Return shape of {@link useInferaGraphNeighbors}. | ||
| * | ||
| * Imperative hook (not a fetch-on-mount hook): the host calls `expand` / | ||
| * `collapse` in response to user actions (click on the "+" affordance, | ||
| * keyboard shortcut, chat tool call). Reading `expanded` is enough for the | ||
| * host to know which nodes are currently in flight or already drilled-down. | ||
| */ | ||
| interface UseInferaGraphNeighborsReturn { | ||
| /** | ||
| * Fetch direct neighbors of `nodeId` (depth defaults to 1). Resolves slug | ||
| * via the configured resolver, calls `dataManager.expandNode`, syncs the | ||
| * scene from the freshly-merged store, and enforces the LRU cap with the | ||
| * just-expanded set marked as protected (so eviction never targets the | ||
| * neighbors the user just asked to see). | ||
| */ | ||
| expand: (nodeIdOrSlug: string, depth?: number) => Promise<void>; | ||
| /** | ||
| * Remove neighbors of `nodeId` that aren't in the protected initial-view | ||
| * set. Inverse of `expand`. Implementations of "protected" are | ||
| * intentionally simple v1: any node currently visible due to another | ||
| * expand still survives because its LRU timestamp is fresher than the | ||
| * collapsing nodes. | ||
| */ | ||
| collapse: (nodeIdOrSlug: string) => Promise<void>; | ||
| /** Per-node lifecycle map. Keys are CANONICAL NodeIds (post-resolution). */ | ||
| expanded: ReadonlyMap<NodeId, NeighborStatus>; | ||
| } | ||
| /** | ||
| * React hook that exposes drilldown / collapse imperatives. | ||
| * | ||
| * Slug-aware: callers may pass either a UUID or a slug. The configured | ||
| * {@link SlugResolver} translates slugs to canonical NodeIds; the resolved | ||
| * id is what survives in `expanded` so the host can reason about identity | ||
| * uniformly. | ||
| * | ||
| * Race-safe: each call captures its own resolved nodeId before awaiting, | ||
| * and the returned promise reflects the outcome of THIS call. If a faster | ||
| * second `expand(sameNode)` overlaps a slow first one, both promises | ||
| * resolve correctly because the underlying `dataManager.expandNode` merges | ||
| * by id (later merges are no-ops for nodes already present). | ||
| * | ||
| * MUST be called inside an `<InferaGraph>` (or standalone `<GraphProvider>`) | ||
| * subtree. | ||
| */ | ||
| declare function useInferaGraphNeighbors(): UseInferaGraphNeighborsReturn; | ||
| /** | ||
| * Creates a NodeRenderFn from a React component. | ||
| * Each node gets its own React root (createRoot). | ||
| * Returns a cleanup function that unmounts the root. | ||
| */ | ||
| declare function createReactNodeRenderFn(Component: React.ComponentType<NodeComponentProps>): NodeRenderFn; | ||
| /** | ||
| * Creates a TooltipRenderFn from a React component. | ||
| * Each tooltip gets its own React root. | ||
| */ | ||
| declare function createReactTooltipRenderFn(Component: React.ComponentType<TooltipComponentProps>): TooltipRenderFn; | ||
| interface ChatTextProps { | ||
| /** | ||
| * The streamed assistant text. May contain markdown and the 0.12.0 | ||
| * citation wire format `[[token|matched-text]]` — both segments | ||
| * required. | ||
| */ | ||
| text: string; | ||
| /** | ||
| * Callback to render a citation token. The library passes both the | ||
| * citation token (slug / id) and the model's exact matched text so the | ||
| * host can render `<a href={`/<type>/${slug}`}>{matchedText}</a>` — | ||
| * the model's casing wins. | ||
| * | ||
| * When omitted, the library renders `matchedText` verbatim — useful | ||
| * for previews; consumers should always provide this in production | ||
| * to wire up the click target. | ||
| */ | ||
| renderCitation?: (token: string, matchedText: string) => React$1.ReactNode; | ||
| /** Optional className applied to the wrapping element. Lets the host theme. */ | ||
| className?: string; | ||
| } | ||
| /** | ||
| * Render an assistant chat message. Lexes `text` with marked's inline | ||
| * lexer (citation extension registered), then walks the resulting token | ||
| * tree, converting each marked token to a React node. Citation tokens | ||
| * call `renderCitation`; raw HTML is escaped (no live elements ever | ||
| * reach the rendered output). | ||
| * | ||
| * Library responsibility: parse + sanitize + emit React nodes. | ||
| * Host responsibility: CSS styling + citation-link wiring. | ||
| */ | ||
| declare function ChatText(props: ChatTextProps): React$1.ReactElement; | ||
| /** | ||
| * Outcome the React layer fires after a tool-call event has been | ||
| * dispatched through the SceneController. Lets host UIs render | ||
| * "N applied, M unknown" badges alongside chat bubbles. Phase 1 (0.8.0). | ||
| */ | ||
| interface ToolCallOutcome { | ||
| /** Tool name (`highlight` / `apply_filter` / `focus` / `annotate`). */ | ||
| tool: string; | ||
| /** Ids the renderer accepted (i.e. resolved against the current store). */ | ||
| appliedIds?: string[]; | ||
| /** Ids the renderer rejected because the store doesn't know them. */ | ||
| unknownIds?: string[]; | ||
| } | ||
| /** | ||
| * Internal: the React context surface that ties {@link useInferaGraphChat} | ||
| * to the live transport + renderer dispatch installed by `<InferaGraph>`. | ||
| * | ||
| * Exposed via getters so the hook always reads the current values | ||
| * without requiring a re-render of the consumer when the transport | ||
| * swaps. | ||
| */ | ||
| interface InferaGraphChatContext { | ||
| /** Active chat transport. `null` when no `llm`/`transport` prop is set. */ | ||
| getTransport: () => { | ||
| chat: (message: string, opts?: ChatOptions) => AsyncIterable<ChatEvent>; | ||
| } | null; | ||
| /** | ||
| * Dispatch a tool-call event into the SceneController. Implementation | ||
| * lives in `<InferaGraph>` because that's where the controller ref | ||
| * is held. | ||
| * | ||
| * May return a {@link ToolCallOutcome} when the controller can report | ||
| * applied / unknown ids for the dispatched event (today: `highlight` | ||
| * and `focus`). Returning `undefined` signals the hook should fall | ||
| * back to its own `computeToolCallOutcome`. | ||
| */ | ||
| dispatch: (event: ChatEvent) => ToolCallOutcome | undefined; | ||
| /** | ||
| * **Optional** — fire when the engine emits a `debug` ChatEvent. | ||
| * Hosts use this to render diagnostic badges underneath assistant | ||
| * bubbles. Phase 1 (0.8.0). | ||
| */ | ||
| onDiagnostic?: (event: Extract<ChatEvent, { | ||
| type: 'debug'; | ||
| }>) => void; | ||
| /** | ||
| * **Optional** — fire after a tool-call dispatch so hosts can show | ||
| * "applied / unknown" outcome badges. Phase 1 (0.8.0). | ||
| */ | ||
| onToolCallOutcome?: (outcome: ToolCallOutcome) => void; | ||
| } | ||
| export { ChatText as C, DEFAULT_EDGE_COLOR as D, type EdgeColorFn as E, type GraphContextValue as G, InferaGraph as I, type NodeColorFn as N, type SlugResolver as S, type ToolCallOutcome as T, type UseInferaGraphContentReturn as U, type NodeColorResolverOptions as a, NodeColorResolver as b, EdgeColorMap as c, type ChatTextProps as d, DEFAULT_NODE_COLOR as e, type EdgeColorContext as f, type EdgeColorMapOptions as g, GraphProvider as h, type GraphProviderProps as i, type InferaGraphChatContext as j, type InferaGraphChatContextHook as k, type InferaGraphChatHook as l, type InferaGraphCommands as m, type InferaGraphProps as n, type InferaGraphSearchHook as o, type NeighborStatus as p, type UseInferaGraphNeighborsReturn as q, createReactNodeRenderFn as r, createReactTooltipRenderFn as s, useInferaGraphChat as t, useInferaGraph as u, useInferaGraphChatContext as v, useInferaGraphCommands as w, useInferaGraphContent as x, useInferaGraphNeighbors as y, useInferaGraphSearch as z }; |
| import * as React$1 from 'react'; | ||
| import React__default, { ReactNode } from 'react'; | ||
| import { G as GraphStore, $ as QueryEngine, A as AIEngine, N as NodeId, d as GraphData, K as InferredEdgeStore, e as NodeData, E as EdgeData, l as LayoutMode, h as NodeRenderConfig, T as TooltipConfig, L as LLMProvider, C as CacheProvider, y as EmbeddingStore, c as ChatEvent, f as PaginatedResult, g as ContentData, B as FilterSpec, a0 as SearchResult, Y as NodeComponentProps, j as NodeRenderFn, a7 as TooltipComponentProps, a9 as TooltipRenderFn, b as ChatOptions } from './AIEngine-C0Rd5U-b.cjs'; | ||
| import { b as DataManager, e as MemoryManager, D as DataAdapter, a as DataAdapterConfig, E as EdgeLabelMap, T as Transport } from './aggregateEdges-Dop7spOx.cjs'; | ||
| /** | ||
| * Slug → node-id resolver. Hosts that route detail pages by human-readable | ||
| * slug (e.g. `/person/adam`) supply this so the library can translate the | ||
| * slug into the canonical node id used by the graph store + adapter. | ||
| * | ||
| * The sync escape hatch (`NodeId` instead of `Promise<NodeId>`) is | ||
| * deliberate: biblegraph's slug→UUID v5 helper is purely deterministic and | ||
| * doesn't need a microtask. Hooks always `await` the return so async | ||
| * resolvers (database lookups, manifest fetches) work too. | ||
| */ | ||
| type SlugResolver = (slug: string) => NodeId | Promise<NodeId>; | ||
| interface GraphContextValue { | ||
| store: GraphStore; | ||
| queryEngine: QueryEngine; | ||
| aiEngine: AIEngine; | ||
| dataManager: DataManager | null; | ||
| /** | ||
| * LRU eviction coordinator. Always present, even when no `maxNodes` cap | ||
| * is configured — `enforceCap` becomes a no-op in that case. | ||
| */ | ||
| memoryManager: MemoryManager; | ||
| /** | ||
| * Active {@link SlugResolver}, or `undefined` when the host opted out. | ||
| * Hooks check this before resolving — when undefined, they treat the | ||
| * input as a raw NodeId (no slug→id translation). | ||
| */ | ||
| slugResolver: SlugResolver | undefined; | ||
| /** | ||
| * Library-internal slug→nodeId cache. Slugs are immutable, so there's no | ||
| * TTL — entries live for the GraphProvider's lifetime. Exposed on the | ||
| * context value (rather than encapsulated) so the two hooks | ||
| * (`useInferaGraphContent` + `useInferaGraphNeighbors`) share one cache. | ||
| */ | ||
| slugCache: Map<string, NodeId>; | ||
| } | ||
| interface GraphProviderProps { | ||
| children: ReactNode; | ||
| /** Static data (existing behavior — wraps in StaticDataAdapter) */ | ||
| data?: GraphData; | ||
| /** DataAdapter for dynamic data fetching */ | ||
| adapter?: DataAdapter; | ||
| /** Config passed to adapter.getInitialView() */ | ||
| initialViewConfig?: DataAdapterConfig; | ||
| /** Slug → nodeId resolver. See {@link SlugResolver}. */ | ||
| slugResolver?: SlugResolver; | ||
| /** | ||
| * Soft cap on the number of nodes retained in the {@link GraphStore}. | ||
| * When the count exceeds the cap the {@link MemoryManager} evicts the | ||
| * oldest non-protected entries. `undefined` (the default) disables the | ||
| * cap so memory grows with the dataset. | ||
| */ | ||
| maxNodes?: number; | ||
| /** | ||
| * Pluggable {@link InferredEdgeStore} consumed by the engine for | ||
| * inferred-relationship persistence. Pass `RemoteInferredEdgeStore` | ||
| * in a browser host to read server-computed edges via HTTP; pass | ||
| * `inMemoryInferredEdgeStore()` for self-contained demos. Omitting | ||
| * leaves the engine without a store — `getInferredEdges()` returns | ||
| * `[]` and compute is a no-op. | ||
| */ | ||
| inferredEdgeStore?: InferredEdgeStore; | ||
| /** Called when initial data is loaded */ | ||
| onReady?: () => void; | ||
| } | ||
| declare function GraphProvider({ children, data, adapter, initialViewConfig, slugResolver, maxNodes, inferredEdgeStore, onReady, }: GraphProviderProps): React__default.JSX.Element; | ||
| /** | ||
| * Fallback color used only when the configured palette is empty. The CSS | ||
| * variable `--ig-node-color` should override this in themed apps; this | ||
| * constant is the absolute floor. | ||
| */ | ||
| declare const DEFAULT_NODE_COLOR = "#3b82f6"; | ||
| /** Function form: read the node, return a CSS hex/rgb color. */ | ||
| type NodeColorFn = (node: NodeData) => string | undefined; | ||
| interface NodeColorResolverOptions { | ||
| /** Override resolver — wins over both the type-keyed map and the palette. */ | ||
| colorFn?: NodeColorFn; | ||
| /** Explicit type → color map. Wins over auto-assignment. */ | ||
| nodeColors?: Record<string, string>; | ||
| /** | ||
| * Pool of colors used to deterministically assign a color to types that | ||
| * are not present in `nodeColors`. Defaults to {@link DEFAULT_PALETTE_32}. | ||
| */ | ||
| palette?: readonly string[]; | ||
| /** Default color used when palette is empty AND nothing else matches. */ | ||
| defaultColor?: string; | ||
| /** | ||
| * Multiplier (0..1) used by {@link NodeColorResolver.resolveHover} to lift | ||
| * the resolved color toward white for hover state. Defaults to 0.25. | ||
| */ | ||
| hoverBrightness?: number; | ||
| } | ||
| /** | ||
| * Resolves resting + hover colors for a node based on its attributes. | ||
| * | ||
| * Resolution order (resting): | ||
| * 1. `colorFn(node)` if it returns a non-undefined string | ||
| * 2. `nodeColors[node.attributes.type]` if it's a string lookup | ||
| * 3. `node.attributes.color` if the consumer set one explicitly | ||
| * 4. `palette[hashStringToIndex(type, palette.length)]` (deterministic auto) | ||
| * 5. `defaultColor` (only reached when palette is empty) | ||
| * | ||
| * Hover: same color, brightened by `hoverBrightness` (default 25 % toward white). | ||
| * | ||
| * Domain knowledge note: this resolver ships ZERO domain-specific defaults | ||
| * — the consumer (e.g. Bible Graph) supplies its own `nodeColors` map. | ||
| */ | ||
| declare class NodeColorResolver { | ||
| private readonly colorFn?; | ||
| private readonly nodeColors; | ||
| private readonly palette; | ||
| private readonly defaultColor; | ||
| private readonly hoverBrightness; | ||
| constructor(options?: NodeColorResolverOptions); | ||
| /** Resting color for `node`. */ | ||
| resolve(node: NodeData): string; | ||
| /** Hover color for `node` — resting color brightened toward white. */ | ||
| resolveHover(node: NodeData): string; | ||
| /** The active palette (for tests + introspection). */ | ||
| getPalette(): readonly string[]; | ||
| } | ||
| /** Fallback edge color when the palette is empty AND no override matches. */ | ||
| declare const DEFAULT_EDGE_COLOR = "#6366f1"; | ||
| /** | ||
| * Per-call context handed to {@link EdgeColorFn}. Carries the resolved | ||
| * resting colors of the edge's source + target nodes (the same hex values | ||
| * {@link NodeColorResolver.resolve} would return for those nodes) so the | ||
| * function can derive a color from its endpoints — for example by | ||
| * blending them, picking one side, or treating same-vs-different as a | ||
| * boolean. | ||
| * | ||
| * Both fields are guaranteed to be valid CSS color strings; callers fall | ||
| * back to {@link DEFAULT_EDGE_COLOR} (or the consumer's chosen | ||
| * fallback) for either side that cannot be resolved against the live | ||
| * graph store. | ||
| */ | ||
| interface EdgeColorContext { | ||
| /** Resolved resting color of the source node. */ | ||
| sourceColor: string; | ||
| /** Resolved resting color of the target node. */ | ||
| targetColor: string; | ||
| } | ||
| /** | ||
| * Function form: read the edge + its endpoint colors, return a CSS | ||
| * hex/rgb color (or `undefined` to fall through to the type-keyed map / | ||
| * palette). | ||
| * | ||
| * The second argument is optional at call sites that pre-date the context | ||
| * — older `(edge) => string` consumers continue to work because they | ||
| * simply ignore the extra parameter. | ||
| */ | ||
| type EdgeColorFn = (edge: EdgeData, ctx: EdgeColorContext) => string | undefined; | ||
| interface EdgeColorMapOptions { | ||
| /** Override resolver — wins over both the type-keyed map and the palette. */ | ||
| colorFn?: EdgeColorFn; | ||
| /** Explicit type → color map. Wins over auto-assignment. */ | ||
| edgeColors?: Record<string, string>; | ||
| /** | ||
| * Pool of colors used to deterministically assign a color to relationship | ||
| * types not present in `edgeColors`. Defaults to {@link DEFAULT_PALETTE_32}. | ||
| */ | ||
| palette?: readonly string[]; | ||
| /** Default color used when palette is empty AND nothing else matches. */ | ||
| defaultColor?: string; | ||
| /** | ||
| * Multiplier (0..1) used by {@link EdgeColorMap.resolveHover} to lift the | ||
| * resolved color toward white for hover state. Defaults to 0.25. | ||
| */ | ||
| hoverBrightness?: number; | ||
| } | ||
| /** | ||
| * Resolves resting + hover colors for an edge based on its `attributes.type` | ||
| * (the relationship type, e.g. `father_of`). | ||
| * | ||
| * Resolution order (resting): | ||
| * 1. `colorFn(edge, ctx)` if it returns a non-undefined string | ||
| * 2. `edgeColors[edge.attributes.type]` | ||
| * 3. `palette[hashStringToIndex(type, palette.length)]` (deterministic auto) | ||
| * 4. `defaultColor` (only reached when palette is empty) | ||
| * | ||
| * Hover: same color, brightened by `hoverBrightness` (default 25 % toward white). | ||
| * | ||
| * Domain knowledge note: this map ships ZERO domain-specific defaults — | ||
| * `father_of`, `married_to`, etc. mean nothing to InferaGraph. The consumer | ||
| * (e.g. Bible Graph) supplies its own `edgeColors` map. | ||
| */ | ||
| declare class EdgeColorMap { | ||
| private readonly colorFn?; | ||
| private readonly edgeColors; | ||
| private readonly palette; | ||
| private readonly defaultColor; | ||
| private readonly hoverBrightness; | ||
| constructor(options?: EdgeColorMapOptions); | ||
| /** | ||
| * Resting color for `edge`. | ||
| * | ||
| * `ctx` carries the resolved endpoint colors so a {@link EdgeColorFn} | ||
| * can derive the edge color from its endpoints (see | ||
| * {@link blendEdgeColors}). The argument is defaulted to the fallback | ||
| * color on both sides so legacy call sites that don't yet plumb | ||
| * endpoint colors through still produce a sensible result. | ||
| */ | ||
| resolve(edge: EdgeData, ctx?: EdgeColorContext): string; | ||
| /** Hover color for `edge` — resting color brightened toward white. */ | ||
| resolveHover(edge: EdgeData, ctx?: EdgeColorContext): string; | ||
| /** The active palette (for tests + introspection). */ | ||
| getPalette(): readonly string[]; | ||
| } | ||
| interface InferaGraphProps { | ||
| data?: GraphData; | ||
| layout?: LayoutMode; | ||
| nodeRender?: NodeRenderConfig; | ||
| tooltip?: TooltipConfig; | ||
| /** Pool of colors for deterministic auto-assignment. */ | ||
| palette?: readonly string[]; | ||
| /** Explicit type → color map for nodes. */ | ||
| nodeColors?: Record<string, string>; | ||
| /** Function override for nodes. */ | ||
| nodeColorFn?: NodeColorFn; | ||
| /** Explicit relationship-type → color map for edges. */ | ||
| edgeColors?: Record<string, string>; | ||
| /** Function override for edges. */ | ||
| edgeColorFn?: EdgeColorFn; | ||
| /** | ||
| * Incoming-edge label map for the default tooltip's natural-language | ||
| * description (e.g. `{ father_of: 'Son of', mother_of: 'Son of' }`). | ||
| * Ignored when `tooltip.renderTooltip` / `tooltip.component` is supplied. | ||
| */ | ||
| incomingEdgeLabels?: EdgeLabelMap; | ||
| /** | ||
| * Outgoing-edge label map for the default tooltip's natural-language | ||
| * description (e.g. `{ father_of: 'Father of' }`). | ||
| */ | ||
| outgoingEdgeLabels?: EdgeLabelMap; | ||
| /** | ||
| * Domain-agnostic visibility predicate. When supplied, only nodes for | ||
| * which the predicate returns `true` are rendered; edges whose source | ||
| * OR target node is filtered out are hidden too. | ||
| * | ||
| * The same predicate applies in **every** visualization mode — graph, | ||
| * tree, and any future mode (geospatial / timeline / chord / etc.). | ||
| * Filter changes are applied as in-place visibility toggles on the | ||
| * existing GPU buffers — there's no mesh teardown, no rebuild, and | ||
| * no layout recompute. Hidden nodes keep their layout positions, so | ||
| * unhiding restores the prior frame instantly. | ||
| * | ||
| * Default: no filter (every node visible). | ||
| */ | ||
| filter?: (node: NodeData) => boolean; | ||
| /** | ||
| * Tree-mode option: edge types that the hierarchical tidy-tree layout | ||
| * treats as parent -> child. Default `['parent_of']` — set to your | ||
| * domain's vocabulary (`['manages']` for org charts, `['supplies']` | ||
| * for supply chains, `['is_a']` for taxonomies, `['cites']` for | ||
| * citation graphs, etc.). Has no effect in graph mode. | ||
| */ | ||
| parentEdgeTypes?: string[]; | ||
| /** | ||
| * Tree-mode option: edge types that pair two nodes at the same depth | ||
| * so they appear adjacent and share children. Default `[]` (no | ||
| * pairing). Has no effect in graph mode. | ||
| */ | ||
| pairedEdgeTypes?: string[]; | ||
| /** | ||
| * LLM provider for AI features (NLQ filtering today; chat / search / highlight | ||
| * land in later phases). The host imports a provider package and passes a | ||
| * configured INSTANCE here. The host never invokes the LLM directly — | ||
| * InferaGraph owns the entire LLM lifecycle. | ||
| * | ||
| * Omitted = AI features are unavailable; the explicit `filter` predicate | ||
| * still works. | ||
| */ | ||
| llm?: LLMProvider; | ||
| /** | ||
| * Cache provider for LLM responses. Defaults to NO CACHING when omitted, so | ||
| * tests + small demos don't accidentally retain old responses. Pass | ||
| * `lruCache()` (built-in) or `@inferagraph/redis-cache-provider` for | ||
| * production. The cache is wiped automatically when the LLM provider | ||
| * instance changes. | ||
| * | ||
| * Also acts as the **Tier 2** embedding storage when `embeddingStore` is | ||
| * not supplied AND the configured `llm` provider implements `embed()` — | ||
| * embeddings are persisted in the cache and similarity is computed | ||
| * in-memory at query time. | ||
| */ | ||
| cache?: CacheProvider; | ||
| /** | ||
| * Optional dedicated {@link EmbeddingStore} (Tier 3 of the embedding | ||
| * progression). When supplied, AIEngine uses the store's vector-native | ||
| * `similar()` for semantic search. The default in-process implementation | ||
| * ships as `inMemoryEmbeddingStore()`; persistent stores live in their | ||
| * own packages. | ||
| * | ||
| * Tier mapping at runtime: | ||
| * - omit both → keyword search only. | ||
| * - `cache` only + provider with `embed()` → Tier 2. | ||
| * - `embeddingStore` + provider with `embed()` → Tier 3. | ||
| */ | ||
| embeddingStore?: EmbeddingStore; | ||
| /** | ||
| * Natural-language query that the LLM compiles into a filter predicate. | ||
| * Combined with the explicit `filter` prop via AND: the developer-set | ||
| * `filter` runs first (proactive scope), then `query` narrows within that | ||
| * scope. Requires `llm`. An empty / undefined `query` is a no-op. | ||
| */ | ||
| query?: string; | ||
| /** | ||
| * Optional explicit chat {@link Transport}. Wins over the implicit | ||
| * "build an in-process transport from `llm`" path. Use this to wire | ||
| * an HTTP transport (Next.js `/api/chat` proxy) so the LLM + cache | ||
| * stay server-side. | ||
| */ | ||
| transport?: Transport; | ||
| /** | ||
| * Toggle visibility of the Phase 5 inferred-edge overlay (dashed | ||
| * lines between nodes the AI inference pipeline thinks are related). | ||
| * Defaults to `false` (overlay hidden) so the explicit graph reads | ||
| * cleanly out of the box. Hosts opt in either by setting this to | ||
| * `true` or by handing the LLM a `set_inferred_visibility` chat | ||
| * tool call. | ||
| * | ||
| * The actual inferred edges are computed by | ||
| * `aiEngine.computeInferredEdges()` and pushed to the renderer | ||
| * separately — toggling this prop only shows/hides whatever has | ||
| * already been computed. | ||
| */ | ||
| showInferredEdges?: boolean; | ||
| /** | ||
| * Pluggable {@link InferredEdgeStore}. When the host toggles | ||
| * `showInferredEdges` to `true`, the library calls | ||
| * `aiEngine.getInferredEdges()` (which delegates to this store) and | ||
| * pipes the result into the renderer. Omitting the store leaves | ||
| * the engine without a persistence layer — the overlay stays empty | ||
| * until the host pushes edges via another route. | ||
| */ | ||
| inferredEdgeStore?: InferredEdgeStore; | ||
| /** | ||
| * Fires `true` when the auto-fetch effect kicks off (the | ||
| * `showInferredEdges` prop just flipped to `true`) and `false` when | ||
| * the fetch resolves, errors, or is cancelled by unmount. Hosts use | ||
| * this to render a brief spinner near the toggle while the inferred | ||
| * edges are loading from the server. | ||
| */ | ||
| onInferredEdgesLoadingChange?: (loading: boolean) => void; | ||
| /** | ||
| * Host-facing chat callback. Called with `text` and `done` events | ||
| * only — tool calls (`apply_filter` / `highlight` / `focus` / | ||
| * `annotate`) are dispatched silently to the renderer. | ||
| * | ||
| * The host writes its own chat UI on top of this callback (input | ||
| * box, message log, conversation pane). InferaGraph owns the | ||
| * streaming / tool-routing logistics. | ||
| */ | ||
| onChat?: (event: ChatEvent) => void; | ||
| /** | ||
| * Phase 1 (RAG architecture, 0.8.0) — fired when the engine emits a | ||
| * `debug` ChatEvent (vector-search, rerank, retrieval-empty, etc.). | ||
| * Hosts use this to render diagnostic badges underneath assistant | ||
| * bubbles for ops visibility. Debug events are NEVER yielded to the | ||
| * iterator — only this callback receives them. | ||
| */ | ||
| onDiagnostic?: (event: Extract<ChatEvent, { | ||
| type: 'debug'; | ||
| }>) => void; | ||
| /** | ||
| * Phase 1 (RAG architecture, 0.8.0) — fired after a tool-call event | ||
| * has been dispatched to the renderer. Lets host UIs surface | ||
| * "N applied, M unknown" badges. The library reports the requested | ||
| * ids as `appliedIds`; future enhancements will reconcile against | ||
| * the store's known-ids set to populate `unknownIds`. | ||
| */ | ||
| onToolCallOutcome?: (outcome: { | ||
| tool: string; | ||
| appliedIds?: string[]; | ||
| unknownIds?: string[]; | ||
| }) => void; | ||
| /** | ||
| * Phase 6 — slug → nodeId resolver. When supplied, hooks like | ||
| * {@link useInferaGraphContent} translate the input through this | ||
| * resolver before calling into the {@link DataAdapter}. Hosts that | ||
| * route by raw UUIDs can omit it. | ||
| */ | ||
| slugResolver?: SlugResolver; | ||
| /** | ||
| * Phase 6 — soft cap on the number of nodes retained in the store. | ||
| * When exceeded, the {@link MemoryManager} evicts the oldest | ||
| * non-protected nodes. `undefined` (default) disables the cap. | ||
| */ | ||
| maxNodes?: number; | ||
| /** | ||
| * Phase 6 — fired when the user clicks a node body. The host typically | ||
| * opens a detail dialog. Receives the canonical NodeId (post-slug | ||
| * resolution) and the node's data snapshot. | ||
| * | ||
| * NOT fired for clicks on the "+" affordance — those go through | ||
| * {@link onExpandRequest} instead. | ||
| */ | ||
| onNodeClick?: (nodeId: NodeId, node: NodeData) => void; | ||
| /** | ||
| * Phase 6 — fired when the user clicks the "+" hover affordance on a | ||
| * node. Receives the canonical NodeId. The host typically calls | ||
| * `useInferaGraphNeighbors().expand(nodeId)`. When omitted, the | ||
| * library installs a default handler that fires the built-in | ||
| * `expand(nodeId)` dispatch. | ||
| */ | ||
| onExpandRequest?: (nodeId: NodeId) => void; | ||
| className?: string; | ||
| style?: React__default.CSSProperties; | ||
| /** | ||
| * Optional children rendered inside the InferaGraph subtree. Useful | ||
| * for hosts that want to embed components that consume | ||
| * {@link useInferaGraphChat} or other hooks that depend on the | ||
| * InferaGraph context. Children render OUTSIDE the WebGL canvas | ||
| * (after the `.ig-container` div) — they're for host-owned chrome / | ||
| * chat UI, not for in-canvas rendering. | ||
| */ | ||
| children?: React__default.ReactNode; | ||
| } | ||
| declare function InferaGraph(props: InferaGraphProps): React__default.JSX.Element; | ||
| interface UseInferaGraphReturn { | ||
| loadData: (data: GraphData) => void; | ||
| nodeCount: number; | ||
| edgeCount: number; | ||
| expandNode: (nodeId: NodeId, depth?: number) => Promise<void>; | ||
| findPath: (fromId: NodeId, toId: NodeId) => Promise<NodeData[]>; | ||
| search: (query: string) => Promise<PaginatedResult<NodeData>>; | ||
| getContent: (nodeId: NodeId) => Promise<ContentData | undefined>; | ||
| isReady: boolean; | ||
| } | ||
| declare function useInferaGraph(): UseInferaGraphReturn; | ||
| /** | ||
| * Public return shape of {@link useInferaGraphChat}. The hook returns a | ||
| * single function that streams a chat with the configured transport. | ||
| * | ||
| * The returned `AsyncIterable` yields ONLY `text` and `done` events — | ||
| * tool calls (`apply_filter`, `highlight`, `focus`, `annotate`) are | ||
| * dispatched silently to the renderer, exactly mirroring the contract | ||
| * of `<InferaGraph onChat>`. | ||
| */ | ||
| interface InferaGraphChatHook { | ||
| /** | ||
| * Send `message` through the active chat transport. Returns an async | ||
| * iterable that yields {@link ChatEvent}s — text + done only by | ||
| * default. Tool-call events are dispatched to the renderer's | ||
| * `setHighlight` / `setFilter` / `focusOn` / `annotate` and not | ||
| * surfaced to the iterator. | ||
| * | ||
| * Pass `signal` to cancel mid-stream. | ||
| * | ||
| * Throws synchronously if no transport (or no `llm` prop) is | ||
| * configured on the host `<InferaGraph>` element. | ||
| */ | ||
| chat: (message: string, opts?: { | ||
| signal?: AbortSignal; | ||
| /** | ||
| * Optional conversation id, forwarded to the active transport. When | ||
| * the HTTP transport is in use the id flows into the request body | ||
| * so the server route can thread it into its `ConversationStore`; | ||
| * with the in-process transport the id is forwarded directly to | ||
| * `AIEngine.chat`. Omit to opt out of conversation memory for this | ||
| * call. | ||
| */ | ||
| conversationId?: string; | ||
| }) => AsyncIterable<ChatEvent>; | ||
| } | ||
| /** | ||
| * React hook that surfaces InferaGraph's chat API to the host. | ||
| * | ||
| * Internally: | ||
| * 1. The hook resolves the active transport via React context | ||
| * (`<InferaGraph>` populates this). | ||
| * 2. When `chat(message)` is called, the hook iterates the transport's | ||
| * full event stream: | ||
| * - tool-call events are dispatched to the SceneController's | ||
| * highlight / focus / annotate / filter sinks. | ||
| * - text + done events are re-yielded to the host. | ||
| * 3. The host iterates the returned async iterable to render text | ||
| * bubbles. The renderer never appears in the host's iteration. | ||
| * | ||
| * MUST be called inside an `<InferaGraph>` subtree. | ||
| */ | ||
| declare function useInferaGraphChat(): InferaGraphChatHook; | ||
| /** | ||
| * Public return shape of {@link useInferaGraphChatContext}. The hook | ||
| * surfaces the renderer's dispatch sink so a host can fire ad-hoc | ||
| * {@link ChatEvent}s without going through `chat()`. | ||
| * | ||
| * The same dispatch path receives chat-driven tool-call events under | ||
| * the hood — calling `dispatch(...)` here goes through the exact same | ||
| * SceneController surface, which means highlights, filters, focus | ||
| * moves, annotations, and the new visual-reset commands all behave | ||
| * identically whether the model emits them or the host fires them. | ||
| */ | ||
| interface InferaGraphChatContextHook { | ||
| /** | ||
| * Dispatch a {@link ChatEvent} into the renderer. Mostly useful for | ||
| * host UI affordances ("Clear conversation" → reset highlight, | ||
| * "Camera home" button → `reset_view`, etc.). | ||
| * | ||
| * Prefer {@link InferaGraphCommands} (`useInferaGraphCommands`) for | ||
| * the typical cases — it's a thin semantic facade on top of this | ||
| * sink. Reach for this hook when you need to dispatch an event | ||
| * variant the facade doesn't surface (e.g. emitting a `debug` | ||
| * ChatEvent for ops visibility, or a synthetic `text` event for | ||
| * a UI-only "Welcome" bubble). | ||
| */ | ||
| dispatch: (event: ChatEvent) => void; | ||
| } | ||
| /** | ||
| * 0.10.0 — public escape-hatch hook returning the renderer's dispatch | ||
| * function. Most hosts should reach for {@link useInferaGraphCommands} | ||
| * instead; this hook exists for host code that needs to dispatch a | ||
| * {@link ChatEvent} variant the semantic facade doesn't expose. | ||
| * | ||
| * MUST be called inside an `<InferaGraph>` subtree. | ||
| */ | ||
| declare function useInferaGraphChatContext(): InferaGraphChatContextHook; | ||
| /** | ||
| * 0.10.0 — semantic facade for host-initiated graph commands. Most hosts | ||
| * use this hook instead of the lower-level | ||
| * {@link useInferaGraphChatContext}. | ||
| * | ||
| * Each method dispatches the equivalent {@link ChatEvent} to the | ||
| * renderer's dispatch sink — exactly the same path the library uses for | ||
| * chat-driven (model-emitted) events. There's no separate "host API" | ||
| * that diverges from the chat contract. | ||
| */ | ||
| interface InferaGraphCommands { | ||
| /** | ||
| * Replace the active highlight set. Pass an empty Set to clear (every | ||
| * node returns to full opacity). | ||
| */ | ||
| setHighlight(ids: ReadonlySet<string>): void; | ||
| /** | ||
| * Move the camera to the supplied node. The library does not have | ||
| * an "unfocus" affordance — hosts that want to recenter should call | ||
| * {@link resetView} instead. | ||
| */ | ||
| focusOn(nodeId: string): void; | ||
| /** | ||
| * Apply a domain-agnostic filter spec to the visible graph. The spec | ||
| * shape mirrors the LLM's `apply_filter` tool-call output: keys are | ||
| * node attribute names, values are arrays of allowed string values. | ||
| * Pass an empty object (`{}`) to clear the filter. | ||
| */ | ||
| applyFilter(spec: FilterSpec): void; | ||
| /** Toggle the inferred-edge overlay. */ | ||
| setInferredVisibility(visible: boolean): void; | ||
| /** Attach a callout to a node. */ | ||
| annotate(nodeId: string, text: string): void; | ||
| /** Drop every annotation currently mounted. */ | ||
| clearAnnotations(): void; | ||
| /** Snap the camera back to its captured initial orientation. */ | ||
| resetView(): void; | ||
| /** | ||
| * Comprehensive reset — highlights + annotations + filter + camera. | ||
| * The "fresh canvas" command. Hosts wire this to "Clear | ||
| * conversation" / "New session" UX. | ||
| */ | ||
| clearVisualState(): void; | ||
| } | ||
| /** | ||
| * 0.10.0 — public, semantic hook for dispatching host-initiated graph | ||
| * commands. Built on top of {@link useInferaGraphChatContext}, but with | ||
| * a flat, named surface so callers don't have to construct | ||
| * {@link ChatEvent} objects by hand. | ||
| * | ||
| * The returned object is memoized for the lifetime of the hook so | ||
| * downstream `useEffect` / `useMemo` deps remain stable across renders. | ||
| * | ||
| * MUST be called inside an `<InferaGraph>` subtree. | ||
| */ | ||
| declare function useInferaGraphCommands(): InferaGraphCommands; | ||
| /** | ||
| * Public return shape of {@link useInferaGraphSearch}. The hook returns a | ||
| * single `search(query)` function that delegates to the AIEngine's | ||
| * auto-detect router (keyword vs semantic). | ||
| * | ||
| * Pair with `<InferaGraph>` props: | ||
| * - `llm` — required for semantic routing. | ||
| * - `cache` — opts the engine into Tier 2 (cache-as-vector-store). | ||
| * - `embeddingStore` — opts the engine into Tier 3 (vector-native store). | ||
| * Without `llm`, every query falls back to the data-layer keyword search. | ||
| */ | ||
| interface InferaGraphSearchHook { | ||
| /** | ||
| * Run an auto-routed search. Short token-only queries hit the data-layer | ||
| * keyword index; sentence-shaped or NLQ inputs go through embeddings. | ||
| * | ||
| * Returns at most `opts.k ?? 25` hits, sorted by descending score. | ||
| */ | ||
| search: (query: string, opts?: { | ||
| k?: number; | ||
| signal?: AbortSignal; | ||
| }) => Promise<SearchResult[]>; | ||
| } | ||
| /** | ||
| * React hook surfacing InferaGraph's Phase 4 search API. | ||
| * | ||
| * Internally: | ||
| * 1. Pulls the {@link AIEngine} out of the GraphContext (populated by | ||
| * `<InferaGraph>` / `<GraphProvider>`). | ||
| * 2. Wraps `engine.search` in a stable `useCallback` so consumers can | ||
| * memoize / debounce without identity churn. | ||
| * 3. Holds the engine behind a ref so prop changes on the host don't | ||
| * invalidate the callback. | ||
| * | ||
| * MUST be called inside an `<InferaGraph>` (or standalone `<GraphProvider>`) | ||
| * subtree. | ||
| */ | ||
| declare function useInferaGraphSearch(): InferaGraphSearchHook; | ||
| /** | ||
| * Return shape of {@link useInferaGraphContent}. | ||
| * | ||
| * Mirrors the host-friendly fetch-state quartet (data + loading + error + | ||
| * refetch). All four fields are always populated — `loading` is `false` | ||
| * when `idOrSlug` is `undefined`, so hosts don't need conditional rendering | ||
| * gymnastics. | ||
| */ | ||
| interface UseInferaGraphContentReturn { | ||
| /** Fetched content, or `undefined` while loading / on error / for invalid input. */ | ||
| data: ContentData | undefined; | ||
| /** `true` while a fetch is in flight (or queued for the current id). */ | ||
| loading: boolean; | ||
| /** Last error encountered, or `undefined` on success / before first fetch. */ | ||
| error: Error | undefined; | ||
| /** Imperatively re-run the fetch for the current id. No-op when id is undefined. */ | ||
| refetch: () => void; | ||
| } | ||
| /** | ||
| * Tier-1 cache for getContent results. Keyed by canonical NodeId (post slug | ||
| * resolution). Populated on every successful fetch; consulted before issuing | ||
| * a network call so re-mounted DetailModals don't re-fetch. | ||
| * | ||
| * Lives at module scope intentionally: the GraphProvider context value is | ||
| * recreated on adapter swap, but that swap also wipes the store and would | ||
| * make any cached content stale. We mirror that lifecycle by clearing this | ||
| * Map whenever the dataManager identity flips (see effect below). | ||
| */ | ||
| /** | ||
| * React hook that fetches detail content for one node. | ||
| * | ||
| * Workflow: | ||
| * 1. If `idOrSlug` is `undefined`, return a disabled-state object — no | ||
| * fetch fires, no spinner. | ||
| * 2. Pass the input through the configured {@link SlugResolver}. When the | ||
| * host opted out (no resolver), the input is treated as a raw NodeId. | ||
| * 3. Hit the per-DataManager cache. On hit, return immediately; on miss, | ||
| * call `dataManager.getContent(uuid)` and cache the result. | ||
| * 4. Touch the {@link MemoryManager} so the node moves to the MRU end | ||
| * and survives any subsequent cap-enforcement pass. | ||
| * 5. Cancel via the standard "captured-token" pattern when the input | ||
| * changes mid-flight; `setData` only fires for the latest call. | ||
| * | ||
| * MUST be called inside an `<InferaGraph>` (or standalone `<GraphProvider>`) | ||
| * subtree. | ||
| */ | ||
| declare function useInferaGraphContent(idOrSlug: string | undefined): UseInferaGraphContentReturn; | ||
| /** Lifecycle status of a single expand call, tracked per-nodeId. */ | ||
| type NeighborStatus = 'loading' | 'loaded' | 'error'; | ||
| /** | ||
| * Return shape of {@link useInferaGraphNeighbors}. | ||
| * | ||
| * Imperative hook (not a fetch-on-mount hook): the host calls `expand` / | ||
| * `collapse` in response to user actions (click on the "+" affordance, | ||
| * keyboard shortcut, chat tool call). Reading `expanded` is enough for the | ||
| * host to know which nodes are currently in flight or already drilled-down. | ||
| */ | ||
| interface UseInferaGraphNeighborsReturn { | ||
| /** | ||
| * Fetch direct neighbors of `nodeId` (depth defaults to 1). Resolves slug | ||
| * via the configured resolver, calls `dataManager.expandNode`, syncs the | ||
| * scene from the freshly-merged store, and enforces the LRU cap with the | ||
| * just-expanded set marked as protected (so eviction never targets the | ||
| * neighbors the user just asked to see). | ||
| */ | ||
| expand: (nodeIdOrSlug: string, depth?: number) => Promise<void>; | ||
| /** | ||
| * Remove neighbors of `nodeId` that aren't in the protected initial-view | ||
| * set. Inverse of `expand`. Implementations of "protected" are | ||
| * intentionally simple v1: any node currently visible due to another | ||
| * expand still survives because its LRU timestamp is fresher than the | ||
| * collapsing nodes. | ||
| */ | ||
| collapse: (nodeIdOrSlug: string) => Promise<void>; | ||
| /** Per-node lifecycle map. Keys are CANONICAL NodeIds (post-resolution). */ | ||
| expanded: ReadonlyMap<NodeId, NeighborStatus>; | ||
| } | ||
| /** | ||
| * React hook that exposes drilldown / collapse imperatives. | ||
| * | ||
| * Slug-aware: callers may pass either a UUID or a slug. The configured | ||
| * {@link SlugResolver} translates slugs to canonical NodeIds; the resolved | ||
| * id is what survives in `expanded` so the host can reason about identity | ||
| * uniformly. | ||
| * | ||
| * Race-safe: each call captures its own resolved nodeId before awaiting, | ||
| * and the returned promise reflects the outcome of THIS call. If a faster | ||
| * second `expand(sameNode)` overlaps a slow first one, both promises | ||
| * resolve correctly because the underlying `dataManager.expandNode` merges | ||
| * by id (later merges are no-ops for nodes already present). | ||
| * | ||
| * MUST be called inside an `<InferaGraph>` (or standalone `<GraphProvider>`) | ||
| * subtree. | ||
| */ | ||
| declare function useInferaGraphNeighbors(): UseInferaGraphNeighborsReturn; | ||
| /** | ||
| * Creates a NodeRenderFn from a React component. | ||
| * Each node gets its own React root (createRoot). | ||
| * Returns a cleanup function that unmounts the root. | ||
| */ | ||
| declare function createReactNodeRenderFn(Component: React.ComponentType<NodeComponentProps>): NodeRenderFn; | ||
| /** | ||
| * Creates a TooltipRenderFn from a React component. | ||
| * Each tooltip gets its own React root. | ||
| */ | ||
| declare function createReactTooltipRenderFn(Component: React.ComponentType<TooltipComponentProps>): TooltipRenderFn; | ||
| interface ChatTextProps { | ||
| /** | ||
| * The streamed assistant text. May contain markdown and the 0.12.0 | ||
| * citation wire format `[[token|matched-text]]` — both segments | ||
| * required. | ||
| */ | ||
| text: string; | ||
| /** | ||
| * Callback to render a citation token. The library passes both the | ||
| * citation token (slug / id) and the model's exact matched text so the | ||
| * host can render `<a href={`/<type>/${slug}`}>{matchedText}</a>` — | ||
| * the model's casing wins. | ||
| * | ||
| * When omitted, the library renders `matchedText` verbatim — useful | ||
| * for previews; consumers should always provide this in production | ||
| * to wire up the click target. | ||
| */ | ||
| renderCitation?: (token: string, matchedText: string) => React$1.ReactNode; | ||
| /** Optional className applied to the wrapping element. Lets the host theme. */ | ||
| className?: string; | ||
| } | ||
| /** | ||
| * Render an assistant chat message. Lexes `text` with marked's inline | ||
| * lexer (citation extension registered), then walks the resulting token | ||
| * tree, converting each marked token to a React node. Citation tokens | ||
| * call `renderCitation`; raw HTML is escaped (no live elements ever | ||
| * reach the rendered output). | ||
| * | ||
| * Library responsibility: parse + sanitize + emit React nodes. | ||
| * Host responsibility: CSS styling + citation-link wiring. | ||
| */ | ||
| declare function ChatText(props: ChatTextProps): React$1.ReactElement; | ||
| /** | ||
| * Outcome the React layer fires after a tool-call event has been | ||
| * dispatched through the SceneController. Lets host UIs render | ||
| * "N applied, M unknown" badges alongside chat bubbles. Phase 1 (0.8.0). | ||
| */ | ||
| interface ToolCallOutcome { | ||
| /** Tool name (`highlight` / `apply_filter` / `focus` / `annotate`). */ | ||
| tool: string; | ||
| /** Ids the renderer accepted (i.e. resolved against the current store). */ | ||
| appliedIds?: string[]; | ||
| /** Ids the renderer rejected because the store doesn't know them. */ | ||
| unknownIds?: string[]; | ||
| } | ||
| /** | ||
| * Internal: the React context surface that ties {@link useInferaGraphChat} | ||
| * to the live transport + renderer dispatch installed by `<InferaGraph>`. | ||
| * | ||
| * Exposed via getters so the hook always reads the current values | ||
| * without requiring a re-render of the consumer when the transport | ||
| * swaps. | ||
| */ | ||
| interface InferaGraphChatContext { | ||
| /** Active chat transport. `null` when no `llm`/`transport` prop is set. */ | ||
| getTransport: () => { | ||
| chat: (message: string, opts?: ChatOptions) => AsyncIterable<ChatEvent>; | ||
| } | null; | ||
| /** | ||
| * Dispatch a tool-call event into the SceneController. Implementation | ||
| * lives in `<InferaGraph>` because that's where the controller ref | ||
| * is held. | ||
| * | ||
| * May return a {@link ToolCallOutcome} when the controller can report | ||
| * applied / unknown ids for the dispatched event (today: `highlight` | ||
| * and `focus`). Returning `undefined` signals the hook should fall | ||
| * back to its own `computeToolCallOutcome`. | ||
| */ | ||
| dispatch: (event: ChatEvent) => ToolCallOutcome | undefined; | ||
| /** | ||
| * **Optional** — fire when the engine emits a `debug` ChatEvent. | ||
| * Hosts use this to render diagnostic badges underneath assistant | ||
| * bubbles. Phase 1 (0.8.0). | ||
| */ | ||
| onDiagnostic?: (event: Extract<ChatEvent, { | ||
| type: 'debug'; | ||
| }>) => void; | ||
| /** | ||
| * **Optional** — fire after a tool-call dispatch so hosts can show | ||
| * "applied / unknown" outcome badges. Phase 1 (0.8.0). | ||
| */ | ||
| onToolCallOutcome?: (outcome: ToolCallOutcome) => void; | ||
| } | ||
| export { ChatText as C, DEFAULT_EDGE_COLOR as D, type EdgeColorFn as E, type GraphContextValue as G, InferaGraph as I, type NodeColorFn as N, type SlugResolver as S, type ToolCallOutcome as T, type UseInferaGraphContentReturn as U, type NodeColorResolverOptions as a, NodeColorResolver as b, EdgeColorMap as c, type ChatTextProps as d, DEFAULT_NODE_COLOR as e, type EdgeColorContext as f, type EdgeColorMapOptions as g, GraphProvider as h, type GraphProviderProps as i, type InferaGraphChatContext as j, type InferaGraphChatContextHook as k, type InferaGraphChatHook as l, type InferaGraphCommands as m, type InferaGraphProps as n, type InferaGraphSearchHook as o, type NeighborStatus as p, type UseInferaGraphNeighborsReturn as q, createReactNodeRenderFn as r, createReactTooltipRenderFn as s, useInferaGraphChat as t, useInferaGraph as u, useInferaGraphChatContext as v, useInferaGraphCommands as w, useInferaGraphContent as x, useInferaGraphNeighbors as y, useInferaGraphSearch as z }; |
| 'use strict'; | ||
| // src/server/createInferredEdgeRouteHandler.ts | ||
| function createInferredEdgeRouteHandler(engine, options = {}) { | ||
| const lazyCompute = options.lazyCompute !== false; | ||
| let inflight; | ||
| return async function inferredEdgeRouteHandler(_req) { | ||
| let edges = await engine.getInferredEdges(); | ||
| if (edges.length === 0 && lazyCompute) { | ||
| if (!inflight) { | ||
| inflight = engine.computeInferredEdges().finally(() => { | ||
| inflight = void 0; | ||
| }); | ||
| } | ||
| await inflight; | ||
| edges = await engine.getInferredEdges(); | ||
| } | ||
| return new Response(JSON.stringify(edges), { | ||
| status: 200, | ||
| headers: { | ||
| "content-type": "application/json", | ||
| "cache-control": "public, max-age=300" | ||
| } | ||
| }); | ||
| }; | ||
| } | ||
| exports.createInferredEdgeRouteHandler = createInferredEdgeRouteHandler; | ||
| //# sourceMappingURL=server.cjs.map | ||
| //# sourceMappingURL=server.cjs.map |
| {"version":3,"sources":["../src/server/createInferredEdgeRouteHandler.ts"],"names":[],"mappings":";;;AA4CO,SAAS,8BAAA,CACd,MAAA,EACA,OAAA,GAAiD,EAAC,EACb;AACrC,EAAA,MAAM,WAAA,GAAc,QAAQ,WAAA,KAAgB,KAAA;AAC5C,EAAA,IAAI,QAAA;AAEJ,EAAA,OAAO,eAAe,yBACpB,IAAA,EACmB;AACnB,IAAA,IAAI,KAAA,GAAQ,MAAM,MAAA,CAAO,gBAAA,EAAiB;AAC1C,IAAA,IAAI,KAAA,CAAM,MAAA,KAAW,CAAA,IAAK,WAAA,EAAa;AAMrC,MAAA,IAAI,CAAC,QAAA,EAAU;AACb,QAAA,QAAA,GAAW,MAAA,CACR,oBAAA,EAAqB,CACrB,OAAA,CAAQ,MAAM;AACb,UAAA,QAAA,GAAW,MAAA;AAAA,QACb,CAAC,CAAA;AAAA,MACL;AACA,MAAA,MAAM,QAAA;AACN,MAAA,KAAA,GAAQ,MAAM,OAAO,gBAAA,EAAiB;AAAA,IACxC;AACA,IAAA,OAAO,IAAI,QAAA,CAAS,IAAA,CAAK,SAAA,CAAU,KAAK,CAAA,EAAG;AAAA,MACzC,MAAA,EAAQ,GAAA;AAAA,MACR,OAAA,EAAS;AAAA,QACP,cAAA,EAAgB,kBAAA;AAAA,QAChB,eAAA,EAAiB;AAAA;AACnB,KACD,CAAA;AAAA,EACH,CAAA;AACF","file":"server.cjs","sourcesContent":["import type { AIEngine } from '../ai/AIEngine.js';\n\n/**\n * Options for {@link createInferredEdgeRouteHandler}.\n */\nexport interface CreateInferredEdgeRouteHandlerOptions {\n /**\n * When `true` (the default), the handler triggers\n * `engine.computeInferredEdges()` on the first request that finds the\n * store empty so a fresh deployment can populate the overlay without\n * an explicit warmup step. Concurrent first-requests share a single\n * in-flight compute.\n *\n * Set `false` to disable lazy compute entirely — the handler then\n * returns whatever the store currently has (often `[]` until a\n * scheduled or manual warmup runs).\n */\n lazyCompute?: boolean;\n}\n\n/**\n * Build a Web-Standard (Request → Promise<Response>) handler that\n * surfaces the persisted inferred-edge overlay as JSON. Pair with\n * {@link RemoteInferredEdgeStore} in the browser to wire the overlay\n * end-to-end without exposing the AIEngine to the client.\n *\n * Wire-up examples:\n *\n * ```ts\n * // Next.js App Router (app/api/inferred-edges/route.ts):\n * import { createInferredEdgeRouteHandler } from '@inferagraph/core/server';\n * import { getServerEngine } from '@/lib/engine';\n * export const GET = createInferredEdgeRouteHandler(await getServerEngine());\n *\n * // Express adapter (Node 20+ with fetch / Request / Response):\n * const handler = createInferredEdgeRouteHandler(engine);\n * app.get('/api/inferred-edges', async (req, res) => {\n * const r = await handler(new Request(`http://x${req.originalUrl}`));\n * res.status(r.status);\n * r.headers.forEach((v, k) => res.setHeader(k, v));\n * res.send(await r.text());\n * });\n * ```\n */\nexport function createInferredEdgeRouteHandler(\n engine: AIEngine,\n options: CreateInferredEdgeRouteHandlerOptions = {},\n): (req: Request) => Promise<Response> {\n const lazyCompute = options.lazyCompute !== false;\n let inflight: Promise<void> | undefined;\n\n return async function inferredEdgeRouteHandler(\n _req: Request,\n ): Promise<Response> {\n let edges = await engine.getInferredEdges();\n if (edges.length === 0 && lazyCompute) {\n // Concurrent-compute idempotency: requests that arrive while a\n // compute is in flight wait on the SAME promise instead of\n // kicking off duplicate runs. The `finally` clears the slot so\n // a later empty-store request can re-trigger compute (the empty\n // state may be re-entered after a clear, an eviction, etc.).\n if (!inflight) {\n inflight = engine\n .computeInferredEdges()\n .finally(() => {\n inflight = undefined;\n });\n }\n await inflight;\n edges = await engine.getInferredEdges();\n }\n return new Response(JSON.stringify(edges), {\n status: 200,\n headers: {\n 'content-type': 'application/json',\n 'cache-control': 'public, max-age=300',\n },\n });\n };\n}\n"]} |
| import { A as AIEngine } from './AIEngine-C0Rd5U-b.cjs'; | ||
| /** | ||
| * Options for {@link createInferredEdgeRouteHandler}. | ||
| */ | ||
| interface CreateInferredEdgeRouteHandlerOptions { | ||
| /** | ||
| * When `true` (the default), the handler triggers | ||
| * `engine.computeInferredEdges()` on the first request that finds the | ||
| * store empty so a fresh deployment can populate the overlay without | ||
| * an explicit warmup step. Concurrent first-requests share a single | ||
| * in-flight compute. | ||
| * | ||
| * Set `false` to disable lazy compute entirely — the handler then | ||
| * returns whatever the store currently has (often `[]` until a | ||
| * scheduled or manual warmup runs). | ||
| */ | ||
| lazyCompute?: boolean; | ||
| } | ||
| /** | ||
| * Build a Web-Standard (Request → Promise<Response>) handler that | ||
| * surfaces the persisted inferred-edge overlay as JSON. Pair with | ||
| * {@link RemoteInferredEdgeStore} in the browser to wire the overlay | ||
| * end-to-end without exposing the AIEngine to the client. | ||
| * | ||
| * Wire-up examples: | ||
| * | ||
| * ```ts | ||
| * // Next.js App Router (app/api/inferred-edges/route.ts): | ||
| * import { createInferredEdgeRouteHandler } from '@inferagraph/core/server'; | ||
| * import { getServerEngine } from '@/lib/engine'; | ||
| * export const GET = createInferredEdgeRouteHandler(await getServerEngine()); | ||
| * | ||
| * // Express adapter (Node 20+ with fetch / Request / Response): | ||
| * const handler = createInferredEdgeRouteHandler(engine); | ||
| * app.get('/api/inferred-edges', async (req, res) => { | ||
| * const r = await handler(new Request(`http://x${req.originalUrl}`)); | ||
| * res.status(r.status); | ||
| * r.headers.forEach((v, k) => res.setHeader(k, v)); | ||
| * res.send(await r.text()); | ||
| * }); | ||
| * ``` | ||
| */ | ||
| declare function createInferredEdgeRouteHandler(engine: AIEngine, options?: CreateInferredEdgeRouteHandlerOptions): (req: Request) => Promise<Response>; | ||
| export { type CreateInferredEdgeRouteHandlerOptions, createInferredEdgeRouteHandler }; |
| import { A as AIEngine } from './AIEngine-C0Rd5U-b.js'; | ||
| /** | ||
| * Options for {@link createInferredEdgeRouteHandler}. | ||
| */ | ||
| interface CreateInferredEdgeRouteHandlerOptions { | ||
| /** | ||
| * When `true` (the default), the handler triggers | ||
| * `engine.computeInferredEdges()` on the first request that finds the | ||
| * store empty so a fresh deployment can populate the overlay without | ||
| * an explicit warmup step. Concurrent first-requests share a single | ||
| * in-flight compute. | ||
| * | ||
| * Set `false` to disable lazy compute entirely — the handler then | ||
| * returns whatever the store currently has (often `[]` until a | ||
| * scheduled or manual warmup runs). | ||
| */ | ||
| lazyCompute?: boolean; | ||
| } | ||
| /** | ||
| * Build a Web-Standard (Request → Promise<Response>) handler that | ||
| * surfaces the persisted inferred-edge overlay as JSON. Pair with | ||
| * {@link RemoteInferredEdgeStore} in the browser to wire the overlay | ||
| * end-to-end without exposing the AIEngine to the client. | ||
| * | ||
| * Wire-up examples: | ||
| * | ||
| * ```ts | ||
| * // Next.js App Router (app/api/inferred-edges/route.ts): | ||
| * import { createInferredEdgeRouteHandler } from '@inferagraph/core/server'; | ||
| * import { getServerEngine } from '@/lib/engine'; | ||
| * export const GET = createInferredEdgeRouteHandler(await getServerEngine()); | ||
| * | ||
| * // Express adapter (Node 20+ with fetch / Request / Response): | ||
| * const handler = createInferredEdgeRouteHandler(engine); | ||
| * app.get('/api/inferred-edges', async (req, res) => { | ||
| * const r = await handler(new Request(`http://x${req.originalUrl}`)); | ||
| * res.status(r.status); | ||
| * r.headers.forEach((v, k) => res.setHeader(k, v)); | ||
| * res.send(await r.text()); | ||
| * }); | ||
| * ``` | ||
| */ | ||
| declare function createInferredEdgeRouteHandler(engine: AIEngine, options?: CreateInferredEdgeRouteHandlerOptions): (req: Request) => Promise<Response>; | ||
| export { type CreateInferredEdgeRouteHandlerOptions, createInferredEdgeRouteHandler }; |
| // src/server/createInferredEdgeRouteHandler.ts | ||
| function createInferredEdgeRouteHandler(engine, options = {}) { | ||
| const lazyCompute = options.lazyCompute !== false; | ||
| let inflight; | ||
| return async function inferredEdgeRouteHandler(_req) { | ||
| let edges = await engine.getInferredEdges(); | ||
| if (edges.length === 0 && lazyCompute) { | ||
| if (!inflight) { | ||
| inflight = engine.computeInferredEdges().finally(() => { | ||
| inflight = void 0; | ||
| }); | ||
| } | ||
| await inflight; | ||
| edges = await engine.getInferredEdges(); | ||
| } | ||
| return new Response(JSON.stringify(edges), { | ||
| status: 200, | ||
| headers: { | ||
| "content-type": "application/json", | ||
| "cache-control": "public, max-age=300" | ||
| } | ||
| }); | ||
| }; | ||
| } | ||
| export { createInferredEdgeRouteHandler }; | ||
| //# sourceMappingURL=server.js.map | ||
| //# sourceMappingURL=server.js.map |
| {"version":3,"sources":["../src/server/createInferredEdgeRouteHandler.ts"],"names":[],"mappings":";AA4CO,SAAS,8BAAA,CACd,MAAA,EACA,OAAA,GAAiD,EAAC,EACb;AACrC,EAAA,MAAM,WAAA,GAAc,QAAQ,WAAA,KAAgB,KAAA;AAC5C,EAAA,IAAI,QAAA;AAEJ,EAAA,OAAO,eAAe,yBACpB,IAAA,EACmB;AACnB,IAAA,IAAI,KAAA,GAAQ,MAAM,MAAA,CAAO,gBAAA,EAAiB;AAC1C,IAAA,IAAI,KAAA,CAAM,MAAA,KAAW,CAAA,IAAK,WAAA,EAAa;AAMrC,MAAA,IAAI,CAAC,QAAA,EAAU;AACb,QAAA,QAAA,GAAW,MAAA,CACR,oBAAA,EAAqB,CACrB,OAAA,CAAQ,MAAM;AACb,UAAA,QAAA,GAAW,MAAA;AAAA,QACb,CAAC,CAAA;AAAA,MACL;AACA,MAAA,MAAM,QAAA;AACN,MAAA,KAAA,GAAQ,MAAM,OAAO,gBAAA,EAAiB;AAAA,IACxC;AACA,IAAA,OAAO,IAAI,QAAA,CAAS,IAAA,CAAK,SAAA,CAAU,KAAK,CAAA,EAAG;AAAA,MACzC,MAAA,EAAQ,GAAA;AAAA,MACR,OAAA,EAAS;AAAA,QACP,cAAA,EAAgB,kBAAA;AAAA,QAChB,eAAA,EAAiB;AAAA;AACnB,KACD,CAAA;AAAA,EACH,CAAA;AACF","file":"server.js","sourcesContent":["import type { AIEngine } from '../ai/AIEngine.js';\n\n/**\n * Options for {@link createInferredEdgeRouteHandler}.\n */\nexport interface CreateInferredEdgeRouteHandlerOptions {\n /**\n * When `true` (the default), the handler triggers\n * `engine.computeInferredEdges()` on the first request that finds the\n * store empty so a fresh deployment can populate the overlay without\n * an explicit warmup step. Concurrent first-requests share a single\n * in-flight compute.\n *\n * Set `false` to disable lazy compute entirely — the handler then\n * returns whatever the store currently has (often `[]` until a\n * scheduled or manual warmup runs).\n */\n lazyCompute?: boolean;\n}\n\n/**\n * Build a Web-Standard (Request → Promise<Response>) handler that\n * surfaces the persisted inferred-edge overlay as JSON. Pair with\n * {@link RemoteInferredEdgeStore} in the browser to wire the overlay\n * end-to-end without exposing the AIEngine to the client.\n *\n * Wire-up examples:\n *\n * ```ts\n * // Next.js App Router (app/api/inferred-edges/route.ts):\n * import { createInferredEdgeRouteHandler } from '@inferagraph/core/server';\n * import { getServerEngine } from '@/lib/engine';\n * export const GET = createInferredEdgeRouteHandler(await getServerEngine());\n *\n * // Express adapter (Node 20+ with fetch / Request / Response):\n * const handler = createInferredEdgeRouteHandler(engine);\n * app.get('/api/inferred-edges', async (req, res) => {\n * const r = await handler(new Request(`http://x${req.originalUrl}`));\n * res.status(r.status);\n * r.headers.forEach((v, k) => res.setHeader(k, v));\n * res.send(await r.text());\n * });\n * ```\n */\nexport function createInferredEdgeRouteHandler(\n engine: AIEngine,\n options: CreateInferredEdgeRouteHandlerOptions = {},\n): (req: Request) => Promise<Response> {\n const lazyCompute = options.lazyCompute !== false;\n let inflight: Promise<void> | undefined;\n\n return async function inferredEdgeRouteHandler(\n _req: Request,\n ): Promise<Response> {\n let edges = await engine.getInferredEdges();\n if (edges.length === 0 && lazyCompute) {\n // Concurrent-compute idempotency: requests that arrive while a\n // compute is in flight wait on the SAME promise instead of\n // kicking off duplicate runs. The `finally` clears the slot so\n // a later empty-store request can re-trigger compute (the empty\n // state may be re-entered after a clear, an eviction, etc.).\n if (!inflight) {\n inflight = engine\n .computeInferredEdges()\n .finally(() => {\n inflight = undefined;\n });\n }\n await inflight;\n edges = await engine.getInferredEdges();\n }\n return new Response(JSON.stringify(edges), {\n status: 200,\n headers: {\n 'content-type': 'application/json',\n 'cache-control': 'public, max-age=300',\n },\n });\n };\n}\n"]} |
+1
-1
@@ -1,2 +0,2 @@ | ||
| export { AnimationManager, BatchOperations, ChatPanel, ClusterEngine, ContextBuilder, CoulombForce, DataSource, DetailPanel, Easings, ExportEngine, FilterEngine, GraphIndexer, GraphMode, InMemoryCacheProvider, IntentParser, KeyboardManager, LayoutRegistry, Minimap, ModeManager, OverlayManager, PluginManager, ResponseHandler, SelectionManager, TimelineEngine, TreeMode, Tween, deterministicVector, exportGraph, importGraph, inMemoryCacheProvider, inMemoryConversationStore, inMemoryEmbeddingStore, inMemoryInferredEdgeStore, lruCache, mockLLMProvider, parseTTL } from './chunk-OBA5CYTR.js'; | ||
| export { AnimationManager, BatchOperations, ChatPanel, ClusterEngine, ContextBuilder, CoulombForce, DataSource, DetailPanel, Easings, ExportEngine, FilterEngine, GraphIndexer, GraphMode, InMemoryCacheProvider, IntentParser, KeyboardManager, LayoutRegistry, Minimap, ModeManager, OverlayManager, PluginManager, RemoteInferredEdgeStore, ResponseHandler, SelectionManager, TimelineEngine, TreeMode, Tween, deterministicVector, exportGraph, importGraph, inMemoryCacheProvider, inMemoryConversationStore, inMemoryEmbeddingStore, inMemoryInferredEdgeStore, lruCache, mockLLMProvider, parseTTL } from './chunk-BZUCT2NB.js'; | ||
| export { AIEngine, BarnesHut, CenteringForce, DampingForce, DataManager, Edge, ForceLayout3D, ForceSimulation, GraphStore, Indexer, LayoutEngine, MemoryManager, Node, QueryEngine, SearchEngine, SpringForce, StaticDataAdapter, TooltipOverlay, TreeLayout, aggregateEdges, buildLLMInferencePrompt, buildPredicateFromSpec, computeEmbeddingInferences, computeGraphInferences, computeLLMInferences, describeNode, httpTransport, inProcessTransport, isKeywordShape, joinNatural, mergeInferences, parseFilterSpec, parseToolCall, resolveEdgeLabel } from './chunk-5TLUXTZS.js'; | ||
@@ -3,0 +3,0 @@ export { contentHash, cosineSimilarity } from './chunk-5NBM34JH.js'; |
+2
-2
| 'use client'; | ||
| export { AnimationManager, BatchOperations, ChatPanel, ClusterEngine, ContextBuilder, CoulombForce, DataSource, DetailPanel, Easings, ExportEngine, FilterEngine, GraphIndexer, GraphMode, InMemoryCacheProvider, IntentParser, KeyboardManager, LayoutRegistry, Minimap, ModeManager, OverlayManager, PluginManager, ResponseHandler, SelectionManager, TimelineEngine, TreeMode, Tween, deterministicVector, exportGraph, importGraph, inMemoryCacheProvider, inMemoryConversationStore, inMemoryEmbeddingStore, inMemoryInferredEdgeStore, lruCache, mockLLMProvider, parseTTL } from './chunk-OBA5CYTR.js'; | ||
| export { AnnotationRenderer, CameraController, ChatText, CustomNodeRenderer, DEFAULT_EDGE_COLOR, DEFAULT_NODE_COLOR, DEFAULT_PALETTE_32, DEFAULT_PULSE_CONFIG, EdgeColorMap, EdgeMesh, GraphProvider, INFERRED_EDGE_ALPHA, INFERRED_EDGE_COLOR, INFERRED_EDGE_DASH_SIZE, INFERRED_EDGE_GAP_SIZE, InferaGraph, InferredEdgeMesh, LabelRenderer, NodeColorResolver, NodeMesh, PulseController, Raycaster, SceneController, ThemeManager, WebGLRenderer, autoColor, brighten, createReactNodeRenderFn, createReactTooltipRenderFn, hashStringToIndex, useInferaGraph, useInferaGraphChat, useInferaGraphChatContext, useInferaGraphCommands, useInferaGraphContent, useInferaGraphNeighbors, useInferaGraphSearch } from './chunk-UWGWYXUV.js'; | ||
| export { AnimationManager, BatchOperations, ChatPanel, ClusterEngine, ContextBuilder, CoulombForce, DataSource, DetailPanel, Easings, ExportEngine, FilterEngine, GraphIndexer, GraphMode, InMemoryCacheProvider, IntentParser, KeyboardManager, LayoutRegistry, Minimap, ModeManager, OverlayManager, PluginManager, RemoteInferredEdgeStore, ResponseHandler, SelectionManager, TimelineEngine, TreeMode, Tween, deterministicVector, exportGraph, importGraph, inMemoryCacheProvider, inMemoryConversationStore, inMemoryEmbeddingStore, inMemoryInferredEdgeStore, lruCache, mockLLMProvider, parseTTL } from './chunk-BZUCT2NB.js'; | ||
| export { AnnotationRenderer, CameraController, ChatText, CustomNodeRenderer, DEFAULT_EDGE_COLOR, DEFAULT_NODE_COLOR, DEFAULT_PALETTE_32, DEFAULT_PULSE_CONFIG, EdgeColorMap, EdgeMesh, GraphProvider, INFERRED_EDGE_ALPHA, INFERRED_EDGE_COLOR, INFERRED_EDGE_DASH_SIZE, INFERRED_EDGE_GAP_SIZE, InferaGraph, InferredEdgeMesh, LabelRenderer, NodeColorResolver, NodeMesh, PulseController, Raycaster, SceneController, ThemeManager, WebGLRenderer, autoColor, brighten, createReactNodeRenderFn, createReactTooltipRenderFn, hashStringToIndex, useInferaGraph, useInferaGraphChat, useInferaGraphChatContext, useInferaGraphCommands, useInferaGraphContent, useInferaGraphNeighbors, useInferaGraphSearch } from './chunk-7ALUJ2FI.js'; | ||
| export { AIEngine, BarnesHut, CenteringForce, DampingForce, DataManager, Edge, ForceLayout3D, ForceSimulation, GraphStore, Indexer, LayoutEngine, MemoryManager, Node, QueryEngine, SearchEngine, SpringForce, StaticDataAdapter, TooltipOverlay, TreeLayout, aggregateEdges, buildLLMInferencePrompt, buildPredicateFromSpec, computeEmbeddingInferences, computeGraphInferences, computeLLMInferences, describeNode, httpTransport, inProcessTransport, isKeywordShape, joinNatural, mergeInferences, parseFilterSpec, parseToolCall, resolveEdgeLabel } from './chunk-5TLUXTZS.js'; | ||
@@ -5,0 +5,0 @@ export { contentHash, cosineSimilarity } from './chunk-5NBM34JH.js'; |
+3
-2
@@ -1,3 +0,4 @@ | ||
| export { C as ChatText, d as ChatTextProps, G as GraphContextValue, h as GraphProvider, i as GraphProviderProps, I as InferaGraph, j as InferaGraphChatContext, k as InferaGraphChatContextHook, l as InferaGraphChatHook, m as InferaGraphCommands, n as InferaGraphProps, o as InferaGraphSearchHook, p as NeighborStatus, S as SlugResolver, T as ToolCallOutcome, U as UseInferaGraphContentReturn, q as UseInferaGraphNeighborsReturn, r as createReactNodeRenderFn, s as createReactTooltipRenderFn, u as useInferaGraph, t as useInferaGraphChat, v as useInferaGraphChatContext, w as useInferaGraphCommands, x as useInferaGraphContent, y as useInferaGraphNeighbors, z as useInferaGraphSearch } from './react-CF6PwBrA.cjs'; | ||
| export { C as ChatText, d as ChatTextProps, G as GraphContextValue, h as GraphProvider, i as GraphProviderProps, I as InferaGraph, j as InferaGraphChatContext, k as InferaGraphChatContextHook, l as InferaGraphChatHook, m as InferaGraphCommands, n as InferaGraphProps, o as InferaGraphSearchHook, p as NeighborStatus, S as SlugResolver, T as ToolCallOutcome, U as UseInferaGraphContentReturn, q as UseInferaGraphNeighborsReturn, r as createReactNodeRenderFn, s as createReactTooltipRenderFn, u as useInferaGraph, t as useInferaGraphChat, v as useInferaGraphChatContext, w as useInferaGraphCommands, x as useInferaGraphContent, y as useInferaGraphNeighbors, z as useInferaGraphSearch } from './react-CqvNdfzx.cjs'; | ||
| import 'react'; | ||
| import './aggregateEdges-CQVCx-mz.cjs'; | ||
| import './AIEngine-C0Rd5U-b.cjs'; | ||
| import './aggregateEdges-Dop7spOx.cjs'; |
+3
-2
@@ -1,3 +0,4 @@ | ||
| export { C as ChatText, d as ChatTextProps, G as GraphContextValue, h as GraphProvider, i as GraphProviderProps, I as InferaGraph, j as InferaGraphChatContext, k as InferaGraphChatContextHook, l as InferaGraphChatHook, m as InferaGraphCommands, n as InferaGraphProps, o as InferaGraphSearchHook, p as NeighborStatus, S as SlugResolver, T as ToolCallOutcome, U as UseInferaGraphContentReturn, q as UseInferaGraphNeighborsReturn, r as createReactNodeRenderFn, s as createReactTooltipRenderFn, u as useInferaGraph, t as useInferaGraphChat, v as useInferaGraphChatContext, w as useInferaGraphCommands, x as useInferaGraphContent, y as useInferaGraphNeighbors, z as useInferaGraphSearch } from './react-WdSuwS3p.js'; | ||
| export { C as ChatText, d as ChatTextProps, G as GraphContextValue, h as GraphProvider, i as GraphProviderProps, I as InferaGraph, j as InferaGraphChatContext, k as InferaGraphChatContextHook, l as InferaGraphChatHook, m as InferaGraphCommands, n as InferaGraphProps, o as InferaGraphSearchHook, p as NeighborStatus, S as SlugResolver, T as ToolCallOutcome, U as UseInferaGraphContentReturn, q as UseInferaGraphNeighborsReturn, r as createReactNodeRenderFn, s as createReactTooltipRenderFn, u as useInferaGraph, t as useInferaGraphChat, v as useInferaGraphChatContext, w as useInferaGraphCommands, x as useInferaGraphContent, y as useInferaGraphNeighbors, z as useInferaGraphSearch } from './react-BehW1k76.js'; | ||
| import 'react'; | ||
| import './aggregateEdges-CQVCx-mz.js'; | ||
| import './AIEngine-C0Rd5U-b.js'; | ||
| import './aggregateEdges-DSr58z_h.js'; |
+1
-1
| 'use client'; | ||
| export { ChatText, GraphProvider, InferaGraph, createReactNodeRenderFn, createReactTooltipRenderFn, useInferaGraph, useInferaGraphChat, useInferaGraphChatContext, useInferaGraphCommands, useInferaGraphContent, useInferaGraphNeighbors, useInferaGraphSearch } from './chunk-UWGWYXUV.js'; | ||
| export { ChatText, GraphProvider, InferaGraph, createReactNodeRenderFn, createReactTooltipRenderFn, useInferaGraph, useInferaGraphChat, useInferaGraphChatContext, useInferaGraphCommands, useInferaGraphContent, useInferaGraphNeighbors, useInferaGraphSearch } from './chunk-7ALUJ2FI.js'; | ||
| import './chunk-5TLUXTZS.js'; | ||
@@ -4,0 +4,0 @@ import './chunk-5NBM34JH.js'; |
+11
-1
| { | ||
| "name": "@inferagraph/core", | ||
| "version": "0.14.6", | ||
| "version": "0.15.0", | ||
| "description": "AI-powered knowledge graph platform with WebGL visualization", | ||
@@ -44,2 +44,12 @@ "repository": { | ||
| }, | ||
| "./server": { | ||
| "import": { | ||
| "types": "./dist/server.d.ts", | ||
| "default": "./dist/server.js" | ||
| }, | ||
| "require": { | ||
| "types": "./dist/server.d.cts", | ||
| "default": "./dist/server.cjs" | ||
| } | ||
| }, | ||
| "./themes/default.css": "./src/themes/default.css", | ||
@@ -46,0 +56,0 @@ "./themes/dark.css": "./src/themes/dark.css" |
+62
-1
@@ -149,3 +149,5 @@ # @inferagraph/core | ||
| | `transport` | `Transport` | Override the default in-process chat transport (e.g., HTTP proxy). | | ||
| | `showInferredEdges` | `boolean` | Toggle the dashed inferred-edge overlay. Default `false`. | | ||
| | `showInferredEdges` | `boolean` | Toggle the dashed inferred-edge overlay. Default `false`. Flipping on triggers an auto-fetch via the configured `inferredEdgeStore`. | | ||
| | `inferredEdgeStore` | `InferredEdgeStore` | Optional pluggable store; auto-fetch consumes this when `showInferredEdges` flips true. Default `RemoteInferredEdgeStore('/api/inferred-edges')` is the common browser shape. | | ||
| | `onInferredEdgesLoadingChange` | `(loading) => void` | Fires `true` when the auto-fetch starts, `false` on success / error / cancel. | | ||
| | `onChat` | `(event) => void` | Receives `text` + `done` events; tool calls dispatch silently to the renderer. | | ||
@@ -185,2 +187,61 @@ | `slugResolver` | `SlugResolver` | Phase 6 — translates input slugs to canonical NodeIds for hooks. | | ||
| ## Inferred Relationships | ||
| The library ships an end-to-end overlay for edges the system *believes* exist between two nodes but that are NOT present as explicit edges in the store. Three pieces: | ||
| 1. **Server-side compute + persistence.** `AIEngine.computeInferredEdges()` fuses graph signals (common neighbors, Jaccard, structural cosine), embedding similarity, and per-node LLM extraction via reciprocal-rank-fusion. Results land in the configured `InferredEdgeStore` (`inMemoryInferredEdgeStore()` for self-contained apps; `@inferagraph/cosmosdb` ships `CosmosInferredEdgeStore` for production persistence). | ||
| 2. **HTTP surface.** `createInferredEdgeRouteHandler(engine)` builds a Web-Standard `(Request) => Promise<Response>` that returns the persisted edges as JSON. Lazy-computes on first request if the store is empty; concurrent first-requests share a single in-flight compute. | ||
| 3. **Client read.** `RemoteInferredEdgeStore(url)` implements `InferredEdgeStore` against the route handler. Hand it to `<InferaGraph inferredEdgeStore={...}>`; the auto-fetch effect kicks in when `showInferredEdges` flips true and pipes the edges into the renderer. | ||
| ### Wiring — Next.js App Router | ||
| ```ts | ||
| // app/api/inferred-edges/route.ts | ||
| import { createInferredEdgeRouteHandler } from '@inferagraph/core/server'; | ||
| import { getServerEngine } from '@/lib/engine'; | ||
| export const GET = createInferredEdgeRouteHandler(await getServerEngine()); | ||
| ``` | ||
| ```tsx | ||
| // app/page.tsx (or a client component) | ||
| import { InferaGraph, RemoteInferredEdgeStore } from '@inferagraph/core'; | ||
| const inferredEdgeStore = new RemoteInferredEdgeStore('/api/inferred-edges'); | ||
| <InferaGraph | ||
| data={data} | ||
| inferredEdgeStore={inferredEdgeStore} | ||
| showInferredEdges={enabled} | ||
| onInferredEdgesLoadingChange={setLoading} | ||
| />; | ||
| ``` | ||
| ### Wiring — Express (Node 20+ with global `Request`/`Response`) | ||
| ```ts | ||
| const handler = createInferredEdgeRouteHandler(engine); | ||
| app.get('/api/inferred-edges', async (req, res) => { | ||
| const r = await handler(new Request(`http://x${req.originalUrl}`)); | ||
| res.status(r.status); | ||
| r.headers.forEach((v, k) => res.setHeader(k, v)); | ||
| res.send(await r.text()); | ||
| }); | ||
| ``` | ||
| ### When to compute | ||
| The lazy first-request compute covers most cold-start cases, but a heavy LLM source can take longer than is friendly inside a route handler. Production deployments usually trigger compute out-of-band: | ||
| - **Scheduled** — a nightly cron / Azure Function / GitHub Actions workflow that calls `engine.computeInferredEdges()` against the shared store. | ||
| - **On reindex** — wire `computeInferredEdges()` into your `GraphIndexer.reconcile()` step so a content refresh that bumps embeddings also recomputes inferred edges. | ||
| - **On startup** — kick a fire-and-forget `computeInferredEdges()` after the server engine is constructed so the first chat / page load reads warm cache. | ||
| The route handler can sit alongside any of those — the `lazyCompute: false` option disables the inline trigger when an upstream pipeline already owns the work. | ||
| ### Per-tick positioning | ||
| Dashed inferred edges track node positions automatically while the force simulation runs. The renderer (`SceneController.applyPositions` → `InferredEdgeMesh.updatePositions`) rewrites the underlying `Float32BufferAttribute` in place on every frame — no buffer re-allocation, no rebuild. Callers push the edge *set* once (via the auto-fetch effect); the controller owns the rest. | ||
| ### Visualization follow-ups | ||
| `InferredEdge` carries `score` (the fused confidence) and `reasoning` (a human-readable rationale when the LLM source contributed). v1 stores these but doesn't yet surface them visually. Open follow-ups: opacity-by-confidence, hover tooltip showing the reasoning, per-source breakdown via `InferredEdge.perSource`. | ||
| ## LLM Providers | ||
@@ -187,0 +248,0 @@ |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
| import * as React$1 from 'react'; | ||
| import React__default, { ReactNode } from 'react'; | ||
| import { G as GraphStore, aa as QueryEngine, A as AIEngine, s as DataManager, a2 as MemoryManager, N as NodeId, M as GraphData, D as DataAdapter, q as DataAdapterConfig, d as NodeData, v as EdgeData, L as LayoutMode, a as NodeRenderConfig, T as TooltipConfig, E as EdgeLabelMap, X as LLMProvider, i as CacheProvider, F as EmbeddingStore, al as Transport, j as ChatEvent, a6 as PaginatedResult, n as ContentData, K as FilterSpec, ab as SearchResult, a5 as NodeComponentProps, c as NodeRenderFn, ai as TooltipComponentProps, ak as TooltipRenderFn, k as ChatOptions } from './aggregateEdges-CQVCx-mz.cjs'; | ||
| /** | ||
| * Slug → node-id resolver. Hosts that route detail pages by human-readable | ||
| * slug (e.g. `/person/adam`) supply this so the library can translate the | ||
| * slug into the canonical node id used by the graph store + adapter. | ||
| * | ||
| * The sync escape hatch (`NodeId` instead of `Promise<NodeId>`) is | ||
| * deliberate: biblegraph's slug→UUID v5 helper is purely deterministic and | ||
| * doesn't need a microtask. Hooks always `await` the return so async | ||
| * resolvers (database lookups, manifest fetches) work too. | ||
| */ | ||
| type SlugResolver = (slug: string) => NodeId | Promise<NodeId>; | ||
| interface GraphContextValue { | ||
| store: GraphStore; | ||
| queryEngine: QueryEngine; | ||
| aiEngine: AIEngine; | ||
| dataManager: DataManager | null; | ||
| /** | ||
| * LRU eviction coordinator. Always present, even when no `maxNodes` cap | ||
| * is configured — `enforceCap` becomes a no-op in that case. | ||
| */ | ||
| memoryManager: MemoryManager; | ||
| /** | ||
| * Active {@link SlugResolver}, or `undefined` when the host opted out. | ||
| * Hooks check this before resolving — when undefined, they treat the | ||
| * input as a raw NodeId (no slug→id translation). | ||
| */ | ||
| slugResolver: SlugResolver | undefined; | ||
| /** | ||
| * Library-internal slug→nodeId cache. Slugs are immutable, so there's no | ||
| * TTL — entries live for the GraphProvider's lifetime. Exposed on the | ||
| * context value (rather than encapsulated) so the two hooks | ||
| * (`useInferaGraphContent` + `useInferaGraphNeighbors`) share one cache. | ||
| */ | ||
| slugCache: Map<string, NodeId>; | ||
| } | ||
| interface GraphProviderProps { | ||
| children: ReactNode; | ||
| /** Static data (existing behavior — wraps in StaticDataAdapter) */ | ||
| data?: GraphData; | ||
| /** DataAdapter for dynamic data fetching */ | ||
| adapter?: DataAdapter; | ||
| /** Config passed to adapter.getInitialView() */ | ||
| initialViewConfig?: DataAdapterConfig; | ||
| /** Slug → nodeId resolver. See {@link SlugResolver}. */ | ||
| slugResolver?: SlugResolver; | ||
| /** | ||
| * Soft cap on the number of nodes retained in the {@link GraphStore}. | ||
| * When the count exceeds the cap the {@link MemoryManager} evicts the | ||
| * oldest non-protected entries. `undefined` (the default) disables the | ||
| * cap so memory grows with the dataset. | ||
| */ | ||
| maxNodes?: number; | ||
| /** Called when initial data is loaded */ | ||
| onReady?: () => void; | ||
| } | ||
| declare function GraphProvider({ children, data, adapter, initialViewConfig, slugResolver, maxNodes, onReady, }: GraphProviderProps): React__default.JSX.Element; | ||
| /** | ||
| * Fallback color used only when the configured palette is empty. The CSS | ||
| * variable `--ig-node-color` should override this in themed apps; this | ||
| * constant is the absolute floor. | ||
| */ | ||
| declare const DEFAULT_NODE_COLOR = "#3b82f6"; | ||
| /** Function form: read the node, return a CSS hex/rgb color. */ | ||
| type NodeColorFn = (node: NodeData) => string | undefined; | ||
| interface NodeColorResolverOptions { | ||
| /** Override resolver — wins over both the type-keyed map and the palette. */ | ||
| colorFn?: NodeColorFn; | ||
| /** Explicit type → color map. Wins over auto-assignment. */ | ||
| nodeColors?: Record<string, string>; | ||
| /** | ||
| * Pool of colors used to deterministically assign a color to types that | ||
| * are not present in `nodeColors`. Defaults to {@link DEFAULT_PALETTE_32}. | ||
| */ | ||
| palette?: readonly string[]; | ||
| /** Default color used when palette is empty AND nothing else matches. */ | ||
| defaultColor?: string; | ||
| /** | ||
| * Multiplier (0..1) used by {@link NodeColorResolver.resolveHover} to lift | ||
| * the resolved color toward white for hover state. Defaults to 0.25. | ||
| */ | ||
| hoverBrightness?: number; | ||
| } | ||
| /** | ||
| * Resolves resting + hover colors for a node based on its attributes. | ||
| * | ||
| * Resolution order (resting): | ||
| * 1. `colorFn(node)` if it returns a non-undefined string | ||
| * 2. `nodeColors[node.attributes.type]` if it's a string lookup | ||
| * 3. `node.attributes.color` if the consumer set one explicitly | ||
| * 4. `palette[hashStringToIndex(type, palette.length)]` (deterministic auto) | ||
| * 5. `defaultColor` (only reached when palette is empty) | ||
| * | ||
| * Hover: same color, brightened by `hoverBrightness` (default 25 % toward white). | ||
| * | ||
| * Domain knowledge note: this resolver ships ZERO domain-specific defaults | ||
| * — the consumer (e.g. Bible Graph) supplies its own `nodeColors` map. | ||
| */ | ||
| declare class NodeColorResolver { | ||
| private readonly colorFn?; | ||
| private readonly nodeColors; | ||
| private readonly palette; | ||
| private readonly defaultColor; | ||
| private readonly hoverBrightness; | ||
| constructor(options?: NodeColorResolverOptions); | ||
| /** Resting color for `node`. */ | ||
| resolve(node: NodeData): string; | ||
| /** Hover color for `node` — resting color brightened toward white. */ | ||
| resolveHover(node: NodeData): string; | ||
| /** The active palette (for tests + introspection). */ | ||
| getPalette(): readonly string[]; | ||
| } | ||
| /** Fallback edge color when the palette is empty AND no override matches. */ | ||
| declare const DEFAULT_EDGE_COLOR = "#6366f1"; | ||
| /** | ||
| * Per-call context handed to {@link EdgeColorFn}. Carries the resolved | ||
| * resting colors of the edge's source + target nodes (the same hex values | ||
| * {@link NodeColorResolver.resolve} would return for those nodes) so the | ||
| * function can derive a color from its endpoints — for example by | ||
| * blending them, picking one side, or treating same-vs-different as a | ||
| * boolean. | ||
| * | ||
| * Both fields are guaranteed to be valid CSS color strings; callers fall | ||
| * back to {@link DEFAULT_EDGE_COLOR} (or the consumer's chosen | ||
| * fallback) for either side that cannot be resolved against the live | ||
| * graph store. | ||
| */ | ||
| interface EdgeColorContext { | ||
| /** Resolved resting color of the source node. */ | ||
| sourceColor: string; | ||
| /** Resolved resting color of the target node. */ | ||
| targetColor: string; | ||
| } | ||
| /** | ||
| * Function form: read the edge + its endpoint colors, return a CSS | ||
| * hex/rgb color (or `undefined` to fall through to the type-keyed map / | ||
| * palette). | ||
| * | ||
| * The second argument is optional at call sites that pre-date the context | ||
| * — older `(edge) => string` consumers continue to work because they | ||
| * simply ignore the extra parameter. | ||
| */ | ||
| type EdgeColorFn = (edge: EdgeData, ctx: EdgeColorContext) => string | undefined; | ||
| interface EdgeColorMapOptions { | ||
| /** Override resolver — wins over both the type-keyed map and the palette. */ | ||
| colorFn?: EdgeColorFn; | ||
| /** Explicit type → color map. Wins over auto-assignment. */ | ||
| edgeColors?: Record<string, string>; | ||
| /** | ||
| * Pool of colors used to deterministically assign a color to relationship | ||
| * types not present in `edgeColors`. Defaults to {@link DEFAULT_PALETTE_32}. | ||
| */ | ||
| palette?: readonly string[]; | ||
| /** Default color used when palette is empty AND nothing else matches. */ | ||
| defaultColor?: string; | ||
| /** | ||
| * Multiplier (0..1) used by {@link EdgeColorMap.resolveHover} to lift the | ||
| * resolved color toward white for hover state. Defaults to 0.25. | ||
| */ | ||
| hoverBrightness?: number; | ||
| } | ||
| /** | ||
| * Resolves resting + hover colors for an edge based on its `attributes.type` | ||
| * (the relationship type, e.g. `father_of`). | ||
| * | ||
| * Resolution order (resting): | ||
| * 1. `colorFn(edge, ctx)` if it returns a non-undefined string | ||
| * 2. `edgeColors[edge.attributes.type]` | ||
| * 3. `palette[hashStringToIndex(type, palette.length)]` (deterministic auto) | ||
| * 4. `defaultColor` (only reached when palette is empty) | ||
| * | ||
| * Hover: same color, brightened by `hoverBrightness` (default 25 % toward white). | ||
| * | ||
| * Domain knowledge note: this map ships ZERO domain-specific defaults — | ||
| * `father_of`, `married_to`, etc. mean nothing to InferaGraph. The consumer | ||
| * (e.g. Bible Graph) supplies its own `edgeColors` map. | ||
| */ | ||
| declare class EdgeColorMap { | ||
| private readonly colorFn?; | ||
| private readonly edgeColors; | ||
| private readonly palette; | ||
| private readonly defaultColor; | ||
| private readonly hoverBrightness; | ||
| constructor(options?: EdgeColorMapOptions); | ||
| /** | ||
| * Resting color for `edge`. | ||
| * | ||
| * `ctx` carries the resolved endpoint colors so a {@link EdgeColorFn} | ||
| * can derive the edge color from its endpoints (see | ||
| * {@link blendEdgeColors}). The argument is defaulted to the fallback | ||
| * color on both sides so legacy call sites that don't yet plumb | ||
| * endpoint colors through still produce a sensible result. | ||
| */ | ||
| resolve(edge: EdgeData, ctx?: EdgeColorContext): string; | ||
| /** Hover color for `edge` — resting color brightened toward white. */ | ||
| resolveHover(edge: EdgeData, ctx?: EdgeColorContext): string; | ||
| /** The active palette (for tests + introspection). */ | ||
| getPalette(): readonly string[]; | ||
| } | ||
| interface InferaGraphProps { | ||
| data?: GraphData; | ||
| layout?: LayoutMode; | ||
| nodeRender?: NodeRenderConfig; | ||
| tooltip?: TooltipConfig; | ||
| /** Pool of colors for deterministic auto-assignment. */ | ||
| palette?: readonly string[]; | ||
| /** Explicit type → color map for nodes. */ | ||
| nodeColors?: Record<string, string>; | ||
| /** Function override for nodes. */ | ||
| nodeColorFn?: NodeColorFn; | ||
| /** Explicit relationship-type → color map for edges. */ | ||
| edgeColors?: Record<string, string>; | ||
| /** Function override for edges. */ | ||
| edgeColorFn?: EdgeColorFn; | ||
| /** | ||
| * Incoming-edge label map for the default tooltip's natural-language | ||
| * description (e.g. `{ father_of: 'Son of', mother_of: 'Son of' }`). | ||
| * Ignored when `tooltip.renderTooltip` / `tooltip.component` is supplied. | ||
| */ | ||
| incomingEdgeLabels?: EdgeLabelMap; | ||
| /** | ||
| * Outgoing-edge label map for the default tooltip's natural-language | ||
| * description (e.g. `{ father_of: 'Father of' }`). | ||
| */ | ||
| outgoingEdgeLabels?: EdgeLabelMap; | ||
| /** | ||
| * Domain-agnostic visibility predicate. When supplied, only nodes for | ||
| * which the predicate returns `true` are rendered; edges whose source | ||
| * OR target node is filtered out are hidden too. | ||
| * | ||
| * The same predicate applies in **every** visualization mode — graph, | ||
| * tree, and any future mode (geospatial / timeline / chord / etc.). | ||
| * Filter changes are applied as in-place visibility toggles on the | ||
| * existing GPU buffers — there's no mesh teardown, no rebuild, and | ||
| * no layout recompute. Hidden nodes keep their layout positions, so | ||
| * unhiding restores the prior frame instantly. | ||
| * | ||
| * Default: no filter (every node visible). | ||
| */ | ||
| filter?: (node: NodeData) => boolean; | ||
| /** | ||
| * Tree-mode option: edge types that the hierarchical tidy-tree layout | ||
| * treats as parent -> child. Default `['parent_of']` — set to your | ||
| * domain's vocabulary (`['manages']` for org charts, `['supplies']` | ||
| * for supply chains, `['is_a']` for taxonomies, `['cites']` for | ||
| * citation graphs, etc.). Has no effect in graph mode. | ||
| */ | ||
| parentEdgeTypes?: string[]; | ||
| /** | ||
| * Tree-mode option: edge types that pair two nodes at the same depth | ||
| * so they appear adjacent and share children. Default `[]` (no | ||
| * pairing). Has no effect in graph mode. | ||
| */ | ||
| pairedEdgeTypes?: string[]; | ||
| /** | ||
| * LLM provider for AI features (NLQ filtering today; chat / search / highlight | ||
| * land in later phases). The host imports a provider package and passes a | ||
| * configured INSTANCE here. The host never invokes the LLM directly — | ||
| * InferaGraph owns the entire LLM lifecycle. | ||
| * | ||
| * Omitted = AI features are unavailable; the explicit `filter` predicate | ||
| * still works. | ||
| */ | ||
| llm?: LLMProvider; | ||
| /** | ||
| * Cache provider for LLM responses. Defaults to NO CACHING when omitted, so | ||
| * tests + small demos don't accidentally retain old responses. Pass | ||
| * `lruCache()` (built-in) or `@inferagraph/redis-cache-provider` for | ||
| * production. The cache is wiped automatically when the LLM provider | ||
| * instance changes. | ||
| * | ||
| * Also acts as the **Tier 2** embedding storage when `embeddingStore` is | ||
| * not supplied AND the configured `llm` provider implements `embed()` — | ||
| * embeddings are persisted in the cache and similarity is computed | ||
| * in-memory at query time. | ||
| */ | ||
| cache?: CacheProvider; | ||
| /** | ||
| * Optional dedicated {@link EmbeddingStore} (Tier 3 of the embedding | ||
| * progression). When supplied, AIEngine uses the store's vector-native | ||
| * `similar()` for semantic search. The default in-process implementation | ||
| * ships as `inMemoryEmbeddingStore()`; persistent stores live in their | ||
| * own packages. | ||
| * | ||
| * Tier mapping at runtime: | ||
| * - omit both → keyword search only. | ||
| * - `cache` only + provider with `embed()` → Tier 2. | ||
| * - `embeddingStore` + provider with `embed()` → Tier 3. | ||
| */ | ||
| embeddingStore?: EmbeddingStore; | ||
| /** | ||
| * Natural-language query that the LLM compiles into a filter predicate. | ||
| * Combined with the explicit `filter` prop via AND: the developer-set | ||
| * `filter` runs first (proactive scope), then `query` narrows within that | ||
| * scope. Requires `llm`. An empty / undefined `query` is a no-op. | ||
| */ | ||
| query?: string; | ||
| /** | ||
| * Optional explicit chat {@link Transport}. Wins over the implicit | ||
| * "build an in-process transport from `llm`" path. Use this to wire | ||
| * an HTTP transport (Next.js `/api/chat` proxy) so the LLM + cache | ||
| * stay server-side. | ||
| */ | ||
| transport?: Transport; | ||
| /** | ||
| * Toggle visibility of the Phase 5 inferred-edge overlay (dashed | ||
| * lines between nodes the AI inference pipeline thinks are related). | ||
| * Defaults to `false` (overlay hidden) so the explicit graph reads | ||
| * cleanly out of the box. Hosts opt in either by setting this to | ||
| * `true` or by handing the LLM a `set_inferred_visibility` chat | ||
| * tool call. | ||
| * | ||
| * The actual inferred edges are computed by | ||
| * `aiEngine.computeInferredEdges()` and pushed to the renderer | ||
| * separately — toggling this prop only shows/hides whatever has | ||
| * already been computed. | ||
| */ | ||
| showInferredEdges?: boolean; | ||
| /** | ||
| * Host-facing chat callback. Called with `text` and `done` events | ||
| * only — tool calls (`apply_filter` / `highlight` / `focus` / | ||
| * `annotate`) are dispatched silently to the renderer. | ||
| * | ||
| * The host writes its own chat UI on top of this callback (input | ||
| * box, message log, conversation pane). InferaGraph owns the | ||
| * streaming / tool-routing logistics. | ||
| */ | ||
| onChat?: (event: ChatEvent) => void; | ||
| /** | ||
| * Phase 1 (RAG architecture, 0.8.0) — fired when the engine emits a | ||
| * `debug` ChatEvent (vector-search, rerank, retrieval-empty, etc.). | ||
| * Hosts use this to render diagnostic badges underneath assistant | ||
| * bubbles for ops visibility. Debug events are NEVER yielded to the | ||
| * iterator — only this callback receives them. | ||
| */ | ||
| onDiagnostic?: (event: Extract<ChatEvent, { | ||
| type: 'debug'; | ||
| }>) => void; | ||
| /** | ||
| * Phase 1 (RAG architecture, 0.8.0) — fired after a tool-call event | ||
| * has been dispatched to the renderer. Lets host UIs surface | ||
| * "N applied, M unknown" badges. The library reports the requested | ||
| * ids as `appliedIds`; future enhancements will reconcile against | ||
| * the store's known-ids set to populate `unknownIds`. | ||
| */ | ||
| onToolCallOutcome?: (outcome: { | ||
| tool: string; | ||
| appliedIds?: string[]; | ||
| unknownIds?: string[]; | ||
| }) => void; | ||
| /** | ||
| * Phase 6 — slug → nodeId resolver. When supplied, hooks like | ||
| * {@link useInferaGraphContent} translate the input through this | ||
| * resolver before calling into the {@link DataAdapter}. Hosts that | ||
| * route by raw UUIDs can omit it. | ||
| */ | ||
| slugResolver?: SlugResolver; | ||
| /** | ||
| * Phase 6 — soft cap on the number of nodes retained in the store. | ||
| * When exceeded, the {@link MemoryManager} evicts the oldest | ||
| * non-protected nodes. `undefined` (default) disables the cap. | ||
| */ | ||
| maxNodes?: number; | ||
| /** | ||
| * Phase 6 — fired when the user clicks a node body. The host typically | ||
| * opens a detail dialog. Receives the canonical NodeId (post-slug | ||
| * resolution) and the node's data snapshot. | ||
| * | ||
| * NOT fired for clicks on the "+" affordance — those go through | ||
| * {@link onExpandRequest} instead. | ||
| */ | ||
| onNodeClick?: (nodeId: NodeId, node: NodeData) => void; | ||
| /** | ||
| * Phase 6 — fired when the user clicks the "+" hover affordance on a | ||
| * node. Receives the canonical NodeId. The host typically calls | ||
| * `useInferaGraphNeighbors().expand(nodeId)`. When omitted, the | ||
| * library installs a default handler that fires the built-in | ||
| * `expand(nodeId)` dispatch. | ||
| */ | ||
| onExpandRequest?: (nodeId: NodeId) => void; | ||
| className?: string; | ||
| style?: React__default.CSSProperties; | ||
| /** | ||
| * Optional children rendered inside the InferaGraph subtree. Useful | ||
| * for hosts that want to embed components that consume | ||
| * {@link useInferaGraphChat} or other hooks that depend on the | ||
| * InferaGraph context. Children render OUTSIDE the WebGL canvas | ||
| * (after the `.ig-container` div) — they're for host-owned chrome / | ||
| * chat UI, not for in-canvas rendering. | ||
| */ | ||
| children?: React__default.ReactNode; | ||
| } | ||
| declare function InferaGraph(props: InferaGraphProps): React__default.JSX.Element; | ||
| interface UseInferaGraphReturn { | ||
| loadData: (data: GraphData) => void; | ||
| nodeCount: number; | ||
| edgeCount: number; | ||
| expandNode: (nodeId: NodeId, depth?: number) => Promise<void>; | ||
| findPath: (fromId: NodeId, toId: NodeId) => Promise<NodeData[]>; | ||
| search: (query: string) => Promise<PaginatedResult<NodeData>>; | ||
| getContent: (nodeId: NodeId) => Promise<ContentData | undefined>; | ||
| isReady: boolean; | ||
| } | ||
| declare function useInferaGraph(): UseInferaGraphReturn; | ||
| /** | ||
| * Public return shape of {@link useInferaGraphChat}. The hook returns a | ||
| * single function that streams a chat with the configured transport. | ||
| * | ||
| * The returned `AsyncIterable` yields ONLY `text` and `done` events — | ||
| * tool calls (`apply_filter`, `highlight`, `focus`, `annotate`) are | ||
| * dispatched silently to the renderer, exactly mirroring the contract | ||
| * of `<InferaGraph onChat>`. | ||
| */ | ||
| interface InferaGraphChatHook { | ||
| /** | ||
| * Send `message` through the active chat transport. Returns an async | ||
| * iterable that yields {@link ChatEvent}s — text + done only by | ||
| * default. Tool-call events are dispatched to the renderer's | ||
| * `setHighlight` / `setFilter` / `focusOn` / `annotate` and not | ||
| * surfaced to the iterator. | ||
| * | ||
| * Pass `signal` to cancel mid-stream. | ||
| * | ||
| * Throws synchronously if no transport (or no `llm` prop) is | ||
| * configured on the host `<InferaGraph>` element. | ||
| */ | ||
| chat: (message: string, opts?: { | ||
| signal?: AbortSignal; | ||
| /** | ||
| * Optional conversation id, forwarded to the active transport. When | ||
| * the HTTP transport is in use the id flows into the request body | ||
| * so the server route can thread it into its `ConversationStore`; | ||
| * with the in-process transport the id is forwarded directly to | ||
| * `AIEngine.chat`. Omit to opt out of conversation memory for this | ||
| * call. | ||
| */ | ||
| conversationId?: string; | ||
| }) => AsyncIterable<ChatEvent>; | ||
| } | ||
| /** | ||
| * React hook that surfaces InferaGraph's chat API to the host. | ||
| * | ||
| * Internally: | ||
| * 1. The hook resolves the active transport via React context | ||
| * (`<InferaGraph>` populates this). | ||
| * 2. When `chat(message)` is called, the hook iterates the transport's | ||
| * full event stream: | ||
| * - tool-call events are dispatched to the SceneController's | ||
| * highlight / focus / annotate / filter sinks. | ||
| * - text + done events are re-yielded to the host. | ||
| * 3. The host iterates the returned async iterable to render text | ||
| * bubbles. The renderer never appears in the host's iteration. | ||
| * | ||
| * MUST be called inside an `<InferaGraph>` subtree. | ||
| */ | ||
| declare function useInferaGraphChat(): InferaGraphChatHook; | ||
| /** | ||
| * Public return shape of {@link useInferaGraphChatContext}. The hook | ||
| * surfaces the renderer's dispatch sink so a host can fire ad-hoc | ||
| * {@link ChatEvent}s without going through `chat()`. | ||
| * | ||
| * The same dispatch path receives chat-driven tool-call events under | ||
| * the hood — calling `dispatch(...)` here goes through the exact same | ||
| * SceneController surface, which means highlights, filters, focus | ||
| * moves, annotations, and the new visual-reset commands all behave | ||
| * identically whether the model emits them or the host fires them. | ||
| */ | ||
| interface InferaGraphChatContextHook { | ||
| /** | ||
| * Dispatch a {@link ChatEvent} into the renderer. Mostly useful for | ||
| * host UI affordances ("Clear conversation" → reset highlight, | ||
| * "Camera home" button → `reset_view`, etc.). | ||
| * | ||
| * Prefer {@link InferaGraphCommands} (`useInferaGraphCommands`) for | ||
| * the typical cases — it's a thin semantic facade on top of this | ||
| * sink. Reach for this hook when you need to dispatch an event | ||
| * variant the facade doesn't surface (e.g. emitting a `debug` | ||
| * ChatEvent for ops visibility, or a synthetic `text` event for | ||
| * a UI-only "Welcome" bubble). | ||
| */ | ||
| dispatch: (event: ChatEvent) => void; | ||
| } | ||
| /** | ||
| * 0.10.0 — public escape-hatch hook returning the renderer's dispatch | ||
| * function. Most hosts should reach for {@link useInferaGraphCommands} | ||
| * instead; this hook exists for host code that needs to dispatch a | ||
| * {@link ChatEvent} variant the semantic facade doesn't expose. | ||
| * | ||
| * MUST be called inside an `<InferaGraph>` subtree. | ||
| */ | ||
| declare function useInferaGraphChatContext(): InferaGraphChatContextHook; | ||
| /** | ||
| * 0.10.0 — semantic facade for host-initiated graph commands. Most hosts | ||
| * use this hook instead of the lower-level | ||
| * {@link useInferaGraphChatContext}. | ||
| * | ||
| * Each method dispatches the equivalent {@link ChatEvent} to the | ||
| * renderer's dispatch sink — exactly the same path the library uses for | ||
| * chat-driven (model-emitted) events. There's no separate "host API" | ||
| * that diverges from the chat contract. | ||
| */ | ||
| interface InferaGraphCommands { | ||
| /** | ||
| * Replace the active highlight set. Pass an empty Set to clear (every | ||
| * node returns to full opacity). | ||
| */ | ||
| setHighlight(ids: ReadonlySet<string>): void; | ||
| /** | ||
| * Move the camera to the supplied node. The library does not have | ||
| * an "unfocus" affordance — hosts that want to recenter should call | ||
| * {@link resetView} instead. | ||
| */ | ||
| focusOn(nodeId: string): void; | ||
| /** | ||
| * Apply a domain-agnostic filter spec to the visible graph. The spec | ||
| * shape mirrors the LLM's `apply_filter` tool-call output: keys are | ||
| * node attribute names, values are arrays of allowed string values. | ||
| * Pass an empty object (`{}`) to clear the filter. | ||
| */ | ||
| applyFilter(spec: FilterSpec): void; | ||
| /** Toggle the inferred-edge overlay. */ | ||
| setInferredVisibility(visible: boolean): void; | ||
| /** Attach a callout to a node. */ | ||
| annotate(nodeId: string, text: string): void; | ||
| /** Drop every annotation currently mounted. */ | ||
| clearAnnotations(): void; | ||
| /** Snap the camera back to its captured initial orientation. */ | ||
| resetView(): void; | ||
| /** | ||
| * Comprehensive reset — highlights + annotations + filter + camera. | ||
| * The "fresh canvas" command. Hosts wire this to "Clear | ||
| * conversation" / "New session" UX. | ||
| */ | ||
| clearVisualState(): void; | ||
| } | ||
| /** | ||
| * 0.10.0 — public, semantic hook for dispatching host-initiated graph | ||
| * commands. Built on top of {@link useInferaGraphChatContext}, but with | ||
| * a flat, named surface so callers don't have to construct | ||
| * {@link ChatEvent} objects by hand. | ||
| * | ||
| * The returned object is memoized for the lifetime of the hook so | ||
| * downstream `useEffect` / `useMemo` deps remain stable across renders. | ||
| * | ||
| * MUST be called inside an `<InferaGraph>` subtree. | ||
| */ | ||
| declare function useInferaGraphCommands(): InferaGraphCommands; | ||
| /** | ||
| * Public return shape of {@link useInferaGraphSearch}. The hook returns a | ||
| * single `search(query)` function that delegates to the AIEngine's | ||
| * auto-detect router (keyword vs semantic). | ||
| * | ||
| * Pair with `<InferaGraph>` props: | ||
| * - `llm` — required for semantic routing. | ||
| * - `cache` — opts the engine into Tier 2 (cache-as-vector-store). | ||
| * - `embeddingStore` — opts the engine into Tier 3 (vector-native store). | ||
| * Without `llm`, every query falls back to the data-layer keyword search. | ||
| */ | ||
| interface InferaGraphSearchHook { | ||
| /** | ||
| * Run an auto-routed search. Short token-only queries hit the data-layer | ||
| * keyword index; sentence-shaped or NLQ inputs go through embeddings. | ||
| * | ||
| * Returns at most `opts.k ?? 25` hits, sorted by descending score. | ||
| */ | ||
| search: (query: string, opts?: { | ||
| k?: number; | ||
| signal?: AbortSignal; | ||
| }) => Promise<SearchResult[]>; | ||
| } | ||
| /** | ||
| * React hook surfacing InferaGraph's Phase 4 search API. | ||
| * | ||
| * Internally: | ||
| * 1. Pulls the {@link AIEngine} out of the GraphContext (populated by | ||
| * `<InferaGraph>` / `<GraphProvider>`). | ||
| * 2. Wraps `engine.search` in a stable `useCallback` so consumers can | ||
| * memoize / debounce without identity churn. | ||
| * 3. Holds the engine behind a ref so prop changes on the host don't | ||
| * invalidate the callback. | ||
| * | ||
| * MUST be called inside an `<InferaGraph>` (or standalone `<GraphProvider>`) | ||
| * subtree. | ||
| */ | ||
| declare function useInferaGraphSearch(): InferaGraphSearchHook; | ||
| /** | ||
| * Return shape of {@link useInferaGraphContent}. | ||
| * | ||
| * Mirrors the host-friendly fetch-state quartet (data + loading + error + | ||
| * refetch). All four fields are always populated — `loading` is `false` | ||
| * when `idOrSlug` is `undefined`, so hosts don't need conditional rendering | ||
| * gymnastics. | ||
| */ | ||
| interface UseInferaGraphContentReturn { | ||
| /** Fetched content, or `undefined` while loading / on error / for invalid input. */ | ||
| data: ContentData | undefined; | ||
| /** `true` while a fetch is in flight (or queued for the current id). */ | ||
| loading: boolean; | ||
| /** Last error encountered, or `undefined` on success / before first fetch. */ | ||
| error: Error | undefined; | ||
| /** Imperatively re-run the fetch for the current id. No-op when id is undefined. */ | ||
| refetch: () => void; | ||
| } | ||
| /** | ||
| * Tier-1 cache for getContent results. Keyed by canonical NodeId (post slug | ||
| * resolution). Populated on every successful fetch; consulted before issuing | ||
| * a network call so re-mounted DetailModals don't re-fetch. | ||
| * | ||
| * Lives at module scope intentionally: the GraphProvider context value is | ||
| * recreated on adapter swap, but that swap also wipes the store and would | ||
| * make any cached content stale. We mirror that lifecycle by clearing this | ||
| * Map whenever the dataManager identity flips (see effect below). | ||
| */ | ||
| /** | ||
| * React hook that fetches detail content for one node. | ||
| * | ||
| * Workflow: | ||
| * 1. If `idOrSlug` is `undefined`, return a disabled-state object — no | ||
| * fetch fires, no spinner. | ||
| * 2. Pass the input through the configured {@link SlugResolver}. When the | ||
| * host opted out (no resolver), the input is treated as a raw NodeId. | ||
| * 3. Hit the per-DataManager cache. On hit, return immediately; on miss, | ||
| * call `dataManager.getContent(uuid)` and cache the result. | ||
| * 4. Touch the {@link MemoryManager} so the node moves to the MRU end | ||
| * and survives any subsequent cap-enforcement pass. | ||
| * 5. Cancel via the standard "captured-token" pattern when the input | ||
| * changes mid-flight; `setData` only fires for the latest call. | ||
| * | ||
| * MUST be called inside an `<InferaGraph>` (or standalone `<GraphProvider>`) | ||
| * subtree. | ||
| */ | ||
| declare function useInferaGraphContent(idOrSlug: string | undefined): UseInferaGraphContentReturn; | ||
| /** Lifecycle status of a single expand call, tracked per-nodeId. */ | ||
| type NeighborStatus = 'loading' | 'loaded' | 'error'; | ||
| /** | ||
| * Return shape of {@link useInferaGraphNeighbors}. | ||
| * | ||
| * Imperative hook (not a fetch-on-mount hook): the host calls `expand` / | ||
| * `collapse` in response to user actions (click on the "+" affordance, | ||
| * keyboard shortcut, chat tool call). Reading `expanded` is enough for the | ||
| * host to know which nodes are currently in flight or already drilled-down. | ||
| */ | ||
| interface UseInferaGraphNeighborsReturn { | ||
| /** | ||
| * Fetch direct neighbors of `nodeId` (depth defaults to 1). Resolves slug | ||
| * via the configured resolver, calls `dataManager.expandNode`, syncs the | ||
| * scene from the freshly-merged store, and enforces the LRU cap with the | ||
| * just-expanded set marked as protected (so eviction never targets the | ||
| * neighbors the user just asked to see). | ||
| */ | ||
| expand: (nodeIdOrSlug: string, depth?: number) => Promise<void>; | ||
| /** | ||
| * Remove neighbors of `nodeId` that aren't in the protected initial-view | ||
| * set. Inverse of `expand`. Implementations of "protected" are | ||
| * intentionally simple v1: any node currently visible due to another | ||
| * expand still survives because its LRU timestamp is fresher than the | ||
| * collapsing nodes. | ||
| */ | ||
| collapse: (nodeIdOrSlug: string) => Promise<void>; | ||
| /** Per-node lifecycle map. Keys are CANONICAL NodeIds (post-resolution). */ | ||
| expanded: ReadonlyMap<NodeId, NeighborStatus>; | ||
| } | ||
| /** | ||
| * React hook that exposes drilldown / collapse imperatives. | ||
| * | ||
| * Slug-aware: callers may pass either a UUID or a slug. The configured | ||
| * {@link SlugResolver} translates slugs to canonical NodeIds; the resolved | ||
| * id is what survives in `expanded` so the host can reason about identity | ||
| * uniformly. | ||
| * | ||
| * Race-safe: each call captures its own resolved nodeId before awaiting, | ||
| * and the returned promise reflects the outcome of THIS call. If a faster | ||
| * second `expand(sameNode)` overlaps a slow first one, both promises | ||
| * resolve correctly because the underlying `dataManager.expandNode` merges | ||
| * by id (later merges are no-ops for nodes already present). | ||
| * | ||
| * MUST be called inside an `<InferaGraph>` (or standalone `<GraphProvider>`) | ||
| * subtree. | ||
| */ | ||
| declare function useInferaGraphNeighbors(): UseInferaGraphNeighborsReturn; | ||
| /** | ||
| * Creates a NodeRenderFn from a React component. | ||
| * Each node gets its own React root (createRoot). | ||
| * Returns a cleanup function that unmounts the root. | ||
| */ | ||
| declare function createReactNodeRenderFn(Component: React.ComponentType<NodeComponentProps>): NodeRenderFn; | ||
| /** | ||
| * Creates a TooltipRenderFn from a React component. | ||
| * Each tooltip gets its own React root. | ||
| */ | ||
| declare function createReactTooltipRenderFn(Component: React.ComponentType<TooltipComponentProps>): TooltipRenderFn; | ||
| interface ChatTextProps { | ||
| /** | ||
| * The streamed assistant text. May contain markdown and the 0.12.0 | ||
| * citation wire format `[[token|matched-text]]` — both segments | ||
| * required. | ||
| */ | ||
| text: string; | ||
| /** | ||
| * Callback to render a citation token. The library passes both the | ||
| * citation token (slug / id) and the model's exact matched text so the | ||
| * host can render `<a href={`/<type>/${slug}`}>{matchedText}</a>` — | ||
| * the model's casing wins. | ||
| * | ||
| * When omitted, the library renders `matchedText` verbatim — useful | ||
| * for previews; consumers should always provide this in production | ||
| * to wire up the click target. | ||
| */ | ||
| renderCitation?: (token: string, matchedText: string) => React$1.ReactNode; | ||
| /** Optional className applied to the wrapping element. Lets the host theme. */ | ||
| className?: string; | ||
| } | ||
| /** | ||
| * Render an assistant chat message. Lexes `text` with marked's inline | ||
| * lexer (citation extension registered), then walks the resulting token | ||
| * tree, converting each marked token to a React node. Citation tokens | ||
| * call `renderCitation`; raw HTML is escaped (no live elements ever | ||
| * reach the rendered output). | ||
| * | ||
| * Library responsibility: parse + sanitize + emit React nodes. | ||
| * Host responsibility: CSS styling + citation-link wiring. | ||
| */ | ||
| declare function ChatText(props: ChatTextProps): React$1.ReactElement; | ||
| /** | ||
| * Outcome the React layer fires after a tool-call event has been | ||
| * dispatched through the SceneController. Lets host UIs render | ||
| * "N applied, M unknown" badges alongside chat bubbles. Phase 1 (0.8.0). | ||
| */ | ||
| interface ToolCallOutcome { | ||
| /** Tool name (`highlight` / `apply_filter` / `focus` / `annotate`). */ | ||
| tool: string; | ||
| /** Ids the renderer accepted (i.e. resolved against the current store). */ | ||
| appliedIds?: string[]; | ||
| /** Ids the renderer rejected because the store doesn't know them. */ | ||
| unknownIds?: string[]; | ||
| } | ||
| /** | ||
| * Internal: the React context surface that ties {@link useInferaGraphChat} | ||
| * to the live transport + renderer dispatch installed by `<InferaGraph>`. | ||
| * | ||
| * Exposed via getters so the hook always reads the current values | ||
| * without requiring a re-render of the consumer when the transport | ||
| * swaps. | ||
| */ | ||
| interface InferaGraphChatContext { | ||
| /** Active chat transport. `null` when no `llm`/`transport` prop is set. */ | ||
| getTransport: () => { | ||
| chat: (message: string, opts?: ChatOptions) => AsyncIterable<ChatEvent>; | ||
| } | null; | ||
| /** | ||
| * Dispatch a tool-call event into the SceneController. Implementation | ||
| * lives in `<InferaGraph>` because that's where the controller ref | ||
| * is held. | ||
| * | ||
| * May return a {@link ToolCallOutcome} when the controller can report | ||
| * applied / unknown ids for the dispatched event (today: `highlight` | ||
| * and `focus`). Returning `undefined` signals the hook should fall | ||
| * back to its own `computeToolCallOutcome`. | ||
| */ | ||
| dispatch: (event: ChatEvent) => ToolCallOutcome | undefined; | ||
| /** | ||
| * **Optional** — fire when the engine emits a `debug` ChatEvent. | ||
| * Hosts use this to render diagnostic badges underneath assistant | ||
| * bubbles. Phase 1 (0.8.0). | ||
| */ | ||
| onDiagnostic?: (event: Extract<ChatEvent, { | ||
| type: 'debug'; | ||
| }>) => void; | ||
| /** | ||
| * **Optional** — fire after a tool-call dispatch so hosts can show | ||
| * "applied / unknown" outcome badges. Phase 1 (0.8.0). | ||
| */ | ||
| onToolCallOutcome?: (outcome: ToolCallOutcome) => void; | ||
| } | ||
| export { ChatText as C, DEFAULT_EDGE_COLOR as D, type EdgeColorFn as E, type GraphContextValue as G, InferaGraph as I, type NodeColorFn as N, type SlugResolver as S, type ToolCallOutcome as T, type UseInferaGraphContentReturn as U, type NodeColorResolverOptions as a, NodeColorResolver as b, EdgeColorMap as c, type ChatTextProps as d, DEFAULT_NODE_COLOR as e, type EdgeColorContext as f, type EdgeColorMapOptions as g, GraphProvider as h, type GraphProviderProps as i, type InferaGraphChatContext as j, type InferaGraphChatContextHook as k, type InferaGraphChatHook as l, type InferaGraphCommands as m, type InferaGraphProps as n, type InferaGraphSearchHook as o, type NeighborStatus as p, type UseInferaGraphNeighborsReturn as q, createReactNodeRenderFn as r, createReactTooltipRenderFn as s, useInferaGraphChat as t, useInferaGraph as u, useInferaGraphChatContext as v, useInferaGraphCommands as w, useInferaGraphContent as x, useInferaGraphNeighbors as y, useInferaGraphSearch as z }; |
| import * as React$1 from 'react'; | ||
| import React__default, { ReactNode } from 'react'; | ||
| import { G as GraphStore, aa as QueryEngine, A as AIEngine, s as DataManager, a2 as MemoryManager, N as NodeId, M as GraphData, D as DataAdapter, q as DataAdapterConfig, d as NodeData, v as EdgeData, L as LayoutMode, a as NodeRenderConfig, T as TooltipConfig, E as EdgeLabelMap, X as LLMProvider, i as CacheProvider, F as EmbeddingStore, al as Transport, j as ChatEvent, a6 as PaginatedResult, n as ContentData, K as FilterSpec, ab as SearchResult, a5 as NodeComponentProps, c as NodeRenderFn, ai as TooltipComponentProps, ak as TooltipRenderFn, k as ChatOptions } from './aggregateEdges-CQVCx-mz.js'; | ||
| /** | ||
| * Slug → node-id resolver. Hosts that route detail pages by human-readable | ||
| * slug (e.g. `/person/adam`) supply this so the library can translate the | ||
| * slug into the canonical node id used by the graph store + adapter. | ||
| * | ||
| * The sync escape hatch (`NodeId` instead of `Promise<NodeId>`) is | ||
| * deliberate: biblegraph's slug→UUID v5 helper is purely deterministic and | ||
| * doesn't need a microtask. Hooks always `await` the return so async | ||
| * resolvers (database lookups, manifest fetches) work too. | ||
| */ | ||
| type SlugResolver = (slug: string) => NodeId | Promise<NodeId>; | ||
| interface GraphContextValue { | ||
| store: GraphStore; | ||
| queryEngine: QueryEngine; | ||
| aiEngine: AIEngine; | ||
| dataManager: DataManager | null; | ||
| /** | ||
| * LRU eviction coordinator. Always present, even when no `maxNodes` cap | ||
| * is configured — `enforceCap` becomes a no-op in that case. | ||
| */ | ||
| memoryManager: MemoryManager; | ||
| /** | ||
| * Active {@link SlugResolver}, or `undefined` when the host opted out. | ||
| * Hooks check this before resolving — when undefined, they treat the | ||
| * input as a raw NodeId (no slug→id translation). | ||
| */ | ||
| slugResolver: SlugResolver | undefined; | ||
| /** | ||
| * Library-internal slug→nodeId cache. Slugs are immutable, so there's no | ||
| * TTL — entries live for the GraphProvider's lifetime. Exposed on the | ||
| * context value (rather than encapsulated) so the two hooks | ||
| * (`useInferaGraphContent` + `useInferaGraphNeighbors`) share one cache. | ||
| */ | ||
| slugCache: Map<string, NodeId>; | ||
| } | ||
| interface GraphProviderProps { | ||
| children: ReactNode; | ||
| /** Static data (existing behavior — wraps in StaticDataAdapter) */ | ||
| data?: GraphData; | ||
| /** DataAdapter for dynamic data fetching */ | ||
| adapter?: DataAdapter; | ||
| /** Config passed to adapter.getInitialView() */ | ||
| initialViewConfig?: DataAdapterConfig; | ||
| /** Slug → nodeId resolver. See {@link SlugResolver}. */ | ||
| slugResolver?: SlugResolver; | ||
| /** | ||
| * Soft cap on the number of nodes retained in the {@link GraphStore}. | ||
| * When the count exceeds the cap the {@link MemoryManager} evicts the | ||
| * oldest non-protected entries. `undefined` (the default) disables the | ||
| * cap so memory grows with the dataset. | ||
| */ | ||
| maxNodes?: number; | ||
| /** Called when initial data is loaded */ | ||
| onReady?: () => void; | ||
| } | ||
| declare function GraphProvider({ children, data, adapter, initialViewConfig, slugResolver, maxNodes, onReady, }: GraphProviderProps): React__default.JSX.Element; | ||
| /** | ||
| * Fallback color used only when the configured palette is empty. The CSS | ||
| * variable `--ig-node-color` should override this in themed apps; this | ||
| * constant is the absolute floor. | ||
| */ | ||
| declare const DEFAULT_NODE_COLOR = "#3b82f6"; | ||
| /** Function form: read the node, return a CSS hex/rgb color. */ | ||
| type NodeColorFn = (node: NodeData) => string | undefined; | ||
| interface NodeColorResolverOptions { | ||
| /** Override resolver — wins over both the type-keyed map and the palette. */ | ||
| colorFn?: NodeColorFn; | ||
| /** Explicit type → color map. Wins over auto-assignment. */ | ||
| nodeColors?: Record<string, string>; | ||
| /** | ||
| * Pool of colors used to deterministically assign a color to types that | ||
| * are not present in `nodeColors`. Defaults to {@link DEFAULT_PALETTE_32}. | ||
| */ | ||
| palette?: readonly string[]; | ||
| /** Default color used when palette is empty AND nothing else matches. */ | ||
| defaultColor?: string; | ||
| /** | ||
| * Multiplier (0..1) used by {@link NodeColorResolver.resolveHover} to lift | ||
| * the resolved color toward white for hover state. Defaults to 0.25. | ||
| */ | ||
| hoverBrightness?: number; | ||
| } | ||
| /** | ||
| * Resolves resting + hover colors for a node based on its attributes. | ||
| * | ||
| * Resolution order (resting): | ||
| * 1. `colorFn(node)` if it returns a non-undefined string | ||
| * 2. `nodeColors[node.attributes.type]` if it's a string lookup | ||
| * 3. `node.attributes.color` if the consumer set one explicitly | ||
| * 4. `palette[hashStringToIndex(type, palette.length)]` (deterministic auto) | ||
| * 5. `defaultColor` (only reached when palette is empty) | ||
| * | ||
| * Hover: same color, brightened by `hoverBrightness` (default 25 % toward white). | ||
| * | ||
| * Domain knowledge note: this resolver ships ZERO domain-specific defaults | ||
| * — the consumer (e.g. Bible Graph) supplies its own `nodeColors` map. | ||
| */ | ||
| declare class NodeColorResolver { | ||
| private readonly colorFn?; | ||
| private readonly nodeColors; | ||
| private readonly palette; | ||
| private readonly defaultColor; | ||
| private readonly hoverBrightness; | ||
| constructor(options?: NodeColorResolverOptions); | ||
| /** Resting color for `node`. */ | ||
| resolve(node: NodeData): string; | ||
| /** Hover color for `node` — resting color brightened toward white. */ | ||
| resolveHover(node: NodeData): string; | ||
| /** The active palette (for tests + introspection). */ | ||
| getPalette(): readonly string[]; | ||
| } | ||
| /** Fallback edge color when the palette is empty AND no override matches. */ | ||
| declare const DEFAULT_EDGE_COLOR = "#6366f1"; | ||
| /** | ||
| * Per-call context handed to {@link EdgeColorFn}. Carries the resolved | ||
| * resting colors of the edge's source + target nodes (the same hex values | ||
| * {@link NodeColorResolver.resolve} would return for those nodes) so the | ||
| * function can derive a color from its endpoints — for example by | ||
| * blending them, picking one side, or treating same-vs-different as a | ||
| * boolean. | ||
| * | ||
| * Both fields are guaranteed to be valid CSS color strings; callers fall | ||
| * back to {@link DEFAULT_EDGE_COLOR} (or the consumer's chosen | ||
| * fallback) for either side that cannot be resolved against the live | ||
| * graph store. | ||
| */ | ||
| interface EdgeColorContext { | ||
| /** Resolved resting color of the source node. */ | ||
| sourceColor: string; | ||
| /** Resolved resting color of the target node. */ | ||
| targetColor: string; | ||
| } | ||
| /** | ||
| * Function form: read the edge + its endpoint colors, return a CSS | ||
| * hex/rgb color (or `undefined` to fall through to the type-keyed map / | ||
| * palette). | ||
| * | ||
| * The second argument is optional at call sites that pre-date the context | ||
| * — older `(edge) => string` consumers continue to work because they | ||
| * simply ignore the extra parameter. | ||
| */ | ||
| type EdgeColorFn = (edge: EdgeData, ctx: EdgeColorContext) => string | undefined; | ||
| interface EdgeColorMapOptions { | ||
| /** Override resolver — wins over both the type-keyed map and the palette. */ | ||
| colorFn?: EdgeColorFn; | ||
| /** Explicit type → color map. Wins over auto-assignment. */ | ||
| edgeColors?: Record<string, string>; | ||
| /** | ||
| * Pool of colors used to deterministically assign a color to relationship | ||
| * types not present in `edgeColors`. Defaults to {@link DEFAULT_PALETTE_32}. | ||
| */ | ||
| palette?: readonly string[]; | ||
| /** Default color used when palette is empty AND nothing else matches. */ | ||
| defaultColor?: string; | ||
| /** | ||
| * Multiplier (0..1) used by {@link EdgeColorMap.resolveHover} to lift the | ||
| * resolved color toward white for hover state. Defaults to 0.25. | ||
| */ | ||
| hoverBrightness?: number; | ||
| } | ||
| /** | ||
| * Resolves resting + hover colors for an edge based on its `attributes.type` | ||
| * (the relationship type, e.g. `father_of`). | ||
| * | ||
| * Resolution order (resting): | ||
| * 1. `colorFn(edge, ctx)` if it returns a non-undefined string | ||
| * 2. `edgeColors[edge.attributes.type]` | ||
| * 3. `palette[hashStringToIndex(type, palette.length)]` (deterministic auto) | ||
| * 4. `defaultColor` (only reached when palette is empty) | ||
| * | ||
| * Hover: same color, brightened by `hoverBrightness` (default 25 % toward white). | ||
| * | ||
| * Domain knowledge note: this map ships ZERO domain-specific defaults — | ||
| * `father_of`, `married_to`, etc. mean nothing to InferaGraph. The consumer | ||
| * (e.g. Bible Graph) supplies its own `edgeColors` map. | ||
| */ | ||
| declare class EdgeColorMap { | ||
| private readonly colorFn?; | ||
| private readonly edgeColors; | ||
| private readonly palette; | ||
| private readonly defaultColor; | ||
| private readonly hoverBrightness; | ||
| constructor(options?: EdgeColorMapOptions); | ||
| /** | ||
| * Resting color for `edge`. | ||
| * | ||
| * `ctx` carries the resolved endpoint colors so a {@link EdgeColorFn} | ||
| * can derive the edge color from its endpoints (see | ||
| * {@link blendEdgeColors}). The argument is defaulted to the fallback | ||
| * color on both sides so legacy call sites that don't yet plumb | ||
| * endpoint colors through still produce a sensible result. | ||
| */ | ||
| resolve(edge: EdgeData, ctx?: EdgeColorContext): string; | ||
| /** Hover color for `edge` — resting color brightened toward white. */ | ||
| resolveHover(edge: EdgeData, ctx?: EdgeColorContext): string; | ||
| /** The active palette (for tests + introspection). */ | ||
| getPalette(): readonly string[]; | ||
| } | ||
| interface InferaGraphProps { | ||
| data?: GraphData; | ||
| layout?: LayoutMode; | ||
| nodeRender?: NodeRenderConfig; | ||
| tooltip?: TooltipConfig; | ||
| /** Pool of colors for deterministic auto-assignment. */ | ||
| palette?: readonly string[]; | ||
| /** Explicit type → color map for nodes. */ | ||
| nodeColors?: Record<string, string>; | ||
| /** Function override for nodes. */ | ||
| nodeColorFn?: NodeColorFn; | ||
| /** Explicit relationship-type → color map for edges. */ | ||
| edgeColors?: Record<string, string>; | ||
| /** Function override for edges. */ | ||
| edgeColorFn?: EdgeColorFn; | ||
| /** | ||
| * Incoming-edge label map for the default tooltip's natural-language | ||
| * description (e.g. `{ father_of: 'Son of', mother_of: 'Son of' }`). | ||
| * Ignored when `tooltip.renderTooltip` / `tooltip.component` is supplied. | ||
| */ | ||
| incomingEdgeLabels?: EdgeLabelMap; | ||
| /** | ||
| * Outgoing-edge label map for the default tooltip's natural-language | ||
| * description (e.g. `{ father_of: 'Father of' }`). | ||
| */ | ||
| outgoingEdgeLabels?: EdgeLabelMap; | ||
| /** | ||
| * Domain-agnostic visibility predicate. When supplied, only nodes for | ||
| * which the predicate returns `true` are rendered; edges whose source | ||
| * OR target node is filtered out are hidden too. | ||
| * | ||
| * The same predicate applies in **every** visualization mode — graph, | ||
| * tree, and any future mode (geospatial / timeline / chord / etc.). | ||
| * Filter changes are applied as in-place visibility toggles on the | ||
| * existing GPU buffers — there's no mesh teardown, no rebuild, and | ||
| * no layout recompute. Hidden nodes keep their layout positions, so | ||
| * unhiding restores the prior frame instantly. | ||
| * | ||
| * Default: no filter (every node visible). | ||
| */ | ||
| filter?: (node: NodeData) => boolean; | ||
| /** | ||
| * Tree-mode option: edge types that the hierarchical tidy-tree layout | ||
| * treats as parent -> child. Default `['parent_of']` — set to your | ||
| * domain's vocabulary (`['manages']` for org charts, `['supplies']` | ||
| * for supply chains, `['is_a']` for taxonomies, `['cites']` for | ||
| * citation graphs, etc.). Has no effect in graph mode. | ||
| */ | ||
| parentEdgeTypes?: string[]; | ||
| /** | ||
| * Tree-mode option: edge types that pair two nodes at the same depth | ||
| * so they appear adjacent and share children. Default `[]` (no | ||
| * pairing). Has no effect in graph mode. | ||
| */ | ||
| pairedEdgeTypes?: string[]; | ||
| /** | ||
| * LLM provider for AI features (NLQ filtering today; chat / search / highlight | ||
| * land in later phases). The host imports a provider package and passes a | ||
| * configured INSTANCE here. The host never invokes the LLM directly — | ||
| * InferaGraph owns the entire LLM lifecycle. | ||
| * | ||
| * Omitted = AI features are unavailable; the explicit `filter` predicate | ||
| * still works. | ||
| */ | ||
| llm?: LLMProvider; | ||
| /** | ||
| * Cache provider for LLM responses. Defaults to NO CACHING when omitted, so | ||
| * tests + small demos don't accidentally retain old responses. Pass | ||
| * `lruCache()` (built-in) or `@inferagraph/redis-cache-provider` for | ||
| * production. The cache is wiped automatically when the LLM provider | ||
| * instance changes. | ||
| * | ||
| * Also acts as the **Tier 2** embedding storage when `embeddingStore` is | ||
| * not supplied AND the configured `llm` provider implements `embed()` — | ||
| * embeddings are persisted in the cache and similarity is computed | ||
| * in-memory at query time. | ||
| */ | ||
| cache?: CacheProvider; | ||
| /** | ||
| * Optional dedicated {@link EmbeddingStore} (Tier 3 of the embedding | ||
| * progression). When supplied, AIEngine uses the store's vector-native | ||
| * `similar()` for semantic search. The default in-process implementation | ||
| * ships as `inMemoryEmbeddingStore()`; persistent stores live in their | ||
| * own packages. | ||
| * | ||
| * Tier mapping at runtime: | ||
| * - omit both → keyword search only. | ||
| * - `cache` only + provider with `embed()` → Tier 2. | ||
| * - `embeddingStore` + provider with `embed()` → Tier 3. | ||
| */ | ||
| embeddingStore?: EmbeddingStore; | ||
| /** | ||
| * Natural-language query that the LLM compiles into a filter predicate. | ||
| * Combined with the explicit `filter` prop via AND: the developer-set | ||
| * `filter` runs first (proactive scope), then `query` narrows within that | ||
| * scope. Requires `llm`. An empty / undefined `query` is a no-op. | ||
| */ | ||
| query?: string; | ||
| /** | ||
| * Optional explicit chat {@link Transport}. Wins over the implicit | ||
| * "build an in-process transport from `llm`" path. Use this to wire | ||
| * an HTTP transport (Next.js `/api/chat` proxy) so the LLM + cache | ||
| * stay server-side. | ||
| */ | ||
| transport?: Transport; | ||
| /** | ||
| * Toggle visibility of the Phase 5 inferred-edge overlay (dashed | ||
| * lines between nodes the AI inference pipeline thinks are related). | ||
| * Defaults to `false` (overlay hidden) so the explicit graph reads | ||
| * cleanly out of the box. Hosts opt in either by setting this to | ||
| * `true` or by handing the LLM a `set_inferred_visibility` chat | ||
| * tool call. | ||
| * | ||
| * The actual inferred edges are computed by | ||
| * `aiEngine.computeInferredEdges()` and pushed to the renderer | ||
| * separately — toggling this prop only shows/hides whatever has | ||
| * already been computed. | ||
| */ | ||
| showInferredEdges?: boolean; | ||
| /** | ||
| * Host-facing chat callback. Called with `text` and `done` events | ||
| * only — tool calls (`apply_filter` / `highlight` / `focus` / | ||
| * `annotate`) are dispatched silently to the renderer. | ||
| * | ||
| * The host writes its own chat UI on top of this callback (input | ||
| * box, message log, conversation pane). InferaGraph owns the | ||
| * streaming / tool-routing logistics. | ||
| */ | ||
| onChat?: (event: ChatEvent) => void; | ||
| /** | ||
| * Phase 1 (RAG architecture, 0.8.0) — fired when the engine emits a | ||
| * `debug` ChatEvent (vector-search, rerank, retrieval-empty, etc.). | ||
| * Hosts use this to render diagnostic badges underneath assistant | ||
| * bubbles for ops visibility. Debug events are NEVER yielded to the | ||
| * iterator — only this callback receives them. | ||
| */ | ||
| onDiagnostic?: (event: Extract<ChatEvent, { | ||
| type: 'debug'; | ||
| }>) => void; | ||
| /** | ||
| * Phase 1 (RAG architecture, 0.8.0) — fired after a tool-call event | ||
| * has been dispatched to the renderer. Lets host UIs surface | ||
| * "N applied, M unknown" badges. The library reports the requested | ||
| * ids as `appliedIds`; future enhancements will reconcile against | ||
| * the store's known-ids set to populate `unknownIds`. | ||
| */ | ||
| onToolCallOutcome?: (outcome: { | ||
| tool: string; | ||
| appliedIds?: string[]; | ||
| unknownIds?: string[]; | ||
| }) => void; | ||
| /** | ||
| * Phase 6 — slug → nodeId resolver. When supplied, hooks like | ||
| * {@link useInferaGraphContent} translate the input through this | ||
| * resolver before calling into the {@link DataAdapter}. Hosts that | ||
| * route by raw UUIDs can omit it. | ||
| */ | ||
| slugResolver?: SlugResolver; | ||
| /** | ||
| * Phase 6 — soft cap on the number of nodes retained in the store. | ||
| * When exceeded, the {@link MemoryManager} evicts the oldest | ||
| * non-protected nodes. `undefined` (default) disables the cap. | ||
| */ | ||
| maxNodes?: number; | ||
| /** | ||
| * Phase 6 — fired when the user clicks a node body. The host typically | ||
| * opens a detail dialog. Receives the canonical NodeId (post-slug | ||
| * resolution) and the node's data snapshot. | ||
| * | ||
| * NOT fired for clicks on the "+" affordance — those go through | ||
| * {@link onExpandRequest} instead. | ||
| */ | ||
| onNodeClick?: (nodeId: NodeId, node: NodeData) => void; | ||
| /** | ||
| * Phase 6 — fired when the user clicks the "+" hover affordance on a | ||
| * node. Receives the canonical NodeId. The host typically calls | ||
| * `useInferaGraphNeighbors().expand(nodeId)`. When omitted, the | ||
| * library installs a default handler that fires the built-in | ||
| * `expand(nodeId)` dispatch. | ||
| */ | ||
| onExpandRequest?: (nodeId: NodeId) => void; | ||
| className?: string; | ||
| style?: React__default.CSSProperties; | ||
| /** | ||
| * Optional children rendered inside the InferaGraph subtree. Useful | ||
| * for hosts that want to embed components that consume | ||
| * {@link useInferaGraphChat} or other hooks that depend on the | ||
| * InferaGraph context. Children render OUTSIDE the WebGL canvas | ||
| * (after the `.ig-container` div) — they're for host-owned chrome / | ||
| * chat UI, not for in-canvas rendering. | ||
| */ | ||
| children?: React__default.ReactNode; | ||
| } | ||
| declare function InferaGraph(props: InferaGraphProps): React__default.JSX.Element; | ||
| interface UseInferaGraphReturn { | ||
| loadData: (data: GraphData) => void; | ||
| nodeCount: number; | ||
| edgeCount: number; | ||
| expandNode: (nodeId: NodeId, depth?: number) => Promise<void>; | ||
| findPath: (fromId: NodeId, toId: NodeId) => Promise<NodeData[]>; | ||
| search: (query: string) => Promise<PaginatedResult<NodeData>>; | ||
| getContent: (nodeId: NodeId) => Promise<ContentData | undefined>; | ||
| isReady: boolean; | ||
| } | ||
| declare function useInferaGraph(): UseInferaGraphReturn; | ||
| /** | ||
| * Public return shape of {@link useInferaGraphChat}. The hook returns a | ||
| * single function that streams a chat with the configured transport. | ||
| * | ||
| * The returned `AsyncIterable` yields ONLY `text` and `done` events — | ||
| * tool calls (`apply_filter`, `highlight`, `focus`, `annotate`) are | ||
| * dispatched silently to the renderer, exactly mirroring the contract | ||
| * of `<InferaGraph onChat>`. | ||
| */ | ||
| interface InferaGraphChatHook { | ||
| /** | ||
| * Send `message` through the active chat transport. Returns an async | ||
| * iterable that yields {@link ChatEvent}s — text + done only by | ||
| * default. Tool-call events are dispatched to the renderer's | ||
| * `setHighlight` / `setFilter` / `focusOn` / `annotate` and not | ||
| * surfaced to the iterator. | ||
| * | ||
| * Pass `signal` to cancel mid-stream. | ||
| * | ||
| * Throws synchronously if no transport (or no `llm` prop) is | ||
| * configured on the host `<InferaGraph>` element. | ||
| */ | ||
| chat: (message: string, opts?: { | ||
| signal?: AbortSignal; | ||
| /** | ||
| * Optional conversation id, forwarded to the active transport. When | ||
| * the HTTP transport is in use the id flows into the request body | ||
| * so the server route can thread it into its `ConversationStore`; | ||
| * with the in-process transport the id is forwarded directly to | ||
| * `AIEngine.chat`. Omit to opt out of conversation memory for this | ||
| * call. | ||
| */ | ||
| conversationId?: string; | ||
| }) => AsyncIterable<ChatEvent>; | ||
| } | ||
| /** | ||
| * React hook that surfaces InferaGraph's chat API to the host. | ||
| * | ||
| * Internally: | ||
| * 1. The hook resolves the active transport via React context | ||
| * (`<InferaGraph>` populates this). | ||
| * 2. When `chat(message)` is called, the hook iterates the transport's | ||
| * full event stream: | ||
| * - tool-call events are dispatched to the SceneController's | ||
| * highlight / focus / annotate / filter sinks. | ||
| * - text + done events are re-yielded to the host. | ||
| * 3. The host iterates the returned async iterable to render text | ||
| * bubbles. The renderer never appears in the host's iteration. | ||
| * | ||
| * MUST be called inside an `<InferaGraph>` subtree. | ||
| */ | ||
| declare function useInferaGraphChat(): InferaGraphChatHook; | ||
| /** | ||
| * Public return shape of {@link useInferaGraphChatContext}. The hook | ||
| * surfaces the renderer's dispatch sink so a host can fire ad-hoc | ||
| * {@link ChatEvent}s without going through `chat()`. | ||
| * | ||
| * The same dispatch path receives chat-driven tool-call events under | ||
| * the hood — calling `dispatch(...)` here goes through the exact same | ||
| * SceneController surface, which means highlights, filters, focus | ||
| * moves, annotations, and the new visual-reset commands all behave | ||
| * identically whether the model emits them or the host fires them. | ||
| */ | ||
| interface InferaGraphChatContextHook { | ||
| /** | ||
| * Dispatch a {@link ChatEvent} into the renderer. Mostly useful for | ||
| * host UI affordances ("Clear conversation" → reset highlight, | ||
| * "Camera home" button → `reset_view`, etc.). | ||
| * | ||
| * Prefer {@link InferaGraphCommands} (`useInferaGraphCommands`) for | ||
| * the typical cases — it's a thin semantic facade on top of this | ||
| * sink. Reach for this hook when you need to dispatch an event | ||
| * variant the facade doesn't surface (e.g. emitting a `debug` | ||
| * ChatEvent for ops visibility, or a synthetic `text` event for | ||
| * a UI-only "Welcome" bubble). | ||
| */ | ||
| dispatch: (event: ChatEvent) => void; | ||
| } | ||
| /** | ||
| * 0.10.0 — public escape-hatch hook returning the renderer's dispatch | ||
| * function. Most hosts should reach for {@link useInferaGraphCommands} | ||
| * instead; this hook exists for host code that needs to dispatch a | ||
| * {@link ChatEvent} variant the semantic facade doesn't expose. | ||
| * | ||
| * MUST be called inside an `<InferaGraph>` subtree. | ||
| */ | ||
| declare function useInferaGraphChatContext(): InferaGraphChatContextHook; | ||
| /** | ||
| * 0.10.0 — semantic facade for host-initiated graph commands. Most hosts | ||
| * use this hook instead of the lower-level | ||
| * {@link useInferaGraphChatContext}. | ||
| * | ||
| * Each method dispatches the equivalent {@link ChatEvent} to the | ||
| * renderer's dispatch sink — exactly the same path the library uses for | ||
| * chat-driven (model-emitted) events. There's no separate "host API" | ||
| * that diverges from the chat contract. | ||
| */ | ||
| interface InferaGraphCommands { | ||
| /** | ||
| * Replace the active highlight set. Pass an empty Set to clear (every | ||
| * node returns to full opacity). | ||
| */ | ||
| setHighlight(ids: ReadonlySet<string>): void; | ||
| /** | ||
| * Move the camera to the supplied node. The library does not have | ||
| * an "unfocus" affordance — hosts that want to recenter should call | ||
| * {@link resetView} instead. | ||
| */ | ||
| focusOn(nodeId: string): void; | ||
| /** | ||
| * Apply a domain-agnostic filter spec to the visible graph. The spec | ||
| * shape mirrors the LLM's `apply_filter` tool-call output: keys are | ||
| * node attribute names, values are arrays of allowed string values. | ||
| * Pass an empty object (`{}`) to clear the filter. | ||
| */ | ||
| applyFilter(spec: FilterSpec): void; | ||
| /** Toggle the inferred-edge overlay. */ | ||
| setInferredVisibility(visible: boolean): void; | ||
| /** Attach a callout to a node. */ | ||
| annotate(nodeId: string, text: string): void; | ||
| /** Drop every annotation currently mounted. */ | ||
| clearAnnotations(): void; | ||
| /** Snap the camera back to its captured initial orientation. */ | ||
| resetView(): void; | ||
| /** | ||
| * Comprehensive reset — highlights + annotations + filter + camera. | ||
| * The "fresh canvas" command. Hosts wire this to "Clear | ||
| * conversation" / "New session" UX. | ||
| */ | ||
| clearVisualState(): void; | ||
| } | ||
| /** | ||
| * 0.10.0 — public, semantic hook for dispatching host-initiated graph | ||
| * commands. Built on top of {@link useInferaGraphChatContext}, but with | ||
| * a flat, named surface so callers don't have to construct | ||
| * {@link ChatEvent} objects by hand. | ||
| * | ||
| * The returned object is memoized for the lifetime of the hook so | ||
| * downstream `useEffect` / `useMemo` deps remain stable across renders. | ||
| * | ||
| * MUST be called inside an `<InferaGraph>` subtree. | ||
| */ | ||
| declare function useInferaGraphCommands(): InferaGraphCommands; | ||
| /** | ||
| * Public return shape of {@link useInferaGraphSearch}. The hook returns a | ||
| * single `search(query)` function that delegates to the AIEngine's | ||
| * auto-detect router (keyword vs semantic). | ||
| * | ||
| * Pair with `<InferaGraph>` props: | ||
| * - `llm` — required for semantic routing. | ||
| * - `cache` — opts the engine into Tier 2 (cache-as-vector-store). | ||
| * - `embeddingStore` — opts the engine into Tier 3 (vector-native store). | ||
| * Without `llm`, every query falls back to the data-layer keyword search. | ||
| */ | ||
| interface InferaGraphSearchHook { | ||
| /** | ||
| * Run an auto-routed search. Short token-only queries hit the data-layer | ||
| * keyword index; sentence-shaped or NLQ inputs go through embeddings. | ||
| * | ||
| * Returns at most `opts.k ?? 25` hits, sorted by descending score. | ||
| */ | ||
| search: (query: string, opts?: { | ||
| k?: number; | ||
| signal?: AbortSignal; | ||
| }) => Promise<SearchResult[]>; | ||
| } | ||
| /** | ||
| * React hook surfacing InferaGraph's Phase 4 search API. | ||
| * | ||
| * Internally: | ||
| * 1. Pulls the {@link AIEngine} out of the GraphContext (populated by | ||
| * `<InferaGraph>` / `<GraphProvider>`). | ||
| * 2. Wraps `engine.search` in a stable `useCallback` so consumers can | ||
| * memoize / debounce without identity churn. | ||
| * 3. Holds the engine behind a ref so prop changes on the host don't | ||
| * invalidate the callback. | ||
| * | ||
| * MUST be called inside an `<InferaGraph>` (or standalone `<GraphProvider>`) | ||
| * subtree. | ||
| */ | ||
| declare function useInferaGraphSearch(): InferaGraphSearchHook; | ||
| /** | ||
| * Return shape of {@link useInferaGraphContent}. | ||
| * | ||
| * Mirrors the host-friendly fetch-state quartet (data + loading + error + | ||
| * refetch). All four fields are always populated — `loading` is `false` | ||
| * when `idOrSlug` is `undefined`, so hosts don't need conditional rendering | ||
| * gymnastics. | ||
| */ | ||
| interface UseInferaGraphContentReturn { | ||
| /** Fetched content, or `undefined` while loading / on error / for invalid input. */ | ||
| data: ContentData | undefined; | ||
| /** `true` while a fetch is in flight (or queued for the current id). */ | ||
| loading: boolean; | ||
| /** Last error encountered, or `undefined` on success / before first fetch. */ | ||
| error: Error | undefined; | ||
| /** Imperatively re-run the fetch for the current id. No-op when id is undefined. */ | ||
| refetch: () => void; | ||
| } | ||
| /** | ||
| * Tier-1 cache for getContent results. Keyed by canonical NodeId (post slug | ||
| * resolution). Populated on every successful fetch; consulted before issuing | ||
| * a network call so re-mounted DetailModals don't re-fetch. | ||
| * | ||
| * Lives at module scope intentionally: the GraphProvider context value is | ||
| * recreated on adapter swap, but that swap also wipes the store and would | ||
| * make any cached content stale. We mirror that lifecycle by clearing this | ||
| * Map whenever the dataManager identity flips (see effect below). | ||
| */ | ||
| /** | ||
| * React hook that fetches detail content for one node. | ||
| * | ||
| * Workflow: | ||
| * 1. If `idOrSlug` is `undefined`, return a disabled-state object — no | ||
| * fetch fires, no spinner. | ||
| * 2. Pass the input through the configured {@link SlugResolver}. When the | ||
| * host opted out (no resolver), the input is treated as a raw NodeId. | ||
| * 3. Hit the per-DataManager cache. On hit, return immediately; on miss, | ||
| * call `dataManager.getContent(uuid)` and cache the result. | ||
| * 4. Touch the {@link MemoryManager} so the node moves to the MRU end | ||
| * and survives any subsequent cap-enforcement pass. | ||
| * 5. Cancel via the standard "captured-token" pattern when the input | ||
| * changes mid-flight; `setData` only fires for the latest call. | ||
| * | ||
| * MUST be called inside an `<InferaGraph>` (or standalone `<GraphProvider>`) | ||
| * subtree. | ||
| */ | ||
| declare function useInferaGraphContent(idOrSlug: string | undefined): UseInferaGraphContentReturn; | ||
| /** Lifecycle status of a single expand call, tracked per-nodeId. */ | ||
| type NeighborStatus = 'loading' | 'loaded' | 'error'; | ||
| /** | ||
| * Return shape of {@link useInferaGraphNeighbors}. | ||
| * | ||
| * Imperative hook (not a fetch-on-mount hook): the host calls `expand` / | ||
| * `collapse` in response to user actions (click on the "+" affordance, | ||
| * keyboard shortcut, chat tool call). Reading `expanded` is enough for the | ||
| * host to know which nodes are currently in flight or already drilled-down. | ||
| */ | ||
| interface UseInferaGraphNeighborsReturn { | ||
| /** | ||
| * Fetch direct neighbors of `nodeId` (depth defaults to 1). Resolves slug | ||
| * via the configured resolver, calls `dataManager.expandNode`, syncs the | ||
| * scene from the freshly-merged store, and enforces the LRU cap with the | ||
| * just-expanded set marked as protected (so eviction never targets the | ||
| * neighbors the user just asked to see). | ||
| */ | ||
| expand: (nodeIdOrSlug: string, depth?: number) => Promise<void>; | ||
| /** | ||
| * Remove neighbors of `nodeId` that aren't in the protected initial-view | ||
| * set. Inverse of `expand`. Implementations of "protected" are | ||
| * intentionally simple v1: any node currently visible due to another | ||
| * expand still survives because its LRU timestamp is fresher than the | ||
| * collapsing nodes. | ||
| */ | ||
| collapse: (nodeIdOrSlug: string) => Promise<void>; | ||
| /** Per-node lifecycle map. Keys are CANONICAL NodeIds (post-resolution). */ | ||
| expanded: ReadonlyMap<NodeId, NeighborStatus>; | ||
| } | ||
| /** | ||
| * React hook that exposes drilldown / collapse imperatives. | ||
| * | ||
| * Slug-aware: callers may pass either a UUID or a slug. The configured | ||
| * {@link SlugResolver} translates slugs to canonical NodeIds; the resolved | ||
| * id is what survives in `expanded` so the host can reason about identity | ||
| * uniformly. | ||
| * | ||
| * Race-safe: each call captures its own resolved nodeId before awaiting, | ||
| * and the returned promise reflects the outcome of THIS call. If a faster | ||
| * second `expand(sameNode)` overlaps a slow first one, both promises | ||
| * resolve correctly because the underlying `dataManager.expandNode` merges | ||
| * by id (later merges are no-ops for nodes already present). | ||
| * | ||
| * MUST be called inside an `<InferaGraph>` (or standalone `<GraphProvider>`) | ||
| * subtree. | ||
| */ | ||
| declare function useInferaGraphNeighbors(): UseInferaGraphNeighborsReturn; | ||
| /** | ||
| * Creates a NodeRenderFn from a React component. | ||
| * Each node gets its own React root (createRoot). | ||
| * Returns a cleanup function that unmounts the root. | ||
| */ | ||
| declare function createReactNodeRenderFn(Component: React.ComponentType<NodeComponentProps>): NodeRenderFn; | ||
| /** | ||
| * Creates a TooltipRenderFn from a React component. | ||
| * Each tooltip gets its own React root. | ||
| */ | ||
| declare function createReactTooltipRenderFn(Component: React.ComponentType<TooltipComponentProps>): TooltipRenderFn; | ||
| interface ChatTextProps { | ||
| /** | ||
| * The streamed assistant text. May contain markdown and the 0.12.0 | ||
| * citation wire format `[[token|matched-text]]` — both segments | ||
| * required. | ||
| */ | ||
| text: string; | ||
| /** | ||
| * Callback to render a citation token. The library passes both the | ||
| * citation token (slug / id) and the model's exact matched text so the | ||
| * host can render `<a href={`/<type>/${slug}`}>{matchedText}</a>` — | ||
| * the model's casing wins. | ||
| * | ||
| * When omitted, the library renders `matchedText` verbatim — useful | ||
| * for previews; consumers should always provide this in production | ||
| * to wire up the click target. | ||
| */ | ||
| renderCitation?: (token: string, matchedText: string) => React$1.ReactNode; | ||
| /** Optional className applied to the wrapping element. Lets the host theme. */ | ||
| className?: string; | ||
| } | ||
| /** | ||
| * Render an assistant chat message. Lexes `text` with marked's inline | ||
| * lexer (citation extension registered), then walks the resulting token | ||
| * tree, converting each marked token to a React node. Citation tokens | ||
| * call `renderCitation`; raw HTML is escaped (no live elements ever | ||
| * reach the rendered output). | ||
| * | ||
| * Library responsibility: parse + sanitize + emit React nodes. | ||
| * Host responsibility: CSS styling + citation-link wiring. | ||
| */ | ||
| declare function ChatText(props: ChatTextProps): React$1.ReactElement; | ||
| /** | ||
| * Outcome the React layer fires after a tool-call event has been | ||
| * dispatched through the SceneController. Lets host UIs render | ||
| * "N applied, M unknown" badges alongside chat bubbles. Phase 1 (0.8.0). | ||
| */ | ||
| interface ToolCallOutcome { | ||
| /** Tool name (`highlight` / `apply_filter` / `focus` / `annotate`). */ | ||
| tool: string; | ||
| /** Ids the renderer accepted (i.e. resolved against the current store). */ | ||
| appliedIds?: string[]; | ||
| /** Ids the renderer rejected because the store doesn't know them. */ | ||
| unknownIds?: string[]; | ||
| } | ||
| /** | ||
| * Internal: the React context surface that ties {@link useInferaGraphChat} | ||
| * to the live transport + renderer dispatch installed by `<InferaGraph>`. | ||
| * | ||
| * Exposed via getters so the hook always reads the current values | ||
| * without requiring a re-render of the consumer when the transport | ||
| * swaps. | ||
| */ | ||
| interface InferaGraphChatContext { | ||
| /** Active chat transport. `null` when no `llm`/`transport` prop is set. */ | ||
| getTransport: () => { | ||
| chat: (message: string, opts?: ChatOptions) => AsyncIterable<ChatEvent>; | ||
| } | null; | ||
| /** | ||
| * Dispatch a tool-call event into the SceneController. Implementation | ||
| * lives in `<InferaGraph>` because that's where the controller ref | ||
| * is held. | ||
| * | ||
| * May return a {@link ToolCallOutcome} when the controller can report | ||
| * applied / unknown ids for the dispatched event (today: `highlight` | ||
| * and `focus`). Returning `undefined` signals the hook should fall | ||
| * back to its own `computeToolCallOutcome`. | ||
| */ | ||
| dispatch: (event: ChatEvent) => ToolCallOutcome | undefined; | ||
| /** | ||
| * **Optional** — fire when the engine emits a `debug` ChatEvent. | ||
| * Hosts use this to render diagnostic badges underneath assistant | ||
| * bubbles. Phase 1 (0.8.0). | ||
| */ | ||
| onDiagnostic?: (event: Extract<ChatEvent, { | ||
| type: 'debug'; | ||
| }>) => void; | ||
| /** | ||
| * **Optional** — fire after a tool-call dispatch so hosts can show | ||
| * "applied / unknown" outcome badges. Phase 1 (0.8.0). | ||
| */ | ||
| onToolCallOutcome?: (outcome: ToolCallOutcome) => void; | ||
| } | ||
| export { ChatText as C, DEFAULT_EDGE_COLOR as D, type EdgeColorFn as E, type GraphContextValue as G, InferaGraph as I, type NodeColorFn as N, type SlugResolver as S, type ToolCallOutcome as T, type UseInferaGraphContentReturn as U, type NodeColorResolverOptions as a, NodeColorResolver as b, EdgeColorMap as c, type ChatTextProps as d, DEFAULT_NODE_COLOR as e, type EdgeColorContext as f, type EdgeColorMapOptions as g, GraphProvider as h, type GraphProviderProps as i, type InferaGraphChatContext as j, type InferaGraphChatContextHook as k, type InferaGraphChatHook as l, type InferaGraphCommands as m, type InferaGraphProps as n, type InferaGraphSearchHook as o, type NeighborStatus as p, type UseInferaGraphNeighborsReturn as q, createReactNodeRenderFn as r, createReactTooltipRenderFn as s, useInferaGraphChat as t, useInferaGraph as u, useInferaGraphChatContext as v, useInferaGraphCommands as w, useInferaGraphContent as x, useInferaGraphNeighbors as y, useInferaGraphSearch as z }; |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
5846924
1.55%49
19.51%48438
1.38%267
29.61%13
30%