@thoughtspot/visual-embed-sdk
Version:
ThoughtSpot Embed SDK
534 lines (511 loc) • 18.3 kB
text/typescript
import isUndefined from 'lodash/isUndefined';
import { ERROR_MESSAGE } from '../errors';
import { Param, BaseViewConfig, RuntimeFilter, RuntimeParameter, ErrorDetailsTypes, EmbedErrorCodes, DefaultAppInitData, VisualizationOverrides } from '../types';
import { TsEmbed } from './ts-embed';
import { buildSpotterSidebarAppInitData } from './spotter-utils';
import { getQueryParamString, getFilterQuery, getRuntimeParameters, setParamIfDefined } from '../utils';
/**
* Configuration for search options
*/
export interface SearchOptions {
/**
* The query string to pass to start the Conversation.
*/
searchQuery: string;
}
/**
* Configuration for the Spotter sidebar.
* Can be used in SpotterEmbed and AppEmbed.
* @group Embed components
* @version SDK: 1.47.0 | ThoughtSpot: 26.4.0.cl
*/
export interface SpotterSidebarViewConfig {
/**
* Controls the visibility of the past conversations sidebar.
* @default false
* @version SDK: 1.47.0 | ThoughtSpot: 26.4.0.cl
*/
enablePastConversationsSidebar?: boolean;
/**
* Custom title text for the sidebar header.
* Defaults to translated "Spotter" text.
* @version SDK: 1.47.0 | ThoughtSpot: 26.4.0.cl
*/
spotterSidebarTitle?: string;
/**
* Boolean to set the default expanded state of the sidebar.
* @default false
* @version SDK: 1.47.0 | ThoughtSpot: 26.4.0.cl
*/
spotterSidebarDefaultExpanded?: boolean;
/**
* Custom label text for the rename action in the conversation edit menu.
* Defaults to translated "Rename" text.
* @version SDK: 1.47.0 | ThoughtSpot: 26.4.0.cl
*/
spotterChatRenameLabel?: string;
/**
* Custom label text for the delete action in the conversation edit menu.
* Defaults to translated "DELETE" text.
* @version SDK: 1.47.0 | ThoughtSpot: 26.4.0.cl
*/
spotterChatDeleteLabel?: string;
/**
* Custom title text for the delete conversation confirmation modal.
* Defaults to translated "Delete chat" text.
* @version SDK: 1.47.0 | ThoughtSpot: 26.4.0.cl
*/
spotterDeleteConversationModalTitle?: string;
/**
* Custom message text for the past conversation banner alert.
* Defaults to translated alert message.
* @version SDK: 1.47.0 | ThoughtSpot: 26.4.0.cl
*/
spotterPastConversationAlertMessage?: string;
/**
* Custom URL for the documentation/best practices link.
* Defaults to ThoughtSpot docs URL based on release version.
* Note: URL must include the protocol (e.g., `https://www.example.com`).
* @version SDK: 1.47.0 | ThoughtSpot: 26.4.0.cl
*/
spotterDocumentationUrl?: string;
/**
* Custom label text for the best practices button in the footer.
* Defaults to translated "Best Practices" text.
* @version SDK: 1.47.0 | ThoughtSpot: 26.4.0.cl
*/
spotterBestPracticesLabel?: string;
/**
* Number of conversations to fetch per batch when loading conversation history.
* @default 30
* @version SDK: 1.47.0 | ThoughtSpot: 26.4.0.cl
*/
spotterConversationsBatchSize?: number;
/**
* Custom title text for the "New Chat" button in the sidebar.
* Defaults to translated "New Chat" text.
* @version SDK: 1.47.0 | ThoughtSpot: 26.4.0.cl
*/
spotterNewChatButtonTitle?: string;
}
/**
* Configuration for customizing Spotter chat UI branding.
* @version SDK: 1.46.0 | ThoughtSpot: 26.4.0.cl
* @group Embed components
*/
export interface SpotterChatViewConfig {
/**
* Hides the ThoughtSpot logo/icon in tool response
* cards. The branding label prefix is controlled
* separately via `toolResponseCardBrandingLabel`.
* External MCP tool branding is not affected.
* @default false
*/
hideToolResponseCardBranding?: boolean;
/**
* Custom label to replace the "ThoughtSpot" prefix
* in tool response cards. Set to an empty string
* `''` to hide the prefix entirely. Works
* independently of `hideToolResponseCardBranding`.
* External MCP tool branding is not affected.
*/
toolResponseCardBrandingLabel?: string;
}
/**
* The configuration for the embedded spotterEmbed options.
* @group Embed components
*/
export interface SpotterEmbedViewConfig extends Omit<BaseViewConfig, 'primaryAction'> {
/**
* The ID of the data source object. For example, Model, View, or Table. Spotter uses
* this object to query data and generate Answers.
*/
worksheetId: string;
/**
* Ability to pass a starting search query to the conversation.
*/
searchOptions?: SearchOptions;
/**
* disableSourceSelection : Disables data source selection
* but still display the selected data source.
*
* Supported embed types: `SpotterEmbed`
* @version SDK: 1.36.0 | ThoughtSpot: 10.6.0.cl
* @example
* ```js
* const embed = new SpotterEmbed('#tsEmbed', {
* ... //other embed view config
* disableSourceSelection : true,
* })
* ```
*/
disableSourceSelection?: boolean;
/**
* hideSourceSelection : Hide data source selection
*
* Supported embed types: `SpotterEmbed`
* @version SDK: 1.36.0 | ThoughtSpot: 10.6.0.cl
* @example
* ```js
* const embed = new SpotterEmbed('#tsEmbed', {
* ... //other embed view config
* hideSourceSelection : true,
* })
* ```
*/
hideSourceSelection?: boolean;
/**
* Flag to control Data panel experience
*
* Supported embed types: `SageEmbed`, `AppEmbed`, `SearchBarEmbed`, `LiveboardEmbed`, `SearchEmbed`
* @deprecated from SDK: 1.46.0 | ThoughtSpot Cloud: 26.3.0.cl
* @default true
* @example
* ```js
* // Replace <EmbedComponent> with embed component name. For example, AppEmbed, or SearchBarEmbed
* const embed = new <EmbedComponent>('#tsEmbed', {
* ... // other embed view config
* dataPanelV2: true,
* })
* ```
*/
dataPanelV2?: boolean;
/**
* showSpotterLimitations : show limitation text
* of the spotter underneath the chat input.
* default is false.
*
* Supported embed types: `SpotterEmbed`
* @version SDK: 1.36.0 | ThoughtSpot: 10.5.0.cl
* @example
* ```js
* const embed = new SpotterEmbed('#tsEmbed', {
* ... //other embed view config
* showSpotterLimitations : true,
* })
* ```
*/
showSpotterLimitations?: boolean;
/**
* hideSampleQuestions : Hide sample questions on
* the initial screen of the conversation.
*
* Supported embed types: `SpotterEmbed`
* @version SDK: 1.36.0 | ThoughtSpot: 10.6.0.cl
* @example
* ```js
* const embed = new SpotterEmbed('#tsEmbed', {
* ... //other embed view config
* hideSampleQuestions : true,
* })
* ```
*/
hideSampleQuestions?: boolean;
/**
* The list of runtime filters to apply to a search Answer,
* visualization, or Liveboard.
*
* Supported embed types: `SpotterEmbed`
* @version SDK: 1.41.0 | ThoughtSpot: 10.13.0.cl
* @example
* ```js
* const embed = new SpotterEmbed('#tsEmbed', {
* // other embed view config
* runtimeFilters: [
* {
* columnName: 'color',
* operator: RuntimeFilterOp.EQ,
* values: ['red'],
* },
* ],
* })
* ```
*/
runtimeFilters?: RuntimeFilter[];
/**
* Flag to control whether runtime filters should be included in the URL.
* If true, filters will be passed via app initialization payload
* (default behavior from SDK 1.45.0).
* If false/undefined, filters are appended to the iframe URL instead.
* (default behavior before SDK 1.45.0).
*
* Supported embed types: `SpotterEmbed`
* @version SDK: 1.41.0 | ThoughtSpot: 10.13.0.cl
* @default true
*/
excludeRuntimeFiltersfromURL?: boolean;
/**
* The list of runtime parameters to apply to the conversation.
*
* Supported embed types: `SpotterEmbed`
* @version SDK: 1.41.0 | ThoughtSpot: 10.13.0.cl
* @example
* ```js
* const embed = new SpotterEmbed('#tsEmbed', {
* // other embed view config
* runtimeParameters: [
* {
* name: 'Integer Param',
* value: 10,
* },
* ],
* })
* ```
*/
runtimeParameters?: RuntimeParameter[];
/**
* Flag to control whether runtime parameters should be included in the URL.
* If true, parameters will be passed via app
* initialization payload (default behavior from SDK 1.45.0).
* If false/undefined, parameters are appended to
* the iframe URL instead (default behavior before SDK 1.45.0).
*
* Supported embed types: `SpotterEmbed`
* @version SDK: 1.41.0 | ThoughtSpot: 10.13.0.cl
* @default true
*/
excludeRuntimeParametersfromURL?: boolean;
/**
* updatedSpotterChatPrompt : Controls the updated spotter chat prompt.
*
* Supported embed types: `SpotterEmbed`
* @version SDK: 1.45.0 | ThoughtSpot: 26.2.0.cl
* @default false
* @example
* ```js
* const embed = new SpotterEmbed('#tsEmbed', {
* ... //other embed view config
* updatedSpotterChatPrompt : true,
* })
* ```
*/
updatedSpotterChatPrompt?: boolean;
/**
* Enables the stop answer generation button in the Spotter embed UI,
* allowing users to interrupt an ongoing answer generation.
*
* Supported embed types: `SpotterEmbed`
* @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl
* @default false
*/
enableStopAnswerGenerationEmbed?: boolean;
/**
* Controls the visibility of the past conversations sidebar.
*
* Supported embed types: `SpotterEmbed`
* @version SDK: 1.46.0 | ThoughtSpot: 26.3.0.cl
* @deprecated from SDK: 1.47.0 | ThoughtSpot: 26.4.0.cl
* Use `spotterSidebarConfig.enablePastConversationsSidebar`.
* @default false
*/
enablePastConversationsSidebar?: boolean;
/**
* Configuration for the Spotter sidebar UI customization.
*
* Supported embed types: `SpotterEmbed`, `AppEmbed`
* @version SDK: 1.47.0 | ThoughtSpot: 26.4.0.cl
* @example
* ```js
* const embed = new SpotterEmbed('#tsEmbed', {
* worksheetId: 'worksheet-id',
* // Deprecated standalone flag (backward compatibility)
* enablePastConversationsSidebar: false,
* // Recommended config; this value takes precedence
* spotterSidebarConfig: {
* enablePastConversationsSidebar: true,
* spotterSidebarTitle: 'My Conversations',
* spotterSidebarDefaultExpanded: true,
* },
* })
* ```
*/
spotterSidebarConfig?: SpotterSidebarViewConfig;
/**
* Configuration for customizing Spotter chat UI
* branding in tool response cards.
*
* Supported embed types: `SpotterEmbed`
* @version SDK: 1.46.0 | ThoughtSpot: 26.4.0.cl
* @example
* ```js
* const embed = new SpotterEmbed('#tsEmbed', {
* ... //other embed view config
* spotterChatConfig: {
* hideToolResponseCardBranding: true,
* toolResponseCardBrandingLabel: 'MyBrand',
* },
* })
* ```
*/
spotterChatConfig?: SpotterChatViewConfig;
}
/**
* The configuration for the embedded spotterEmbed options.
* Use {@link SpotterEmbedViewConfig} instead
* @deprecated from SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl
* @group Embed components
*/
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface ConversationViewConfig extends SpotterEmbedViewConfig {}
/**
* APP_INIT data shape for SpotterEmbed.
* @internal
*/
export interface SpotterAppInitData extends DefaultAppInitData {
embedParams?: {
spotterSidebarConfig?: SpotterSidebarViewConfig;
visualOverridesParams?: VisualizationOverrides | null;
};
}
/**
* Embed ThoughtSpot AI Conversation.
* @version SDK: 1.37.0 | ThoughtSpot: 10.9.0.cl
* @group Embed components
* @example
* ```js
* const conversation = new SpotterEmbed('#tsEmbed', {
* worksheetId: 'worksheetId',
* searchOptions: {
* searchQuery: 'searchQuery',
* },
* });
* conversation.render();
* ```
*/
export class SpotterEmbed extends TsEmbed {
constructor(container: HTMLElement, protected viewConfig: SpotterEmbedViewConfig) {
viewConfig = {
embedComponentType: 'conversation',
excludeRuntimeFiltersfromURL: true,
excludeRuntimeParametersfromURL: true,
...viewConfig,
}
super(container, viewConfig);
}
/**
* Extends the default APP_INIT payload with `embedParams.spotterSidebarConfig`
* so the conv-assist app can read sidebar configuration on initialisation.
*
* Precedence for `enablePastConversationsSidebar`:
* `spotterSidebarConfig.enablePastConversationsSidebar` wins over the
* deprecated top-level `enablePastConversationsSidebar` flag; if the former
* is absent the latter is used as a fallback.
*
* An invalid `spotterDocumentationUrl` triggers a validation error and is
* excluded from the payload rather than forwarded to the app.
*/
protected async getAppInitData(): Promise<SpotterAppInitData> {
const defaultAppInitData = await super.getAppInitData();
return buildSpotterSidebarAppInitData(defaultAppInitData, this.viewConfig, this.handleError.bind(this));
}
protected getEmbedParamsObject() {
const {
worksheetId,
searchOptions,
disableSourceSelection,
hideSourceSelection,
dataPanelV2,
showSpotterLimitations,
hideSampleQuestions,
runtimeFilters,
excludeRuntimeFiltersfromURL,
runtimeParameters,
excludeRuntimeParametersfromURL,
updatedSpotterChatPrompt,
enableStopAnswerGenerationEmbed,
spotterChatConfig,
} = this.viewConfig;
if (!worksheetId) {
this.handleError({
errorType: ErrorDetailsTypes.VALIDATION_ERROR,
message: ERROR_MESSAGE.SPOTTER_EMBED_WORKSHEED_ID_NOT_FOUND,
code: EmbedErrorCodes.WORKSHEET_ID_NOT_FOUND,
error: ERROR_MESSAGE.SPOTTER_EMBED_WORKSHEED_ID_NOT_FOUND,
});
}
const queryParams = this.getBaseQueryParams();
queryParams[Param.SpotterEnabled] = true;
// Boolean params
setParamIfDefined(queryParams, Param.DisableSourceSelection, disableSourceSelection, true);
setParamIfDefined(queryParams, Param.HideSourceSelection, hideSourceSelection, true);
setParamIfDefined(queryParams, Param.DataPanelV2Enabled, dataPanelV2, true);
setParamIfDefined(queryParams, Param.ShowSpotterLimitations, showSpotterLimitations, true);
setParamIfDefined(queryParams, Param.HideSampleQuestions, hideSampleQuestions, true);
setParamIfDefined(queryParams, Param.UpdatedSpotterChatPrompt, updatedSpotterChatPrompt, true);
setParamIfDefined(queryParams, Param.EnableStopAnswerGenerationEmbed, enableStopAnswerGenerationEmbed, true);
// Handle spotterChatConfig params
if (spotterChatConfig) {
const {
hideToolResponseCardBranding,
toolResponseCardBrandingLabel,
} = spotterChatConfig;
setParamIfDefined(queryParams, Param.HideToolResponseCardBranding, hideToolResponseCardBranding, true);
setParamIfDefined(queryParams, Param.ToolResponseCardBrandingLabel, toolResponseCardBrandingLabel);
}
return queryParams;
}
public getIframeSrc(): string {
const {
worksheetId,
searchOptions,
runtimeFilters,
excludeRuntimeFiltersfromURL,
runtimeParameters,
excludeRuntimeParametersfromURL,
} = this.viewConfig;
const path = 'insights/conv-assist';
const queryParams = this.getEmbedParamsObject();
let query = '';
const queryParamsString = getQueryParamString(queryParams, true);
if (queryParamsString) {
query = `?${queryParamsString}`;
}
const filterQuery = getFilterQuery(runtimeFilters || []);
if (filterQuery && !excludeRuntimeFiltersfromURL) {
query += `&${filterQuery}`;
}
const parameterQuery = getRuntimeParameters(runtimeParameters || []);
if (parameterQuery && !excludeRuntimeParametersfromURL) {
query += `&${parameterQuery}`;
}
const tsPostHashParams = this.getThoughtSpotPostUrlParams({
worksheet: worksheetId,
query: searchOptions?.searchQuery || '',
});
return `${this.getEmbedBasePath(query)}/embed/${path}${tsPostHashParams}`;
}
public async render(): Promise<SpotterEmbed> {
await super.render();
const src = this.getIframeSrc();
await this.renderIFrame(src);
return this;
}
}
/**
* Embed ThoughtSpot AI Conversation.
* Use {@link SpotterEmbed} instead
* @version SDK: 1.37.0 | ThoughtSpot: 10.9.0.cl
* @deprecated from SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl
* @group Embed components
* @example
* ```js
* const conversation = new SpotterEmbed('#tsEmbed', {
* worksheetId: 'worksheetId',
* searchOptions: {
* searchQuery: 'searchQuery',
* },
* });
* conversation.render();
* ```
*/
export class ConversationEmbed extends SpotterEmbed {
constructor(container: HTMLElement, protected viewConfig: ConversationViewConfig) {
viewConfig = {
embedComponentType: 'conversation',
excludeRuntimeFiltersfromURL: true,
excludeRuntimeParametersfromURL: true,
...viewConfig,
}
super(container, viewConfig);
}
}