diff --git a/common/changes/@gooddata/sdk-ui-all/SHA_master_2026-05-07-09-18.json b/common/changes/@gooddata/sdk-ui-all/SHA_master_2026-05-07-09-18.json new file mode 100644 index 00000000000..1768f79482c --- /dev/null +++ b/common/changes/@gooddata/sdk-ui-all/SHA_master_2026-05-07-09-18.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@gooddata/sdk-ui-all", + "comment": "sdk-gen-ai: add support for generating conversation titles", + "type": "none" + } + ], + "packageName": "@gooddata/sdk-ui-all" +} diff --git a/common/changes/@gooddata/sdk-ui-all/jacek-meta_2026-05-07-09-30.json b/common/changes/@gooddata/sdk-ui-all/jacek-meta_2026-05-07-09-30.json new file mode 100644 index 00000000000..5c845d40ec4 --- /dev/null +++ b/common/changes/@gooddata/sdk-ui-all/jacek-meta_2026-05-07-09-30.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "Fix yamlDatasetToDeclarative emitting reference.type='fact' for APPROXIMATE_COUNT aggregated facts against attribute targets (CQ-2147)", + "type": "none", + "packageName": "@gooddata/sdk-ui-all" + } + ], + "packageName": "@gooddata/sdk-ui-all", + "email": "jan.soubusta@gooddata.com" +} diff --git a/common/config/rush/version-policies.json b/common/config/rush/version-policies.json index 462a45eb3f6..96d04b9645e 100644 --- a/common/config/rush/version-policies.json +++ b/common/config/rush/version-policies.json @@ -12,14 +12,14 @@ { "definitionName": "lockStepVersion", "policyName": "sdk", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "nextBump": "prerelease", "mainProject": "@gooddata/sdk-ui-all" }, { "definitionName": "lockStepVersion", "policyName": "sdk-examples", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "nextBump": "prerelease", "mainProject": "@gooddata/sdk-ui-all" } diff --git a/examples/sdk-interactive-examples/examples-template/package.json b/examples/sdk-interactive-examples/examples-template/package.json index 60d902cc1e7..e2ac2456aaf 100644 --- a/examples/sdk-interactive-examples/examples-template/package.json +++ b/examples/sdk-interactive-examples/examples-template/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-interactive-examples-template", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "private": false, "description": "GoodData interactive example template", "license": "LicenseRef-LICENSE", diff --git a/examples/sdk-interactive-examples/examples/example-attributefilter/package.json b/examples/sdk-interactive-examples/examples/example-attributefilter/package.json index 79b7eb05b07..6d22cd42aec 100644 --- a/examples/sdk-interactive-examples/examples/example-attributefilter/package.json +++ b/examples/sdk-interactive-examples/examples/example-attributefilter/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-interactive-example-attributefilter", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "private": false, "description": "This example demonstrates how to use the AttributeFilter component to filter data in a visualization.", "license": "LicenseRef-LICENSE", diff --git a/examples/sdk-interactive-examples/examples/example-chartconfig/package.json b/examples/sdk-interactive-examples/examples/example-chartconfig/package.json index 961db3574a6..babae2ccfd3 100644 --- a/examples/sdk-interactive-examples/examples/example-chartconfig/package.json +++ b/examples/sdk-interactive-examples/examples/example-chartconfig/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-interactive-example-chartconfig", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "private": false, "description": "This interactive example demonstrates how to manipulate the chart config.", "license": "LicenseRef-LICENSE", diff --git a/examples/sdk-interactive-examples/examples/example-columnchart/package.json b/examples/sdk-interactive-examples/examples/example-columnchart/package.json index 2bd3dc80b6f..cfed6abe21e 100644 --- a/examples/sdk-interactive-examples/examples/example-columnchart/package.json +++ b/examples/sdk-interactive-examples/examples/example-columnchart/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-interactive-example-columnchart", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "private": false, "description": "This example demonstrates the usage of the ColumnChart component with the viewBy and stackBy properties.", "license": "LicenseRef-LICENSE", diff --git a/examples/sdk-interactive-examples/examples/example-combochart/package.json b/examples/sdk-interactive-examples/examples/example-combochart/package.json index c36976cbffa..58401dba2aa 100644 --- a/examples/sdk-interactive-examples/examples/example-combochart/package.json +++ b/examples/sdk-interactive-examples/examples/example-combochart/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-interactive-example-combochart", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "private": false, "description": "Example demonstrates ComboChart secondaryMeasures definition. ", "license": "LicenseRef-LICENSE", diff --git a/examples/sdk-interactive-examples/examples/example-dashboard/package.json b/examples/sdk-interactive-examples/examples/example-dashboard/package.json index 6fada208756..51a7b701c99 100644 --- a/examples/sdk-interactive-examples/examples/example-dashboard/package.json +++ b/examples/sdk-interactive-examples/examples/example-dashboard/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-interactive-example-dashboard", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "private": false, "description": "This example shows how to use the Dashboard component.", "license": "LicenseRef-LICENSE", diff --git a/examples/sdk-interactive-examples/examples/example-datefilter/package.json b/examples/sdk-interactive-examples/examples/example-datefilter/package.json index ea5b15623b5..16a187b862f 100644 --- a/examples/sdk-interactive-examples/examples/example-datefilter/package.json +++ b/examples/sdk-interactive-examples/examples/example-datefilter/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-interactive-example-datefilter", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "private": false, "description": "Example demonstrates usage of Date Filter component.", "license": "LicenseRef-LICENSE", diff --git a/examples/sdk-interactive-examples/examples/example-dependentfilters/package.json b/examples/sdk-interactive-examples/examples/example-dependentfilters/package.json index 0cab838c579..bfa3694a985 100644 --- a/examples/sdk-interactive-examples/examples/example-dependentfilters/package.json +++ b/examples/sdk-interactive-examples/examples/example-dependentfilters/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-interactive-example-dependentfilters", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "private": false, "description": "This example demonstrates how to use multiple attribute filters linked together to filter data in a visualization.", "license": "LicenseRef-LICENSE", diff --git a/examples/sdk-interactive-examples/examples/example-execute/package.json b/examples/sdk-interactive-examples/examples/example-execute/package.json index f069cd0e8f9..cd5d2877ac9 100644 --- a/examples/sdk-interactive-examples/examples/example-execute/package.json +++ b/examples/sdk-interactive-examples/examples/example-execute/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-interactive-example-execute", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "private": false, "description": "This example demonstrates using Execute component and build custom visualization.", "license": "LicenseRef-LICENSE", diff --git a/examples/sdk-interactive-examples/examples/example-granularity/package.json b/examples/sdk-interactive-examples/examples/example-granularity/package.json index 710d4be3b5b..c37be785af7 100644 --- a/examples/sdk-interactive-examples/examples/example-granularity/package.json +++ b/examples/sdk-interactive-examples/examples/example-granularity/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-interactive-example-granularity", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "private": false, "description": "This example exmplains DateFilter granularity ", "license": "LicenseRef-LICENSE", diff --git a/examples/sdk-interactive-examples/examples/example-headline/package.json b/examples/sdk-interactive-examples/examples/example-headline/package.json index ae4e22b8585..4844ee83ed7 100644 --- a/examples/sdk-interactive-examples/examples/example-headline/package.json +++ b/examples/sdk-interactive-examples/examples/example-headline/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-interactive-example-headline", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "private": false, "description": "This example shows how to use the Headline component.", "license": "LicenseRef-LICENSE", diff --git a/examples/sdk-interactive-examples/examples/example-pivottable/package.json b/examples/sdk-interactive-examples/examples/example-pivottable/package.json index 53d4b7f2bab..47fe696097d 100644 --- a/examples/sdk-interactive-examples/examples/example-pivottable/package.json +++ b/examples/sdk-interactive-examples/examples/example-pivottable/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-interactive-example-pivottable", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "private": false, "description": "Basic PivotTable manipulation.", "license": "LicenseRef-LICENSE", diff --git a/examples/sdk-interactive-examples/examples/example-relativedatefilter/package.json b/examples/sdk-interactive-examples/examples/example-relativedatefilter/package.json index 77d6068d721..0c3a3c1abc7 100644 --- a/examples/sdk-interactive-examples/examples/example-relativedatefilter/package.json +++ b/examples/sdk-interactive-examples/examples/example-relativedatefilter/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-interactive-example-relativedatefilter", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "private": false, "description": "Example demonstrates how to set relative DateFilter for visualization.", "license": "LicenseRef-LICENSE", diff --git a/examples/sdk-interactive-examples/examples/example-repeater/package.json b/examples/sdk-interactive-examples/examples/example-repeater/package.json index 6214820d7f6..3c3eff828b7 100644 --- a/examples/sdk-interactive-examples/examples/example-repeater/package.json +++ b/examples/sdk-interactive-examples/examples/example-repeater/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-interactive-example-repeater", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "private": false, "description": "This example demonstrates how to use Repeater component.", "license": "LicenseRef-LICENSE", diff --git a/examples/sdk-interactive-examples/package.json b/examples/sdk-interactive-examples/package.json index 22ab416283f..932ffd79e9b 100644 --- a/examples/sdk-interactive-examples/package.json +++ b/examples/sdk-interactive-examples/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-interactive-examples", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "private": false, "description": "GoodData React interactive examples", "license": "LicenseRef-LICENSE", diff --git a/libs/api-client-tiger/api/api-client-tiger.api.md b/libs/api-client-tiger/api/api-client-tiger.api.md index b3ccf5b92eb..10133dd8643 100644 --- a/libs/api-client-tiger/api/api-client-tiger.api.md +++ b/libs/api-client-tiger/api/api-client-tiger.api.md @@ -7654,6 +7654,7 @@ export class ConversationsAi extends BaseAPI implements ConversationsAiInterface getConversationsApiV1AiWorkspacesWorkspaceIdChatConversationsGet(requestParameters: ConversationsAiGetConversationsApiV1AiWorkspacesWorkspaceIdChatConversationsGetRequest, options?: AxiosRequestConfig): AxiosPromise; patchConversationApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdPatch(requestParameters: ConversationsAiPatchConversationApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdPatchRequest, options?: AxiosRequestConfig): AxiosPromise; postConversationsApiV1AiWorkspacesWorkspaceIdChatConversationsPost(requestParameters: ConversationsAiPostConversationsApiV1AiWorkspacesWorkspaceIdChatConversationsPostRequest, options?: AxiosRequestConfig): AxiosPromise; + postGenerateConversationTitleApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdGenerateTitlePost(requestParameters: ConversationsAiPostGenerateConversationTitleApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdGenerateTitlePostRequest, options?: AxiosRequestConfig): AxiosPromise; } // @public @@ -7681,6 +7682,11 @@ function ConversationsAi_PostConversationsApiV1AiWorkspacesWorkspaceIdChatConver export { ConversationsAi_PostConversationsApiV1AiWorkspacesWorkspaceIdChatConversationsPost } export { ConversationsAi_PostConversationsApiV1AiWorkspacesWorkspaceIdChatConversationsPost as GenAiApi_PostConversations } +// @public +function ConversationsAi_PostGenerateConversationTitleApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdGenerateTitlePost(axios: AxiosInstance, basePath: string, requestParameters: ConversationsAiPostGenerateConversationTitleApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdGenerateTitlePostRequest, options?: AxiosRequestConfig, configuration?: Configuration_2): AxiosPromise; +export { ConversationsAi_PostGenerateConversationTitleApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdGenerateTitlePost } +export { ConversationsAi_PostGenerateConversationTitleApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdGenerateTitlePost as GenAiApi_PostGenerateConversationTitle } + // @public export function ConversationsAiAxiosParamCreator_DeleteConversationApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdDelete(workspaceId: string, conversationId: string, options?: AxiosRequestConfig, configuration?: Configuration_2): Promise; @@ -7696,6 +7702,9 @@ export function ConversationsAiAxiosParamCreator_PatchConversationApiV1AiWorkspa // @public export function ConversationsAiAxiosParamCreator_PostConversationsApiV1AiWorkspacesWorkspaceIdChatConversationsPost(workspaceId: string, isPreview?: boolean, options?: AxiosRequestConfig, configuration?: Configuration_2): Promise; +// @public +export function ConversationsAiAxiosParamCreator_PostGenerateConversationTitleApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdGenerateTitlePost(workspaceId: string, conversationId: string, options?: AxiosRequestConfig, configuration?: Configuration_2): Promise; + // @public interface ConversationsAiDeleteConversationApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdDeleteRequest { readonly conversationId: string; @@ -7729,6 +7738,7 @@ export interface ConversationsAiInterface { getConversationsApiV1AiWorkspacesWorkspaceIdChatConversationsGet(requestParameters: ConversationsAiGetConversationsApiV1AiWorkspacesWorkspaceIdChatConversationsGetRequest, options?: AxiosRequestConfig): AxiosPromise; patchConversationApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdPatch(requestParameters: ConversationsAiPatchConversationApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdPatchRequest, options?: AxiosRequestConfig): AxiosPromise; postConversationsApiV1AiWorkspacesWorkspaceIdChatConversationsPost(requestParameters: ConversationsAiPostConversationsApiV1AiWorkspacesWorkspaceIdChatConversationsPostRequest, options?: AxiosRequestConfig): AxiosPromise; + postGenerateConversationTitleApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdGenerateTitlePost(requestParameters: ConversationsAiPostGenerateConversationTitleApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdGenerateTitlePostRequest, options?: AxiosRequestConfig): AxiosPromise; } // @public @@ -7748,6 +7758,14 @@ interface ConversationsAiPostConversationsApiV1AiWorkspacesWorkspaceIdChatConver export { ConversationsAiPostConversationsApiV1AiWorkspacesWorkspaceIdChatConversationsPostRequest } export { ConversationsAiPostConversationsApiV1AiWorkspacesWorkspaceIdChatConversationsPostRequest as GenAiApiPostConversationsRequest } +// @public +interface ConversationsAiPostGenerateConversationTitleApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdGenerateTitlePostRequest { + readonly conversationId: string; + readonly workspaceId: string; +} +export { ConversationsAiPostGenerateConversationTitleApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdGenerateTitlePostRequest } +export { ConversationsAiPostGenerateConversationTitleApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdGenerateTitlePostRequest as GenAiApiPostGenerateConversationTitleRequest } + // @public export class CookieSecurityConfigurationApi extends MetadataBaseApi implements CookieSecurityConfigurationApiInterface { getEntityCookieSecurityConfigurations(requestParameters: CookieSecurityConfigurationApiGetEntityCookieSecurityConfigurationsRequest, options?: AxiosRequestConfig): AxiosPromise; diff --git a/libs/api-client-tiger/package.json b/libs/api-client-tiger/package.json index fa77b42c339..a54cd0e5167 100644 --- a/libs/api-client-tiger/package.json +++ b/libs/api-client-tiger/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/api-client-tiger", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "description": "API Client for GoodData Cloud and GoodData.CN", "license": "MIT", "author": "GoodData", diff --git a/libs/api-client-tiger/src/endpoints/genAI/index.ts b/libs/api-client-tiger/src/endpoints/genAI/index.ts index e5e31678e23..4d2c911f453 100644 --- a/libs/api-client-tiger/src/endpoints/genAI/index.ts +++ b/libs/api-client-tiger/src/endpoints/genAI/index.ts @@ -45,11 +45,13 @@ import { type ConversationsAiGetConversationsApiV1AiWorkspacesWorkspaceIdChatConversationsGetRequest, type ConversationsAiPatchConversationApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdPatchRequest, type ConversationsAiPostConversationsApiV1AiWorkspacesWorkspaceIdChatConversationsPostRequest, + type ConversationsAiPostGenerateConversationTitleApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdGenerateTitlePostRequest, ConversationsAi_DeleteConversationApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdDelete, ConversationsAi_GetConversationApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdGet, ConversationsAi_GetConversationsApiV1AiWorkspacesWorkspaceIdChatConversationsGet, ConversationsAi_PatchConversationApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdPatch, ConversationsAi_PostConversationsApiV1AiWorkspacesWorkspaceIdChatConversationsPost, + ConversationsAi_PostGenerateConversationTitleApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdGenerateTitlePost, type ItemsAiGetConversationItemsApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdItemsGetRequest, ItemsAi_GetConversationItemsApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdItemsGet, type KnowledgeAiCreateDocumentRequest, @@ -143,6 +145,8 @@ export { type ResponsesAiPatchResponseApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdResponsesResponseIdPatchRequest as GenAiApiPatchConversationResponseRequest, VisualizationsAi_PatchVisualizationApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdVisualizationsVisualizationIdPatch as GenAiApi_PatchVisualization, type VisualizationsAiPatchVisualizationApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdVisualizationsVisualizationIdPatchRequest as GenAiApiPatchVisualizationRequest, + ConversationsAi_PostGenerateConversationTitleApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdGenerateTitlePost as GenAiApi_PostGenerateConversationTitle, + type ConversationsAiPostGenerateConversationTitleApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdGenerateTitlePostRequest as GenAiApiPostGenerateConversationTitleRequest, // Knowledge Documents KnowledgeAi_CreateDocument as GenAiApi_CreateKnowledgeDocument, type KnowledgeAiCreateDocumentRequest as GenAiApiCreateKnowledgeDocumentRequest, diff --git a/libs/api-client-tiger/src/generated/ai-json-api/api.ts b/libs/api-client-tiger/src/generated/ai-json-api/api.ts index 8728d305735..a4c88966c05 100644 --- a/libs/api-client-tiger/src/generated/ai-json-api/api.ts +++ b/libs/api-client-tiger/src/generated/ai-json-api/api.ts @@ -1460,6 +1460,55 @@ export async function ConversationsAiAxiosParamCreator_PostConversationsApiV1AiW } +// ConversationsAi FP - ConversationsAiAxiosParamCreator +/** + * + * @summary Post Generate Conversation Title + * @param {string} workspaceId + * @param {string} conversationId + * @param {*} [options] Override http request option. + * @param {Configuration} [configuration] Optional configuration. + * @throws {RequiredError} + */ +export async function ConversationsAiAxiosParamCreator_PostGenerateConversationTitleApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdGenerateTitlePost( + workspaceId: string, conversationId: string, + options: AxiosRequestConfig = {}, + configuration?: Configuration, +): Promise { + // verify required parameter 'workspaceId' is not null or undefined + assertParamExists('postGenerateConversationTitleApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdGenerateTitlePost', 'workspaceId', workspaceId) + // verify required parameter 'conversationId' is not null or undefined + assertParamExists('postGenerateConversationTitleApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdGenerateTitlePost', 'conversationId', conversationId) + const localVarPath = `/api/v1/ai/workspaces/{workspace_id}/chat/conversations/{conversation_id}/generateTitle` + .replace(`{${"workspace_id"}}`, encodeURIComponent(String(workspaceId))) + .replace(`{${"conversation_id"}}`, encodeURIComponent(String(conversationId))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + const headersFromBaseOptions = baseOptions?.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = { + ...localVarHeaderParameter, + ...headersFromBaseOptions, + ...options.headers, + }; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; +} + + // ConversationsAi Api FP /** @@ -1591,6 +1640,32 @@ export async function ConversationsAi_PostConversationsApiV1AiWorkspacesWorkspac } +// ConversationsAi Api FP +/** + * + * @summary Post Generate Conversation Title + * @param {AxiosInstance} axios Axios instance. + * @param {string} basePath Base path. + * @param {ConversationsAiPostGenerateConversationTitleApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdGenerateTitlePostRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @param {Configuration} [configuration] Optional configuration. + * @throws {RequiredError} + */ +export async function ConversationsAi_PostGenerateConversationTitleApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdGenerateTitlePost( + axios: AxiosInstance, basePath: string, + requestParameters: ConversationsAiPostGenerateConversationTitleApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdGenerateTitlePostRequest, + options?: AxiosRequestConfig, + configuration?: Configuration, +): AxiosPromise { + const localVarAxiosArgs = await ConversationsAiAxiosParamCreator_PostGenerateConversationTitleApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdGenerateTitlePost( + requestParameters.workspaceId, requestParameters.conversationId, + options || {}, + configuration, + ); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, basePath); +} + + /** * ConversationsAi - interface * @export @@ -1647,6 +1722,16 @@ export interface ConversationsAiInterface { */ postConversationsApiV1AiWorkspacesWorkspaceIdChatConversationsPost(requestParameters: ConversationsAiPostConversationsApiV1AiWorkspacesWorkspaceIdChatConversationsPostRequest, options?: AxiosRequestConfig): AxiosPromise; + /** + * + * @summary Post Generate Conversation Title + * @param {ConversationsAiPostGenerateConversationTitleApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdGenerateTitlePostRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof ConversationsAiInterface + */ + postGenerateConversationTitleApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdGenerateTitlePost(requestParameters: ConversationsAiPostGenerateConversationTitleApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdGenerateTitlePostRequest, options?: AxiosRequestConfig): AxiosPromise; + } /** @@ -1775,6 +1860,27 @@ export interface ConversationsAiPostConversationsApiV1AiWorkspacesWorkspaceIdCha readonly isPreview?: boolean } +/** + * Request parameters for postGenerateConversationTitleApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdGenerateTitlePost operation in ConversationsAi. + * @export + * @interface ConversationsAiPostGenerateConversationTitleApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdGenerateTitlePostRequest + */ +export interface ConversationsAiPostGenerateConversationTitleApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdGenerateTitlePostRequest { + /** + * + * @type {string} + * @memberof ConversationsAiPostGenerateConversationTitleApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdGenerateTitlePost + */ + readonly workspaceId: string + + /** + * + * @type {string} + * @memberof ConversationsAiPostGenerateConversationTitleApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdGenerateTitlePost + */ + readonly conversationId: string +} + /** * ConversationsAi - object-oriented interface * @export @@ -1841,6 +1947,18 @@ export class ConversationsAi extends BaseAPI implements ConversationsAiInterface public postConversationsApiV1AiWorkspacesWorkspaceIdChatConversationsPost(requestParameters: ConversationsAiPostConversationsApiV1AiWorkspacesWorkspaceIdChatConversationsPostRequest, options?: AxiosRequestConfig) { return ConversationsAi_PostConversationsApiV1AiWorkspacesWorkspaceIdChatConversationsPost(this.axios, this.basePath, requestParameters, options, this.configuration); } + + /** + * + * @summary Post Generate Conversation Title + * @param {ConversationsAiPostGenerateConversationTitleApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdGenerateTitlePostRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof ConversationsAi + */ + public postGenerateConversationTitleApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdGenerateTitlePost(requestParameters: ConversationsAiPostGenerateConversationTitleApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdGenerateTitlePostRequest, options?: AxiosRequestConfig) { + return ConversationsAi_PostGenerateConversationTitleApiV1AiWorkspacesWorkspaceIdChatConversationsConversationIdGenerateTitlePost(this.axios, this.basePath, requestParameters, options, this.configuration); + } } diff --git a/libs/api-client-tiger/src/generated/ai-json-api/openapi-spec.json b/libs/api-client-tiger/src/generated/ai-json-api/openapi-spec.json index c6d6264d9a5..6e8da6bf6c9 100644 --- a/libs/api-client-tiger/src/generated/ai-json-api/openapi-spec.json +++ b/libs/api-client-tiger/src/generated/ai-json-api/openapi-spec.json @@ -289,6 +289,53 @@ } } }, + "/api/v1/ai/workspaces/{workspace_id}/chat/conversations/{conversation_id}/generateTitle": { + "post": { + "tags": [ + "Conversations" + ], + "summary": "Post Generate Conversation Title", + "operationId": "post_generate_conversation_title_api_v1_ai_workspaces__workspace_id__chat_conversations__conversation_id__generateTitle_post", + "parameters": [ + { + "name": "workspace_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Workspace Id" + } + }, + { + "name": "conversation_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Conversation Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConversationResponse" + } + } + } + }, + "404": { + "description": "Conversation not found." + }, + "422": { + "description": "Title cannot be generated yet or LLM is not configured." + } + } + } + }, "/api/v1/ai/workspaces/{workspace_id}/chat/conversations/{conversation_id}/items": { "get": { "tags": [ diff --git a/libs/sdk-backend-base/api/sdk-backend-base.api.md b/libs/sdk-backend-base/api/sdk-backend-base.api.md index 860b416afe8..0d4afb3e874 100644 --- a/libs/sdk-backend-base/api/sdk-backend-base.api.md +++ b/libs/sdk-backend-base/api/sdk-backend-base.api.md @@ -822,6 +822,8 @@ export class DummyChatConversations implements IChatConversations { // (undocumented) delete(_conversationId: string): Promise; // (undocumented) + generateTitle(_conversationId: string): Promise; + // (undocumented) getConversation(_conversationId: string): Promise; // (undocumented) getConversationItemsQuery(): IChatConversationItemsQuery; diff --git a/libs/sdk-backend-base/package.json b/libs/sdk-backend-base/package.json index 00a0d7db3c4..fe8d3db4767 100644 --- a/libs/sdk-backend-base/package.json +++ b/libs/sdk-backend-base/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-backend-base", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "description": "GoodData.UI SDK - Base for backend implementations", "license": "MIT", "author": "GoodData", diff --git a/libs/sdk-backend-base/src/dummyBackend/DummyGenAIChatThread.ts b/libs/sdk-backend-base/src/dummyBackend/DummyGenAIChatThread.ts index 23bacdaa281..fc3fc2c30fc 100644 --- a/libs/sdk-backend-base/src/dummyBackend/DummyGenAIChatThread.ts +++ b/libs/sdk-backend-base/src/dummyBackend/DummyGenAIChatThread.ts @@ -154,6 +154,9 @@ export class DummyChatConversations implements IChatConversations { delete(_conversationId: string): Promise { throw new Error("Method not implemented."); } + generateTitle(_conversationId: string): Promise { + throw new Error("Method not implemented."); + } getConversation(_conversationId: string): Promise { throw new Error("Method not implemented."); } diff --git a/libs/sdk-backend-mockingbird/package.json b/libs/sdk-backend-mockingbird/package.json index 0a9e3e56997..a84a2e352e8 100644 --- a/libs/sdk-backend-mockingbird/package.json +++ b/libs/sdk-backend-mockingbird/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-backend-mockingbird", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "description": "Mock GoodData Backend SPI implementation", "license": "MIT", "author": "GoodData", diff --git a/libs/sdk-backend-spi/api/sdk-backend-spi.api.md b/libs/sdk-backend-spi/api/sdk-backend-spi.api.md index 3a1d6b696da..797a3b8c2b4 100644 --- a/libs/sdk-backend-spi/api/sdk-backend-spi.api.md +++ b/libs/sdk-backend-spi/api/sdk-backend-spi.api.md @@ -725,6 +725,7 @@ export type IChatConversationReasoningContent = { export interface IChatConversations { create(): Promise; delete(conversationId: string): Promise; + generateTitle(conversationId: string): Promise; getConversation(conversationId: string): Promise; getConversationItemsQuery(): IChatConversationItemsQuery; getConversationThread(conversationId: string): IChatConversationThread; diff --git a/libs/sdk-backend-spi/package.json b/libs/sdk-backend-spi/package.json index 7f32cd203e7..5ed71d5df7e 100644 --- a/libs/sdk-backend-spi/package.json +++ b/libs/sdk-backend-spi/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-backend-spi", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "description": "GoodData Backend SPI abstraction interfaces", "license": "MIT", "author": "GoodData", diff --git a/libs/sdk-backend-spi/src/workspace/genAI/index.ts b/libs/sdk-backend-spi/src/workspace/genAI/index.ts index 62442d5bf4b..94ad364c471 100644 --- a/libs/sdk-backend-spi/src/workspace/genAI/index.ts +++ b/libs/sdk-backend-spi/src/workspace/genAI/index.ts @@ -740,6 +740,11 @@ export interface IChatConversations { */ delete(conversationId: string): Promise; + /** + * Generate title for a conversation. + */ + generateTitle(conversationId: string): Promise; + /** * Get conversation by id. */ diff --git a/libs/sdk-backend-tiger/package.json b/libs/sdk-backend-tiger/package.json index 325cf53d483..ebaf8544766 100644 --- a/libs/sdk-backend-tiger/package.json +++ b/libs/sdk-backend-tiger/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-backend-tiger", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "description": "GoodData Backend SPI implementation for GoodData Cloud and GoodData.CN", "license": "MIT", "author": "GoodData", diff --git a/libs/sdk-backend-tiger/src/backend/workspace/genAI/ChatConversations.ts b/libs/sdk-backend-tiger/src/backend/workspace/genAI/ChatConversations.ts index 43b5201a3a1..bbf936f11ca 100644 --- a/libs/sdk-backend-tiger/src/backend/workspace/genAI/ChatConversations.ts +++ b/libs/sdk-backend-tiger/src/backend/workspace/genAI/ChatConversations.ts @@ -13,6 +13,7 @@ import { GenAiApi_PatchConversationResponse, GenAiApi_PatchVisualization, GenAiApi_PostConversations, + GenAiApi_PostGenerateConversationTitle, GenAiApi_PostMessages, } from "@gooddata/api-client-tiger/endpoints/genAI"; import { ServerPaging } from "@gooddata/sdk-backend-base"; @@ -95,6 +96,16 @@ export class ChatConversationsService implements IChatConversations { }); } + async generateTitle(conversationId: string): Promise { + return await this.authCall(async (client) => { + const response = await GenAiApi_PostGenerateConversationTitle(client.axios, client.basePath, { + workspaceId: this.workspaceId, + conversationId, + }); + return convertChatConversationFromBackend(response.data); + }); + } + async getConversation(conversationId: string): Promise { return await this.authCall(async (client) => { const response = await GenAiApi_GetConversation(client.axios, client.basePath, { diff --git a/libs/sdk-code-convertors/package.json b/libs/sdk-code-convertors/package.json index 6c5fc8ccc76..2aa18373093 100644 --- a/libs/sdk-code-convertors/package.json +++ b/libs/sdk-code-convertors/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-code-convertors", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "description": "GoodData AAC declarative converters", "license": "MIT", "author": "GoodData", diff --git a/libs/sdk-code-convertors/python/pyproject.toml b/libs/sdk-code-convertors/python/pyproject.toml index a373d098dd6..fae2e217ec0 100644 --- a/libs/sdk-code-convertors/python/pyproject.toml +++ b/libs/sdk-code-convertors/python/pyproject.toml @@ -2,7 +2,7 @@ [project] name = "gooddata-code-convertors" -version = "11.35.0a1" +version = "11.35.0a2" description = "GoodData AAC YAML / Declarative API code converters (WASM-powered)" readme = "README.md" license = { file = "LICENSE" } diff --git a/libs/sdk-code-convertors/python/src/gooddata_code_convertors/_types.py b/libs/sdk-code-convertors/python/src/gooddata_code_convertors/_types.py index 68a97967d29..771dd39cac6 100644 --- a/libs/sdk-code-convertors/python/src/gooddata_code_convertors/_types.py +++ b/libs/sdk-code-convertors/python/src/gooddata_code_convertors/_types.py @@ -1,5 +1,5 @@ # (C) 2026 GoodData Corporation -# schema-hash: 7a407027c7b9eb05b67431df553793f227fddced628421322b5574879979714c +# schema-hash: 33fb2515f2ec8bec50f7549dc1e3ebd5f4f2200351cfb0c55859c029bf46e6d8 from __future__ import annotations diff --git a/libs/sdk-code-convertors/src/__tests__/datasetAggAwareRoundTrip.test.ts b/libs/sdk-code-convertors/src/__tests__/datasetAggAwareRoundTrip.test.ts index a0a3dd76c5f..3abf79b0c3e 100644 --- a/libs/sdk-code-convertors/src/__tests__/datasetAggAwareRoundTrip.test.ts +++ b/libs/sdk-code-convertors/src/__tests__/datasetAggAwareRoundTrip.test.ts @@ -29,9 +29,12 @@ const noEntities: ExportEntities = []; * - one base NORMAL dataset with plain `dataSourceTableId` */ describe("dataset agg-aware round-trip (CQ-2302)", () => { - function roundTrip(declarative: DeclarativeDataset): DeclarativeDataset { + function roundTrip( + declarative: DeclarativeDataset, + entities: ExportEntities = noEntities, + ): DeclarativeDataset { const { json: yamlAsJson } = declarativeDatasetToYaml(declarative, profile, {}); - return yamlDatasetToDeclarative(noEntities, yamlAsJson as Dataset, profile.data_source); + return yamlDatasetToDeclarative(entities, yamlAsJson as Dataset, profile.data_source); } describe("AUXILIARY dataset", () => { @@ -161,7 +164,7 @@ describe("dataset agg-aware round-trip (CQ-2302)", () => { sourceColumnDataType: "HLL", sourceFactReference: { operation: "APPROXIMATE_COUNT", - reference: { id: "users_hll", type: "fact" }, + reference: { id: "country_id", type: "attribute" }, }, }, { @@ -187,18 +190,21 @@ describe("dataset agg-aware round-trip (CQ-2302)", () => { expect(hllFact?.sourceColumnDataType).toBe("HLL"); }); - it("preserves aggregatedFacts[] including APPROXIMATE_COUNT operation", () => { + it("preserves aggregatedFacts[] including APPROXIMATE_COUNT operation against an attribute target", () => { const out = roundTrip(preAgg); const agg = out.aggregatedFacts ?? []; expect(agg).toHaveLength(2); + // APPROXIMATE_COUNT must reference an attribute (gdc-nas CQ-2147 / CommonModel.SourceReferenceOperation.isAllowedForAttribute). const approx = agg.find((f) => f.id === "users_approx"); expect(approx?.sourceFactReference.operation).toBe("APPROXIMATE_COUNT"); - expect(approx?.sourceFactReference.reference.id).toBe("users_hll"); + expect(approx?.sourceFactReference.reference.id).toBe("country_id"); + expect(approx?.sourceFactReference.reference.type).toBe("attribute"); expect(approx?.sourceColumnDataType).toBe("HLL"); const sum = agg.find((f) => f.id === "amount_sum"); expect(sum?.sourceFactReference.operation).toBe("SUM"); + expect(sum?.sourceFactReference.reference.type).toBe("fact"); }); it("preserves the reference to the AUX dataset", () => { @@ -277,4 +283,80 @@ describe("dataset agg-aware round-trip (CQ-2302)", () => { expect(out.sql).toBeUndefined(); }); }); + + /** + * Direct YAML→declarative coverage for an APPROXIMATE_COUNT aggregated fact whose + * `assigned_to` points at an attribute on an AUXILIARY dataset. + * + * Mirrors the canonical xfail repro in gooddata-python-sdk + * (`tests/catalog/unit_tests/test_aac_agg_aware.py + * ::test_pre_aggregation_approximate_count_attribute_target_round_trips`). + * + * Platform contract: gdc-nas CQ-2147 / SourceReferenceOperation.isAllowedForFact() === false + * for APPROXIMATE_COUNT — the only valid `reference.type` is `"attribute"`. + */ + describe("APPROXIMATE_COUNT against attribute target (CQ-2147)", () => { + const aux: Dataset = { + type: "dataset", + id: "orders", + title: "Orders", + dataset_type: "auxiliary", + fields: { + unique_customer: { type: "attribute", title: "Unique Customer", data_type: "STRING" }, + }, + } as Dataset; + + const aac: Dataset = { + type: "dataset", + id: "agg_orders_country_daily", + title: "Agg", + table_path: "agg_orders_country_daily", + data_source: "demo-ds", + dataset_type: "standard", + precedence: 1, + fields: { + "agg_orders_country_daily.unique_customers_hll": { + type: "aggregated_fact", + source_column: "unique_customers_hll", + data_type: "HLL", + aggregated_as: "APPROXIMATE_COUNT", + assigned_to: "attribute/unique_customer", + }, + }, + } as Dataset; + + const entities: ExportEntities = [ + { id: "orders", type: "dataset", path: "orders.yaml", data: aux }, + { id: aac.id, type: "dataset", path: `${aac.id}.yaml`, data: aac }, + ]; + + it("resolves prefixed assigned_to as attribute", () => { + const out = yamlDatasetToDeclarative(entities, aac, "demo-ds"); + const af = out.aggregatedFacts?.[0]; + expect(af?.sourceFactReference.operation).toBe("APPROXIMATE_COUNT"); + expect(af?.sourceColumnDataType).toBe("HLL"); + expect(af?.sourceFactReference.reference.id).toBe("unique_customer"); + expect(af?.sourceFactReference.reference.type).toBe("attribute"); + }); + + it("resolves bare assigned_to as attribute via entities lookup", () => { + const aacBare: Dataset = { + ...aac, + fields: { + "agg_orders_country_daily.unique_customers_hll": { + type: "aggregated_fact", + source_column: "unique_customers_hll", + data_type: "HLL", + aggregated_as: "APPROXIMATE_COUNT", + assigned_to: "unique_customer", + }, + }, + } as Dataset; + + const out = yamlDatasetToDeclarative(entities, aacBare, "demo-ds"); + const af = out.aggregatedFacts?.[0]; + expect(af?.sourceFactReference.reference.id).toBe("unique_customer"); + expect(af?.sourceFactReference.reference.type).toBe("attribute"); + }); + }); }); diff --git a/libs/sdk-code-convertors/src/from/declarativeDatasetToYaml.ts b/libs/sdk-code-convertors/src/from/declarativeDatasetToYaml.ts index d164804254c..9a163b10062 100644 --- a/libs/sdk-code-convertors/src/from/declarativeDatasetToYaml.ts +++ b/libs/sdk-code-convertors/src/from/declarativeDatasetToYaml.ts @@ -212,7 +212,13 @@ function processDeclarativeAggregatedFact( ); map.add(new Pair("aggregated_as", fact.sourceFactReference.operation)); - map.add(new Pair("assigned_to", fact.sourceFactReference.reference.id)); + // Re-emit the `attribute/` prefix so YAML→declarative does not need entities scope to + // recover the type for HLL synopses (gdc-nas CQ-2147). Fact targets stay bare for + // backward compatibility with existing AAC YAML. + const reference = fact.sourceFactReference.reference; + map.add( + new Pair("assigned_to", reference.type === "attribute" ? `attribute/${reference.id}` : reference.id), + ); if (fact.description) { map.add(new Pair("description", fact.description ?? "")); diff --git a/libs/sdk-code-convertors/src/to/yamlDatasetToDeclarative.ts b/libs/sdk-code-convertors/src/to/yamlDatasetToDeclarative.ts index 863a23a3505..5f02c0f9045 100644 --- a/libs/sdk-code-convertors/src/to/yamlDatasetToDeclarative.ts +++ b/libs/sdk-code-convertors/src/to/yamlDatasetToDeclarative.ts @@ -112,7 +112,7 @@ export function yamlDatasetToDeclarative( output.facts = buildFacts(input.fields); output.attributes = buildAttributes(input.fields, input.dataset_type === "auxiliary"); - const aggregatedFacts = buildAggregatedFacts(input.fields); + const aggregatedFacts = buildAggregatedFacts(input.fields, entities); if (aggregatedFacts.length > 0) { output.aggregatedFacts = aggregatedFacts; } @@ -120,6 +120,44 @@ export function yamlDatasetToDeclarative( return output; } +/** + * Resolve `assigned_to` on an aggregated fact to a (id, type) pair. + * + * `assigned_to` is a free-form string in AAC YAML. It can be either: + * - prefixed (`attribute/` or `fact/`) — type is taken from the prefix and `` is returned bare, + * - bare (``) — type is inferred by searching `entities` for an `attribute` field with a matching id; + * falls back to `"fact"` if no such attribute is found. + * + * Platform contract (gdc-nas CQ-2147 / SourceReferenceOperation): `APPROXIMATE_COUNT` requires + * `type === "attribute"`, `SUM` requires `type === "fact"`. We do not enforce that here — the + * server validates and rejects mismatches — but correct type resolution is what makes the + * payload acceptable in the first place. + */ +function resolveAggregatedFactReference( + assignedTo: string, + entities: ExportEntities, +): { id: string; type: "attribute" | "fact" } { + const slashIndex = assignedTo.indexOf("/"); + if (slashIndex > 0) { + const prefix = assignedTo.slice(0, slashIndex); + const id = assignedTo.slice(slashIndex + 1); + if (prefix === "attribute" || prefix === "fact") { + return { id, type: prefix }; + } + } + + const isAttribute = entities.some((entity) => { + if (entity.type !== "dataset") { + return false; + } + const fields = (entity.data as Dataset).fields; + const field = fields?.[assignedTo]; + return field !== undefined && (field as Attribute).type === "attribute"; + }); + + return { id: assignedTo, type: isAttribute ? "attribute" : "fact" }; +} + /** * Build `grain` declarative prop. */ @@ -275,7 +313,10 @@ export function buildFacts(fields?: Fields): DeclarativeFact[] { /** * Build declarative aggregated facts out of AaC fields */ -export function buildAggregatedFacts(fields?: Fields): DeclarativeAggregatedFact[] { +export function buildAggregatedFacts( + fields?: Fields, + entities: ExportEntities = [], +): DeclarativeAggregatedFact[] { if (!fields) { return []; } @@ -286,16 +327,15 @@ export function buildAggregatedFacts(fields?: Fields): DeclarativeAggregatedFact return null; } + const reference = resolveAggregatedFactReference(field.assigned_to, entities); + const output: DeclarativeAggregatedFact = { id, sourceColumn: field.source_column ?? id, sourceColumnDataType: field.data_type, sourceFactReference: { operation: field.aggregated_as, - reference: { - id: field.assigned_to, - type: "fact", - }, + reference, }, }; diff --git a/libs/sdk-code-schemas/api/sdk-code-schemas.api.md b/libs/sdk-code-schemas/api/sdk-code-schemas.api.md index 33205b3fc06..a62f02a9c4c 100644 --- a/libs/sdk-code-schemas/api/sdk-code-schemas.api.md +++ b/libs/sdk-code-schemas/api/sdk-code-schemas.api.md @@ -5293,10 +5293,6 @@ export const metadata_v1: { using: { $ref: string; description: string; - $semantic: { - type: string; - source: string; - }; }; conditions: { type: string; diff --git a/libs/sdk-code-schemas/package.json b/libs/sdk-code-schemas/package.json index 71987d282ee..d279cb989b1 100644 --- a/libs/sdk-code-schemas/package.json +++ b/libs/sdk-code-schemas/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-code-schemas", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "description": "GoodData AAC JSON Schema types and compiled schemas", "license": "MIT", "author": "GoodData", diff --git a/libs/sdk-code-schemas/schemas/v1/src/common.json b/libs/sdk-code-schemas/schemas/v1/src/common.json index 1ce8d5cce61..67b1fee3559 100644 --- a/libs/sdk-code-schemas/schemas/v1/src/common.json +++ b/libs/sdk-code-schemas/schemas/v1/src/common.json @@ -1071,11 +1071,7 @@ }, "using": { "$ref": "#/$defs/metricIdentifier", - "description": "Reference to the metric being filtered.", - "$semantic": { - "type": "reference", - "source": "metric.id" - } + "description": "Reference to the metric being filtered." }, "conditions": { "type": "array", diff --git a/libs/sdk-code-schemas/schemas/v1/src/dashboard.json b/libs/sdk-code-schemas/schemas/v1/src/dashboard.json index b6bcf81a467..c37475853f3 100644 --- a/libs/sdk-code-schemas/schemas/v1/src/dashboard.json +++ b/libs/sdk-code-schemas/schemas/v1/src/dashboard.json @@ -301,10 +301,10 @@ "description": "A list of dashboard filters to be ignored for this widget", "items": { "type": "string", - "description": "An id of the dashboard label, attribute or date to be ignored", + "description": "An id of the dashboard label, attribute, metric or date to be ignored", "$semantic": { "type": "reference", - "sources": ["dateDataset.id", "attribute.id", "label.id"], + "sources": ["dateDataset.id", "attribute.id", "label.id", "metric.id"], "typePrefix": true } } diff --git a/libs/sdk-code-schemas/src/v1/metadata.json b/libs/sdk-code-schemas/src/v1/metadata.json index b6d8a9d9824..d0f7b5720a8 100644 --- a/libs/sdk-code-schemas/src/v1/metadata.json +++ b/libs/sdk-code-schemas/src/v1/metadata.json @@ -3465,8 +3465,7 @@ }, "using": { "$ref": "#/$defs/metricIdentifier", - "description": "Reference to the metric being filtered.", - "$semantic": { "type": "reference", "source": "metric.id" } + "description": "Reference to the metric being filtered." }, "conditions": { "type": "array", @@ -4974,10 +4973,10 @@ "description": "A list of dashboard filters to be ignored for this widget", "items": { "type": "string", - "description": "An id of the dashboard label, attribute or date to be ignored", + "description": "An id of the dashboard label, attribute, metric or date to be ignored", "$semantic": { "type": "reference", - "sources": ["dateDataset.id", "attribute.id", "label.id"], + "sources": ["dateDataset.id", "attribute.id", "label.id", "metric.id"], "typePrefix": true } } diff --git a/libs/sdk-e2e-utils/package.json b/libs/sdk-e2e-utils/package.json index d21b975f70c..692a0dec734 100644 --- a/libs/sdk-e2e-utils/package.json +++ b/libs/sdk-e2e-utils/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-e2e-utils", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "description": "GoodData utility functions for Playwright E2E tests", "license": "MIT", "author": "GoodData", diff --git a/libs/sdk-embedding/package.json b/libs/sdk-embedding/package.json index 658c67447d8..557f63ae6a2 100644 --- a/libs/sdk-embedding/package.json +++ b/libs/sdk-embedding/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-embedding", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "description": "GoodData Embedding APIs", "license": "MIT", "author": "GoodData", diff --git a/libs/sdk-model/package.json b/libs/sdk-model/package.json index 22b7a896bcb..3a65ca0cdb3 100644 --- a/libs/sdk-model/package.json +++ b/libs/sdk-model/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-model", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "description": "GoodData Model definitions used by UI components and Backend SPI", "license": "MIT", "author": "GoodData", diff --git a/libs/sdk-pluggable-application-model/package.json b/libs/sdk-pluggable-application-model/package.json index 0307f94b80d..2abdaee83a2 100644 --- a/libs/sdk-pluggable-application-model/package.json +++ b/libs/sdk-pluggable-application-model/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-pluggable-application-model", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "description": "GoodData SDK model contracts for pluggable applications", "license": "MIT", "author": "GoodData Corporation", diff --git a/libs/sdk-ui-all/package.json b/libs/sdk-ui-all/package.json index badaa806dcb..6528bb17a41 100644 --- a/libs/sdk-ui-all/package.json +++ b/libs/sdk-ui-all/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-ui-all", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "description": "GoodData SDK - All-In-One", "license": "LicenseRef-LICENSE", "author": "GoodData", diff --git a/libs/sdk-ui-catalog/package.json b/libs/sdk-ui-catalog/package.json index 5588475ed91..0a91b2a0bee 100644 --- a/libs/sdk-ui-catalog/package.json +++ b/libs/sdk-ui-catalog/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-ui-catalog", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "description": "GoodData SDK - Analytics Catalog", "license": "MIT", "author": "GoodData Corporation", diff --git a/libs/sdk-ui-charts/package.json b/libs/sdk-ui-charts/package.json index 5d5cbeb9401..bc976cb1eec 100644 --- a/libs/sdk-ui-charts/package.json +++ b/libs/sdk-ui-charts/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-ui-charts", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "description": "GoodData.UI SDK - Charts", "license": "LicenseRef-LICENSE", "author": "GoodData Corporation", diff --git a/libs/sdk-ui-dashboard/package.json b/libs/sdk-ui-dashboard/package.json index 8be09230b56..800016313ff 100644 --- a/libs/sdk-ui-dashboard/package.json +++ b/libs/sdk-ui-dashboard/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-ui-dashboard", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "description": "GoodData SDK - Dashboard Component", "license": "LicenseRef-LICENSE", "author": "GoodData Corporation", diff --git a/libs/sdk-ui-ext/package.json b/libs/sdk-ui-ext/package.json index ca0f0965bfa..a1e24fc5035 100644 --- a/libs/sdk-ui-ext/package.json +++ b/libs/sdk-ui-ext/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-ui-ext", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "description": "GoodData.UI SDK - Extensions", "license": "LicenseRef-LICENSE", "author": "GoodData Corporation", diff --git a/libs/sdk-ui-filters/package.json b/libs/sdk-ui-filters/package.json index 988075a1dbc..e4d962010a6 100644 --- a/libs/sdk-ui-filters/package.json +++ b/libs/sdk-ui-filters/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-ui-filters", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "description": "GoodData.UI SDK - Filter Components", "license": "MIT", "author": "GoodData Corporation", diff --git a/libs/sdk-ui-gen-ai/package.json b/libs/sdk-ui-gen-ai/package.json index e2025dd4eb9..528c5211e74 100644 --- a/libs/sdk-ui-gen-ai/package.json +++ b/libs/sdk-ui-gen-ai/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-ui-gen-ai", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "description": "GoodData GenAI SDK", "license": "MIT", "author": "GoodData Corporation", diff --git a/libs/sdk-ui-gen-ai/src/components/ConversationDeleteDialog.tsx b/libs/sdk-ui-gen-ai/src/components/ConversationDeleteDialog.tsx index 285e6db8c4e..678453c7b2a 100644 --- a/libs/sdk-ui-gen-ai/src/components/ConversationDeleteDialog.tsx +++ b/libs/sdk-ui-gen-ai/src/components/ConversationDeleteDialog.tsx @@ -5,9 +5,9 @@ import { useMemo } from "react"; import { FormattedMessage, defineMessages, useIntl } from "react-intl"; import { useSelector } from "react-redux"; -import { type IChatConversation } from "@gooddata/sdk-backend-spi"; import { ConfirmDialog } from "@gooddata/sdk-ui-kit"; +import { type IChatConversationLocal } from "../model.js"; import { catalogItemsSelector } from "../store/chatWindow/chatWindowSelectors.js"; import { generateTemporaryTitle } from "../utils.js"; import { collectReferences, replaceReferences } from "./completion/references.js"; @@ -20,7 +20,7 @@ const messages = defineMessages({ }); type ConversationDeleteDialogProps = { - conversation: IChatConversation; + conversation: IChatConversationLocal; onDelete: () => void; onClose: () => void; }; diff --git a/libs/sdk-ui-gen-ai/src/components/GenAIChatConversations.tsx b/libs/sdk-ui-gen-ai/src/components/GenAIChatConversations.tsx index cb946ba2a72..a86c9a11e5e 100644 --- a/libs/sdk-ui-gen-ai/src/components/GenAIChatConversations.tsx +++ b/libs/sdk-ui-gen-ai/src/components/GenAIChatConversations.tsx @@ -6,7 +6,6 @@ import cx from "classnames"; import { FormattedMessage, useIntl } from "react-intl"; import { connect, useSelector } from "react-redux"; -import { type IChatConversation } from "@gooddata/sdk-backend-spi"; import { DefaultUiMenuInteractiveItemWrapper, type IUiMenuItem, @@ -16,6 +15,7 @@ import { UiMenu, } from "@gooddata/sdk-ui-kit"; +import { type IChatConversationLocal } from "../model.js"; import { catalogItemsSelector } from "../store/chatWindow/chatWindowSelectors.js"; import { setHistoryAction } from "../store/chatWindow/chatWindowSlice.js"; import { conversationSelector, conversationsSelector } from "../store/messages/messagesSelectors.js"; @@ -54,7 +54,7 @@ function GenAIChatConversationsComponent({ const { isFullscreen, isSmallScreen } = useFullscreenCheck(); const { isHistory } = useHistoryCheck(); - const [conversationToDelete, setConversationToDelete] = useState(); + const [conversationToDelete, setConversationToDelete] = useState(); const catalogItems = useSelector(catalogItemsSelector); @@ -106,7 +106,7 @@ function GenAIChatConversationsComponent({ }, []); const handleSelect = useCallback( - (conversation: IChatConversation) => { + (conversation: IChatConversationLocal) => { loadConversation({ conversation }); setHistory({ isHistory: false }); }, @@ -130,8 +130,11 @@ function GenAIChatConversationsComponent({ node={ref.current} showBackdrop={false} header={ -
- {intl.formatMessage({ id: "gd.gen-ai.conversations.title" })} +
+
+ {intl.formatMessage({ id: "gd.gen-ai.conversations.title" })} +
+
} closeLabel={intl.formatMessage({ id: "gd.gen-ai.conversations.close-label" })} @@ -144,7 +147,6 @@ function GenAIChatConversationsComponent({ "gd-gen-ai-chat__window__conversations--isSmallScreen": isSmallScreen, })} > -
{(conversations ?? []).length === 0 ? (
@@ -164,17 +166,17 @@ function GenAIChatConversationsComponent({ if (event.key === "Delete" && focusedItem) { event.preventDefault(); event.stopPropagation(); - setConversationToDelete(focusedItem.data as IChatConversation); + setConversationToDelete(focusedItem.data as IChatConversationLocal); } }} InteractiveItemWrapper={(props) => { - const data = props.item.data as IChatConversation; + const data = props.item.data as IChatConversationLocal; return (
@@ -192,7 +194,7 @@ function GenAIChatConversationsComponent({ }} items={menuItems} onSelect={(item, event) => { - handleSelect(item.data as IChatConversation); + handleSelect(item.data as IChatConversationLocal); event.stopPropagation(); event.preventDefault(); }} diff --git a/libs/sdk-ui-gen-ai/src/components/utils/conversationGrouper.ts b/libs/sdk-ui-gen-ai/src/components/utils/conversationGrouper.ts index ca02da58ca4..ac261615221 100644 --- a/libs/sdk-ui-gen-ai/src/components/utils/conversationGrouper.ts +++ b/libs/sdk-ui-gen-ai/src/components/utils/conversationGrouper.ts @@ -1,6 +1,6 @@ // (C) 2026 GoodData Corporation -import { type IChatConversation } from "@gooddata/sdk-backend-spi"; +import { type IChatConversationLocal } from "../../model.js"; export enum ConversationDateGroup { TODAY = "TODAY", @@ -15,7 +15,7 @@ export type ConversationDateGroupConfig = { export type ConversationDateBucket = { group: ConversationDateGroup; - conversations: IChatConversation[]; + conversations: IChatConversationLocal[]; }; export const DEFAULT_CONVERSATION_DATE_GROUPS: ConversationDateGroupConfig[] = [ @@ -25,11 +25,11 @@ export const DEFAULT_CONVERSATION_DATE_GROUPS: ConversationDateGroupConfig[] = [ ]; export function groupConversationsByDate( - conversations: IChatConversation[] | undefined = [], + conversations: IChatConversationLocal[] | undefined = [], groups: ConversationDateGroupConfig[] = DEFAULT_CONVERSATION_DATE_GROUPS, now: Date = new Date(), ): ConversationDateBucket[] { - const conversationGroups = new Map(); + const conversationGroups = new Map(); groups.forEach((group) => { conversationGroups.set(group.group, []); diff --git a/libs/sdk-ui-gen-ai/src/components/utils/tests/conversationGrouper.test.ts b/libs/sdk-ui-gen-ai/src/components/utils/tests/conversationGrouper.test.ts index 38f6d50e6fa..03de1de8395 100644 --- a/libs/sdk-ui-gen-ai/src/components/utils/tests/conversationGrouper.test.ts +++ b/libs/sdk-ui-gen-ai/src/components/utils/tests/conversationGrouper.test.ts @@ -2,15 +2,14 @@ import { describe, expect, it } from "vitest"; -import { type IChatConversation } from "@gooddata/sdk-backend-spi"; - +import { type IChatConversationLocal } from "../../../model.js"; import { ConversationDateGroup, type ConversationDateGroupConfig, groupConversationsByDate, } from "../conversationGrouper.js"; -function createConversation(id: string, updatedAt: string): IChatConversation { +function createConversation(id: string, updatedAt: string): IChatConversationLocal { return { id, createdAt: updatedAt, @@ -29,7 +28,7 @@ describe("groupConversationsByDate", () => { const now = new Date("2026-05-04T09:24:00.000Z"); it("should group conversations to default date buckets", () => { - const conversations: IChatConversation[] = [ + const conversations: IChatConversationLocal[] = [ createConversation("today", daysAgoIso(0, now)), createConversation("last-7-days", daysAgoIso(3, now)), createConversation("older", daysAgoIso(14, now)), @@ -52,7 +51,7 @@ describe("groupConversationsByDate", () => { }); it("should use custom configuration and preserve configured order", () => { - const conversations: IChatConversation[] = [ + const conversations: IChatConversationLocal[] = [ createConversation("older", daysAgoIso(5, now)), createConversation("newer", daysAgoIso(0, now)), ]; diff --git a/libs/sdk-ui-gen-ai/src/model.ts b/libs/sdk-ui-gen-ai/src/model.ts index e3f3458017e..486f1f9ea21 100644 --- a/libs/sdk-ui-gen-ai/src/model.ts +++ b/libs/sdk-ui-gen-ai/src/model.ts @@ -3,6 +3,7 @@ import { v4 as uuidv4 } from "uuid"; import { + type IChatConversation, type IChatConversationContent, type IChatConversationError, type IChatConversationItem, @@ -347,6 +348,14 @@ export const makeAssistantMessage = ( */ export type Message = UserMessage | AssistantMessage; +/** + * Represents a local chat conversation that extends the base `IChatConversation` type. + * Includes additional optional properties specific to the local context. + */ +export type IChatConversationLocal = IChatConversation & { + generatingTitle?: boolean; +}; + /** * Chat conversation item with local ID. * @public diff --git a/libs/sdk-ui-gen-ai/src/store/messages/messagesSelectors.ts b/libs/sdk-ui-gen-ai/src/store/messages/messagesSelectors.ts index 2b3dd51a7b5..35625f210d2 100644 --- a/libs/sdk-ui-gen-ai/src/store/messages/messagesSelectors.ts +++ b/libs/sdk-ui-gen-ai/src/store/messages/messagesSelectors.ts @@ -2,9 +2,7 @@ import { createSelector } from "@reduxjs/toolkit"; -import { type IChatConversation } from "@gooddata/sdk-backend-spi"; - -import { type IChatConversationLocalItem, type Message } from "../../model.js"; +import { type IChatConversationLocal, type IChatConversationLocalItem, type Message } from "../../model.js"; import { type RootState } from "../types.js"; import { messagesSliceName } from "./messagesSlice.js"; @@ -53,19 +51,17 @@ export const conversationMessagesSelector: (state: RootState) => IChatConversati state.conversationItemsOrder.map((id) => state.conversationItems[id]), ); -export const conversationSelector: (state: RootState) => IChatConversation | "new" | undefined = +export const conversationSelector: (state: RootState) => IChatConversationLocal | "new" | undefined = createSelector(messagesSliceSelector, (state) => state.currentConversation); export const conversationByIdSelector: ( state: RootState, conversationId: string, -) => IChatConversation | undefined = createSelector( +) => IChatConversationLocal | undefined = createSelector( [messagesSliceSelector, (_state: RootState, conversationId: string) => conversationId], (state, conversationId) => state.conversations?.find((conversation) => conversation.id === conversationId), ); -export const conversationsSelector: (state: RootState) => IChatConversation[] | undefined = createSelector( - messagesSliceSelector, - (state) => state.conversations, -); +export const conversationsSelector: (state: RootState) => IChatConversationLocal[] | undefined = + createSelector(messagesSliceSelector, (state) => state.conversations); diff --git a/libs/sdk-ui-gen-ai/src/store/messages/messagesSlice.ts b/libs/sdk-ui-gen-ai/src/store/messages/messagesSlice.ts index 1168018dfaf..3d2e44b826c 100644 --- a/libs/sdk-ui-gen-ai/src/store/messages/messagesSlice.ts +++ b/libs/sdk-ui-gen-ai/src/store/messages/messagesSlice.ts @@ -2,11 +2,7 @@ import { type PayloadAction, type Reducer, createSlice } from "@reduxjs/toolkit"; -import { - type IChatConversation, - type IChatConversationItem, - type IChatSuggestionsItem, -} from "@gooddata/sdk-backend-spi"; +import { type IChatConversationItem, type IChatSuggestionsItem } from "@gooddata/sdk-backend-spi"; import { type GenAIChatInteractionUserFeedback } from "@gooddata/sdk-model"; import { type SdkErrorType } from "@gooddata/sdk-ui"; @@ -14,6 +10,7 @@ import { type AssistantMessage, type Contents, type IChatConversationErrorContent, + type IChatConversationLocal, type IChatConversationLocalContent, type IChatConversationLocalItem, type IChatConversationMultipartLocalPart, @@ -49,13 +46,13 @@ type MessagesSliceState = { /** * A list of conversations. */ - conversations: IChatConversation[] | undefined; + conversations: IChatConversationLocal[] | undefined; /** * The current conversation. * - "new": indicates UI is in a transient state to start a brand new conversation. * - undefined: no conversation is selected */ - currentConversation: IChatConversation | "new" | undefined; + currentConversation: IChatConversationLocal | "new" | undefined; /** * Conversation items. */ @@ -129,13 +126,13 @@ const setNormalizedMessages = (state: MessagesSliceState, messages: Message[]) = state.loaded = true; }; -const setNormalizedConversations = (state: MessagesSliceState, conversations: IChatConversation[]) => { +const setNormalizedConversations = (state: MessagesSliceState, conversations: IChatConversationLocal[]) => { state.conversations = conversations; }; const setNormalizedConversation = ( state: MessagesSliceState, - conversation: IChatConversation | "new", + conversation: IChatConversationLocal | "new", items: IChatConversationLocalItem[] = [], ) => { state.currentConversation = conversation; @@ -272,7 +269,7 @@ const messagesSlice = createSlice({ { payload: { conversations }, }: PayloadAction<{ - conversations: IChatConversation[]; + conversations: IChatConversationLocal[]; }>, ) => { setNormalizedConversations(state, conversations); @@ -282,7 +279,7 @@ const messagesSlice = createSlice({ { payload: { currentConversation, conversationItems, threadId }, }: PayloadAction<{ - currentConversation: IChatConversation | "new"; + currentConversation: IChatConversationLocal | "new"; conversationItems: IChatConversationLocalItem[]; threadId?: string; }>, @@ -309,7 +306,7 @@ const messagesSlice = createSlice({ state, { payload: { conversation, threadId }, - }: PayloadAction<{ conversation: IChatConversation; threadId: string }>, + }: PayloadAction<{ conversation: IChatConversationLocal; threadId: string }>, ) => { state.conversations = [conversation, ...(state.conversations ?? [])]; state.currentConversation = conversation; @@ -454,13 +451,41 @@ const messagesSlice = createSlice({ userMessage.id = payload.interactionId ?? userMessage.id; } }, + evaluateConversationTitleAction: ( + state, + { + payload, + }: PayloadAction<{ + conversation: IChatConversationLocal; + generatingTitle: boolean; + title?: string; + }>, + ) => { + const conversation = state.conversations?.find((c) => c.id === payload.conversation.id); + if (!conversation) { + return; + } + + conversation.generatingTitle = payload.generatingTitle; + if (payload.title) { + conversation.title = payload.title; + if ( + state.currentConversation !== "new" && + state.currentConversation?.id === conversation.id + ) { + state.currentConversation.title = payload.title; + } + } + }, evaluateMessageUpdateAction: ( state, { payload, }: PayloadAction<{ userMessageId: string; + conversation: IChatConversationLocal; message: IChatConversationItem | UserMessage; + isStartMessage: boolean; interactionId?: string; }>, ) => { @@ -539,7 +564,7 @@ const messagesSlice = createSlice({ }, setCurrentConversationAction: ( state, - { payload }: PayloadAction<{ conversation: IChatConversation }>, + { payload }: PayloadAction<{ conversation: IChatConversationLocal }>, ) => { const existing = state.conversations?.find((c) => c.id === payload.conversation.id); @@ -875,7 +900,7 @@ const messagesSlice = createSlice({ deleteConversationSuccessAction: (state, _action: PayloadAction<{ conversationId: string }>) => state, deleteConversationFailureAction: ( state, - { payload }: PayloadAction<{ conversation: IChatConversation; error: Error }>, + { payload }: PayloadAction<{ conversation: IChatConversationLocal; error: Error }>, ) => { state.conversations = [...(state.conversations ?? []), payload.conversation]; }, @@ -896,6 +921,7 @@ export const { clearThreadErrorAction, clearThreadSuccessAction, clearConversationSuccessAction, + evaluateConversationTitleAction, evaluateMessageAction, evaluateMessageErrorAction, evaluateMessageStreamingAction, diff --git a/libs/sdk-ui-gen-ai/src/store/sideEffects/index.ts b/libs/sdk-ui-gen-ai/src/store/sideEffects/index.ts index 252974872e7..9ae1b02fd18 100644 --- a/libs/sdk-ui-gen-ai/src/store/sideEffects/index.ts +++ b/libs/sdk-ui-gen-ai/src/store/sideEffects/index.ts @@ -5,6 +5,7 @@ import { call, fork, takeEvery, takeLatest } from "redux-saga/effects"; import { clearThreadAction, deleteConversationAction, + evaluateMessageUpdateAction, loadThreadAction, newMessageAction, saveVisualisationRenderStatusAction, @@ -22,6 +23,7 @@ import { onThreadClear } from "./onThreadClear.js"; import { onThreadLoad } from "./onThreadLoad.js"; import { onUserFeedback } from "./onUserFeedback.js"; import { onUserMessage } from "./onUserMessage.js"; +import { onUserMessageUpdate } from "./onUserMessageUpdate.js"; import { onVerboseStore } from "./onVerboseStore.js"; import { onVisualisationRender } from "./onVisualisationRender.js"; import { onVisualizationSave } from "./onVisualizationSave.js"; @@ -42,6 +44,7 @@ export function* rootSaga() { yield takeEvery(saveVisualisationRenderStatusAction.type, onVisualisationRender); //conversations API yield takeEvery(deleteConversationAction.type, onConversationDelete); + yield takeEvery(evaluateMessageUpdateAction.type, onUserMessageUpdate); //others yield takeEvery(setVerboseAction.type, onVerboseStore); yield fork(onEvent); diff --git a/libs/sdk-ui-gen-ai/src/store/sideEffects/onConversationDelete.ts b/libs/sdk-ui-gen-ai/src/store/sideEffects/onConversationDelete.ts index a3875790f02..a85b6165ed9 100644 --- a/libs/sdk-ui-gen-ai/src/store/sideEffects/onConversationDelete.ts +++ b/libs/sdk-ui-gen-ai/src/store/sideEffects/onConversationDelete.ts @@ -3,8 +3,9 @@ import { type PayloadAction } from "@reduxjs/toolkit"; import { call, getContext, put, select } from "redux-saga/effects"; -import { type IAnalyticalBackend, type IChatConversation } from "@gooddata/sdk-backend-spi"; +import { type IAnalyticalBackend } from "@gooddata/sdk-backend-spi"; +import { type IChatConversationLocal } from "../../model.js"; import { conversationByIdSelector } from "../messages/messagesSelectors.js"; import { deleteConversationFailureAction, @@ -21,7 +22,7 @@ export function* onConversationDelete({ const backend: IAnalyticalBackend = yield getContext("backend"); const workspace: string = yield getContext("workspace"); - const conversation: IChatConversation | undefined = yield select( + const conversation: IChatConversationLocal | undefined = yield select( conversationByIdSelector, payload.conversationId, ); diff --git a/libs/sdk-ui-gen-ai/src/store/sideEffects/onThreadClear.ts b/libs/sdk-ui-gen-ai/src/store/sideEffects/onThreadClear.ts index 3b3ce0c3165..59ce5b8a08e 100644 --- a/libs/sdk-ui-gen-ai/src/store/sideEffects/onThreadClear.ts +++ b/libs/sdk-ui-gen-ai/src/store/sideEffects/onThreadClear.ts @@ -2,12 +2,9 @@ import { call, getContext, put, race, select, take } from "redux-saga/effects"; -import { - type IAnalyticalBackend, - type IChatConversation, - type IUserWorkspaceSettings, -} from "@gooddata/sdk-backend-spi"; +import { type IAnalyticalBackend, type IUserWorkspaceSettings } from "@gooddata/sdk-backend-spi"; +import { type IChatConversationLocal } from "../../model.js"; import { settingsSelector } from "../chatWindow/chatWindowSelectors.js"; import { threadIdSelector } from "../messages/messagesSelectors.js"; import { @@ -71,7 +68,7 @@ function* resetConversation() { .getChatConversations({ isPreview }) .getConversationThread(conversationId); - const [results, cancelAction]: [results: IChatConversation, ReturnType] = + const [results, cancelAction]: [results: IChatConversationLocal, ReturnType] = yield race([call(chatThread.reset.bind(chatThread)), take(cancelAsyncAction.type)]); if (cancelAction) { diff --git a/libs/sdk-ui-gen-ai/src/store/sideEffects/onThreadLoad.ts b/libs/sdk-ui-gen-ai/src/store/sideEffects/onThreadLoad.ts index 313519b8e7f..6af321b76d0 100644 --- a/libs/sdk-ui-gen-ai/src/store/sideEffects/onThreadLoad.ts +++ b/libs/sdk-ui-gen-ai/src/store/sideEffects/onThreadLoad.ts @@ -4,7 +4,6 @@ import { call, cancelled, getContext, put, race, select, take } from "redux-saga import { type IAnalyticalBackend, - type IChatConversation, type IChatConversationItem, type IChatConversationItemsQueryResult, type IChatConversationThread, @@ -16,6 +15,7 @@ import { import { type AssistantMessage, type Contents, + type IChatConversationLocal, type Message, isAssistantMessage, isSemanticSearchContents, @@ -163,7 +163,7 @@ function* fetchAllConversations() { const workspace: string = yield getContext("workspace"); const isPreview: boolean | undefined = yield getContext("isPreview"); - const conversations: IChatConversation[] | undefined = yield select(conversationsSelector); + const conversations: IChatConversationLocal[] | undefined = yield select(conversationsSelector); // Already loaded if (conversations) { @@ -197,8 +197,8 @@ function* fetchCurrentConversation() { const workspace: string = yield getContext("workspace"); const isPreview: boolean | undefined = yield getContext("isPreview"); - const conversations: IChatConversation[] | undefined = yield select(conversationsSelector); - const conversation: "new" | IChatConversation | undefined = yield select(conversationSelector); + const conversations: IChatConversationLocal[] | undefined = yield select(conversationsSelector); + const conversation: "new" | IChatConversationLocal | undefined = yield select(conversationSelector); // New conversation selected if (conversation === "new") { diff --git a/libs/sdk-ui-gen-ai/src/store/sideEffects/onUserFeedback.ts b/libs/sdk-ui-gen-ai/src/store/sideEffects/onUserFeedback.ts index d33f61e838e..9a90bb6446c 100644 --- a/libs/sdk-ui-gen-ai/src/store/sideEffects/onUserFeedback.ts +++ b/libs/sdk-ui-gen-ai/src/store/sideEffects/onUserFeedback.ts @@ -3,14 +3,10 @@ import { type PayloadAction } from "@reduxjs/toolkit"; import { getContext, put, select } from "redux-saga/effects"; -import { - type IAnalyticalBackend, - type IChatConversation, - type IUserWorkspaceSettings, -} from "@gooddata/sdk-backend-spi"; +import { type IAnalyticalBackend, type IUserWorkspaceSettings } from "@gooddata/sdk-backend-spi"; import { type GenAIChatInteractionUserFeedback } from "@gooddata/sdk-model"; -import { type IChatConversationLocalItem, type Message } from "../../model.js"; +import { type IChatConversationLocal, type IChatConversationLocalItem, type Message } from "../../model.js"; import { settingsSelector } from "../chatWindow/chatWindowSelectors.js"; import { conversationMessagesSelector, @@ -39,7 +35,7 @@ export function* onUserFeedback({ if (settings?.enableAiAgenticConversations) { try { - const conversation: IChatConversation = yield select(conversationSelector); + const conversation: IChatConversationLocal = yield select(conversationSelector); const messages: IChatConversationLocalItem[] = yield select(conversationMessagesSelector); const message = messages.find((message) => message.localId === payload.assistantMessageId); diff --git a/libs/sdk-ui-gen-ai/src/store/sideEffects/onUserMessage.ts b/libs/sdk-ui-gen-ai/src/store/sideEffects/onUserMessage.ts index 5c5b69ebefe..5dc7248427b 100644 --- a/libs/sdk-ui-gen-ai/src/store/sideEffects/onUserMessage.ts +++ b/libs/sdk-ui-gen-ai/src/store/sideEffects/onUserMessage.ts @@ -4,7 +4,6 @@ import { call, cancel, cancelled, getContext, put, select } from "redux-saga/eff import { type IAnalyticalBackend, - type IChatConversation, type IChatConversationError, type IChatConversationItem, type IChatConversationThreadQuery, @@ -24,6 +23,7 @@ import { import { type AssistantMessage, + type IChatConversationLocal, type IChatConversationLocalItem, type Message, isChatConversationLocalItem, @@ -64,7 +64,7 @@ import { convertMessageToChatConversation, extractError } from "./utils.js"; * @internal */ export function* onUserMessage({ payload }: ReturnType) { - const conversation: IChatConversation | "new" | undefined = yield select(conversationSelector); + const conversation: IChatConversationLocal | "new" | undefined = yield select(conversationSelector); let message = payload; if (conversation && !isChatConversationLocalItem(message)) { @@ -300,7 +300,8 @@ function* conversationUserMessage(message: IChatConversationLocalItem) { const isPreview: boolean | undefined = yield getContext("isPreview"); // Check current conversation - const conversationState: IChatConversation | "new" | undefined = yield select(conversationSelector); + const conversationState: IChatConversationLocal | "new" | undefined = + yield select(conversationSelector); // Check state if (conversationState !== "new" && !conversationState?.id) { @@ -313,12 +314,12 @@ function* conversationUserMessage(message: IChatConversationLocalItem) { // Set evaluation state in store yield put(evaluateMessageAction({ message: initialAssistantMessage })); - let conversation: IChatConversation; + let conversation: IChatConversationLocal; // If we are in the transient new-conversation state, create the conversation first if (conversationState === "new") { const api = backend.workspace(workspace).genAI().getChatConversations({ isPreview }); - const created: IChatConversation = yield call(api.create.bind(api)); - const updated: IChatConversation = yield call(api.update.bind(api), created.id, { + const created: IChatConversationLocal = yield call(api.create.bind(api)); + const updated: IChatConversationLocal = yield call(api.update.bind(api), created.id, { title: generateTitleFromQuestion(message.content.text), }); // Store it as current conversation and clear the transient flag @@ -341,9 +342,11 @@ function* conversationUserMessage(message: IChatConversationLocalItem) { // multiple interaction IDs. It returns the last message that needs to be completed. const result: EvaluateUserConversationMessageResult = yield call( evaluateUserConversationMessage, + conversation, message, initialAssistantMessage, chatThreadQuery, + conversationState === "new", ); lastAssistantMessage = result.lastAssistantMessage; } catch (e) { @@ -393,9 +396,11 @@ type EvaluateUserConversationMessageResult = { }; function* evaluateUserConversationMessage( + conversation: IChatConversationLocal, userMessage: IChatConversationLocalItem, assistantMessage: IChatConversationLocalItem, preparedChatThread: IChatConversationThreadQuery, + isStartMessage: boolean, ) { let reader: | ReadableStreamReader @@ -450,9 +455,11 @@ function* evaluateUserConversationMessage( if (value.role === "user" && currentUserMessage) { yield put( evaluateMessageUpdateAction({ + conversation, userMessageId: currentUserMessage.localId, interactionId: value.id, message: value, + isStartMessage, }), ); //reset diff --git a/libs/sdk-ui-gen-ai/src/store/sideEffects/onUserMessageUpdate.ts b/libs/sdk-ui-gen-ai/src/store/sideEffects/onUserMessageUpdate.ts new file mode 100644 index 00000000000..46e8c8301eb --- /dev/null +++ b/libs/sdk-ui-gen-ai/src/store/sideEffects/onUserMessageUpdate.ts @@ -0,0 +1,70 @@ +// (C) 2026 GoodData Corporation + +import { call, getContext, put, select } from "redux-saga/effects"; + +import { type IAnalyticalBackend } from "@gooddata/sdk-backend-spi"; + +import { type IChatConversationLocal } from "../../model.js"; +import { conversationByIdSelector } from "../messages/messagesSelectors.js"; +import { + evaluateConversationTitleAction, + type evaluateMessageUpdateAction, +} from "../messages/messagesSlice.js"; + +/** + * Load thread history and put it to the store. + * @internal + */ +export function* onUserMessageUpdate({ payload }: ReturnType) { + // Retrieve backend from context + const backend: IAnalyticalBackend = yield getContext("backend"); + const workspace: string = yield getContext("workspace"); + + if (!payload.isStartMessage) { + return; + } + + const conversation: IChatConversationLocal = yield select( + conversationByIdSelector, + payload.conversation.id, + ); + + if (!conversation) { + return; + } + + if (conversation.generatingTitle) { + return; + } + + yield put( + evaluateConversationTitleAction({ + conversation: conversation, + generatingTitle: true, + }), + ); + + try { + const conversationsCall = backend.workspace(workspace).genAI().getChatConversations(); + + const updated: IChatConversationLocal = yield call( + conversationsCall.generateTitle.bind(conversationsCall), + payload.conversation.id, + ); + + yield put( + evaluateConversationTitleAction({ + conversation: updated, + generatingTitle: false, + title: updated.title, + }), + ); + } catch { + yield put( + evaluateConversationTitleAction({ + conversation: conversation, + generatingTitle: false, + }), + ); + } +} diff --git a/libs/sdk-ui-gen-ai/src/store/sideEffects/onVisualisationRender.ts b/libs/sdk-ui-gen-ai/src/store/sideEffects/onVisualisationRender.ts index 0346dbd9090..d3328dc651e 100644 --- a/libs/sdk-ui-gen-ai/src/store/sideEffects/onVisualisationRender.ts +++ b/libs/sdk-ui-gen-ai/src/store/sideEffects/onVisualisationRender.ts @@ -3,9 +3,9 @@ import { type PayloadAction } from "@reduxjs/toolkit"; import { call, getContext, put, select } from "redux-saga/effects"; -import { type IAnalyticalBackend, type IChatConversation } from "@gooddata/sdk-backend-spi"; +import { type IAnalyticalBackend } from "@gooddata/sdk-backend-spi"; -import { type Message, isVisualizationContents } from "../../model.js"; +import { type IChatConversationLocal, type Message, isVisualizationContents } from "../../model.js"; import { conversationSelector, messagesSelector } from "../messages/messagesSelectors.js"; import { saveVisualisationRenderStatusSuccessAction } from "../messages/messagesSlice.js"; import { extractError } from "./utils.js"; @@ -25,7 +25,7 @@ export function* onVisualisationRender({ // Retrieve backend from context const backend: IAnalyticalBackend = yield getContext("backend"); const workspace: string = yield getContext("workspace"); - const conversation: IChatConversation = yield select(conversationSelector); + const conversation: IChatConversationLocal = yield select(conversationSelector); try { if (conversation) { diff --git a/libs/sdk-ui-gen-ai/src/store/sideEffects/onVisualizationSave.ts b/libs/sdk-ui-gen-ai/src/store/sideEffects/onVisualizationSave.ts index 5952b2d20a5..259b279def9 100644 --- a/libs/sdk-ui-gen-ai/src/store/sideEffects/onVisualizationSave.ts +++ b/libs/sdk-ui-gen-ai/src/store/sideEffects/onVisualizationSave.ts @@ -3,7 +3,7 @@ import { type PayloadAction } from "@reduxjs/toolkit"; import { call, getContext, put, select } from "redux-saga/effects"; -import { type IAnalyticalBackend, type IChatConversation } from "@gooddata/sdk-backend-spi"; +import { type IAnalyticalBackend } from "@gooddata/sdk-backend-spi"; import { type IAttributeOrMeasure, type IBucket, @@ -20,6 +20,7 @@ import { mapVisualizationClusteringToChartConfig } from "../../clustering/cluste import { prepareExecution } from "../../components/messages/contents/useExecution.js"; import { mapVisualizationForecastToChartConfig } from "../../forecast/forecastMapping.js"; import { + type IChatConversationLocal, type IChatConversationLocalItem, type IChatConversationMultipartLocalPart, type Message, @@ -44,7 +45,7 @@ export function* onVisualizationSave({ // Retrieve backend from context const backend: IAnalyticalBackend = yield getContext("backend"); const workspace: string = yield getContext("workspace"); - const conversation: IChatConversation = yield select(conversationSelector); + const conversation: IChatConversationLocal = yield select(conversationSelector); try { if (conversation) { diff --git a/libs/sdk-ui-gen-ai/src/store/sideEffects/onVisualizationSuccessSave.ts b/libs/sdk-ui-gen-ai/src/store/sideEffects/onVisualizationSuccessSave.ts index 0fdb89ce1f7..33320493096 100644 --- a/libs/sdk-ui-gen-ai/src/store/sideEffects/onVisualizationSuccessSave.ts +++ b/libs/sdk-ui-gen-ai/src/store/sideEffects/onVisualizationSuccessSave.ts @@ -3,10 +3,10 @@ import { type PayloadAction } from "@reduxjs/toolkit"; import { call, getContext, select } from "redux-saga/effects"; -import { type IAnalyticalBackend, type IChatConversation } from "@gooddata/sdk-backend-spi"; +import { type IAnalyticalBackend } from "@gooddata/sdk-backend-spi"; import { type GenAIChatInteractionUserVisualisation } from "@gooddata/sdk-model"; -import { type Message } from "../../model.js"; +import { type IChatConversationLocal, type Message } from "../../model.js"; import { getVisualizationHref } from "../../utils.js"; import { conversationSelector, messagesSelector } from "../messages/messagesSelectors.js"; @@ -21,7 +21,7 @@ export function* onVisualizationSuccessSave({ // Retrieve backend from context const backend: IAnalyticalBackend = yield getContext("backend"); const workspace: string = yield getContext("workspace"); - const conversation: IChatConversation = yield select(conversationSelector); + const conversation: IChatConversationLocal = yield select(conversationSelector); if (conversation) { if (payload.explore) { diff --git a/libs/sdk-ui-gen-ai/src/tests/utils.test.ts b/libs/sdk-ui-gen-ai/src/tests/utils.test.ts index 704bbf1e721..b01a4c4afd1 100644 --- a/libs/sdk-ui-gen-ai/src/tests/utils.test.ts +++ b/libs/sdk-ui-gen-ai/src/tests/utils.test.ts @@ -9,6 +9,10 @@ describe("generateTitleFromQuestion", () => { expect(generateTitleFromQuestion(" short question ")).toBe("short question"); }); + it("should normalize whitespace and remove invisible characters", () => { + expect(generateTitleFromQuestion(" first\n\t\u200Bsecond third ")).toBe("first second third"); + }); + it("should truncate to 50 characters and append ellipsis when text is longer", () => { const input = `${"a".repeat(50)}extra`; @@ -28,4 +32,10 @@ describe("generateTitleFromQuestion", () => { expect(generateTitleFromQuestion(input)).toBe(`${prefix}{metric/}...`); }); + + it("should sanitize text before truncation and still append ellipsis when truncated", () => { + const input = ` ${"a".repeat(30)}\n\t${"b".repeat(30)} `; + + expect(generateTitleFromQuestion(input)).toBe(`${"a".repeat(30)} ${"b".repeat(19)}...`); + }); }); diff --git a/libs/sdk-ui-gen-ai/src/utils.ts b/libs/sdk-ui-gen-ai/src/utils.ts index 38d54c2aee4..f60fe1a6e75 100644 --- a/libs/sdk-ui-gen-ai/src/utils.ts +++ b/libs/sdk-ui-gen-ai/src/utils.ts @@ -2,10 +2,10 @@ import { type IntlShape } from "react-intl"; -import { type IChatConversation } from "@gooddata/sdk-backend-spi"; import { type IAttributeOrMeasure } from "@gooddata/sdk-model"; import { REFERENCE_REGEX } from "./components/completion/references.js"; +import { type IChatConversationLocal } from "./model.js"; export function getVisualizationHref(wsId: string, visId: string) { return `/analyze/#/${wsId}/${visId}/edit`; @@ -45,7 +45,7 @@ export function getHeadlineComparison(metrics: IAttributeOrMeasure[]) { }; } -export function generateTemporaryTitle(intl: IntlShape, data: IChatConversation): string { +export function generateTemporaryTitle(intl: IntlShape, data: IChatConversationLocal): string { return intl.formatMessage( { id: "gd.chat.conversation.generating-title" }, { @@ -58,30 +58,34 @@ export function generateTemporaryTitle(intl: IntlShape, data: IChatConversation) export function generateTitleFromQuestion(text: string): string { const maxTitleLength = 50; + const sanitizedText = text + .replace(/[\u007F-\u009F\u200B-\u200D\u2060\uFEFF]/g, "") + .replace(/\s+/g, " ") + .trim(); - if (text.length <= maxTitleLength) { - return text.trim(); + if (sanitizedText.length <= maxTitleLength) { + return sanitizedText; } let sliceEnd = maxTitleLength; - const slicedText = text.slice(0, sliceEnd); + const slicedText = sanitizedText.slice(0, sliceEnd); const lastOpeningBrace = slicedText.lastIndexOf("{"); const lastClosingBrace = slicedText.lastIndexOf("}"); if (lastOpeningBrace > lastClosingBrace) { - const referenceStart = text.slice(lastOpeningBrace); + const referenceStart = sanitizedText.slice(lastOpeningBrace); REFERENCE_REGEX.lastIndex = 0; const referenceMatch = REFERENCE_REGEX.exec(referenceStart); if (referenceMatch?.index === 0) { sliceEnd = lastOpeningBrace + referenceMatch[0].length; } else { - const closingBrace = text.indexOf("}", sliceEnd); + const closingBrace = sanitizedText.indexOf("}", sliceEnd); if (closingBrace !== -1) { sliceEnd = closingBrace + 1; } } } - return `${text.slice(0, sliceEnd).trim()}...`; + return `${sanitizedText.slice(0, sliceEnd).trim()}...`; } diff --git a/libs/sdk-ui-gen-ai/styles/scss/conversations.scss b/libs/sdk-ui-gen-ai/styles/scss/conversations.scss index daa0a50b7c5..20db708c4dd 100644 --- a/libs/sdk-ui-gen-ai/styles/scss/conversations.scss +++ b/libs/sdk-ui-gen-ai/styles/scss/conversations.scss @@ -7,8 +7,9 @@ display: flex; align-items: center; align-self: stretch; + width: 100%; - padding: 5px 5px 0 10px; + padding: 5px 5px 5px 10px; color: kit-variables.$gd-color-text; font-size: 14px; font-style: normal; @@ -66,7 +67,6 @@ flex-direction: column; display: flex; - padding-top: 5px; margin-left: -10px; margin-right: -10px; align-items: stretch; diff --git a/libs/sdk-ui-geo/package.json b/libs/sdk-ui-geo/package.json index 1a393da380a..b462a5c60c9 100644 --- a/libs/sdk-ui-geo/package.json +++ b/libs/sdk-ui-geo/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-ui-geo", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "description": "GoodData.UI SDK - Geo Charts", "license": "MIT", "author": "GoodData Corporation", diff --git a/libs/sdk-ui-kit/api/sdk-ui-kit.api.md b/libs/sdk-ui-kit/api/sdk-ui-kit.api.md index b507f0dc7ec..ed667759808 100644 --- a/libs/sdk-ui-kit/api/sdk-ui-kit.api.md +++ b/libs/sdk-ui-kit/api/sdk-ui-kit.api.md @@ -20,6 +20,7 @@ import { CSSProperties } from 'react'; import { CsvDelimiterPreset } from '@gooddata/sdk-model'; import { CsvDelimiterValidationError } from '@gooddata/sdk-model'; import { DebouncedFunc } from 'lodash-es'; +import { DependencyList } from 'react'; import { Dispatch } from 'react'; import { EditorView } from '@codemirror/view'; import { ElementType } from 'react'; @@ -8685,7 +8686,7 @@ export type UseCurrencyFormatDefaultsConfig = { }; // @internal (undocumented) -export function useElementSize(): { +export function useElementSize(deps?: DependencyList): { ref: RefObject; height: number; width: number; diff --git a/libs/sdk-ui-kit/package.json b/libs/sdk-ui-kit/package.json index 21427c77e92..923f8053112 100644 --- a/libs/sdk-ui-kit/package.json +++ b/libs/sdk-ui-kit/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-ui-kit", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "description": "GoodData SDK - UI Building Components", "license": "MIT", "author": "GoodData Corporation", diff --git a/libs/sdk-ui-kit/src/@ui/UiDrawer/UiDrawer.tsx b/libs/sdk-ui-kit/src/@ui/UiDrawer/UiDrawer.tsx index 59167bfe2fa..82bea9471f0 100644 --- a/libs/sdk-ui-kit/src/@ui/UiDrawer/UiDrawer.tsx +++ b/libs/sdk-ui-kit/src/@ui/UiDrawer/UiDrawer.tsx @@ -7,6 +7,7 @@ import { Portal } from "react-portal"; import { bem } from "../@utils/bem.js"; import { makeDialogKeyboardNavigation } from "../@utils/keyboardNavigation.js"; import { OverlayContent, OverlayProvider } from "../@utils/OverlayStack.js"; +import { useElementSize } from "../hooks/useElementSize.js"; import { UiAutofocus } from "../UiFocusManager/UiAutofocus.js"; import { UiFocusTrap } from "../UiFocusManager/UiFocusTrap.js"; import { UiReturnFocusOnUnmount } from "../UiFocusManager/UiReturnFocusOnUnmount.js"; @@ -41,6 +42,7 @@ export function UiDrawer({ accessibilityConfig, }: IUiDrawerProps) { const ref = useRef(null); + const { isOpen, isFullyOpen, view, backdropStyle, contentStyle } = useToggleDrawer( open ?? false, transition ?? {}, @@ -61,6 +63,8 @@ export function UiDrawer({ [onEscapeKey], ); + const { ref: headerRef, height } = useElementSize([isOpen, ref.current]); + if (!isOpen) { return null; } @@ -83,7 +87,11 @@ export function UiDrawer({ }>
-
+
{header} {showCloseButton ? (
diff --git a/libs/sdk-ui-kit/src/@ui/hooks/useElementSize.tsx b/libs/sdk-ui-kit/src/@ui/hooks/useElementSize.tsx index 38a12e7fdbf..0a4258e6222 100644 --- a/libs/sdk-ui-kit/src/@ui/hooks/useElementSize.tsx +++ b/libs/sdk-ui-kit/src/@ui/hooks/useElementSize.tsx @@ -1,10 +1,11 @@ -// (C) 2024-2025 GoodData Corporation -import { useLayoutEffect, useRef, useState } from "react"; +// (C) 2024-2026 GoodData Corporation + +import { type DependencyList, useLayoutEffect, useRef, useState } from "react"; /** * @internal */ -export function useElementSize() { +export function useElementSize(deps?: DependencyList) { const ref = useRef(null); const [height, setHeight] = useState(0); const [width, setWidth] = useState(0); @@ -31,7 +32,8 @@ export function useElementSize() { setWidth(0); } }; - }, []); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, deps ?? []); return { ref, diff --git a/libs/sdk-ui-loaders/package.json b/libs/sdk-ui-loaders/package.json index 05d5ae61af1..41d9dc229eb 100644 --- a/libs/sdk-ui-loaders/package.json +++ b/libs/sdk-ui-loaders/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-ui-loaders", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "description": "GoodData SDK Runtime Component Loaders", "license": "LicenseRef-LICENSE", "author": "GoodData Corporation", diff --git a/libs/sdk-ui-pivot/package.json b/libs/sdk-ui-pivot/package.json index fe05b7ee8ed..abf8af70071 100644 --- a/libs/sdk-ui-pivot/package.json +++ b/libs/sdk-ui-pivot/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-ui-pivot", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "description": "GoodData.UI SDK - Pivot Table", "license": "MIT", "author": "GoodData Corporation", diff --git a/libs/sdk-ui-pluggable-application/package.json b/libs/sdk-ui-pluggable-application/package.json index cf569206860..0b3d738b5a6 100644 --- a/libs/sdk-ui-pluggable-application/package.json +++ b/libs/sdk-ui-pluggable-application/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-ui-pluggable-application", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "description": "GoodData SDK React helpers for pluggable applications", "license": "MIT", "author": "GoodData Corporation", diff --git a/libs/sdk-ui-semantic-search/package.json b/libs/sdk-ui-semantic-search/package.json index c89761f4cee..abcafa56275 100644 --- a/libs/sdk-ui-semantic-search/package.json +++ b/libs/sdk-ui-semantic-search/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-ui-semantic-search", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "description": "GoodData SDK TypeScript & React skeleton", "license": "MIT", "author": "GoodData Corporation", diff --git a/libs/sdk-ui-theme-provider/package.json b/libs/sdk-ui-theme-provider/package.json index 09c5a261b86..de36e9298b3 100644 --- a/libs/sdk-ui-theme-provider/package.json +++ b/libs/sdk-ui-theme-provider/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-ui-theme-provider", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "description": "GoodData SDK - Theme provider", "license": "MIT", "author": "GoodData Corporation", diff --git a/libs/sdk-ui-vis-commons/package.json b/libs/sdk-ui-vis-commons/package.json index 2c677ef7e49..e7504ce1390 100644 --- a/libs/sdk-ui-vis-commons/package.json +++ b/libs/sdk-ui-vis-commons/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-ui-vis-commons", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "description": "GoodData.UI SDK - common functionality for different types of visualizations", "license": "MIT", "author": "GoodData Corporation", diff --git a/libs/sdk-ui/package.json b/libs/sdk-ui/package.json index 7790991f3fe..628056e993f 100644 --- a/libs/sdk-ui/package.json +++ b/libs/sdk-ui/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-ui", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "description": "GoodData.UI SDK - Core", "license": "MIT", "author": "GoodData Corporation", diff --git a/libs/util/package.json b/libs/util/package.json index f31d988dd12..234cb1cad1f 100644 --- a/libs/util/package.json +++ b/libs/util/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/util", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "description": "GoodData Utility Functions", "license": "MIT", "author": "GoodData", diff --git a/tools/app-toolkit/package.json b/tools/app-toolkit/package.json index 57ec0731036..cc3d932fa8a 100644 --- a/tools/app-toolkit/package.json +++ b/tools/app-toolkit/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/app-toolkit", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "description": "CLI with useful tools for creating and maintaining GoodData web applications.", "license": "LicenseRef-LICENSE", "author": "GoodData", diff --git a/tools/catalog-export/package.json b/tools/catalog-export/package.json index ddc7a73802a..9d9a56758f7 100644 --- a/tools/catalog-export/package.json +++ b/tools/catalog-export/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/catalog-export", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "description": "GoodData SDK Catalog Export tooling", "license": "MIT", "author": "GoodData", diff --git a/tools/eslint-config/package.json b/tools/eslint-config/package.json index 86bde8676bf..762af917965 100644 --- a/tools/eslint-config/package.json +++ b/tools/eslint-config/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/eslint-config", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "license": "MIT", "author": "GoodData", "repository": { diff --git a/tools/i18n-toolkit/package.json b/tools/i18n-toolkit/package.json index 96e2c4eb8b7..6dcf06726ed 100644 --- a/tools/i18n-toolkit/package.json +++ b/tools/i18n-toolkit/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/i18n-toolkit", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "description": "Localization validator to validate localization complexity and intl and html format.", "license": "MIT", "author": "GoodData", diff --git a/tools/lint-config/package.json b/tools/lint-config/package.json index a8b521229b8..89385678fe9 100644 --- a/tools/lint-config/package.json +++ b/tools/lint-config/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/lint-config", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "description": "Linter-agnostic shared rules for @gooddata/eslint-config and @gooddata/oxlint-config", "license": "MIT", "author": "GoodData", diff --git a/tools/mock-handling/package.json b/tools/mock-handling/package.json index e7c05b19449..4a96184bfd6 100644 --- a/tools/mock-handling/package.json +++ b/tools/mock-handling/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/mock-handling", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "description": "GoodData SDK Mock data capture and management tool", "license": "MIT", "author": "GoodData", diff --git a/tools/oxlint-config/package.json b/tools/oxlint-config/package.json index d312550bc65..07010d97297 100644 --- a/tools/oxlint-config/package.json +++ b/tools/oxlint-config/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/oxlint-config", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "license": "MIT", "author": "GoodData", "repository": { diff --git a/tools/plugin-toolkit/package.json b/tools/plugin-toolkit/package.json index 677a52cf501..5c83fa5ed05 100644 --- a/tools/plugin-toolkit/package.json +++ b/tools/plugin-toolkit/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/plugin-toolkit", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "description": "GoodData Set of Tools for working with Plugins", "license": "LicenseRef-LICENSE", "author": "GoodData", diff --git a/tools/reference-workspace/package.json b/tools/reference-workspace/package.json index 9d60f84148e..381a480679a 100644 --- a/tools/reference-workspace/package.json +++ b/tools/reference-workspace/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/reference-workspace", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "description": "GoodData SDK - Reference Workspace for tests", "license": "MIT", "author": "GoodData", diff --git a/tools/stylelint-config/package.json b/tools/stylelint-config/package.json index 8144b14bf32..1f2abb5e8d8 100644 --- a/tools/stylelint-config/package.json +++ b/tools/stylelint-config/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/stylelint-config", - "version": "11.35.0-alpha.1", + "version": "11.35.0-alpha.2", "description": "GoodData CSS Style Guide", "keywords": [ "config",