import { LimeWebComponent } from '../core/lime-web-component'; import { LimeWebComponentContext } from '../core/context'; import { JsonValue } from '../json/json-value'; /** * Represents context information that a provider contributes to the AI chat. * * Each contribution is additive - all providers' contributions are collected * into an array when the AI chat gathers context. * * @alpha * @group AI Context */ export interface AIContextContribution { /** * Structured data about the current state. * * This data will be serialized and included in the AI prompt context. * Keep the data focused and relevant to avoid filling the context window * with noise. * * @example Describe the record the component is showing * ```ts * import { AIContextContribution } from '@limetech/lime-web-components'; * * const contribution: AIContextContribution = { * data: { * currentObject: { * limetype: 'deal', * id: 123, * descriptive: 'Acme Corp - Enterprise License', * }, * availableRelations: ['company', 'person', 'activity'], * }, * }; * ``` */ data?: Record; /** * Describes the purpose of this component and what it shows. * * Use this to help the AI understand what the component is for, * how users interact with it, and what the data represents. * This is typically hardcoded by component developers. * * Do NOT use this to restate structured data in natural language. * The AI can read the data directly. * * @example Good and bad purpose texts * ```ts * import { AIContextContribution } from '@limetech/lime-web-components'; * * // Good. Explains what the component is and what clicking it does. * const infoTile: AIContextContribution = { * purpose: * 'Shows a single aggregated value from a filter. ' + * 'Clicking navigates to the explorer with that filter applied.', * }; * * // Good. Explains how to read the data and how users change it. * const pipeline: AIContextContribution = { * purpose: * 'Displays deals in a pipeline view grouped by stage. ' + * 'Users drag deals between columns to change their stage.', * }; * * // Bad. This restates structured data, so it belongs in `data` instead. * const dealCard: AIContextContribution = { * purpose: 'The user is viewing deal 123.', * }; * ``` */ purpose?: string; } /** * A fully enriched context contribution ready for the AI. * * This is the "output" of context gathering — includes the provider's * contribution plus UI context information added by the registry. * * @alpha * @group AI Context */ export interface GatheredAIContext extends AIContextContribution { /** * The provider's {@link AIContextProvider.contextType | contextType} label. * * Identifies the kind of context this contribution represents * (e.g. `'current-user'`, `'table-view'`, `'explorer-view'`). * Copied from the provider by the registry at gather time. */ type: string; /** * The component's {@link LimeWebComponentContext} at the time of gathering. * * The {@link LimeWebComponentContext} injected into the component by * the framework. Describes the component's own limetype/id and, * via the `parent` chain, its ancestors in the UI hierarchy. * * The ancestor chain may be incomplete, so do not rely on it to reach * every record above the component. * * @example A company record open in the main view * ```ts * import { LimeWebComponentContext } from '@limetech/lime-web-components'; * * const ancestorContext: LimeWebComponentContext = { * limetype: 'company', * id: 3184, * }; * ``` * * @example A deal opened in the side panel from a company's deal list * ```ts * import { LimeWebComponentContext } from '@limetech/lime-web-components'; * * const ancestorContext: LimeWebComponentContext = { * limetype: 'deal', * id: 2045, * // The deal list the component is nested in. It has no id of its own, and * // the company the list belongs to is not part of the chain. * parent: { * limetype: 'deal', * id: null, * }, * }; * ``` */ ancestorContext: LimeWebComponentContext; } /** * A provider that contributes context information to the AI chat. * * Components register providers to share relevant state with the AI. * When the AI chat needs context, it calls `getContext()` on all * registered providers and collects their contributions. * * Typically, providers are registered when a component is created or * connected to the DOM, and unregistered when disconnected. Alternatively, * some components may register/unregister based on visibility if context * should only be provided while the component is visible to the user. * * The `register()` method returns an unregister function for cleanup. * * @example Registering a provider in a Stencil component * ```ts * import { Component, Element, State } from '@stencil/core'; * import { * LimeWebComponent, * PlatformServiceName, * } from '@limetech/lime-web-components'; * * interface SelectableItem { * id: number; * name: string; * } * * @Component({ tag: 'my-custom-panel' }) * export class MyCustomPanel { * @Element() * private host: HTMLElement & LimeWebComponent; * * @State() * private selectedItems: SelectableItem[] = []; * * private unregisterContext?: () => void; * * public connectedCallback() { * const registry = platform.get(PlatformServiceName.AIContextRegistry); * * this.unregisterContext = registry.register( * { * contextType: 'my-custom-panel', * getContext: () => ({ * purpose: * 'Lets users select items for bulk operations. ' + * 'Selected items can be exported or bulk-edited.', * data: { * selectedItemIds: this.selectedItems.map( * (item) => item.id * ), * selectionCount: this.selectedItems.length, * }, * }), * }, * this.host * ); * } * * public disconnectedCallback() { * this.unregisterContext?.(); * } * } * ``` * * @alpha * @group AI Context */ export interface AIContextProvider { /** * Semantic label for grouping related providers. * * Multiple providers can share the same contextType (e.g., multiple * info-tiles on a page). Each registration gets a unique internal key, * so there are no collisions. This label is used for debugging and * potential future grouping/filtering of context. * * Use a descriptive name that identifies the component or feature type. */ readonly contextType: string; /** * Get context from this provider. * * Called when the AI chat needs to gather context from all providers. * Should return data that the component already has available - this * method is synchronous because providers should only return data * that is already rendered on-screen. * * The implementation should be fast and avoid expensive operations * since it's called on every message. * * @returns The context contribution, or `null` if no context is available * (e.g., required state hasn't loaded yet). */ getContext(): AIContextContribution | null; } /** * Registry for AI context providers. * * The AIContextRegistry manages context providers that contribute information * to the AI chat. Components register providers to share their state, and the * AI chat gathers context from all providers when needed. * * @example Gathering context for an AI request * ```ts * import { * createLogger, * PlatformServiceName, * } from '@limetech/lime-web-components'; * * const logger = createLogger('my-component'); * * const registry = platform.get(PlatformServiceName.AIContextRegistry); * const contributions = registry.gatherContext(); * * for (const contribution of contributions) { * logger.debug('Gathered context', { * type: contribution.type, * limetype: contribution.ancestorContext.limetype, * purpose: contribution.purpose, * data: contribution.data, * }); * } * ``` * * @see {@link AIContextProvider} for provider interface * @see {@link AIContextContribution} for contribution format * * @alpha * @group AI Context */ export interface AIContextRegistry { /** * Register a context provider. * * The provider will be called when context is gathered via `gatherContext()`. * Multiple providers with the same contextType can coexist - each registration * gets a unique internal key. * * @param provider - The provider to register. * @param element - The host element of the component registering this * provider. Enables the registry to: * - Read the component's `LimeWebComponentContext` to enrich contributions * - Skip providers whose elements are not currently connected to the DOM * @returns A function to unregister the provider. Call this when the * component is disconnected or the context is no longer relevant. * * @example Register a provider and unregister it on disconnect * ```ts * import { Element } from '@stencil/core'; * import { * LimeObject, * LimeWebComponent, * PlatformServiceName, * } from '@limetech/lime-web-components'; * * class DealCard { * @Element() * private host: HTMLElement & LimeWebComponent; * * private deal: LimeObject | null = null; * * private unregister?: () => void; * * public async connectedCallback() { * const repository = platform.get( * PlatformServiceName.LimeObjectRepository * ); * * if (context.limetype && context.id) { * this.deal = await repository.loadObject( * context.limetype, * context.id, * { properties: ['descriptive'] } * ); * } * * const registry = platform.get(PlatformServiceName.AIContextRegistry); * * this.unregister = registry.register( * { * contextType: 'deal-card', * getContext: () => ({ * purpose: * 'Shows full details of a deal record. ' + * 'Users can edit fields and manage related records.', * data: { * limetype: 'deal', * id: context.id, * descriptive: this.deal?.getValue('descriptive') ?? null, * }, * }), * }, * this.host * ); * } * * public disconnectedCallback() { * this.unregister?.(); * } * } * ``` */ register(provider: AIContextProvider, element: HTMLElement & LimeWebComponent): () => void; /** * Get all registered providers. * * Useful for debugging or introspection. * * @returns Array of all registered providers. */ getProviders(): AIContextProvider[]; /** * Gather context from all registered providers. * * Calls `getContext()` on each provider and collects the results. * Providers that return `null` (indicating no context available) and * providers that throw errors are skipped (errors log a warning). * * Each contribution is enriched with the component's `LimeWebComponentContext`. * * @returns An array of enriched context contributions, one from each * provider that returned a non-null contribution. */ gatherContext(): GatheredAIContext[]; }