From aacb2b461dff98482ea33b8432cd32cbd580fd4e Mon Sep 17 00:00:00 2001 From: meta-d Date: Sat, 28 Dec 2024 23:26:09 +0800 Subject: [PATCH 01/19] feat: semantic model updates --- .../semantic-model/model/model.component.html | 129 ++++++++++-------- .../semantic-model/model/model.component.ts | 1 + .../semantic-model/model/model.service.ts | 1 + apps/cloud/src/assets/i18n/zh-Hans.json | 1 + 4 files changed, 75 insertions(+), 57 deletions(-) diff --git a/apps/cloud/src/app/features/semantic-model/model/model.component.html b/apps/cloud/src/app/features/semantic-model/model/model.component.html index d317155a..8abb42e4 100644 --- a/apps/cloud/src/app/features/semantic-model/model/model.component.html +++ b/apps/cloud/src/app/features/semantic-model/model/model.component.html @@ -77,12 +77,17 @@ - - @@ -103,6 +108,9 @@ cdkDrag [cdkDragData]="entity" > + @if (cube() === entity.name) { + + } @switch (entity.type) { @case(SemanticModelEntityType.CUBE) { more_vert @@ -299,30 +307,30 @@ -
- - - - - - - - -
+
+ + + + + + +
+ +
@@ -391,19 +399,19 @@ -
- - - - - -
+
+ + +
+ + +
@@ -425,19 +433,26 @@ - - - - - - + +
+ @if (entity.type === SemanticModelEntityType.CUBE) { + + } + + +
+
@if (message.data['header']) { diff --git a/apps/cloud/src/app/features/semantic-model/model/model.component.ts b/apps/cloud/src/app/features/semantic-model/model/model.component.ts index 1c449c23..be28353c 100644 --- a/apps/cloud/src/app/features/semantic-model/model/model.component.ts +++ b/apps/cloud/src/app/features/semantic-model/model/model.component.ts @@ -188,6 +188,7 @@ export class ModelComponent extends TranslationBaseComponent implements IsDirty public readonly copilotEnabled$ = this.appService.copilotEnabled$ private readonly dimensions = toSignal(this.modelService.dimensions$) + readonly cube = computed(() => this.modelService.model()?.cube) model: ISemanticModel diff --git a/apps/cloud/src/app/features/semantic-model/model/model.service.ts b/apps/cloud/src/app/features/semantic-model/model/model.service.ts index a4a85716..db4c843b 100644 --- a/apps/cloud/src/app/features/semantic-model/model/model.service.ts +++ b/apps/cloud/src/app/features/semantic-model/model/model.service.ts @@ -217,6 +217,7 @@ export class SemanticModelService { | Signals |-------------------------------------------------------------------------- */ + readonly model = toSignal(this.model$) readonly modelType = toSignal(this.modelType$) readonly dialect = toSignal(this.model$.pipe(map((model) => model?.dataSource?.type?.type))) readonly isDirty = this.dirtyCheckResult.dirty diff --git a/apps/cloud/src/assets/i18n/zh-Hans.json b/apps/cloud/src/assets/i18n/zh-Hans.json index f4815d8a..bae10107 100644 --- a/apps/cloud/src/assets/i18n/zh-Hans.json +++ b/apps/cloud/src/assets/i18n/zh-Hans.json @@ -765,6 +765,7 @@ "EditCubeVariable": "编辑立方体变量", "Variables": "参数变量", "OpenCubeDesigner": "打开多维数据集属性编辑器", + "IsDefault": "是默认", "QUERY": { "TITLE": "查询实验室", From 2c74c2dc51b1c108fd2ba26af7e2fedc8002a6bd Mon Sep 17 00:00:00 2001 From: meta-d Date: Sun, 29 Dec 2024 22:34:56 +0800 Subject: [PATCH 02/19] feat: sort executions of tool --- .../studio/components/toolset/toolset.component.html | 8 ++++---- .../studio/components/toolset/toolset.component.ts | 4 +++- .../panel/agent-execution/execution.component.ts | 3 ++- .../xpert/studio/services/execution.service.ts | 12 +++++++++++- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/apps/cloud/src/app/features/xpert/studio/components/toolset/toolset.component.html b/apps/cloud/src/app/features/xpert/studio/components/toolset/toolset.component.html index bbb20e04..cb00f630 100644 --- a/apps/cloud/src/app/features/xpert/studio/components/toolset/toolset.component.html +++ b/apps/cloud/src/app/features/xpert/studio/components/toolset/toolset.component.html @@ -18,20 +18,20 @@ [matTooltip]="item.tool.description" matTooltipPosition="after" > - @for (execution of item.executions | keyvalue; track execution.key) { - @switch (execution.value.status) { + @for (execution of item.executions; track execution) { + @switch (execution.status) { @case (eXpertAgentExecutionEnum.RUNNING) { } @case (eXpertAgentExecutionEnum.ERROR) {
} @case (eXpertAgentExecutionEnum.SUCCESS) {
diff --git a/apps/cloud/src/app/features/xpert/studio/components/toolset/toolset.component.ts b/apps/cloud/src/app/features/xpert/studio/components/toolset/toolset.component.ts index 9ef692bf..ead06e13 100644 --- a/apps/cloud/src/app/features/xpert/studio/components/toolset/toolset.component.ts +++ b/apps/cloud/src/app/features/xpert/studio/components/toolset/toolset.component.ts @@ -50,7 +50,9 @@ export class XpertStudioNodeToolsetComponent { readonly tools = computed(() => { const tools = this.availableTools() const executions = this.toolExecutions() - return tools?.map((tool) => ({tool, executions: executions?.[tool.name]})) + return tools?.map((tool) => ({ + tool, + executions: Object.values(executions?.[tool.name] ?? {}).sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime())})) }) private get hostElement(): HTMLElement { diff --git a/apps/cloud/src/app/features/xpert/studio/panel/agent-execution/execution.component.ts b/apps/cloud/src/app/features/xpert/studio/panel/agent-execution/execution.component.ts index 55a556d1..90772471 100644 --- a/apps/cloud/src/app/features/xpert/studio/panel/agent-execution/execution.component.ts +++ b/apps/cloud/src/app/features/xpert/studio/panel/agent-execution/execution.component.ts @@ -232,7 +232,8 @@ export function processEvents( } case ChatMessageEventTypeEnum.ON_TOOL_START: { executionService.updateToolExecution(event.data.name, event.data.metadata?.langgraph_checkpoint_ns, { - status: XpertAgentExecutionStatusEnum.RUNNING + status: XpertAgentExecutionStatusEnum.RUNNING, + createdAt: new Date() }) break } diff --git a/apps/cloud/src/app/features/xpert/studio/services/execution.service.ts b/apps/cloud/src/app/features/xpert/studio/services/execution.service.ts index 5454acf6..2c525ae4 100644 --- a/apps/cloud/src/app/features/xpert/studio/services/execution.service.ts +++ b/apps/cloud/src/app/features/xpert/studio/services/execution.service.ts @@ -105,12 +105,22 @@ export class XpertExecutionService { this.#messages.set([]) } + /** + * Update execution of tool call + * + * @param name Tool's name + * @param id Execution run id + * @param execution Execution entity + */ updateToolExecution(name: string, id: string, execution: Partial) { this.toolExecutions.update((state) => ({ ...state, [name]: { ...(state[name] ?? {}), - [id]: execution + [id]: { + ...(state[name]?.[id] ?? {}), + ...execution + } } })) } From 3f6707b7fc90545fa4d2065366c90b3729504236 Mon Sep 17 00:00:00 2001 From: meta-d Date: Sun, 29 Dec 2024 22:37:18 +0800 Subject: [PATCH 03/19] version 3.0.9 --- packages/adapter/package.json | 2 +- packages/analytics/package.json | 2 +- packages/angular/package.json | 2 +- packages/auth/package.json | 2 +- packages/common/package.json | 2 +- packages/config/package.json | 2 +- packages/contracts/package.json | 2 +- packages/copilot-angular/package.json | 2 +- packages/copilot/package.json | 2 +- packages/core/package.json | 2 +- packages/duckdb/package.json | 2 +- packages/echarts/package.json | 2 +- packages/server-ai/package.json | 2 +- packages/server/package.json | 2 +- packages/sql/package.json | 2 +- packages/store/package.json | 2 +- packages/xmla/package.json | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/packages/adapter/package.json b/packages/adapter/package.json index 9f4228fc..c90dd236 100644 --- a/packages/adapter/package.json +++ b/packages/adapter/package.json @@ -1,6 +1,6 @@ { "name": "@metad/adapter", - "version": "3.0.8", + "version": "3.0.9", "dependencies": { "axios": "^0.21.4", "clickhouse": "^2.4.1", diff --git a/packages/analytics/package.json b/packages/analytics/package.json index af96f4b9..4484296c 100644 --- a/packages/analytics/package.json +++ b/packages/analytics/package.json @@ -1,6 +1,6 @@ { "name": "@metad/analytics", - "version": "3.0.8", + "version": "3.0.9", "type": "commonjs", "license": "MIT", "scripts": { diff --git a/packages/angular/package.json b/packages/angular/package.json index 8ee7f7bb..6e0d2b89 100644 --- a/packages/angular/package.json +++ b/packages/angular/package.json @@ -1,6 +1,6 @@ { "name": "@metad/ocap-angular", - "version": "3.0.8", + "version": "3.0.9", "keywords": [ "metad", "ocap", diff --git a/packages/auth/package.json b/packages/auth/package.json index b87535e6..9005289d 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -1,6 +1,6 @@ { "name": "@metad/server-auth", - "version": "3.0.8", + "version": "3.0.9", "type": "commonjs", "dependencies": { "@nestjs/common": "^8.0.0", diff --git a/packages/common/package.json b/packages/common/package.json index 58642fda..9cdbb1db 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -1,6 +1,6 @@ { "name": "@metad/server-common", - "version": "3.0.8", + "version": "3.0.9", "type": "commonjs", "dependencies": { "@nestjs/typeorm": "^8.0.3", diff --git a/packages/config/package.json b/packages/config/package.json index 322e8150..ec72697f 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -1,6 +1,6 @@ { "name": "@metad/server-config", - "version": "3.0.8", + "version": "3.0.9", "type": "commonjs", "dependencies": { "@nestjs/common": "^8.0.0", diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 078c6b9b..e6cd6f05 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -1,6 +1,6 @@ { "name": "@metad/contracts", - "version": "3.0.8", + "version": "3.0.9", "scripts": { "docs": "yarn typedoc --out ./.docs" } diff --git a/packages/copilot-angular/package.json b/packages/copilot-angular/package.json index 53dddd25..7e5194d2 100644 --- a/packages/copilot-angular/package.json +++ b/packages/copilot-angular/package.json @@ -1,6 +1,6 @@ { "name": "@metad/copilot-angular", - "version": "3.0.8", + "version": "3.0.9", "peerDependencies": { "@angular/common": "^17.3.0", "@angular/core": "^17.3.0", diff --git a/packages/copilot/package.json b/packages/copilot/package.json index b0a7ab29..90c658ab 100644 --- a/packages/copilot/package.json +++ b/packages/copilot/package.json @@ -1,6 +1,6 @@ { "name": "@metad/copilot", - "version": "3.0.8", + "version": "3.0.9", "scripts": { "docs": "yarn typedoc --out ./.docs" }, diff --git a/packages/core/package.json b/packages/core/package.json index 29a529a5..65757b71 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@metad/ocap-core", - "version": "3.0.8", + "version": "3.0.9", "dependencies": { "tslib": "^2.3.0" }, diff --git a/packages/duckdb/package.json b/packages/duckdb/package.json index 2a11e823..344599a5 100644 --- a/packages/duckdb/package.json +++ b/packages/duckdb/package.json @@ -1,6 +1,6 @@ { "name": "@metad/ocap-duckdb", - "version": "3.0.8", + "version": "3.0.9", "keywords": [ "metad", "ocap", diff --git a/packages/echarts/package.json b/packages/echarts/package.json index 668bb3bd..4d33a1ac 100644 --- a/packages/echarts/package.json +++ b/packages/echarts/package.json @@ -1,6 +1,6 @@ { "name": "@metad/ocap-echarts", - "version": "3.0.8", + "version": "3.0.9", "keywords": [ "metad", "ocap", diff --git a/packages/server-ai/package.json b/packages/server-ai/package.json index 141bcf76..b856976d 100644 --- a/packages/server-ai/package.json +++ b/packages/server-ai/package.json @@ -1,6 +1,6 @@ { "name": "@metad/server-ai", - "version": "3.0.8", + "version": "3.0.9", "type": "commonjs", "dependencies": { "@apidevtools/swagger-parser": "^10.1.0", diff --git a/packages/server/package.json b/packages/server/package.json index c541c8e3..4f17c7a4 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@metad/server-core", - "version": "3.0.8", + "version": "3.0.9", "type": "commonjs", "dependencies": { "@casl/ability": "^5.4.3", diff --git a/packages/sql/package.json b/packages/sql/package.json index ba60197d..c75ecf32 100644 --- a/packages/sql/package.json +++ b/packages/sql/package.json @@ -1,6 +1,6 @@ { "name": "@metad/ocap-sql", - "version": "3.0.8", + "version": "3.0.9", "keywords": [ "metad", "ocap", diff --git a/packages/store/package.json b/packages/store/package.json index 9af8b50c..67e4846d 100644 --- a/packages/store/package.json +++ b/packages/store/package.json @@ -1,6 +1,6 @@ { "name": "@metad/store", - "version": "3.0.8", + "version": "3.0.9", "keywords": [ "metad", "ocap", diff --git a/packages/xmla/package.json b/packages/xmla/package.json index b42f49eb..5b859d5d 100644 --- a/packages/xmla/package.json +++ b/packages/xmla/package.json @@ -1,6 +1,6 @@ { "name": "@metad/ocap-xmla", - "version": "3.0.8", + "version": "3.0.9", "scripts": { "docs": "yarn typedoc --out ./.docs" }, From bf1ceb6e3fe4815ae30fe425624abfb138e50a05 Mon Sep 17 00:00:00 2001 From: meta-d Date: Sun, 29 Dec 2024 23:39:37 +0800 Subject: [PATCH 04/19] feat: project documents --- packages/analytics/README.md | 12 ++++++++++-- packages/server-ai/README.md | 9 +++++++++ packages/server/README.md | 2 +- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/packages/analytics/README.md b/packages/analytics/README.md index e56be00d..dbd9e46f 100644 --- a/packages/analytics/README.md +++ b/packages/analytics/README.md @@ -1,3 +1,11 @@ -# ocap-analytics +# Server Analytics -Analytics platform server for ocap project. +The `Server Analytics` project is built on the functionalities of the `Server Core` and `Server AI` projects to create an intelligent decision-making platform for data analysis. + +Core Functional Modules: + +- **Semantic Model**: A semantic model of data that provides the foundation for indicator management and visualization. +- **Indicator**: A system for managing data indicators. +- **Story**: A dashboard for data visualization. +- **Project**: Provides the foundation for creating stories and managing indicators. +- **ChatBI**: Offers conversational data analysis capabilities. diff --git a/packages/server-ai/README.md b/packages/server-ai/README.md index 5c836b79..f8a9c7e6 100644 --- a/packages/server-ai/README.md +++ b/packages/server-ai/README.md @@ -1 +1,10 @@ # Server AI + +`Server AI` is an AI functionality project built on top of the server-core's basic features, aimed at providing developers with a comprehensive artificial intelligence solution. The goal of `Server AI` is to simplify the development process of AI applications, allowing developers to focus on implementing business logic without having to worry too much about underlying technical details. + +Core Modules: +- **Copilot**: Provides basic AI model functionalities, such as model configuration, access statistics, and model memory. +- **Chat**: Offers functionality for recording conversations with agents. +- **Xpert**: Provides a platform for orchestrating multiple intelligent agents. +- **Knowledge**: Offers various types of knowledge base functionalities. +- **Integration**: Provides integration capabilities with various third-party platforms. diff --git a/packages/server/README.md b/packages/server/README.md index 66787d8e..87fd6154 100644 --- a/packages/server/README.md +++ b/packages/server/README.md @@ -1,6 +1,6 @@ # ocap-server -The basic server package for the ocap project. +The basic server package for the projects. ## English From 47718f76329813d7109ed2f4499d69a4f8023268 Mon Sep 17 00:00:00 2001 From: meta-d Date: Tue, 31 Dec 2024 11:28:03 +0800 Subject: [PATCH 05/19] feat: show indicators component --- .../xpert/publish/publish.component.html | 4 +- .../component-message.component.html | 23 +----- .../component-message.component.ts | 20 +----- .../indicators/indicators.component.html | 39 ++++++++++ .../indicators/indicators.component.scss | 7 ++ .../indicators/indicators.component.ts | 72 +++++++++++++++++++ .../xpert/studio/header/header.component.html | 6 +- .../xpert/studio/header/header.component.ts | 2 +- apps/cloud/src/assets/i18n/zh-Hans.json | 4 +- .../builtin/chatbi-lark/_position.yaml | 4 +- .../ai/toolset/builtin/chatbi/_position.yaml | 5 +- 11 files changed, 135 insertions(+), 51 deletions(-) create mode 100644 apps/cloud/src/app/features/chat/component-message/indicators/indicators.component.html create mode 100644 apps/cloud/src/app/features/chat/component-message/indicators/indicators.component.scss create mode 100644 apps/cloud/src/app/features/chat/component-message/indicators/indicators.component.ts diff --git a/apps/cloud/src/app/@shared/xpert/publish/publish.component.html b/apps/cloud/src/app/@shared/xpert/publish/publish.component.html index 8314faf7..af890458 100644 --- a/apps/cloud/src/app/@shared/xpert/publish/publish.component.html +++ b/apps/cloud/src/app/@shared/xpert/publish/publish.component.html @@ -1,5 +1,5 @@
-
+
{{ 'PAC.Xpert.PublishThirdPlatforms' | translate: { Default: 'Publish to third-party platforms' } }} @@ -49,7 +49,7 @@
-
{{ 'PAC.Xpert.PublishTo' | translate: { Default: 'Publish to ' } }} {{integration()?.provider || '?'}}
diff --git a/apps/cloud/src/app/features/chat/component-message/component-message.component.html b/apps/cloud/src/app/features/chat/component-message/component-message.component.html index ce1db656..4eff482e 100644 --- a/apps/cloud/src/app/features/chat/component-message/component-message.component.html +++ b/apps/cloud/src/app/features/chat/component-message/component-message.component.html @@ -51,27 +51,6 @@ } } @case ('Indicators') { -
- @for (indicator of indicators(); track indicator.indicator) { - - @if (indicatorExplorer() === indicator.indicator) { - - } - } -
+ } } diff --git a/apps/cloud/src/app/features/chat/component-message/component-message.component.ts b/apps/cloud/src/app/features/chat/component-message/component-message.component.ts index cb569fcf..77f10695 100644 --- a/apps/cloud/src/app/features/chat/component-message/component-message.component.ts +++ b/apps/cloud/src/app/features/chat/component-message/component-message.component.ts @@ -30,6 +30,7 @@ import { TranslateModule } from '@ngx-translate/core' import { compact, uniq } from 'lodash-es' import { MarkdownModule } from 'ngx-markdown' import { Store } from '../../../@core' +import { ChatComponentIndicatorsComponent } from './indicators/indicators.component' @Component({ standalone: true, @@ -49,7 +50,7 @@ import { Store } from '../../../@core' AnalyticalCardModule, NxWidgetKpiComponent, NgmIndicatorComponent, - NgmIndicatorExplorerComponent + ChatComponentIndicatorsComponent ], selector: 'pac-chat-component-message', templateUrl: './component-message.component.html', @@ -92,9 +93,6 @@ export class ChatComponentMessageComponent { readonly explains = signal([]) - readonly indicatorExplorer = signal(null) - readonly indicatorTagType = signal(IndicatorTagEnum.MOM) - constructor() { effect( () => { @@ -136,20 +134,6 @@ export class ChatComponentMessageComponent { }) } - toggleIndicatorTagType() { - this.indicatorTagType.update((tagType) => { - if (IndicatorTagEnum[tagType + 1]) { - return tagType + 1 - } else { - return IndicatorTagEnum[IndicatorTagEnum[0]] // Ensure to start from 0 - } - }) - } - - toggleIndicator(indicator: string) { - this.indicatorExplorer.update((state) => state === indicator ? null : indicator) - } - openExplorer() { this.#dialog .open(StoryExplorerComponent, { diff --git a/apps/cloud/src/app/features/chat/component-message/indicators/indicators.component.html b/apps/cloud/src/app/features/chat/component-message/indicators/indicators.component.html new file mode 100644 index 00000000..2f37ab5a --- /dev/null +++ b/apps/cloud/src/app/features/chat/component-message/indicators/indicators.component.html @@ -0,0 +1,39 @@ +@for (indicator of showIndicators(); track indicator.indicator; let last = $last) { + + @if (indicatorExplorer() === indicator.indicator) { + + } +} + +@if (indicators()?.length > pageSize()) { +
+ @if (hasMore()) { + + } + @if (showIndicators()?.length > pageSize()) { + + } +
+} \ No newline at end of file diff --git a/apps/cloud/src/app/features/chat/component-message/indicators/indicators.component.scss b/apps/cloud/src/app/features/chat/component-message/indicators/indicators.component.scss new file mode 100644 index 00000000..e39313d8 --- /dev/null +++ b/apps/cloud/src/app/features/chat/component-message/indicators/indicators.component.scss @@ -0,0 +1,7 @@ +:host { + @apply flex flex-col items-stretch; +} + +.indicator.last:not(.has-more) { + @apply rounded-b-xl; +} \ No newline at end of file diff --git a/apps/cloud/src/app/features/chat/component-message/indicators/indicators.component.ts b/apps/cloud/src/app/features/chat/component-message/indicators/indicators.component.ts new file mode 100644 index 00000000..9bd6fc54 --- /dev/null +++ b/apps/cloud/src/app/features/chat/component-message/indicators/indicators.component.ts @@ -0,0 +1,72 @@ +import { DragDropModule } from '@angular/cdk/drag-drop' +import { CdkMenuModule } from '@angular/cdk/menu' +import { CommonModule } from '@angular/common' +import { ChangeDetectionStrategy, Component, computed, input, signal } from '@angular/core' +import { FormsModule, ReactiveFormsModule } from '@angular/forms' +import { MatTooltipModule } from '@angular/material/tooltip' +import { RouterModule } from '@angular/router' +import { NgmIndicatorComponent, NgmIndicatorExplorerComponent } from '@metad/ocap-angular/indicator' +import { DataSettings, IndicatorTagEnum, TimeGranularity } from '@metad/ocap-core' +import { TranslateModule } from '@ngx-translate/core' + +@Component({ + standalone: true, + imports: [ + CommonModule, + FormsModule, + ReactiveFormsModule, + DragDropModule, + CdkMenuModule, + RouterModule, + TranslateModule, + MatTooltipModule, + + NgmIndicatorComponent, + NgmIndicatorExplorerComponent + ], + selector: 'pac-chat-component-indicators', + templateUrl: './indicators.component.html', + styleUrl: 'indicators.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ChatComponentIndicatorsComponent { + eTimeGranularity = TimeGranularity + + // Inputs + readonly indicators = input>() + + // States + readonly pageSize = signal(5) + readonly pageNo = signal(0) + + readonly showIndicators = computed(() => { + return this.indicators()?.slice(0, (this.pageNo() + 1) * this.pageSize()) + }) + + readonly hasMore = computed(() => this.indicators().length > (this.pageNo() + 1) * this.pageSize()) + + readonly indicatorExplorer = signal(null) + readonly indicatorTagType = signal(IndicatorTagEnum.MOM) + + toggleIndicatorTagType() { + this.indicatorTagType.update((tagType) => { + if (IndicatorTagEnum[tagType + 1]) { + return tagType + 1 + } else { + return IndicatorTagEnum[IndicatorTagEnum[0]] // Ensure to start from 0 + } + }) + } + + toggleIndicator(indicator: string) { + this.indicatorExplorer.update((state) => (state === indicator ? null : indicator)) + } + + showMore() { + this.pageNo.update((currentPage) => currentPage + 1) + } + + showLess() { + this.pageNo.update((currentPage) => currentPage - 1) + } +} diff --git a/apps/cloud/src/app/features/xpert/studio/header/header.component.html b/apps/cloud/src/app/features/xpert/studio/header/header.component.html index 0c13baa7..3df32e48 100644 --- a/apps/cloud/src/app/features/xpert/studio/header/header.component.html +++ b/apps/cloud/src/app/features/xpert/studio/header/header.component.html @@ -283,9 +283,9 @@ {{ 'PAC.Xpert.Draft' | translate: {Default: 'Draft'} }} } @if (item.version === version()) { -
- current -
+
+ current +
} } diff --git a/apps/cloud/src/app/features/xpert/studio/header/header.component.ts b/apps/cloud/src/app/features/xpert/studio/header/header.component.ts index 978276f8..856fe087 100644 --- a/apps/cloud/src/app/features/xpert/studio/header/header.component.ts +++ b/apps/cloud/src/app/features/xpert/studio/header/header.component.ts @@ -64,7 +64,7 @@ export class XpertStudioHeaderComponent { readonly latest = computed(() => this.team()?.latest) readonly versions = computed(() => { const versions = this.apiService.versions()?.filter(nonBlank) - return sortBy(versions, 'version').reverse() + return versions.sort((a, b) => Number(b.version) - Number(a.version)) }) readonly draft = computed(() => this.apiService.draft()) readonly unsaved = this.apiService.unsaved diff --git a/apps/cloud/src/assets/i18n/zh-Hans.json b/apps/cloud/src/assets/i18n/zh-Hans.json index bae10107..48dcb389 100644 --- a/apps/cloud/src/assets/i18n/zh-Hans.json +++ b/apps/cloud/src/assets/i18n/zh-Hans.json @@ -1533,7 +1533,9 @@ "Event_on_retriever_start": "知识检索开始", "Event_on_retriever_end": "知识检索结束", "OpenExplorer": "打开数据浏览器", - "OpenExplain": "打开解释器" + "OpenExplain": "打开解释器", + "ShowMoreIndicators": "展示更多指标", + "ShowLessIndicators": "展示少一些指标" }, "ChatBI": { "Title": "ChatBI-数据分析与洞察", diff --git a/packages/analytics/src/ai/toolset/builtin/chatbi-lark/_position.yaml b/packages/analytics/src/ai/toolset/builtin/chatbi-lark/_position.yaml index 49d0bbe3..ebfc9760 100644 --- a/packages/analytics/src/ai/toolset/builtin/chatbi-lark/_position.yaml +++ b/packages/analytics/src/ai/toolset/builtin/chatbi-lark/_position.yaml @@ -1,5 +1,5 @@ - welcome - get_cube_context - dimension_member_retriever -- show_indicators -- answer_question \ No newline at end of file +- answer_question +- create_indicator \ No newline at end of file diff --git a/packages/analytics/src/ai/toolset/builtin/chatbi/_position.yaml b/packages/analytics/src/ai/toolset/builtin/chatbi/_position.yaml index 7bad478b..1131269d 100644 --- a/packages/analytics/src/ai/toolset/builtin/chatbi/_position.yaml +++ b/packages/analytics/src/ai/toolset/builtin/chatbi/_position.yaml @@ -1,5 +1,6 @@ - get_available_cubes - get_cube_context - dimension_member_retriever -- show_indicators -- answer_question \ No newline at end of file +- answer_question +- create_indicator +- show_indicators \ No newline at end of file From e63261ac00eddb0aff5cdb7617819f9376c29347 Mon Sep 17 00:00:00 2001 From: meta-d Date: Tue, 31 Dec 2024 12:19:41 +0800 Subject: [PATCH 06/19] feat: refresh tools positions --- .../components/toolset/toolset.component.ts | 30 +++++++--- .../xpert/studio/domain/xpert-api.service.ts | 7 ++- .../panel/toolset/toolset.component.html | 16 ++++- .../studio/panel/toolset/toolset.component.ts | 59 ++++++++++++++----- .../xpert/studio/studio.component.html | 18 +++--- .../features/xpert/studio/studio.component.ts | 2 +- .../builtin/configure/configure.component.ts | 51 ++++++++++++---- apps/cloud/src/assets/i18n/zh-Hans.json | 3 +- .../contracts/src/ai/xpert-toolset.model.ts | 1 + .../src/xpert-toolset/xpert-toolset.entity.ts | 4 +- 10 files changed, 141 insertions(+), 50 deletions(-) diff --git a/apps/cloud/src/app/features/xpert/studio/components/toolset/toolset.component.ts b/apps/cloud/src/app/features/xpert/studio/components/toolset/toolset.component.ts index ead06e13..8727b29e 100644 --- a/apps/cloud/src/app/features/xpert/studio/components/toolset/toolset.component.ts +++ b/apps/cloud/src/app/features/xpert/studio/components/toolset/toolset.component.ts @@ -3,14 +3,13 @@ import { MatTooltipModule } from '@angular/material/tooltip' import { FFlowModule } from '@foblex/flow' import { NgmSpinComponent } from '@metad/ocap-angular/common' import { TranslateModule } from '@ngx-translate/core' -import { TXpertTeamNode, XpertAgentExecutionStatusEnum, XpertToolsetService } from 'apps/cloud/src/app/@core' +import { TXpertTeamNode, XpertAgentExecutionStatusEnum, IXpertToolset } from 'apps/cloud/src/app/@core' import { EmojiAvatarComponent } from 'apps/cloud/src/app/@shared/avatar' import { derivedAsync } from 'ngxtension/derived-async' import { of } from 'rxjs' import { XpertStudioApiService } from '../../domain' import { XpertExecutionService } from '../../services/execution.service' import { XpertStudioComponent } from '../../studio.component' -import { KeyValuePipe } from '@angular/common' @Component({ selector: 'xpert-studio-node-toolset', @@ -18,7 +17,7 @@ import { KeyValuePipe } from '@angular/common' styleUrls: ['./toolset.component.scss'], standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, - imports: [FFlowModule, MatTooltipModule, TranslateModule, KeyValuePipe, EmojiAvatarComponent, NgmSpinComponent], + imports: [FFlowModule, MatTooltipModule, TranslateModule, EmojiAvatarComponent, NgmSpinComponent], host: { tabindex: '-1', '[class.selected]': 'isSelected', @@ -30,18 +29,28 @@ export class XpertStudioNodeToolsetComponent { readonly elementRef = inject(ElementRef) readonly apiService = inject(XpertStudioApiService) - readonly toolsetService = inject(XpertToolsetService) readonly executionService = inject(XpertExecutionService) readonly xpertStudioComponent = inject(XpertStudioComponent) + // Inputs readonly node = input() - readonly toolset = computed(() => this.node().entity) + + // States + readonly toolset = computed(() => this.node().entity as IXpertToolset) + readonly positions = computed(() => this.toolset()?.options?.toolPositions) readonly toolsetDetail = derivedAsync(() => { return this.toolset() ? this.apiService.getToolset(this.toolset().id) : of(null) }) - readonly availableTools = computed(() => this.toolsetDetail()?.tools.filter((_) => _.enabled)) + readonly availableTools = computed(() => { + const positions = this.positions() + const tools = this.toolsetDetail()?.tools.filter((_) => _.enabled) + + return positions && tools + ? tools.sort((a, b) => (positions[a.name] ?? Infinity) - (positions[b.name] ?? Infinity)) + : tools + }) readonly xpert = this.xpertStudioComponent.xpert readonly agentConfig = computed(() => this.xpert()?.agentConfig) @@ -52,7 +61,10 @@ export class XpertStudioNodeToolsetComponent { const executions = this.toolExecutions() return tools?.map((tool) => ({ tool, - executions: Object.values(executions?.[tool.name] ?? {}).sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime())})) + executions: Object.values(executions?.[tool.name] ?? {}).sort( + (a, b) => a.createdAt.getTime() - b.createdAt.getTime() + ) + })) }) private get hostElement(): HTMLElement { @@ -60,9 +72,9 @@ export class XpertStudioNodeToolsetComponent { } constructor() { - effect(() => { + // effect(() => { // console.log(this.node()) - }) + // }) } protected emitSelectionChangeEvent(event: MouseEvent): void { diff --git a/apps/cloud/src/app/features/xpert/studio/domain/xpert-api.service.ts b/apps/cloud/src/app/features/xpert/studio/domain/xpert-api.service.ts index e74f2bd8..111e7940 100644 --- a/apps/cloud/src/app/features/xpert/studio/domain/xpert-api.service.ts +++ b/apps/cloud/src/app/features/xpert/studio/domain/xpert-api.service.ts @@ -374,7 +374,7 @@ export class XpertStudioApiService { this.#reload.next(EReloadReason.JUST_RELOAD) } - public updateNode(key: string, value: Partial): void { + private updateNode(key: string, value: Partial): void { new UpdateNodeHandler(this.store).handle(new UpdateNodeRequest(key, value)) } public removeNode(key: string) { @@ -448,6 +448,11 @@ export class XpertStudioApiService { }, reason) } + updateToolset(key: string, toolset: IXpertToolset) { + this.updateNode(key, {entity: toolset}) + this.#reload.next(EReloadReason.TOOLSET_CREATED) + } + updateCanvas(event: FCanvasChangeEvent) { this.updateXpertOptions({ position: event.position, scale: event.scale }, EReloadReason.CANVAS_CHANGED) } diff --git a/apps/cloud/src/app/features/xpert/studio/panel/toolset/toolset.component.html b/apps/cloud/src/app/features/xpert/studio/panel/toolset/toolset.component.html index d2c6e821..b76efd78 100644 --- a/apps/cloud/src/app/features/xpert/studio/panel/toolset/toolset.component.html +++ b/apps/cloud/src/app/features/xpert/studio/panel/toolset/toolset.component.html @@ -7,7 +7,15 @@
-
+
+ + +
} -
\ No newline at end of file +
+ +@if (loading()) { + +} \ No newline at end of file diff --git a/apps/cloud/src/app/features/xpert/studio/panel/toolset/toolset.component.ts b/apps/cloud/src/app/features/xpert/studio/panel/toolset/toolset.component.ts index 33c98279..1ae3dd9d 100644 --- a/apps/cloud/src/app/features/xpert/studio/panel/toolset/toolset.component.ts +++ b/apps/cloud/src/app/features/xpert/studio/panel/toolset/toolset.component.ts @@ -1,18 +1,25 @@ -import { ChangeDetectionStrategy, Component, computed, ElementRef, inject, input } from '@angular/core' +import { ChangeDetectionStrategy, Component, computed, ElementRef, inject, input, signal } from '@angular/core' import { FormsModule } from '@angular/forms' import { MatSlideToggleModule } from '@angular/material/slide-toggle' import { MatTooltipModule } from '@angular/material/tooltip' -import { CloseSvgComponent } from '@metad/ocap-angular/common' +import { CloseSvgComponent, NgmSpinComponent } from '@metad/ocap-angular/common' import { NgmDensityDirective } from '@metad/ocap-angular/core' import { TranslateModule } from '@ngx-translate/core' -import { IXpertToolset, TXpertTeamNode, XpertToolsetService } from 'apps/cloud/src/app/@core' +import { + getErrorMessage, + injectToastr, + IXpertToolset, + TXpertTeamNode, + XpertToolsetService +} from 'apps/cloud/src/app/@core' import { EmojiAvatarComponent } from 'apps/cloud/src/app/@shared/avatar' import { uniq } from 'lodash-es' -import { derivedAsync } from 'ngxtension/derived-async' -import { of } from 'rxjs' import { XpertToolTestComponent } from '../../../tools' +import { XpertStudioApiService } from '../../domain' import { XpertStudioComponent } from '../../studio.component' import { XpertStudioPanelComponent } from '../panel.component' +import { derivedAsync } from 'ngxtension/derived-async' +import { of } from 'rxjs' @Component({ selector: 'xpert-studio-panel-toolset', @@ -28,7 +35,8 @@ import { XpertStudioPanelComponent } from '../panel.component' CloseSvgComponent, EmojiAvatarComponent, XpertToolTestComponent, - NgmDensityDirective + NgmDensityDirective, + NgmSpinComponent ] }) export class XpertStudioPanelToolsetComponent { @@ -36,22 +44,43 @@ export class XpertStudioPanelToolsetComponent { readonly xpertStudioComponent = inject(XpertStudioComponent) readonly panelComponent = inject(XpertStudioPanelComponent) readonly toolsetService = inject(XpertToolsetService) + readonly studioService = inject(XpertStudioApiService) + readonly #toastr = injectToastr() + // Inputs readonly node = input() + + // States readonly toolsetId = computed(() => this.node()?.key) + readonly toolset = computed(() => this.node()?.entity as IXpertToolset) readonly xpert = this.xpertStudioComponent.xpert readonly agentConfig = computed(() => this.xpert()?.agentConfig) + readonly positions = computed(() => this.toolset()?.options?.toolPositions) - readonly toolset = derivedAsync( - () => (this.toolsetId() ? this.toolsetService.getOneById(this.toolsetId(), { relations: ['tools'] }) : of(null)), - { initialValue: this.node()?.entity as IXpertToolset } - ) + readonly toolsetDetail = derivedAsync(() => { + return this.toolsetId() ? this.studioService.getToolset(this.toolsetId()) : of(null) + }) - readonly tools = computed(() => - this.toolset() - ?.tools.filter((_) => _.enabled) - .reverse() - ) + readonly tools = computed(() => { + const positions = this.positions() + return this.toolsetDetail()?.tools?.filter((_) => _.enabled).sort((a, b) => (positions[a.name] ?? Infinity) - (positions[b.name] ?? Infinity)) + }) + + readonly loading = signal(false) + + refresh() { + this.loading.set(true) + this.toolsetService.getOneById(this.toolsetId()).subscribe({ + next: (toolset) => { + this.loading.set(false) + this.studioService.updateToolset(this.node().key, toolset) + }, + error: (error) => { + this.loading.set(false) + this.#toastr.error(getErrorMessage(error)) + } + }) + } getSensitive(name: string) { return this.agentConfig()?.interruptBefore?.includes(name) diff --git a/apps/cloud/src/app/features/xpert/studio/studio.component.html b/apps/cloud/src/app/features/xpert/studio/studio.component.html index fb4ce3e2..0dab6a45 100644 --- a/apps/cloud/src/app/features/xpert/studio/studio.component.html +++ b/apps/cloud/src/app/features/xpert/studio/studio.component.html @@ -61,16 +61,18 @@ } } - @if (!node.parentId && rootAgent().key !== node.key) { + @if (!node.parentId) {
-
- - - -
+ @if (rootAgent().key !== node.key) { + + }
} diff --git a/apps/cloud/src/app/features/xpert/studio/studio.component.ts b/apps/cloud/src/app/features/xpert/studio/studio.component.ts index e5aa6039..4a4c7ccd 100644 --- a/apps/cloud/src/app/features/xpert/studio/studio.component.ts +++ b/apps/cloud/src/app/features/xpert/studio/studio.component.ts @@ -258,7 +258,7 @@ export class XpertStudioComponent { } public onSizeChange(event: IRect, node: TXpertTeamNode) { - this.apiService.updateNode(node.key, { position: event }) + this.apiService.moveNode(node.key, event) } private mousePosition = { diff --git a/apps/cloud/src/app/features/xpert/tools/builtin/configure/configure.component.ts b/apps/cloud/src/app/features/xpert/tools/builtin/configure/configure.component.ts index 45684b64..23e66a38 100644 --- a/apps/cloud/src/app/features/xpert/tools/builtin/configure/configure.component.ts +++ b/apps/cloud/src/app/features/xpert/tools/builtin/configure/configure.component.ts @@ -1,7 +1,17 @@ import { CommonModule } from '@angular/common' -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, computed, effect, inject, model, signal } from '@angular/core' +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + computed, + effect, + inject, + model, + signal +} from '@angular/core' import { FormBuilder, FormsModule, ReactiveFormsModule } from '@angular/forms' import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog' +import { MatTooltipModule } from '@angular/material/tooltip' import { routeAnimations } from '@metad/core' import { NgmI18nPipe } from '@metad/ocap-angular/core' import { TranslateModule } from '@ngx-translate/core' @@ -16,13 +26,12 @@ import { XpertToolsetService } from 'apps/cloud/src/app/@core' import { EmojiAvatarComponent } from 'apps/cloud/src/app/@shared/avatar' +import { omit } from 'lodash-es' import { derivedAsync } from 'ngxtension/derived-async' import { BehaviorSubject, of } from 'rxjs' import { map, switchMap } from 'rxjs/operators' import { XpertToolBuiltinAuthorizeComponent } from '../authorize/authorize.component' import { XpertToolBuiltinToolComponent } from '../tool/tool.component' -import { omit } from 'lodash-es' -import { MatTooltipModule } from '@angular/material/tooltip' @Component({ standalone: true, @@ -99,13 +108,20 @@ export class XpertToolConfigureBuiltinComponent { readonly dirty = signal(false) constructor() { - effect(() => { - const tools = this.tools() - this.toolset.update((state) => (state ? { - ...state, - tools - } : null)) - }, { allowSignalWrites: true }) + effect( + () => { + const tools = this.tools() + this.toolset.update((state) => + state + ? { + ...state, + tools + } + : null + ) + }, + { allowSignalWrites: true } + ) } openAuthorize(toolset?: IXpertToolset) { @@ -154,7 +170,13 @@ export class XpertToolConfigureBuiltinComponent { save() { this.loading.set(true) - this.xpertToolsetService.update(this.toolset().id, omit(this.toolset(), 'tags')).subscribe({ + this.xpertToolsetService.update(this.toolset().id, { + ...omit(this.toolset(), 'tags'), + options: { + ...(this.toolset().options ?? {}), + toolPositions: this.getToolPositions() + } + }).subscribe({ next: (toolset) => { this.#toastr.success('PAC.Messages.UpdatedSuccessfully', { Default: 'Updated successfully' }) this.loading.set(false) @@ -166,4 +188,11 @@ export class XpertToolConfigureBuiltinComponent { } }) } + + getToolPositions() { + return this.builtinTools().reduce((acc, tool, index) => { + acc[tool.identity.name] = index + return acc + }, {}) + } } diff --git a/apps/cloud/src/assets/i18n/zh-Hans.json b/apps/cloud/src/assets/i18n/zh-Hans.json index 48dcb389..0e2aa895 100644 --- a/apps/cloud/src/assets/i18n/zh-Hans.json +++ b/apps/cloud/src/assets/i18n/zh-Hans.json @@ -2096,7 +2096,8 @@ "PublishToLarketc": "发布到飞书等.", "ConfigurationRequired": "需要进行配置", "XpertPublished": "专家已发布成功", - "XpertPublishDeleted": "专家发布已删除" + "XpertPublishDeleted": "专家发布已删除", + "RefreshToolset": "刷新,同步工具集配置" }, "title": { "short": "Xpert AI" diff --git a/packages/contracts/src/ai/xpert-toolset.model.ts b/packages/contracts/src/ai/xpert-toolset.model.ts index ec10baf9..b8600dcb 100644 --- a/packages/contracts/src/ai/xpert-toolset.model.ts +++ b/packages/contracts/src/ai/xpert-toolset.model.ts @@ -59,6 +59,7 @@ export interface IXpertToolset extends IBasePerWorkspaceEntityModel, TXpertTools export type TXpertToolsetOptions = { baseUrl?: string + toolPositions?: Record [key: string]: any } diff --git a/packages/server-ai/src/xpert-toolset/xpert-toolset.entity.ts b/packages/server-ai/src/xpert-toolset/xpert-toolset.entity.ts index c441a999..7e08dd08 100644 --- a/packages/server-ai/src/xpert-toolset/xpert-toolset.entity.ts +++ b/packages/server-ai/src/xpert-toolset/xpert-toolset.entity.ts @@ -1,4 +1,4 @@ -import { ITag, IXpertTool, IXpertToolset, TAvatar, TToolCredentials, XpertToolsetCategoryEnum } from '@metad/contracts' +import { ITag, IXpertTool, IXpertToolset, TAvatar, TToolCredentials, TXpertToolsetOptions, XpertToolsetCategoryEnum } from '@metad/contracts' import { Tag } from '@metad/server-core' import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger' import { IsJSON, IsOptional, IsString } from 'class-validator' @@ -39,7 +39,7 @@ export class XpertToolset extends WorkspaceBaseEntity implements IXpertToolset { @IsJSON() @IsOptional() @Column({ type: 'json', nullable: true }) - options?: Record + options?: TXpertToolsetOptions @ApiPropertyOptional({ type: () => Object }) @IsJSON() From d75217f0f097bb5eb3f8a0e940c6785ac8f1fc64 Mon Sep 17 00:00:00 2001 From: meta-d Date: Tue, 31 Dec 2024 14:49:11 +0800 Subject: [PATCH 07/19] feat: chatbi toolset --- .../component-message.component.ts | 22 +++++++++---------- .../indicators/indicators.component.html | 3 ++- .../indicators/indicators.component.scss | 2 +- .../components/agent/agent.component.html | 4 ++-- .../studio/panel/toolset/toolset.component.ts | 4 +++- .../toolset/builtin/indicator/indicator.yaml | 1 + 6 files changed, 19 insertions(+), 17 deletions(-) diff --git a/apps/cloud/src/app/features/chat/component-message/component-message.component.ts b/apps/cloud/src/app/features/chat/component-message/component-message.component.ts index 77f10695..a9704bd9 100644 --- a/apps/cloud/src/app/features/chat/component-message/component-message.component.ts +++ b/apps/cloud/src/app/features/chat/component-message/component-message.component.ts @@ -20,9 +20,9 @@ import { RouterModule } from '@angular/router' import { AnalyticalCardModule } from '@metad/ocap-angular/analytical-card' import { NgmCommonModule } from '@metad/ocap-angular/common' import { NgmDSCoreService } from '@metad/ocap-angular/core' -import { NgmIndicatorComponent, NgmIndicatorExplorerComponent } from '@metad/ocap-angular/indicator' +import { NgmIndicatorComponent } from '@metad/ocap-angular/indicator' import { NgmSelectionModule, SlicersCapacity } from '@metad/ocap-angular/selection' -import { DataSettings, Indicator, IndicatorTagEnum, TimeGranularity } from '@metad/ocap-core' +import { DataSettings, Indicator, TimeGranularity } from '@metad/ocap-core' import { StoryExplorerComponent } from '@metad/story' import { ExplainComponent } from '@metad/story/story' import { NxWidgetKpiComponent } from '@metad/story/widgets/kpi' @@ -70,7 +70,7 @@ export class ChatComponentMessageComponent { readonly message = input() // Outputs - readonly register = output<{id: string; indicators?: Indicator[]}[]>() + readonly register = output<{ id: string; indicators?: Indicator[] }[]>() // States readonly data = computed(() => this.message()?.data as any) @@ -97,11 +97,12 @@ export class ChatComponentMessageComponent { effect( () => { if (this.dataSource()) { - this.register.emit([{ - id: this.dataSource(), - indicators: this.indicators() - }]) - // this.homeComponent.registerSemanticModel(this.dataSource()) + this.register.emit([ + { + id: this.dataSource(), + indicators: this.indicators() + } + ]) } }, { allowSignalWrites: true } @@ -110,10 +111,7 @@ export class ChatComponentMessageComponent { effect( () => { if (this.dataSources()) { - this.register.emit(this.dataSources().map((id) => ({id}))) - // this.dataSources().forEach((dataSource) => { - // this.homeComponent.registerSemanticModel(dataSource) - // }) + this.register.emit(this.dataSources().map((id) => ({ id }))) } }, { allowSignalWrites: true } diff --git a/apps/cloud/src/app/features/chat/component-message/indicators/indicators.component.html b/apps/cloud/src/app/features/chat/component-message/indicators/indicators.component.html index 2f37ab5a..267b8922 100644 --- a/apps/cloud/src/app/features/chat/component-message/indicators/indicators.component.html +++ b/apps/cloud/src/app/features/chat/component-message/indicators/indicators.component.html @@ -3,7 +3,8 @@ [class.active]="indicatorExplorer() === indicator.indicator" [class.last]="last" [class.has-more]="hasMore()" - [dataSettings]="indicator" [indicatorCode]="indicator.indicator" + [dataSettings]="indicator" + [indicatorCode]="indicator.indicator" [lookBack]="12" [timeGranularity]="eTimeGranularity.Month" [tagType]="indicatorTagType()" diff --git a/apps/cloud/src/app/features/chat/component-message/indicators/indicators.component.scss b/apps/cloud/src/app/features/chat/component-message/indicators/indicators.component.scss index e39313d8..9e6f59d2 100644 --- a/apps/cloud/src/app/features/chat/component-message/indicators/indicators.component.scss +++ b/apps/cloud/src/app/features/chat/component-message/indicators/indicators.component.scss @@ -2,6 +2,6 @@ @apply flex flex-col items-stretch; } -.indicator.last:not(.has-more) { +.indicator.last:not(.has-more):not(.active) { @apply rounded-b-xl; } \ No newline at end of file diff --git a/apps/cloud/src/app/features/xpert/studio/components/agent/agent.component.html b/apps/cloud/src/app/features/xpert/studio/components/agent/agent.component.html index bcb32202..817a8065 100644 --- a/apps/cloud/src/app/features/xpert/studio/components/agent/agent.component.html +++ b/apps/cloud/src/app/features/xpert/studio/components/agent/agent.component.html @@ -2,7 +2,7 @@
} -
+
@@ -11,7 +11,7 @@
-
+
diff --git a/apps/cloud/src/app/features/xpert/studio/panel/toolset/toolset.component.ts b/apps/cloud/src/app/features/xpert/studio/panel/toolset/toolset.component.ts index 1ae3dd9d..4816f01d 100644 --- a/apps/cloud/src/app/features/xpert/studio/panel/toolset/toolset.component.ts +++ b/apps/cloud/src/app/features/xpert/studio/panel/toolset/toolset.component.ts @@ -63,7 +63,9 @@ export class XpertStudioPanelToolsetComponent { readonly tools = computed(() => { const positions = this.positions() - return this.toolsetDetail()?.tools?.filter((_) => _.enabled).sort((a, b) => (positions[a.name] ?? Infinity) - (positions[b.name] ?? Infinity)) + const tools = this.toolsetDetail()?.tools?.filter((_) => _.enabled) + return positions && tools ? tools.sort((a, b) => (positions[a.name] ?? Infinity) - (positions[b.name] ?? Infinity)) + : tools }) readonly loading = signal(false) diff --git a/packages/analytics/src/ai/toolset/builtin/indicator/indicator.yaml b/packages/analytics/src/ai/toolset/builtin/indicator/indicator.yaml index 011d94bd..83d874a9 100644 --- a/packages/analytics/src/ai/toolset/builtin/indicator/indicator.yaml +++ b/packages/analytics/src/ai/toolset/builtin/indicator/indicator.yaml @@ -1,4 +1,5 @@ identity: + not_implemented: true author: Xpert AI name: indicator label: From 7b93182b0d48adcdda933b7ffded2c22f6119ca7 Mon Sep 17 00:00:00 2001 From: meta-d Date: Tue, 31 Dec 2024 16:26:39 +0800 Subject: [PATCH 08/19] feat: chat with feishu message one by one queue --- packages/analytics/src/chatbi/conversation.ts | 4 +- packages/analytics/src/chatbi/message.ts | 4 +- .../src/chatbi/tools/end-conversation.ts | 4 +- packages/analytics/src/chatbi/types.ts | 2 + .../src/integration-lark/chat/message.ts | 3 +- .../commands/handlers/chat-xpert.handler.ts | 149 +++++++++--------- .../commands/handlers/lark-message.handler.ts | 4 +- .../integration-lark/conversation.service.ts | 66 +++++++- .../src/integration-lark/lark.module.ts | 6 +- .../src/integration-lark/lark.service.ts | 42 +++-- .../server-ai/src/integration-lark/types.ts | 2 +- .../commands/handlers/chat.handler.ts | 1 - .../handlers/get-xpert-agent.handler.ts | 2 +- packages/server/src/core/redis.module.ts | 20 ++- 14 files changed, 204 insertions(+), 105 deletions(-) diff --git a/packages/analytics/src/chatbi/conversation.ts b/packages/analytics/src/chatbi/conversation.ts index f8c721e6..32eb49c6 100644 --- a/packages/analytics/src/chatbi/conversation.ts +++ b/packages/analytics/src/chatbi/conversation.ts @@ -355,7 +355,7 @@ ${createAgentStepsInstructions( async ask(text: string, message?: ChatLarkMessage) { // Running, please wait if (this.status === 'running') { - const chatStack = {text, message: new ChatLarkMessage(this.chatContext, text, this)} + const chatStack = {text, message: new ChatLarkMessage(this.chatContext as any, text, this)} await chatStack.message.update({status: 'waiting'}) this.chatStack.push(chatStack) return @@ -365,7 +365,7 @@ ${createAgentStepsInstructions( this.status = 'running' // Send thinking message to user // this.thinkingMessageId = messageId ?? await createThinkingMessage(this.chatContext, text) - this.message = message ?? new ChatLarkMessage(this.chatContext, text, this) + this.message = message ?? new ChatLarkMessage(this.chatContext as any, text, this) await this.message.update({status: 'thinking'}) // Init new thread diff --git a/packages/analytics/src/chatbi/message.ts b/packages/analytics/src/chatbi/message.ts index 5670608d..b641faeb 100644 --- a/packages/analytics/src/chatbi/message.ts +++ b/packages/analytics/src/chatbi/message.ts @@ -1,4 +1,4 @@ -import { ChatLarkContext, ChatLarkMessageStatus } from '@metad/server-ai' +import { ChatLarkContext, ChatLarkMessageStatus, LarkService } from '@metad/server-ai' import { C_CHATBI_END_CONVERSATION, IChatBIConversation } from './types' export { ChatLarkMessageStatus } @@ -23,7 +23,7 @@ export class ChatLarkMessage { private elements = [] constructor( - private chatContext: ChatLarkContext, + private chatContext: ChatLarkContext & {larkService: LarkService}, private text: string, private conversation: IChatBIConversation ) {} diff --git a/packages/analytics/src/chatbi/tools/end-conversation.ts b/packages/analytics/src/chatbi/tools/end-conversation.ts index bf326ae4..4a39451c 100644 --- a/packages/analytics/src/chatbi/tools/end-conversation.ts +++ b/packages/analytics/src/chatbi/tools/end-conversation.ts @@ -1,5 +1,5 @@ import { tool } from '@langchain/core/tools' -import { ChatLarkContext } from '@metad/server-ai' +import { ChatLarkContext, LarkService } from '@metad/server-ai' import { take } from 'rxjs/operators' import { z } from 'zod' import { ChatBIConversation } from '../conversation' @@ -20,7 +20,7 @@ export function createEndTool(context: ChatContext) { ) } -export async function errorWithEndMessage(context: ChatLarkContext, error: string, conversation: ChatBIConversation) { +export async function errorWithEndMessage(context: ChatLarkContext & {larkService?: LarkService}, error: string, conversation: ChatBIConversation) { const { larkService } = context const data = { config: { diff --git a/packages/analytics/src/chatbi/types.ts b/packages/analytics/src/chatbi/types.ts index 6dd639cb..9825cc4c 100644 --- a/packages/analytics/src/chatbi/types.ts +++ b/packages/analytics/src/chatbi/types.ts @@ -56,6 +56,8 @@ export type ChatBILarkMessage = { export type ChatBILarkContext = ChatLarkContext & { userId?: string text?: string + + larkService?: LarkService } export type IChatBIConversation = { diff --git a/packages/server-ai/src/integration-lark/chat/message.ts b/packages/server-ai/src/integration-lark/chat/message.ts index 6705346f..1ce4f035 100644 --- a/packages/server-ai/src/integration-lark/chat/message.ts +++ b/packages/server-ai/src/integration-lark/chat/message.ts @@ -12,6 +12,7 @@ import { LARK_REJECT, TLarkConversationStatus } from '../types' +import { LarkService } from '../lark.service' export type ChatLarkMessageStatus = IChatMessage['status'] | 'continuing' | 'waiting' | TLarkConversationStatus @@ -64,7 +65,7 @@ export class ChatLarkMessage extends Serializable implements ChatLarkMessageFiel public elements = [] constructor( - private chatContext: ChatLarkContext, + private chatContext: ChatLarkContext & {larkService: LarkService}, private options: { userId?: string xpertId?: string diff --git a/packages/server-ai/src/integration-lark/commands/handlers/chat-xpert.handler.ts b/packages/server-ai/src/integration-lark/commands/handlers/chat-xpert.handler.ts index c28d49a8..a8685b03 100644 --- a/packages/server-ai/src/integration-lark/commands/handlers/chat-xpert.handler.ts +++ b/packages/server-ai/src/integration-lark/commands/handlers/chat-xpert.handler.ts @@ -25,6 +25,9 @@ export class LarkChatXpertHandler implements ICommandHandler>( new XpertChatCommand( { @@ -40,88 +43,92 @@ export class LarkChatXpertHandler implements ICommandHandler { - if (event.data) { - const message = event.data - if (message.type === ChatMessageTypeEnum.MESSAGE) { - if (typeof message.data === 'string') { - responseMessageContent += message.data - } else { - if (message.data?.type === 'update') { - larkMessage.update(message.data.data) + return new Promise((resolve, reject) => { + let responseMessageContent = '' + observable.subscribe({ + next: (event) => { + if (event.data) { + const message = event.data + if (message.type === ChatMessageTypeEnum.MESSAGE) { + if (typeof message.data === 'string') { + responseMessageContent += message.data } else { - console.log(`未处理的消息:`, message) - } - } - } else if (message.type === ChatMessageTypeEnum.EVENT) { - switch (message.event) { - case ChatMessageEventTypeEnum.ON_CONVERSATION_START: { - this.conversationService - .setConversation(userId, xpertId, message.data.id) - .catch((err) => { - this.#logger.error(err) - }) - break - } - case ChatMessageEventTypeEnum.ON_MESSAGE_START: { - larkMessage.messageId = message.data.id - break - } - case ChatMessageEventTypeEnum.ON_CONVERSATION_END: { - if ( - message.data.status === XpertAgentExecutionStatusEnum.INTERRUPTED && - message.data.operation - ) { - larkMessage.confirm(message.data.operation).catch((err) => { - this.#logger.error(err) - }) + if (message.data?.type === 'update') { + larkMessage.update(message.data.data) + } else { + console.log(`未处理的消息:`, message) } - break } - default: { - console.log(`未处理的事件: ${message.event}`) + } else if (message.type === ChatMessageTypeEnum.EVENT) { + switch (message.event) { + case ChatMessageEventTypeEnum.ON_CONVERSATION_START: { + this.conversationService + .setConversation(userId, xpertId, message.data.id) + .catch((err) => { + this.#logger.error(err) + }) + break + } + case ChatMessageEventTypeEnum.ON_MESSAGE_START: { + larkMessage.messageId = message.data.id + break + } + case ChatMessageEventTypeEnum.ON_CONVERSATION_END: { + if ( + message.data.status === XpertAgentExecutionStatusEnum.INTERRUPTED && + message.data.operation + ) { + larkMessage.confirm(message.data.operation).catch((err) => { + this.#logger.error(err) + }) + } + break + } + default: { + console.log(`未处理的事件: ${message.event}`) + } } } + } else { + console.log(event) } - } else { - console.log(event) - } - }, - error: (error) => { - console.error(error) - }, - complete: () => { - console.log('End chat with lark') - if (responseMessageContent) { - larkMessage - .update({ - status: XpertAgentExecutionStatusEnum.SUCCESS, - elements: [{ tag: 'markdown', content: responseMessageContent }] - }) - .catch((error) => { - this.#logger.error(error) - }) - } else if (command.options?.reject) { - larkMessage - .update({ - status: XpertAgentExecutionStatusEnum.SUCCESS, - elements: [] - }) + }, + error: (error) => { + console.error(error) + reject(error) + }, + complete: () => { + console.log('End chat with lark') + if (responseMessageContent) { + larkMessage + .update({ + status: XpertAgentExecutionStatusEnum.SUCCESS, + elements: [{ tag: 'markdown', content: responseMessageContent }] + }) + .catch((error) => { + this.#logger.error(error) + }) + } else if (command.options?.reject) { + larkMessage + .update({ + status: XpertAgentExecutionStatusEnum.SUCCESS, + elements: [] + }) + .catch((error) => { + this.#logger.error(error) + }) + } + + this.saveLarkMessage(larkMessage) + .then(() => resolve(larkMessage)) .catch((error) => { this.#logger.error(error) + reject(error) }) } - - this.saveLarkMessage(larkMessage).catch((error) => { - this.#logger.error(error) - }) - } + }) }) + } async saveLarkMessage(message: ChatLarkMessage) { diff --git a/packages/server-ai/src/integration-lark/commands/handlers/lark-message.handler.ts b/packages/server-ai/src/integration-lark/commands/handlers/lark-message.handler.ts index baa78ad6..f7c0bae9 100644 --- a/packages/server-ai/src/integration-lark/commands/handlers/lark-message.handler.ts +++ b/packages/server-ai/src/integration-lark/commands/handlers/lark-message.handler.ts @@ -5,11 +5,13 @@ import { LarkConversationService } from '../../conversation.service' import { LarkChatAgentCommand } from '../chat-agent.command' import { LarkChatXpertCommand } from '../chat-xpert.command' import { LarkMessageCommand } from '../mesage.command' +import { LarkService } from '../../lark.service' @CommandHandler(LarkMessageCommand) export class LarkMessageHandler implements ICommandHandler { constructor( private readonly conversationService: LarkConversationService, + private readonly larkService: LarkService, private readonly commandBus: CommandBus ) {} @@ -25,7 +27,7 @@ export class LarkMessageHandler implements ICommandHandler { const userId = RequestContext.currentUserId() const larkMessage = new ChatLarkMessage( - input, + {...input, larkService: this.larkService }, { xpertId: integration.options.xpertId, userId, diff --git a/packages/server-ai/src/integration-lark/conversation.service.ts b/packages/server-ai/src/integration-lark/conversation.service.ts index 3cdf12a6..cd5d5ecd 100644 --- a/packages/server-ai/src/integration-lark/conversation.service.ts +++ b/packages/server-ai/src/integration-lark/conversation.service.ts @@ -1,19 +1,27 @@ import { IChatConversation } from '@metad/contracts' -import { CACHE_MANAGER, Inject, Injectable, Logger, forwardRef } from '@nestjs/common' +import { REDIS_OPTIONS } from '@metad/server-core' +import { CACHE_MANAGER, forwardRef, Inject, Injectable, Logger, OnModuleDestroy } from '@nestjs/common' import { CommandBus, QueryBus } from '@nestjs/cqrs' +import * as Bull from 'bull' +import { Queue } from 'bull' import { Cache } from 'cache-manager' +import * as Redis from 'ioredis' +import { Observable } from 'rxjs' import { GetChatConversationQuery } from '../chat-conversation' import { ChatLarkMessage } from './chat/message' +import { LarkMessageCommand } from './commands' import { LarkChatXpertCommand } from './commands/chat-xpert.command' import { LarkService } from './lark.service' import { ChatLarkContext } from './types' @Injectable() -export class LarkConversationService { +export class LarkConversationService implements OnModuleDestroy { readonly #logger = new Logger(LarkConversationService.name) public readonly prefix = 'chat' + private userQueues: Map = new Map() + constructor( private readonly commandBus: CommandBus, private readonly queryBus: QueryBus, @@ -21,6 +29,8 @@ export class LarkConversationService { private readonly cacheManager: Cache, @Inject(forwardRef(() => LarkService)) private readonly larkService: LarkService, + @Inject(REDIS_OPTIONS) + private readonly redisOptions: Redis.RedisOptions ) {} async getConversation(userId: string, xpertId: string) { @@ -39,7 +49,11 @@ export class LarkConversationService { ) const lastMessage = conversation.messages.slice(-1)[0] if (lastMessage?.thirdPartyMessage) { - const message = new ChatLarkMessage(chatContext, {...lastMessage.thirdPartyMessage, messageId: lastMessage.id } as any, this) + const message = new ChatLarkMessage( + { ...chatContext, larkService: this.larkService }, + { ...lastMessage.thirdPartyMessage, messageId: lastMessage.id } as any, + this + ) await message.update({ status: 'end' }) } await this.cacheManager.del(this.prefix + `/${userId}/${xpertId}`) @@ -65,4 +79,50 @@ export class LarkConversationService { }) ) } + + /** + * Get or create user queue + * + * @param userId + * @returns + */ + async getUserQueue(userId: string): Promise { + if (!this.userQueues.has(userId)) { + const queue = new Bull(`user-${userId}`, { + redis: this.redisOptions + }) + + /** + * Bind processing logic, maximum concurrency is one + */ + queue.process(1, async (job) => { + console.log(`Processing job for user ${userId}:`, job.data) + + await this.commandBus.execute>(new LarkMessageCommand(job.data)) + + return `Processed message: ${job.data.message}` + }) + + // completed event + queue.on('completed', (job) => { + console.log(`Job ${job.id} for user ${userId} completed.`) + }) + + // failed event + queue.on('failed', (job, error) => { + console.error(`Job ${job.id} for user ${userId} failed:`, error.message) + }) + + // Save user's queue + this.userQueues.set(userId, queue) + } + + return this.userQueues.get(userId) + } + + async onModuleDestroy() { + for (const queue of this.userQueues.values()) { + await queue.close() + } + } } diff --git a/packages/server-ai/src/integration-lark/lark.module.ts b/packages/server-ai/src/integration-lark/lark.module.ts index a4fc9e5c..c992b17d 100644 --- a/packages/server-ai/src/integration-lark/lark.module.ts +++ b/packages/server-ai/src/integration-lark/lark.module.ts @@ -1,4 +1,4 @@ -import { IntegrationModule, RoleModule, UserModule } from '@metad/server-core' +import { IntegrationModule, RedisModule, RoleModule, UserModule } from '@metad/server-core' import { CacheModule, CacheStore, Module } from '@nestjs/common' import { ConfigModule, ConfigService } from '@nestjs/config' import { CqrsModule } from '@nestjs/cqrs' @@ -15,6 +15,7 @@ import { LarkService } from './lark.service' RouterModule.forRoutes([{ path: '/lark', module: IntegrationLarkModule }]), CacheModule.registerAsync({ imports: [ConfigModule], + // @todo relace with REDIS_OPTIONS useFactory: async (configService: ConfigService) => { const host = configService.get('REDIS_HOST') || 'localhost' const port = configService.get('REDIS_PORT') || 6379 @@ -24,7 +25,7 @@ import { LarkService } from './lark.service' const store = await redisStore({ socket: { host, - port, + port }, password }) @@ -37,6 +38,7 @@ import { LarkService } from './lark.service' }, inject: [ConfigService] }), + RedisModule, CqrsModule, UserModule, RoleModule, diff --git a/packages/server-ai/src/integration-lark/lark.service.ts b/packages/server-ai/src/integration-lark/lark.service.ts index f9d55f21..10f1db94 100644 --- a/packages/server-ai/src/integration-lark/lark.service.ts +++ b/packages/server-ai/src/integration-lark/lark.service.ts @@ -161,19 +161,32 @@ export class LarkService { const user = RequestContext.currentUser() // await this.getUser(client, tenant.id, data.sender.sender_id.union_id) - await this.commandBus.execute>( - new LarkMessageCommand({ - tenant, - organizationId, - integrationId: integration.id, - integration, - user, - message: data as any, - chatId, - chatType: data.message.chat_type, - larkService: this - }) - ) + const userQueue = await this.conversation.getUserQueue(user.id) + // 添加任务到队列 + await userQueue.add({ + tenant, + organizationId, + integrationId: integration.id, + integration, + user, + message: data as any, + chatId, + chatType: data.message.chat_type, + // larkService: this + }) + // await this.commandBus.execute>( + // new LarkMessageCommand({ + // tenant, + // organizationId, + // integrationId: integration.id, + // integration, + // user, + // message: data as any, + // chatId, + // chatType: data.message.chat_type, + // larkService: this + // }) + // ) } catch(err) { console.error(err) } @@ -263,7 +276,7 @@ export class LarkService { organizationId, integrationId: integration.id, integration, - larkService: this, + // larkService: this, user }, user.id, xpertId) @@ -580,4 +593,5 @@ export class LarkService { .catch((err) => subscriber.error(err)) }) } + } diff --git a/packages/server-ai/src/integration-lark/types.ts b/packages/server-ai/src/integration-lark/types.ts index 462cb084..b6bbe2cf 100644 --- a/packages/server-ai/src/integration-lark/types.ts +++ b/packages/server-ai/src/integration-lark/types.ts @@ -19,7 +19,7 @@ export type ChatLarkContext = { integrationId: string integration: IIntegration user: IUser - larkService: LarkService + // larkService: LarkService chatId?: string chatType?: 'p2p' | 'group' | string message?: T diff --git a/packages/server-ai/src/xpert-agent/commands/handlers/chat.handler.ts b/packages/server-ai/src/xpert-agent/commands/handlers/chat.handler.ts index ca1dfb8b..eaf689be 100644 --- a/packages/server-ai/src/xpert-agent/commands/handlers/chat.handler.ts +++ b/packages/server-ai/src/xpert-agent/commands/handlers/chat.handler.ts @@ -64,7 +64,6 @@ export class XpertAgentChatHandler implements ICommandHandler>( new XpertAgentExecuteCommand(input, agentKey, xpert, { ...(options ?? {}), - isDraft: true, rootExecutionId: execution.id, thread_id, execution, diff --git a/packages/server-ai/src/xpert/queries/handlers/get-xpert-agent.handler.ts b/packages/server-ai/src/xpert/queries/handlers/get-xpert-agent.handler.ts index 36d638f0..0dbedfa7 100644 --- a/packages/server-ai/src/xpert/queries/handlers/get-xpert-agent.handler.ts +++ b/packages/server-ai/src/xpert/queries/handlers/get-xpert-agent.handler.ts @@ -17,7 +17,7 @@ export class GetXpertAgentHandler implements IQueryHandler { public async execute(command: GetXpertAgentQuery): Promise { const { id, agentKey, draft } = command const xpert = await this.service.findOne(id, { - relations: ['agent', 'copilotModel', 'copilotModel.copilot', 'agents', 'agents.copilotModel', 'knowledgebases', 'toolsets', 'executors'] + relations: ['agent', 'agent.copilotModel', 'copilotModel', 'copilotModel.copilot', 'agents', 'agents.copilotModel', 'knowledgebases', 'toolsets', 'executors'] }) const tenantId = xpert.tenantId diff --git a/packages/server/src/core/redis.module.ts b/packages/server/src/core/redis.module.ts index 433da56d..a6da19c2 100644 --- a/packages/server/src/core/redis.module.ts +++ b/packages/server/src/core/redis.module.ts @@ -1,14 +1,26 @@ import { Module } from '@nestjs/common' import { ConfigModule, ConfigService } from '@nestjs/config' +import * as Redis from 'ioredis' import { createClient } from 'redis' export const REDIS_CLIENT = 'REDIS_CLIENT' +export const REDIS_OPTIONS = 'Redis_Options' @Module({ - imports: [ - ConfigModule - ], + imports: [ConfigModule], providers: [ + { + inject: [ConfigService], + provide: REDIS_OPTIONS, + useFactory: async (configService: ConfigService) => { + return { + host: configService.get('REDIS_HOST') || 'localhost', + port: configService.get('REDIS_PORT') || 6379, + username: configService.get('REDIS.USERNAME') || '', + password: configService.get('REDIS_PASSWORD') || '' + } as Redis.RedisOptions + } + }, { inject: [ConfigService], provide: REDIS_CLIENT, @@ -28,6 +40,6 @@ export const REDIS_CLIENT = 'REDIS_CLIENT' } } ], - exports: [REDIS_CLIENT] + exports: [REDIS_OPTIONS, REDIS_CLIENT] }) export class RedisModule {} From 91e4895b0f449d12cf8184fde4767d238f00aa87 Mon Sep 17 00:00:00 2001 From: meta-d Date: Tue, 31 Dec 2024 20:53:13 +0800 Subject: [PATCH 09/19] feat: translate in toolset --- .../builtin/chatbi-lark/chatbi-lark.ts | 2 +- .../builtin/chatbi-lark/tools/welcome.ts | 202 +++++++++--------- packages/analytics/src/app.module.ts | 1 - packages/contracts/src/integration/lark.ts | 33 ++- .../src/integration-lark/chat/message.ts | 33 +-- .../commands/handlers/lark-message.handler.ts | 3 - .../integration-lark/conversation.service.ts | 71 +++--- .../src/integration-lark/lark.module.ts | 23 +- .../src/integration-lark/lark.service.ts | 65 +++--- .../server-ai/src/integration-lark/types.ts | 6 +- .../commands/handlers/execute.handler.ts | 10 +- .../xpert-agent/commands/handlers/types.ts | 2 +- .../provider/builtin/builtin-toolset.ts | 21 ++ .../xpert-toolset/xpert-toolset.service.ts | 14 +- packages/server/src/i18n/en/toolset.json | 13 ++ packages/server/src/i18n/zh/toolset.json | 13 ++ 16 files changed, 275 insertions(+), 237 deletions(-) create mode 100644 packages/server/src/i18n/en/toolset.json create mode 100644 packages/server/src/i18n/zh/toolset.json diff --git a/packages/analytics/src/ai/toolset/builtin/chatbi-lark/chatbi-lark.ts b/packages/analytics/src/ai/toolset/builtin/chatbi-lark/chatbi-lark.ts index 4de43271..4719d476 100644 --- a/packages/analytics/src/ai/toolset/builtin/chatbi-lark/chatbi-lark.ts +++ b/packages/analytics/src/ai/toolset/builtin/chatbi-lark/chatbi-lark.ts @@ -24,7 +24,7 @@ export class ChatBILarkToolset extends AbstractChatBIToolset { const tools = this.toolset.tools.filter((_) => _.enabled) if (tools.find((_) => _.name === ChatBILarkToolsEnum.WELCOME)) { this.tools.push( - createWelcomeTool({ + createWelcomeTool(this, { dsCoreService: this.dsCoreService, models: this.models, logger: this.logger diff --git a/packages/analytics/src/ai/toolset/builtin/chatbi-lark/tools/welcome.ts b/packages/analytics/src/ai/toolset/builtin/chatbi-lark/tools/welcome.ts index 5324b5aa..f377d28d 100644 --- a/packages/analytics/src/ai/toolset/builtin/chatbi-lark/tools/welcome.ts +++ b/packages/analytics/src/ai/toolset/builtin/chatbi-lark/tools/welcome.ts @@ -1,18 +1,20 @@ import { tool } from '@langchain/core/tools' +import { Command, LangGraphRunnableConfig } from '@langchain/langgraph' import { ChatMessageTypeEnum, JSONValue } from '@metad/contracts' import { ChatLarkMessage } from '@metad/server-ai' import { shortuuid } from '@metad/server-common' -import { flatten, Logger } from '@nestjs/common' +import { Logger } from '@nestjs/common' import { z } from 'zod' import { ChatBILarkContext, ChatBILarkToolsEnum } from '../types' +import { AbstractChatBIToolset } from '../../chatbi/chatbi-toolset' -export function createWelcomeTool(context: Partial) { +export function createWelcomeTool(chatbi: AbstractChatBIToolset, context: Partial) { const logger = new Logger('WelcomeTool') const { models: _models } = context return tool( - async ({ models, more }, config): Promise => { + async ({ language, models, more }, config: LangGraphRunnableConfig) => { logger.debug( - `[ChatBI] [Copilot Tool] [Welcome] models: ${JSON.stringify(models, null, 2)} and more: ${JSON.stringify(more, null, 2)}` + `[ChatBI] [Welcome] Language: ${language}, models: ${JSON.stringify(models, null, 2)} and more: ${JSON.stringify(more, null, 2)}` ) const { subscriber } = config.configurable @@ -20,66 +22,100 @@ export function createWelcomeTool(context: Partial) { const elements = [] elements.push({ tag: 'markdown', - content: '猜你想问:' + content: await chatbi.translate('toolset.ChatBI.GuessAsk', { lang: language }) }) - elements.push( - ...flatten( - models.map(({ modelId, cubeName, prompts }) => { - const chatModel = _models.find( - (model) => model.modelId === modelId && model.entity === cubeName - ) - if (!chatModel) { - return [] - } - return [ + for await (const model of models) { + const {modelId, cubeName, prompts} = model + const chatModel = _models.find( + (model) => model.modelId === modelId && model.entity === cubeName + ) + if (!chatModel) { + throw new Error(await chatbi.translate('toolset.ChatBI.Error.NoModel', { lang: language, args: {model: modelId, cube: cubeName} })) + } + + const questionPrefix = await chatbi.translate('toolset.ChatBI.AnalyzeDataset', { lang: language, args: {cube: chatModel.entityCaption} }) + + elements.push( + { + tag: 'markdown', + content: await chatbi.translate('toolset.ChatBI.QuestionsAboutDataset', + { + lang: language, + args: { + cube: chatModel.entityCaption + } + }) + }, + { + tag: 'column_set', + columns: [ { - tag: 'markdown', - content: `- 关于数据集 “${chatModel.entityCaption}”, 您可能关心的问题:` + tag: 'column', + width: '23px' }, { - tag: 'column_set', - columns: [ - { - tag: 'column', - width: '23px' - }, - { - tag: 'column', - elements: [ - ...prompts.map((prompt) => { - const fullPrompt = `分析数据集 “${chatModel.entityCaption}”:` + prompt - return { - tag: 'button', - text: { - tag: 'plain_text', - content: prompt - }, - type: 'primary_text', - complex_interaction: true, - width: 'default', - size: 'small', - value: fullPrompt, - hover_tips: { - tag: 'plain_text', - content: fullPrompt - } - } - }) - ] - } + tag: 'column', + elements: [ + ...prompts.map((prompt) => { + const fullPrompt = questionPrefix + prompt + return { + tag: 'button', + text: { + tag: 'plain_text', + content: prompt + }, + type: 'primary_text', + complex_interaction: true, + width: 'default', + size: 'small', + value: fullPrompt, + hover_tips: { + tag: 'plain_text', + content: fullPrompt + } + } + }) ] } ] - }) + } ) - ) + } + if (more?.length) { elements.push({ tag: 'markdown', - content: `- 更多数据集:` + content: await chatbi.translate('toolset.ChatBI.MoreDatasets', { lang: language }) }) + const columnSet = [] + for await (const model of more) { + const { modelId, cubeName } = model + const chatModel = _models.find( + (model) => model.modelId === modelId && model.entity === cubeName + ) + + if (!chatModel) { + throw new Error(await chatbi.translate('toolset.ChatBI.Error.NoModel', { lang: language, args: {model: modelId, cube: cubeName} })) + } + + const welcomeMessage = await chatbi.translate('toolset.ChatBI.GiveWelcomeMessage', {lang: language, args: {cube: chatModel.entityCaption}}) + + columnSet.push({ + tag: 'button', + text: { + tag: 'plain_text', + content: chatModel.entityCaption + }, + type: 'primary_text', + complex_interaction: true, + width: 'default', + size: 'small', + value: welcomeMessage + }) + } + elements.push({ tag: 'column_set', columns: [ @@ -89,38 +125,11 @@ export function createWelcomeTool(context: Partial) { }, { tag: 'column', - elements: [ - ...more.map(({ modelId, cubeName }) => { - const chatModel = _models.find( - (model) => model.modelId === modelId && model.entity === cubeName - ) - - if (!chatModel) { - throw new Error(`No model found for ${modelId} and ${cubeName}`) - } - - return { - tag: 'button', - text: { - tag: 'plain_text', - content: chatModel.entityCaption - }, - type: 'primary_text', - complex_interaction: true, - width: 'default', - size: 'small', - value: `针对数据集 “${chatModel.entityCaption}” 给出欢迎信息` - } - }) - ] + elements: columnSet } ] }) } - elements.push({ - tag: 'markdown', - content: `您也可以对我说 “**结束对话**” 来结束本轮对话。` - }) subscriber.next({ data: { @@ -133,7 +142,7 @@ export function createWelcomeTool(context: Partial) { header: { title: { tag: 'plain_text', - content: 'Hi, 我是 ChatBI, 我可以根据你的问题分析数据、生成图表' + content: await chatbi.translate('toolset.ChatBI.Welcome', {lang: language,}) }, subtitle: { tag: 'plain_text', @@ -147,31 +156,26 @@ export function createWelcomeTool(context: Partial) { } }) - // larkMessage.update({ - // elements, - // header: { - // title: { - // tag: 'plain_text', - // content: 'Hi, 我是 ChatBI, 我可以根据你的问题分析数据、生成图表' - // }, - // subtitle: { - // tag: 'plain_text', - // content: '' - // }, - // template: ChatLarkMessage.headerTemplate, - // icon: ChatLarkMessage.logoIcon - // }, - // action: (action) => { - // larkMessage.ask(action.value) - // } - // }) - - return 'The welcome message and opening questions have been sent to the user, no need to respond with any content.' + const toolCallId = config.metadata.tool_call_id + return new Command({ + update: { + sys_language: language, + // update the message history + messages: [ + { + role: 'tool', + content: 'The welcome message and opening questions have been sent to the user, no need to respond with any content.', + tool_call_id: toolCallId + } + ] + } + }) }, { name: ChatBILarkToolsEnum.WELCOME, description: 'Show welcome message to guidle user ask questions abount models.', schema: z.object({ + language: z.enum(['en', 'zh']).describe('Language ​​used by user'), models: z .array( z.object({ diff --git a/packages/analytics/src/app.module.ts b/packages/analytics/src/app.module.ts index a4cc738a..6a98494a 100644 --- a/packages/analytics/src/app.module.ts +++ b/packages/analytics/src/app.module.ts @@ -65,7 +65,6 @@ import { ChatBIModelModule } from './chatbi-model' CacheModule.register(), CqrsModule, forwardRef(() => TenantModule), - // forwardRef(() => EmployeeModule), forwardRef(() => OrganizationModule), ProjectModule, CollectionModule, diff --git a/packages/contracts/src/integration/lark.ts b/packages/contracts/src/integration/lark.ts index 4cd0b3cf..956d40a5 100644 --- a/packages/contracts/src/integration/lark.ts +++ b/packages/contracts/src/integration/lark.ts @@ -1,5 +1,14 @@ import { IIntegration, IntegrationEnum, TIntegrationProvider } from '../integration.model' +export type TIntegrationLarkOptions = { + appId: string + appSecret: string + verificationToken: string + encryptKey: string + xpertId: string + prefLanguage: string +} + export const IntegrationLarkProvider: TIntegrationProvider = { name: IntegrationEnum.LARK, label: { @@ -31,7 +40,29 @@ export const IntegrationLarkProvider: TIntegrationProvider = { zh_Hans: '选择一个对应的数字专家' }, selectUrl: '/api/xpert/select-options' - } + }, + // prefLanguage: { + // type: 'select', + // title: { + // en_US: 'Preferred Language', + // zh_Hans: '首选语言' + // }, + // options: [ + // { + // value: 'en', + // label: { + // en_US: 'English', + // zh_Hans: '英语' + // } + // }, { + // value: 'zh', + // label: { + // en_US: 'Chinese', + // zh_Hans: '中文' + // } + // } + // ] + // }, }, required: ['appId', 'appSecret'], secret: ['appSecret', 'verificationToken', 'encryptKey'] diff --git a/packages/server-ai/src/integration-lark/chat/message.ts b/packages/server-ai/src/integration-lark/chat/message.ts index 1ce4f035..ec553811 100644 --- a/packages/server-ai/src/integration-lark/chat/message.ts +++ b/packages/server-ai/src/integration-lark/chat/message.ts @@ -1,12 +1,8 @@ import { Serializable } from '@langchain/core/load/serializable' import { I18nObject, IChatMessage, TSensitiveOperation } from '@metad/contracts' import { Logger } from '@nestjs/common' -import { LarkConversationService } from '../conversation.service' import { ChatLarkContext, - isConfirmAction, - isEndAction, - isRejectAction, LARK_CONFIRM, LARK_END_CONVERSATION, LARK_REJECT, @@ -23,6 +19,7 @@ export interface ChatLarkMessageFields { messageId: string; // Status of lark message status: ChatLarkMessageStatus + language: string header: any elements: any[] } @@ -37,7 +34,8 @@ export class ChatLarkMessage extends Serializable implements ChatLarkMessageFiel id: this.id, messageId: this.messageId, header: this.header, - elements: this.elements + elements: this.elements, + language: this.language, } } @@ -56,6 +54,7 @@ export class ChatLarkMessage extends Serializable implements ChatLarkMessageFiel public status: ChatLarkMessageStatus = 'thinking' // ID of IChatMessage public messageId: string + public language: string get larkService() { return this.chatContext.larkService @@ -71,12 +70,12 @@ export class ChatLarkMessage extends Serializable implements ChatLarkMessageFiel xpertId?: string text?: string } & Partial, - private conversation: LarkConversationService ) { super(options) this.id = options.id this.messageId = options.messageId this.status = options.status + this.language = options.language this.header = options.header this.elements = options.elements ?? [] } @@ -143,7 +142,6 @@ export class ChatLarkMessage extends Serializable implements ChatLarkMessageFiel } getEndAction() { - console.log(`3: ${this.status}`) return { tag: 'action', layout: 'default', @@ -222,10 +220,7 @@ export class ChatLarkMessage extends Serializable implements ChatLarkMessageFiel this.header = options.header } - console.log(`1: ${this.status}`) const elements = this.getCard() - console.log(`4: ${this.status}`) - if (this.id) { this.larkService .patchAction(this.chatContext, this.id, { @@ -234,7 +229,7 @@ export class ChatLarkMessage extends Serializable implements ChatLarkMessageFiel }) .subscribe({ next: (message) => { - this.onAction(message.action, options?.action) + // this.onAction(message.action, options?.action) }, error: (err) => { console.error(err) @@ -249,7 +244,7 @@ export class ChatLarkMessage extends Serializable implements ChatLarkMessageFiel }, { next: async (message) => { - this.onAction(message.action, options?.action) + // this.onAction(message.action, options?.action) }, error: (err) => { console.error(err) @@ -261,20 +256,6 @@ export class ChatLarkMessage extends Serializable implements ChatLarkMessageFiel } } - async onAction(action: { value: string }, callback?: (action) => void) { - if (isEndAction(action?.value)) { - await this.conversation.endConversation(this.chatContext, this.options.userId, this.options.xpertId) - } else if (isConfirmAction(action?.value)) { - await this.conversation.confirm(this.options.xpertId, this) - } else if (isRejectAction(action?.value)) { - await this.conversation.reject(this.options.xpertId, this) - } else if (typeof action?.value === 'string') { - await this.conversation.ask(this.options.xpertId, action.value, this) - } else { - callback?.(action) - } - } - async confirm(operation: TSensitiveOperation) { await this.update({ status: 'interrupted', diff --git a/packages/server-ai/src/integration-lark/commands/handlers/lark-message.handler.ts b/packages/server-ai/src/integration-lark/commands/handlers/lark-message.handler.ts index f7c0bae9..f4b0a2dd 100644 --- a/packages/server-ai/src/integration-lark/commands/handlers/lark-message.handler.ts +++ b/packages/server-ai/src/integration-lark/commands/handlers/lark-message.handler.ts @@ -1,7 +1,6 @@ import { RequestContext } from '@metad/server-core' import { CommandBus, CommandHandler, ICommandHandler } from '@nestjs/cqrs' import { ChatLarkMessage } from '../../chat/message' -import { LarkConversationService } from '../../conversation.service' import { LarkChatAgentCommand } from '../chat-agent.command' import { LarkChatXpertCommand } from '../chat-xpert.command' import { LarkMessageCommand } from '../mesage.command' @@ -10,7 +9,6 @@ import { LarkService } from '../../lark.service' @CommandHandler(LarkMessageCommand) export class LarkMessageHandler implements ICommandHandler { constructor( - private readonly conversationService: LarkConversationService, private readonly larkService: LarkService, private readonly commandBus: CommandBus ) {} @@ -33,7 +31,6 @@ export class LarkMessageHandler implements ICommandHandler { userId, text }, - this.conversationService ) return await this.commandBus.execute( diff --git a/packages/server-ai/src/integration-lark/conversation.service.ts b/packages/server-ai/src/integration-lark/conversation.service.ts index cd5d5ecd..c63ec677 100644 --- a/packages/server-ai/src/integration-lark/conversation.service.ts +++ b/packages/server-ai/src/integration-lark/conversation.service.ts @@ -1,5 +1,5 @@ import { IChatConversation } from '@metad/contracts' -import { REDIS_OPTIONS } from '@metad/server-core' +import { REDIS_OPTIONS, runWithRequestContext, UserService } from '@metad/server-core' import { CACHE_MANAGER, forwardRef, Inject, Injectable, Logger, OnModuleDestroy } from '@nestjs/common' import { CommandBus, QueryBus } from '@nestjs/cqrs' import * as Bull from 'bull' @@ -12,7 +12,7 @@ import { ChatLarkMessage } from './chat/message' import { LarkMessageCommand } from './commands' import { LarkChatXpertCommand } from './commands/chat-xpert.command' import { LarkService } from './lark.service' -import { ChatLarkContext } from './types' +import { ChatLarkContext, isConfirmAction, isEndAction, isRejectAction } from './types' @Injectable() export class LarkConversationService implements OnModuleDestroy { @@ -23,6 +23,7 @@ export class LarkConversationService implements OnModuleDestroy { private userQueues: Map = new Map() constructor( + private readonly userService: UserService, private readonly commandBus: CommandBus, private readonly queryBus: QueryBus, @Inject(CACHE_MANAGER) @@ -41,43 +42,44 @@ export class LarkConversationService implements OnModuleDestroy { return await this.cacheManager.set(this.prefix + `/${userId}/${xpertId}`, conversationId) } - async endConversation(chatContext: ChatLarkContext, userId: string, xpertId: string) { - const id = await this.getConversation(userId, xpertId) - if (id) { - const conversation = await this.queryBus.execute( - new GetChatConversationQuery({ id }, ['messages']) - ) - const lastMessage = conversation.messages.slice(-1)[0] - if (lastMessage?.thirdPartyMessage) { - const message = new ChatLarkMessage( - { ...chatContext, larkService: this.larkService }, - { ...lastMessage.thirdPartyMessage, messageId: lastMessage.id } as any, - this - ) - await message.update({ status: 'end' }) - } - await this.cacheManager.del(this.prefix + `/${userId}/${xpertId}`) - } - } - async ask(xpertId: string, content: string, message: ChatLarkMessage) { await this.commandBus.execute(new LarkChatXpertCommand(xpertId, content, message)) } - async confirm(xpertId: string, message: ChatLarkMessage) { - await this.commandBus.execute( - new LarkChatXpertCommand(xpertId, null, message, { - confirm: true - }) + async onAction(action: string, chatContext: ChatLarkContext, userId: string, xpertId: string) { + const id = await this.getConversation(userId, xpertId) + + const conversation = await this.queryBus.execute( + new GetChatConversationQuery({ id }, ['messages']) ) - } + const lastMessage = conversation.messages.slice(-1)[0] - async reject(xpertId: string, message: ChatLarkMessage) { - await this.commandBus.execute( - new LarkChatXpertCommand(xpertId, null, message, { - reject: true - }) + const message = new ChatLarkMessage( + { ...chatContext, larkService: this.larkService }, + { ...(lastMessage?.thirdPartyMessage ?? {}), messageId: lastMessage.id } as any, ) + + if (isEndAction(action)) { + await message.update({ status: 'end' }) + await this.cacheManager.del(this.prefix + `/${userId}/${xpertId}`) + } else if (isConfirmAction(action)) { + await this.commandBus.execute( + new LarkChatXpertCommand(xpertId, null, message, { + confirm: true + }) + ) + } else if (isRejectAction(action)) { + await this.commandBus.execute( + new LarkChatXpertCommand(xpertId, null, message, { + reject: true + }) + ) + } else { + await this.commandBus.execute(new LarkChatXpertCommand(xpertId, action, new ChatLarkMessage( + { ...chatContext, larkService: this.larkService }, + null, + ))) + } } /** @@ -96,11 +98,8 @@ export class LarkConversationService implements OnModuleDestroy { * Bind processing logic, maximum concurrency is one */ queue.process(1, async (job) => { - console.log(`Processing job for user ${userId}:`, job.data) - await this.commandBus.execute>(new LarkMessageCommand(job.data)) - - return `Processed message: ${job.data.message}` + return `Processed message: ${job.id}` }) // completed event diff --git a/packages/server-ai/src/integration-lark/lark.module.ts b/packages/server-ai/src/integration-lark/lark.module.ts index c992b17d..f7663e67 100644 --- a/packages/server-ai/src/integration-lark/lark.module.ts +++ b/packages/server-ai/src/integration-lark/lark.module.ts @@ -1,8 +1,8 @@ -import { IntegrationModule, RedisModule, RoleModule, UserModule } from '@metad/server-core' +import { IntegrationModule, REDIS_OPTIONS, RedisModule, RoleModule, UserModule } from '@metad/server-core' import { CacheModule, CacheStore, Module } from '@nestjs/common' -import { ConfigModule, ConfigService } from '@nestjs/config' import { CqrsModule } from '@nestjs/cqrs' import { redisStore } from 'cache-manager-redis-yet' +import { RedisOptions } from 'ioredis' import { RouterModule } from 'nest-router' import { LarkTokenStrategy } from './auth/lark-token.strategy' import { CommandHandlers } from './commands/handlers' @@ -14,20 +14,15 @@ import { LarkService } from './lark.service' imports: [ RouterModule.forRoutes([{ path: '/lark', module: IntegrationLarkModule }]), CacheModule.registerAsync({ - imports: [ConfigModule], - // @todo relace with REDIS_OPTIONS - useFactory: async (configService: ConfigService) => { - const host = configService.get('REDIS_HOST') || 'localhost' - const port = configService.get('REDIS_PORT') || 6379 - // const username = configService.get('REDIS.USERNAME') || '' - const password = configService.get('REDIS_PASSWORD') || '' - + imports: [RedisModule], + useFactory: async (redisOptions: RedisOptions) => { const store = await redisStore({ socket: { - host, - port + host: redisOptions.host, + port: redisOptions.port }, - password + username: redisOptions.username, + password: redisOptions.password }) return { @@ -36,7 +31,7 @@ import { LarkService } from './lark.service' // ttl: configService.get('CACHE_TTL'), } }, - inject: [ConfigService] + inject: [REDIS_OPTIONS] }), RedisModule, CqrsModule, diff --git a/packages/server-ai/src/integration-lark/lark.service.ts b/packages/server-ai/src/integration-lark/lark.service.ts index 10f1db94..3ed94e8e 100644 --- a/packages/server-ai/src/integration-lark/lark.service.ts +++ b/packages/server-ai/src/integration-lark/lark.service.ts @@ -10,7 +10,7 @@ import { isEqual } from 'date-fns' import express from 'express' import { filter, Observable, Observer, Subject, Subscriber } from 'rxjs' import { LarkBotMenuCommand, LarkMessageCommand } from './commands' -import { ChatLarkContext, isEndAction, LarkMessage } from './types' +import { ChatLarkContext, isConversationAction, isEndAction, LarkMessage } from './types' import { LarkConversationService } from './conversation.service' @Injectable() @@ -172,21 +172,7 @@ export class LarkService { message: data as any, chatId, chatType: data.message.chat_type, - // larkService: this }) - // await this.commandBus.execute>( - // new LarkMessageCommand({ - // tenant, - // organizationId, - // integrationId: integration.id, - // integration, - // user, - // message: data as any, - // chatId, - // chatType: data.message.chat_type, - // larkService: this - // }) - // ) } catch(err) { console.error(err) } @@ -217,7 +203,7 @@ export class LarkService { } * */ - const { event_key } = data + // const { event_key } = data this.logger.debug('application.bot.menu_v6:') this.logger.debug(data) // const organizationId = environment.larkConfig.organizationId @@ -265,31 +251,28 @@ export class LarkService { this.logger.debug('card.action.trigger:') this.logger.debug(data) const messageId = data.context.open_message_id - if (messageId) { - if(isEndAction(data.action?.value) && xpertId) { - const user = RequestContext.currentUser() - const tenant = integration.tenant - const organizationId = integration.organizationId - await this.conversation.endConversation( - { - tenant, - organizationId, - integrationId: integration.id, - integration, - // larkService: this, - user - }, - user.id, xpertId) - return true - } else if (this.actions.get(messageId)) { - this.actions.get(messageId).next(data) - return true - } else { - this.errorMessage( - { integrationId: integration.id, chatId: data.context.open_chat_id }, - new Error(`响应动作不存在或已超时!`) - ) - } + if (messageId && xpertId) { + const user = RequestContext.currentUser() + const tenant = integration.tenant + const organizationId = integration.organizationId + await this.conversation.onAction( + data.action.value, + { + tenant, + organizationId, + integrationId: integration.id, + integration, + user + }, + user.id, + xpertId + ) + return true + } else { + this.errorMessage( + { integrationId: integration.id, chatId: data.context.open_chat_id }, + new Error(`响应动作不存在或已超时!`) + ) } return false } diff --git a/packages/server-ai/src/integration-lark/types.ts b/packages/server-ai/src/integration-lark/types.ts index b6bbe2cf..b6ef4cb9 100644 --- a/packages/server-ai/src/integration-lark/types.ts +++ b/packages/server-ai/src/integration-lark/types.ts @@ -1,5 +1,4 @@ import { ITenant, IUser, IIntegration, TChatConversationStatus } from "@metad/contracts" -import { LarkService } from "./lark.service" export type LarkMessage = { data: { @@ -19,7 +18,6 @@ export type ChatLarkContext = { integrationId: string integration: IIntegration user: IUser - // larkService: LarkService chatId?: string chatType?: 'p2p' | 'group' | string message?: T @@ -78,4 +76,8 @@ export function isConfirmAction(value: string) { export function isRejectAction(value: string) { return value === `"${LARK_REJECT}"` || value === LARK_REJECT +} + +export function isConversationAction(value: string) { + return isEndAction(value) || isConfirmAction(value) || isRejectAction(value) } \ No newline at end of file diff --git a/packages/server-ai/src/xpert-agent/commands/handlers/execute.handler.ts b/packages/server-ai/src/xpert-agent/commands/handlers/execute.handler.ts index 9e01df63..af34ae87 100644 --- a/packages/server-ai/src/xpert-agent/commands/handlers/execute.handler.ts +++ b/packages/server-ai/src/xpert-agent/commands/handlers/execute.handler.ts @@ -1,6 +1,6 @@ import { NotFoundException } from '@nestjs/common' import { BaseChatModel } from '@langchain/core/language_models/chat_models' -import { AIMessageChunk, HumanMessage, isAIMessage, isAIMessageChunk, isToolMessage, MessageContent, ToolMessage } from '@langchain/core/messages' +import { AIMessageChunk, HumanMessage, isAIMessage, isAIMessageChunk, MessageContent, ToolMessage } from '@langchain/core/messages' import { get_lc_unique_name, Serializable } from '@langchain/core/load/serializable' import { SystemMessagePromptTemplate } from '@langchain/core/prompts' import { Annotation, CompiledStateGraph, isCommand, LangGraphRunnableConfig, NodeInterrupt } from '@langchain/langgraph' @@ -33,7 +33,6 @@ export class XpertAgentExecuteHandler implements ICommandHandler(new GetXpertAgentQuery(xpert.id, agentKey, command.options?.isDraft)) @@ -162,7 +162,7 @@ export class XpertAgentExecuteHandler implements ICommandHandler { const { summary, memories } = state - let systemTemplate = `{{language}}\nCurrent time: ${new Date().toISOString()}\n${parseXmlString(agent.prompt) ?? ''}` + let systemTemplate = `{{sys_language}}\nCurrent time: ${new Date().toISOString()}\n${parseXmlString(agent.prompt) ?? ''}` if (memories?.length) { systemTemplate += `\n\n\n${memoryPrompt(memories)}\n` } @@ -208,7 +208,7 @@ export class XpertAgentExecuteHandler implements ICommandHandler b ?? a, default: () => '' }), - language: Annotation({ + sys_language: Annotation({ reducer: (a, b) => b ?? a, default: () => null }), diff --git a/packages/server-ai/src/xpert-toolset/provider/builtin/builtin-toolset.ts b/packages/server-ai/src/xpert-toolset/provider/builtin/builtin-toolset.ts index 7adf5870..6451d0d9 100644 --- a/packages/server-ai/src/xpert-toolset/provider/builtin/builtin-toolset.ts +++ b/packages/server-ai/src/xpert-toolset/provider/builtin/builtin-toolset.ts @@ -13,6 +13,16 @@ export type TBuiltinToolsetParams = { queryBus: QueryBus } +export type TranslateOptions = { + lang?: string; + args?: ({ + [k: string]: any; + } | string)[] | { + [k: string]: any; + }; + debug?: boolean; +} + export abstract class BuiltinToolset extends BaseToolset { static provider = '' protected logger = new Logger(this.constructor.name) @@ -49,4 +59,15 @@ export abstract class BuiltinToolset extends BaseToolset { } abstract _validateCredentials(credentials: TToolCredentials): Promise + + /** + * Translate language text + * + * @param key + * @param options + * @returns + */ + async translate(key: string, options?: TranslateOptions) { + return await this.toolsetService.translate(key, options) + } } diff --git a/packages/server-ai/src/xpert-toolset/xpert-toolset.service.ts b/packages/server-ai/src/xpert-toolset/xpert-toolset.service.ts index aa983be6..b2f5fdd5 100644 --- a/packages/server-ai/src/xpert-toolset/xpert-toolset.service.ts +++ b/packages/server-ai/src/xpert-toolset/xpert-toolset.service.ts @@ -4,8 +4,7 @@ import { CommandBus, ICommand, QueryBus } from '@nestjs/cqrs' import { InjectRepository } from '@nestjs/typeorm' import { FindConditions, IsNull, Not, Repository } from 'typeorm' import { XpertToolset } from './xpert-toolset.entity' -import { CopilotService } from '../copilot' -import { AiProviderRole, ITag, IUser, IXpertToolset, TagCategoryEnum, XpertToolsetCategoryEnum } from '@metad/contracts' +import { ITag, IUser, IXpertToolset, TagCategoryEnum, XpertToolsetCategoryEnum } from '@metad/contracts' import { assign } from 'lodash' import { GetXpertWorkspaceQuery } from '../xpert-workspace' import { DEFAULT_TOOL_TAG_MAP, defaultToolTags } from './utils/tags' @@ -14,6 +13,7 @@ import { ToolProviderNotFoundError } from './errors' import { TToolsetProviderSchema } from './types' import { ToolProviderDTO } from './dto' import { ConfigService } from '@metad/server-config' +import { I18nService, translateOptions } from 'nestjs-i18n'; import { createBuiltinToolset } from './provider/builtin' @Injectable() @@ -28,7 +28,7 @@ export class XpertToolsetService extends TenantOrganizationAwareCrudService, - private readonly copilotService: CopilotService, + private readonly i18n: I18nService, private readonly commandBus: CommandBus, private readonly queryBus: QueryBus ) { @@ -46,10 +46,6 @@ export class XpertToolsetService extends TenantOrganizationAwareCrudService) { const _entity = await super.findOne(id) @@ -161,4 +157,8 @@ export class XpertToolsetService extends TenantOrganizationAwareCrudService Date: Tue, 31 Dec 2024 22:06:03 +0800 Subject: [PATCH 10/19] feat: end nodes tool and agent --- .../components/agent/agent.component.html | 6 +++ .../components/agent/agent.component.ts | 2 +- .../components/toolset/toolset.component.html | 5 +++ .../components/toolset/toolset.component.ts | 4 ++ .../panel/toolset/toolset.component.html | 9 +++++ .../studio/panel/toolset/toolset.component.ts | 11 ++++++ .../panel/xpert-agent/agent.component.html | 37 ++++++++++++++----- .../panel/xpert-agent/agent.component.ts | 17 +++++---- apps/cloud/src/assets/i18n/zh-Hans.json | 6 ++- packages/contracts/src/ai/xpert.model.ts | 1 + .../integration-lark/conversation.service.ts | 2 +- .../commands/handlers/execute.handler.ts | 9 +++-- .../commands/handlers/react_agent_executor.ts | 7 ++-- .../handlers/complete-tool-calls.handler.ts | 2 +- 14 files changed, 92 insertions(+), 26 deletions(-) diff --git a/apps/cloud/src/app/features/xpert/studio/components/agent/agent.component.html b/apps/cloud/src/app/features/xpert/studio/components/agent/agent.component.html index 817a8065..4d651b35 100644 --- a/apps/cloud/src/app/features/xpert/studio/components/agent/agent.component.html +++ b/apps/cloud/src/app/features/xpert/studio/components/agent/agent.component.html @@ -29,6 +29,12 @@ matTooltipPosition="above"> } + @if(isEnd()) { + + + }
{{xpertAgent().title}}
diff --git a/apps/cloud/src/app/features/xpert/studio/components/agent/agent.component.ts b/apps/cloud/src/app/features/xpert/studio/components/agent/agent.component.ts index f002d5ea..d045f879 100644 --- a/apps/cloud/src/app/features/xpert/studio/components/agent/agent.component.ts +++ b/apps/cloud/src/app/features/xpert/studio/components/agent/agent.component.ts @@ -48,7 +48,7 @@ export class XpertStudioNodeAgentComponent { readonly agentUniqueName = computed(() => agentUniqueName(this.xpertAgent())) readonly isSensitive = computed(() => this.agentConfig()?.interruptBefore?.includes(this.agentUniqueName())) - + readonly isEnd = computed(() => this.agentConfig()?.endNodes?.includes(this.agentUniqueName())) private get hostElement(): HTMLElement { return this.elementRef.nativeElement diff --git a/apps/cloud/src/app/features/xpert/studio/components/toolset/toolset.component.html b/apps/cloud/src/app/features/xpert/studio/components/toolset/toolset.component.html index cb00f630..5da9b163 100644 --- a/apps/cloud/src/app/features/xpert/studio/components/toolset/toolset.component.html +++ b/apps/cloud/src/app/features/xpert/studio/components/toolset/toolset.component.html @@ -47,6 +47,11 @@ {{item.tool.name}} + @if (isEnd(item.tool.name)) { + + }
}
\ No newline at end of file diff --git a/apps/cloud/src/app/features/xpert/studio/components/toolset/toolset.component.ts b/apps/cloud/src/app/features/xpert/studio/components/toolset/toolset.component.ts index 8727b29e..dea2a355 100644 --- a/apps/cloud/src/app/features/xpert/studio/components/toolset/toolset.component.ts +++ b/apps/cloud/src/app/features/xpert/studio/components/toolset/toolset.component.ts @@ -85,4 +85,8 @@ export class XpertStudioNodeToolsetComponent { isSensitive(name: string) { return this.agentConfig()?.interruptBefore?.includes(name) } + + isEnd(name: string) { + return this.agentConfig()?.endNodes?.includes(name) + } } diff --git a/apps/cloud/src/app/features/xpert/studio/panel/toolset/toolset.component.html b/apps/cloud/src/app/features/xpert/studio/panel/toolset/toolset.component.html index b76efd78..b32a7e65 100644 --- a/apps/cloud/src/app/features/xpert/studio/panel/toolset/toolset.component.html +++ b/apps/cloud/src/app/features/xpert/studio/panel/toolset/toolset.component.html @@ -40,6 +40,15 @@ matTooltipPosition="above" >{{'PAC.Xpert.Sensitive' | translate: {Default: 'Sensitive'} }}
+ +
+ {{'PAC.Xpert.End' | translate: {Default: 'End'} }} +
}
diff --git a/apps/cloud/src/app/features/xpert/studio/panel/toolset/toolset.component.ts b/apps/cloud/src/app/features/xpert/studio/panel/toolset/toolset.component.ts index 4816f01d..2b869e63 100644 --- a/apps/cloud/src/app/features/xpert/studio/panel/toolset/toolset.component.ts +++ b/apps/cloud/src/app/features/xpert/studio/panel/toolset/toolset.component.ts @@ -97,6 +97,17 @@ export class XpertStudioPanelToolsetComponent { }) } + isEnd(name: string) { + return this.agentConfig()?.endNodes?.includes(name) + } + + updateEnd(name: string, value: boolean) { + const endNodes = value + ? uniq([...(this.agentConfig()?.endNodes ?? []), name]) + : (this.agentConfig()?.endNodes?.filter((_) => _ !== name) ?? []) + this.xpertStudioComponent.updateXpertAgentConfig({ endNodes }) + } + closePanel() { this.panelComponent.close() } diff --git a/apps/cloud/src/app/features/xpert/studio/panel/xpert-agent/agent.component.html b/apps/cloud/src/app/features/xpert/studio/panel/xpert-agent/agent.component.html index 543d2040..51852cae 100644 --- a/apps/cloud/src/app/features/xpert/studio/panel/xpert-agent/agent.component.html +++ b/apps/cloud/src/app/features/xpert/studio/panel/xpert-agent/agent.component.html @@ -99,17 +99,36 @@ /> @if (!isPrimaryAgent()) { -
+ +
-
-
{{'PAC.Xpert.Sensitive' | translate: {Default: 'Sensitive'} }}
+
+ +
+
{{'PAC.Xpert.Node' | translate: {Default: 'Node'} }}
+
+ +
+ +
{{'PAC.Xpert.Sensitive' | translate: {Default: 'Sensitive'} }}
+ +
+ +
+ +
{{'PAC.Xpert.End' | translate: {Default: 'End'} }}
+
-
} diff --git a/apps/cloud/src/app/features/xpert/studio/panel/xpert-agent/agent.component.ts b/apps/cloud/src/app/features/xpert/studio/panel/xpert-agent/agent.component.ts index c3001155..c09abd94 100644 --- a/apps/cloud/src/app/features/xpert/studio/panel/xpert-agent/agent.component.ts +++ b/apps/cloud/src/app/features/xpert/studio/panel/xpert-agent/agent.component.ts @@ -103,6 +103,7 @@ export class XpertStudioPanelAgentComponent { readonly agentUniqueName = computed(() => agentUniqueName(this.xpertAgent())) readonly agentConfig = computed(() => this.xpert()?.agentConfig) readonly isSensitive = computed(() => this.agentConfig()?.interruptBefore?.includes(this.agentUniqueName())) + readonly isEnd = computed(() => this.agentConfig()?.endNodes?.includes(this.agentUniqueName())) readonly isPrimaryAgent = computed(() => !!this.xpertAgent()?.xpertId) readonly parameters = computed(() => this.xpertAgent()?.parameters) @@ -205,17 +206,19 @@ export class XpertStudioPanelAgentComponent { this.apiService.updateXpertAgent(this.key(), { parameters: event }) } - getSensitive(name: string) { - return this.agentConfig()?.interruptBefore?.includes(name) - } - updateSensitive(value: boolean) { const name = this.agentUniqueName() const interruptBefore = value ? uniq([...(this.agentConfig()?.interruptBefore ?? []), name]) : (this.agentConfig()?.interruptBefore?.filter((_) => _ !== name) ?? []) - this.xpertStudioComponent.updateXpertAgentConfig({ - interruptBefore - }) + this.xpertStudioComponent.updateXpertAgentConfig({ interruptBefore }) + } + + updateEnd(value: boolean) { + const name = this.agentUniqueName() + const endNodes = value + ? uniq([...(this.agentConfig()?.endNodes ?? []), name]) + : (this.agentConfig()?.endNodes?.filter((_) => _ !== name) ?? []) + this.xpertStudioComponent.updateXpertAgentConfig({ endNodes }) } } diff --git a/apps/cloud/src/assets/i18n/zh-Hans.json b/apps/cloud/src/assets/i18n/zh-Hans.json index 0e2aa895..41f190da 100644 --- a/apps/cloud/src/assets/i18n/zh-Hans.json +++ b/apps/cloud/src/assets/i18n/zh-Hans.json @@ -2097,7 +2097,11 @@ "ConfigurationRequired": "需要进行配置", "XpertPublished": "专家已发布成功", "XpertPublishDeleted": "专家发布已删除", - "RefreshToolset": "刷新,同步工具集配置" + "RefreshToolset": "刷新,同步工具集配置", + "End": "终点", + "EndTool": "终点工具,完成后对话结束", + "EndAgentTip": "终点智能体,完成后对话结束", + "Node": "节点" }, "title": { "short": "Xpert AI" diff --git a/packages/contracts/src/ai/xpert.model.ts b/packages/contracts/src/ai/xpert.model.ts index 315924eb..5b8a4f2f 100644 --- a/packages/contracts/src/ai/xpert.model.ts +++ b/packages/contracts/src/ai/xpert.model.ts @@ -150,6 +150,7 @@ export type TXpertAgentConfig = { timeout?: number; interruptBefore?: string[] + endNodes?: string[] stateVariables?: TStateVariable[] } diff --git a/packages/server-ai/src/integration-lark/conversation.service.ts b/packages/server-ai/src/integration-lark/conversation.service.ts index c63ec677..c1d80bc4 100644 --- a/packages/server-ai/src/integration-lark/conversation.service.ts +++ b/packages/server-ai/src/integration-lark/conversation.service.ts @@ -1,5 +1,5 @@ import { IChatConversation } from '@metad/contracts' -import { REDIS_OPTIONS, runWithRequestContext, UserService } from '@metad/server-core' +import { REDIS_OPTIONS, UserService } from '@metad/server-core' import { CACHE_MANAGER, forwardRef, Inject, Injectable, Logger, OnModuleDestroy } from '@nestjs/common' import { CommandBus, QueryBus } from '@nestjs/cqrs' import * as Bull from 'bull' diff --git a/packages/server-ai/src/xpert-agent/commands/handlers/execute.handler.ts b/packages/server-ai/src/xpert-agent/commands/handlers/execute.handler.ts index af34ae87..c9d43c6a 100644 --- a/packages/server-ai/src/xpert-agent/commands/handlers/execute.handler.ts +++ b/packages/server-ai/src/xpert-agent/commands/handlers/execute.handler.ts @@ -1,6 +1,6 @@ import { NotFoundException } from '@nestjs/common' import { BaseChatModel } from '@langchain/core/language_models/chat_models' -import { AIMessageChunk, HumanMessage, isAIMessage, isAIMessageChunk, MessageContent, ToolMessage } from '@langchain/core/messages' +import { AIMessageChunk, HumanMessage, isAIMessage, isAIMessageChunk, isToolMessage, MessageContent, ToolMessage } from '@langchain/core/messages' import { get_lc_unique_name, Serializable } from '@langchain/core/load/serializable' import { SystemMessagePromptTemplate } from '@langchain/core/prompts' import { Annotation, CompiledStateGraph, isCommand, LangGraphRunnableConfig, NodeInterrupt } from '@langchain/langgraph' @@ -159,6 +159,7 @@ export class XpertAgentExecuteHandler implements ICommandHandler { const { summary, memories } = state @@ -455,9 +456,9 @@ export class XpertAgentExecuteHandler implements ICommandHandler( @@ -472,6 +473,8 @@ export class XpertAgentExecuteHandler implements ICommandHandler tools?: (StructuredToolInterface | RunnableToolLike)[]; + endNodes?: string[] summarize?: TSummarize }; @@ -140,7 +141,7 @@ export function createReactAgent( checkpointSaver, interruptBefore, interruptAfter, - // state, + endNodes, tags, store, } = props; @@ -204,13 +205,13 @@ export function createReactAgent( if (subAgents) { Object.keys(subAgents).forEach((name) => { workflow.addNode(name, subAgents[name].node) - .addEdge(name, "agent") + .addEdge(name, endNodes?.includes(name) ? END :"agent") }) } tools?.forEach((tool) => { const name = tool.name workflow.addNode(name, new ToolNode([tool])) - .addEdge(name, "agent") + .addEdge(name, endNodes?.includes(tool.name) ? END : "agent") }) if (summarize?.enabled) { diff --git a/packages/server-ai/src/xpert-agent/queries/handlers/complete-tool-calls.handler.ts b/packages/server-ai/src/xpert-agent/queries/handlers/complete-tool-calls.handler.ts index ea91ec7f..f452cf97 100644 --- a/packages/server-ai/src/xpert-agent/queries/handlers/complete-tool-calls.handler.ts +++ b/packages/server-ai/src/xpert-agent/queries/handlers/complete-tool-calls.handler.ts @@ -63,7 +63,7 @@ export class CompleteToolCallsHandler implements IQueryHandler ({ + parameters: subAgents[toolCall.name].parameters?.map((param) => ({ name: param.name, title: param.title, description: param.description, From 9a52f9c17a45aa2a34c2cf8dc05e35665d1cafac Mon Sep 17 00:00:00 2001 From: meta-d Date: Tue, 31 Dec 2024 23:18:28 +0800 Subject: [PATCH 11/19] feat: agent config Recursion Limit and max concurrency --- .../xpert/studio/header/header.component.html | 97 +++++++++++++++++++ .../xpert/studio/header/header.component.ts | 22 ++++- apps/cloud/src/assets/i18n/zh-Hans.json | 7 +- .../commands/handlers/execute.handler.ts | 3 +- 4 files changed, 122 insertions(+), 7 deletions(-) diff --git a/apps/cloud/src/app/features/xpert/studio/header/header.component.html b/apps/cloud/src/app/features/xpert/studio/header/header.component.html index 3df32e48..c8db765e 100644 --- a/apps/cloud/src/app/features/xpert/studio/header/header.component.html +++ b/apps/cloud/src/app/features/xpert/studio/header/header.component.html @@ -18,6 +18,15 @@
+ + +
+
+
+ +
+
+
+ +
+ {{ 'PAC.Xpert.MaxConcurrency' | translate: {Default: 'Max Concurrency'} }} +
+ +
+
+ + + + +
+
+ +
+
+ + +
+ {{ 'PAC.Xpert.RecursionLimit' | translate: {Default: 'Recursion Limit'} }} +
+ + +
+
+ + + + +
+
+
+
+ + diff --git a/apps/cloud/src/app/features/xpert/studio/header/header.component.ts b/apps/cloud/src/app/features/xpert/studio/header/header.component.ts index 856fe087..9f9a8112 100644 --- a/apps/cloud/src/app/features/xpert/studio/header/header.component.ts +++ b/apps/cloud/src/app/features/xpert/studio/header/header.component.ts @@ -10,31 +10,35 @@ import { ChatConversationService, getErrorMessage, IChatConversation, - IntegrationEnum, OrderTypeEnum, ToastrService, XpertService } from 'apps/cloud/src/app/@core' -import { MaterialModule } from 'apps/cloud/src/app/@shared/material.module' import { InDevelopmentComponent } from 'apps/cloud/src/app/@theme' import { formatRelative } from 'date-fns' -import { sortBy } from 'lodash-es' import { distinctUntilChanged, filter, map, shareReplay, switchMap } from 'rxjs' -import { getDateLocale } from '../../../../@core' +import { getDateLocale, TXpertAgentConfig } from '../../../../@core' import { XpertStudioApiService } from '../domain' import { XpertExecutionService } from '../services/execution.service' import { XpertStudioComponent } from '../studio.component' import { XpertStudioFeaturesComponent } from '../features/features.component' import { Dialog } from '@angular/cdk/dialog' import { XpertPublishComponent } from 'apps/cloud/src/app/@shared/xpert' +import { FormsModule } from '@angular/forms' +import { MatTooltipModule } from '@angular/material/tooltip' +import { MatInputModule } from '@angular/material/input' +import { MatSliderModule } from '@angular/material/slider' @Component({ selector: 'xpert-studio-header', standalone: true, imports: [ CommonModule, + FormsModule, CdkMenuModule, - MaterialModule, + MatTooltipModule, + MatInputModule, + MatSliderModule, TranslateModule, NgmTooltipDirective, InDevelopmentComponent @@ -84,6 +88,10 @@ export class XpertStudioHeaderComponent { return null }) + readonly agentConfig = computed(() => this.xpert()?.agentConfig) + readonly maxConcurrency = computed(() => this.agentConfig()?.maxConcurrency) + readonly recursionLimit = computed(() => this.agentConfig()?.recursionLimit) + readonly publishing = signal(false) // Executions @@ -141,6 +149,10 @@ export class XpertStudioHeaderComponent { }) } + updateAgentConfig(config: Partial) { + this.apiService.updateXpertAgentConfig(config) + } + openConversation(item: IChatConversation) { this.sidePanel.set('preview') this.executionService.setConversation(item) diff --git a/apps/cloud/src/assets/i18n/zh-Hans.json b/apps/cloud/src/assets/i18n/zh-Hans.json index 41f190da..a6b59b17 100644 --- a/apps/cloud/src/assets/i18n/zh-Hans.json +++ b/apps/cloud/src/assets/i18n/zh-Hans.json @@ -2101,7 +2101,12 @@ "End": "终点", "EndTool": "终点工具,完成后对话结束", "EndAgentTip": "终点智能体,完成后对话结束", - "Node": "节点" + "Node": "节点", + "MaxConcurrencyTip": "控制最大并行调用数", + "RecursionLimitTip": "限制智能体可以递归的次数", + "RecursionLimit": "递归次数限制", + "MaxConcurrency": "最大并发请求", + "AgentSettings": "智能体设置" }, "title": { "short": "Xpert AI" diff --git a/packages/server-ai/src/xpert-agent/commands/handlers/execute.handler.ts b/packages/server-ai/src/xpert-agent/commands/handlers/execute.handler.ts index c9d43c6a..17fea100 100644 --- a/packages/server-ai/src/xpert-agent/commands/handlers/execute.handler.ts +++ b/packages/server-ai/src/xpert-agent/commands/handlers/execute.handler.ts @@ -212,7 +212,8 @@ export class XpertAgentExecuteHandler implements ICommandHandler Date: Tue, 31 Dec 2024 23:24:22 +0800 Subject: [PATCH 12/19] feat: language in chatbi for lark --- .../src/ai/toolset/builtin/chatbi-lark/tools/welcome.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/analytics/src/ai/toolset/builtin/chatbi-lark/tools/welcome.ts b/packages/analytics/src/ai/toolset/builtin/chatbi-lark/tools/welcome.ts index f377d28d..176e45ce 100644 --- a/packages/analytics/src/ai/toolset/builtin/chatbi-lark/tools/welcome.ts +++ b/packages/analytics/src/ai/toolset/builtin/chatbi-lark/tools/welcome.ts @@ -58,7 +58,7 @@ export function createWelcomeTool(chatbi: AbstractChatBIToolset, context: Partia tag: 'column', elements: [ ...prompts.map((prompt) => { - const fullPrompt = questionPrefix + prompt + const fullPrompt = `- ` + questionPrefix + prompt return { tag: 'button', text: { @@ -159,7 +159,7 @@ export function createWelcomeTool(chatbi: AbstractChatBIToolset, context: Partia const toolCallId = config.metadata.tool_call_id return new Command({ update: { - sys_language: language, + sys_language: `Answer in language '${language}'.`, // update the message history messages: [ { From 6d9fbec3e1550c4ff2b5806c7d154de030835cef Mon Sep 17 00:00:00 2001 From: meta-d Date: Wed, 1 Jan 2025 00:28:55 +0800 Subject: [PATCH 13/19] feat: lark chat integration --- .../toolset/builtin/chatbi-lark/tools/welcome.ts | 6 +++--- .../src/integration-lark/chat/message.ts | 6 +----- .../commands/handlers/lark-message.handler.ts | 4 ---- .../src/integration-lark/conversation.service.ts | 16 ++++++++++++---- .../src/integration-lark/lark.service.ts | 5 +++-- packages/server/src/i18n/en/toolset.json | 4 ++-- 6 files changed, 21 insertions(+), 20 deletions(-) diff --git a/packages/analytics/src/ai/toolset/builtin/chatbi-lark/tools/welcome.ts b/packages/analytics/src/ai/toolset/builtin/chatbi-lark/tools/welcome.ts index 176e45ce..3e0a4c53 100644 --- a/packages/analytics/src/ai/toolset/builtin/chatbi-lark/tools/welcome.ts +++ b/packages/analytics/src/ai/toolset/builtin/chatbi-lark/tools/welcome.ts @@ -39,7 +39,7 @@ export function createWelcomeTool(chatbi: AbstractChatBIToolset, context: Partia elements.push( { tag: 'markdown', - content: await chatbi.translate('toolset.ChatBI.QuestionsAboutDataset', + content: `- ` + await chatbi.translate('toolset.ChatBI.QuestionsAboutDataset', { lang: language, args: { @@ -58,7 +58,7 @@ export function createWelcomeTool(chatbi: AbstractChatBIToolset, context: Partia tag: 'column', elements: [ ...prompts.map((prompt) => { - const fullPrompt = `- ` + questionPrefix + prompt + const fullPrompt = questionPrefix + prompt return { tag: 'button', text: { @@ -164,7 +164,7 @@ export function createWelcomeTool(chatbi: AbstractChatBIToolset, context: Partia messages: [ { role: 'tool', - content: 'The welcome message and opening questions have been sent to the user, no need to respond with any content.', + content: '', tool_call_id: toolCallId } ] diff --git a/packages/server-ai/src/integration-lark/chat/message.ts b/packages/server-ai/src/integration-lark/chat/message.ts index ec553811..473ce8a4 100644 --- a/packages/server-ai/src/integration-lark/chat/message.ts +++ b/packages/server-ai/src/integration-lark/chat/message.ts @@ -66,8 +66,6 @@ export class ChatLarkMessage extends Serializable implements ChatLarkMessageFiel constructor( private chatContext: ChatLarkContext & {larkService: LarkService}, private options: { - userId?: string - xpertId?: string text?: string } & Partial, ) { @@ -106,7 +104,7 @@ export class ChatLarkMessage extends Serializable implements ChatLarkMessageFiel content: this.getTitle() }, subtitle: { - tag: 'plain_text', // 固定值 plain_text。 + tag: 'plain_text', content: this.getSubtitle() }, template: ChatLarkMessage.headerTemplate, @@ -122,8 +120,6 @@ export class ChatLarkMessage extends Serializable implements ChatLarkMessageFiel getCard() { const elements = [...this.elements] - console.log(`2: ${this.status}`) - if (elements[elements.length - 1]?.tag !== 'hr') { elements.push({ tag: 'hr' }) } diff --git a/packages/server-ai/src/integration-lark/commands/handlers/lark-message.handler.ts b/packages/server-ai/src/integration-lark/commands/handlers/lark-message.handler.ts index f4b0a2dd..f2c66154 100644 --- a/packages/server-ai/src/integration-lark/commands/handlers/lark-message.handler.ts +++ b/packages/server-ai/src/integration-lark/commands/handlers/lark-message.handler.ts @@ -22,13 +22,9 @@ export class LarkMessageHandler implements ICommandHandler { const textContent = JSON.parse(content) const text = textContent.text as string - const userId = RequestContext.currentUserId() - const larkMessage = new ChatLarkMessage( {...input, larkService: this.larkService }, { - xpertId: integration.options.xpertId, - userId, text }, ) diff --git a/packages/server-ai/src/integration-lark/conversation.service.ts b/packages/server-ai/src/integration-lark/conversation.service.ts index c1d80bc4..429fd3b5 100644 --- a/packages/server-ai/src/integration-lark/conversation.service.ts +++ b/packages/server-ai/src/integration-lark/conversation.service.ts @@ -1,5 +1,5 @@ import { IChatConversation } from '@metad/contracts' -import { REDIS_OPTIONS, UserService } from '@metad/server-core' +import { REDIS_OPTIONS } from '@metad/server-core' import { CACHE_MANAGER, forwardRef, Inject, Injectable, Logger, OnModuleDestroy } from '@nestjs/common' import { CommandBus, QueryBus } from '@nestjs/cqrs' import * as Bull from 'bull' @@ -23,7 +23,6 @@ export class LarkConversationService implements OnModuleDestroy { private userQueues: Map = new Map() constructor( - private readonly userService: UserService, private readonly commandBus: CommandBus, private readonly queryBus: QueryBus, @Inject(CACHE_MANAGER) @@ -39,7 +38,7 @@ export class LarkConversationService implements OnModuleDestroy { } async setConversation(userId: string, xpertId: string, conversationId: string) { - return await this.cacheManager.set(this.prefix + `/${userId}/${xpertId}`, conversationId) + return await this.cacheManager.set(this.prefix + `/${userId}/${xpertId}`, conversationId, 1000 * 60 * 10) } async ask(xpertId: string, content: string, message: ChatLarkMessage) { @@ -49,6 +48,15 @@ export class LarkConversationService implements OnModuleDestroy { async onAction(action: string, chatContext: ChatLarkContext, userId: string, xpertId: string) { const id = await this.getConversation(userId, xpertId) + if (!id) { + const {integration, chatId} = chatContext + + return this.larkService.errorMessage( + { integrationId: integration.id, chatId }, + new Error(`响应动作不存在或会话已超时!`) + ) + } + const conversation = await this.queryBus.execute( new GetChatConversationQuery({ id }, ['messages']) ) @@ -77,7 +85,7 @@ export class LarkConversationService implements OnModuleDestroy { } else { await this.commandBus.execute(new LarkChatXpertCommand(xpertId, action, new ChatLarkMessage( { ...chatContext, larkService: this.larkService }, - null, + {text: action}, ))) } } diff --git a/packages/server-ai/src/integration-lark/lark.service.ts b/packages/server-ai/src/integration-lark/lark.service.ts index 3ed94e8e..e9a3e63c 100644 --- a/packages/server-ai/src/integration-lark/lark.service.ts +++ b/packages/server-ai/src/integration-lark/lark.service.ts @@ -262,7 +262,8 @@ export class LarkService { organizationId, integrationId: integration.id, integration, - user + user, + chatId: data.context.open_chat_id }, user.id, xpertId @@ -271,7 +272,7 @@ export class LarkService { } else { this.errorMessage( { integrationId: integration.id, chatId: data.context.open_chat_id }, - new Error(`响应动作不存在或已超时!`) + new Error(`出现内部错误!`) ) } return false diff --git a/packages/server/src/i18n/en/toolset.json b/packages/server/src/i18n/en/toolset.json index d52586e1..39af9042 100644 --- a/packages/server/src/i18n/en/toolset.json +++ b/packages/server/src/i18n/en/toolset.json @@ -1,8 +1,8 @@ { "ChatBI": { "GuessAsk": "Guess you want to ask:", - "QuestionsAboutDataset": "Questions you may want to ask about the dataset \"{cube}\":", - "AnalyzeDataset": "Analyze the dataset \"{cube}\": ", + "QuestionsAboutDataset": "Questions you may want to ask about the dataset '{cube}':", + "AnalyzeDataset": "Analyze the dataset '{cube}': ", "MoreDatasets": "- More datasets:", "GiveWelcomeMessage": "Give a welcome message for the dataset '{cube}'", "Welcome": "Hi, I am ChatBI. I can analyze the data and generate charts based on your questions.", From 3a0644e7fd2bea722828b514da0a4d39661dfab5 Mon Sep 17 00:00:00 2001 From: meta-d Date: Thu, 2 Jan 2025 12:17:06 +0800 Subject: [PATCH 14/19] feat: translate lark message --- .../builtin/chatbi-lark/tools/welcome.ts | 2 + .../commands/handlers/chatbi.handler.ts | 6 +- packages/contracts/src/types.ts | 10 ++++ .../src/integration-lark/chat/message.ts | 55 +++++++++++-------- .../commands/handlers/lark-message.handler.ts | 29 +++++++--- .../commands/mesage.command.ts | 2 +- .../integration-lark/conversation.service.ts | 42 ++++++++++---- .../src/integration-lark/lark.service.ts | 37 +++++++------ .../server-ai/src/integration-lark/types.ts | 4 +- .../provider/builtin/builtin-toolset.ts | 12 +--- packages/server/src/i18n/en/integration.json | 15 +++++ packages/server/src/i18n/zh/integration.json | 15 +++++ 12 files changed, 151 insertions(+), 78 deletions(-) create mode 100644 packages/server/src/i18n/en/integration.json create mode 100644 packages/server/src/i18n/zh/integration.json diff --git a/packages/analytics/src/ai/toolset/builtin/chatbi-lark/tools/welcome.ts b/packages/analytics/src/ai/toolset/builtin/chatbi-lark/tools/welcome.ts index 3e0a4c53..48cd13cc 100644 --- a/packages/analytics/src/ai/toolset/builtin/chatbi-lark/tools/welcome.ts +++ b/packages/analytics/src/ai/toolset/builtin/chatbi-lark/tools/welcome.ts @@ -11,6 +11,7 @@ import { AbstractChatBIToolset } from '../../chatbi/chatbi-toolset' export function createWelcomeTool(chatbi: AbstractChatBIToolset, context: Partial) { const logger = new Logger('WelcomeTool') const { models: _models } = context + return tool( async ({ language, models, more }, config: LangGraphRunnableConfig) => { logger.debug( @@ -138,6 +139,7 @@ export function createWelcomeTool(chatbi: AbstractChatBIToolset, context: Partia id: shortuuid(), type: 'update', data: { + language, elements, header: { title: { diff --git a/packages/analytics/src/chatbi/commands/handlers/chatbi.handler.ts b/packages/analytics/src/chatbi/commands/handlers/chatbi.handler.ts index 4a5435e9..f1545487 100644 --- a/packages/analytics/src/chatbi/commands/handlers/chatbi.handler.ts +++ b/packages/analytics/src/chatbi/commands/handlers/chatbi.handler.ts @@ -45,7 +45,7 @@ export class ChatBIHandler implements ICommandHandler { public async execute(command: ChatBICommand): Promise { const { input } = command - const { tenant, organizationId, user, larkService } = input + const { tenant, organizationId, userId, larkService } = input const conversation = await this.getUserConversation(input) if (!conversation) { @@ -60,7 +60,7 @@ export class ChatBIHandler implements ICommandHandler { await this.commandBus.execute(new CopilotCheckLimitCommand({ tenantId: tenant.id, organizationId, - userId: user.id, + userId, copilot: conversation.copilot })) } catch(err) { @@ -105,7 +105,7 @@ export class ChatBIHandler implements ICommandHandler { * @returns ChatBIConversation */ async createChatConversation(input: ChatBILarkContext) { - const { tenant, organizationId, user } = input + const { tenant, organizationId } = input const tenantId = tenant.id const copilot = await this.copilotService.findOneByRole(AiProviderRole.Primary, tenantId, organizationId) // const copilot = items.find((item) => item.role === AiProviderRole.Primary) diff --git a/packages/contracts/src/types.ts b/packages/contracts/src/types.ts index fa498976..670ddb81 100644 --- a/packages/contracts/src/types.ts +++ b/packages/contracts/src/types.ts @@ -31,4 +31,14 @@ export type TDeleteResult = { * Not all drivers support this */ affected?: number | null; +} + +export type TranslateOptions = { + lang?: string; + args?: ({ + [k: string]: any; + } | string)[] | { + [k: string]: any; + }; + debug?: boolean; } \ No newline at end of file diff --git a/packages/server-ai/src/integration-lark/chat/message.ts b/packages/server-ai/src/integration-lark/chat/message.ts index 473ce8a4..03ae8c09 100644 --- a/packages/server-ai/src/integration-lark/chat/message.ts +++ b/packages/server-ai/src/integration-lark/chat/message.ts @@ -1,5 +1,5 @@ import { Serializable } from '@langchain/core/load/serializable' -import { I18nObject, IChatMessage, TSensitiveOperation } from '@metad/contracts' +import { I18nObject, IChatMessage, TranslateOptions, TSensitiveOperation } from '@metad/contracts' import { Logger } from '@nestjs/common' import { ChatLarkContext, @@ -78,16 +78,17 @@ export class ChatLarkMessage extends Serializable implements ChatLarkMessageFiel this.elements = options.elements ?? [] } - getTitle() { + async getTitle() { + const status = await this.translate('integration.Lark.Status_' + this.status, {lang: this.language,}) switch (this.status) { case 'thinking': - return '正在思考...' + return status case 'continuing': - return '继续思考...' + return status case 'waiting': - return '还在思考,请稍后...' + return status case 'interrupted': - return '请确认' + return status default: return '' } @@ -97,11 +98,11 @@ export class ChatLarkMessage extends Serializable implements ChatLarkMessageFiel return this.options.text } - getHeader() { + async getHeader() { return { title: { tag: 'plain_text', - content: this.getTitle() + content: await this.getTitle() }, subtitle: { tag: 'plain_text', @@ -109,15 +110,15 @@ export class ChatLarkMessage extends Serializable implements ChatLarkMessageFiel }, template: ChatLarkMessage.headerTemplate, ud_icon: { - token: 'myai_colorful', // 图标的 token + token: 'myai_colorful', style: { - color: 'red' // 图标颜色 + color: 'red' } } } } - getCard() { + async getCard() { const elements = [...this.elements] if (elements[elements.length - 1]?.tag !== 'hr') { @@ -126,10 +127,10 @@ export class ChatLarkMessage extends Serializable implements ChatLarkMessageFiel if (this.status === 'end') { elements.push({ tag: 'markdown', - content: `对话已结束。如果您有其他问题,欢迎随时再来咨询。` + content: await this.translate('integration.Lark.ConversationEnded', {lang: this.language,}) }) } else { - elements.push(this.getEndAction()) + elements.push(await this.getEndAction()) } return { @@ -137,17 +138,17 @@ export class ChatLarkMessage extends Serializable implements ChatLarkMessageFiel } } - getEndAction() { + async getEndAction() { return { tag: 'action', layout: 'default', actions: [ - ...(this.status === 'interrupted' ? this.getInterruptedActions() : []), + ...(this.status === 'interrupted' ? (await this.getInterruptedActions()) : []), { tag: 'button', text: { tag: 'plain_text', - content: '结束对话' + content: await this.translate('integration.Lark.EndConversation', {lang: this.language,}) }, type: 'text', complex_interaction: true, @@ -159,7 +160,7 @@ export class ChatLarkMessage extends Serializable implements ChatLarkMessageFiel tag: 'button', text: { tag: 'plain_text', - content: '帮助文档' + content: await this.translate('integration.Lark.HelpDoc', {lang: this.language,}) }, type: 'text', complex_interaction: true, @@ -173,13 +174,13 @@ export class ChatLarkMessage extends Serializable implements ChatLarkMessageFiel } } - getInterruptedActions() { + async getInterruptedActions() { return [ { tag: 'button', text: { tag: 'plain_text', - content: '确认' + content: await this.translate('integration.Lark.Confirm', {lang: this.language,}) }, type: 'primary', width: 'default', @@ -190,7 +191,7 @@ export class ChatLarkMessage extends Serializable implements ChatLarkMessageFiel tag: 'button', text: { tag: 'plain_text', - content: '拒绝' + content: await this.translate('integration.Lark.Reject', {lang: this.language,}) }, type: 'danger', width: 'default', @@ -204,8 +205,12 @@ export class ChatLarkMessage extends Serializable implements ChatLarkMessageFiel status?: ChatLarkMessageStatus elements?: any[] header?: any + language?: string action?: (action) => void }) { + if (options?.language) { + this.language = options.language + } if (options?.status) { this.status = options.status } @@ -216,12 +221,12 @@ export class ChatLarkMessage extends Serializable implements ChatLarkMessageFiel this.header = options.header } - const elements = this.getCard() + const elements = await this.getCard() if (this.id) { this.larkService .patchAction(this.chatContext, this.id, { ...elements, - header: this.header ?? this.getHeader() + header: this.header ?? (await this.getHeader()) }) .subscribe({ next: (message) => { @@ -236,7 +241,7 @@ export class ChatLarkMessage extends Serializable implements ChatLarkMessageFiel this.chatContext, { ...elements, - header: this.header ?? this.getHeader() + header: this.header ?? (await this.getHeader()) }, { next: async (message) => { @@ -261,6 +266,10 @@ export class ChatLarkMessage extends Serializable implements ChatLarkMessageFiel } }) } + + async translate(key: string, options: TranslateOptions) { + return await this.larkService.translate(key, options) + } } export type ChatStack = { diff --git a/packages/server-ai/src/integration-lark/commands/handlers/lark-message.handler.ts b/packages/server-ai/src/integration-lark/commands/handlers/lark-message.handler.ts index f2c66154..ed5223f1 100644 --- a/packages/server-ai/src/integration-lark/commands/handlers/lark-message.handler.ts +++ b/packages/server-ai/src/integration-lark/commands/handlers/lark-message.handler.ts @@ -1,31 +1,42 @@ -import { RequestContext } from '@metad/server-core' +import { IntegrationService } from '@metad/server-core' import { CommandBus, CommandHandler, ICommandHandler } from '@nestjs/cqrs' import { ChatLarkMessage } from '../../chat/message' import { LarkChatAgentCommand } from '../chat-agent.command' import { LarkChatXpertCommand } from '../chat-xpert.command' import { LarkMessageCommand } from '../mesage.command' import { LarkService } from '../../lark.service' +import { LarkConversationService } from '../../conversation.service' @CommandHandler(LarkMessageCommand) export class LarkMessageHandler implements ICommandHandler { constructor( private readonly larkService: LarkService, + private readonly conversationService: LarkConversationService, + private readonly integrationService: IntegrationService, private readonly commandBus: CommandBus ) {} public async execute(command: LarkMessageCommand): Promise { - const { input } = command - const { integration, message } = input + const { options } = command + const { userId, integrationId, message, input } = options + const integration = await this.integrationService.findOneByIdString(integrationId) if (integration.options?.xpertId) { - const { content } = message.message - const textContent = JSON.parse(content) - const text = textContent.text as string + let text = input + if (!text && message) { + const { content } = message.message + const textContent = JSON.parse(content) + text = textContent.text as string + } + + // Conversation last message + const lastMessage = await this.conversationService.getLastMessage(userId, integration.options.xpertId) const larkMessage = new ChatLarkMessage( - {...input, larkService: this.larkService }, + {...options, larkService: this.larkService }, { - text + text, + language: lastMessage?.thirdPartyMessage?.language }, ) @@ -34,6 +45,6 @@ export class LarkMessageHandler implements ICommandHandler { ) } - return await this.commandBus.execute(new LarkChatAgentCommand(input)) + return await this.commandBus.execute(new LarkChatAgentCommand(options)) } } diff --git a/packages/server-ai/src/integration-lark/commands/mesage.command.ts b/packages/server-ai/src/integration-lark/commands/mesage.command.ts index fb6e4679..1134990d 100644 --- a/packages/server-ai/src/integration-lark/commands/mesage.command.ts +++ b/packages/server-ai/src/integration-lark/commands/mesage.command.ts @@ -5,6 +5,6 @@ export class LarkMessageCommand implements ICommand { static readonly type = '[Lark] Message' constructor( - public readonly input: ChatLarkContext + public readonly options: ChatLarkContext ) {} } diff --git a/packages/server-ai/src/integration-lark/conversation.service.ts b/packages/server-ai/src/integration-lark/conversation.service.ts index 429fd3b5..a7aadc36 100644 --- a/packages/server-ai/src/integration-lark/conversation.service.ts +++ b/packages/server-ai/src/integration-lark/conversation.service.ts @@ -1,5 +1,5 @@ import { IChatConversation } from '@metad/contracts' -import { REDIS_OPTIONS } from '@metad/server-core' +import { REDIS_OPTIONS, RequestContext } from '@metad/server-core' import { CACHE_MANAGER, forwardRef, Inject, Injectable, Logger, OnModuleDestroy } from '@nestjs/common' import { CommandBus, QueryBus } from '@nestjs/cqrs' import * as Bull from 'bull' @@ -45,22 +45,37 @@ export class LarkConversationService implements OnModuleDestroy { await this.commandBus.execute(new LarkChatXpertCommand(xpertId, content, message)) } + /** + * Get last message of user's conversation. + * + * @param userId + * @param xpertId + * @returns + */ + async getLastMessage(userId: string, xpertId: string) { + const id = await this.getConversation(userId, xpertId) + if (id) { + const conversation = await this.queryBus.execute( + new GetChatConversationQuery({ id }, ['messages']) + ) + return conversation.messages.slice(-1)[0] + } + return null + } + async onAction(action: string, chatContext: ChatLarkContext, userId: string, xpertId: string) { const id = await this.getConversation(userId, xpertId) if (!id) { - const {integration, chatId} = chatContext + const {integrationId, chatId} = chatContext return this.larkService.errorMessage( - { integrationId: integration.id, chatId }, - new Error(`响应动作不存在或会话已超时!`) + { integrationId, chatId }, + new Error(await this.larkService.translate('integration.Lark.ActionSessionTimedOut', {})) ) } - const conversation = await this.queryBus.execute( - new GetChatConversationQuery({ id }, ['messages']) - ) - const lastMessage = conversation.messages.slice(-1)[0] + const lastMessage = await this.getLastMessage(userId, xpertId) const message = new ChatLarkMessage( { ...chatContext, larkService: this.larkService }, @@ -83,10 +98,13 @@ export class LarkConversationService implements OnModuleDestroy { }) ) } else { - await this.commandBus.execute(new LarkChatXpertCommand(xpertId, action, new ChatLarkMessage( - { ...chatContext, larkService: this.larkService }, - {text: action}, - ))) + const user = RequestContext.currentUser() + const userQueue = await this.getUserQueue(user.id) + // Adding task to user's queue + await userQueue.add({ + ...chatContext, + input: action, + }) } } diff --git a/packages/server-ai/src/integration-lark/lark.service.ts b/packages/server-ai/src/integration-lark/lark.service.ts index e9a3e63c..f61f5532 100644 --- a/packages/server-ai/src/integration-lark/lark.service.ts +++ b/packages/server-ai/src/integration-lark/lark.service.ts @@ -1,5 +1,5 @@ import * as lark from '@larksuiteoapi/node-sdk' -import { IIntegration, IUser } from '@metad/contracts' +import { IIntegration, IUser, TranslateOptions } from '@metad/contracts' import { nonNullable } from '@metad/copilot' import { ConfigService, IEnvironment } from '@metad/server-config' import { UserService, RoleService, RequestContext } from '@metad/server-core' @@ -9,8 +9,9 @@ import { Cache } from 'cache-manager' import { isEqual } from 'date-fns' import express from 'express' import { filter, Observable, Observer, Subject, Subscriber } from 'rxjs' -import { LarkBotMenuCommand, LarkMessageCommand } from './commands' -import { ChatLarkContext, isConversationAction, isEndAction, LarkMessage } from './types' +import { I18nService } from 'nestjs-i18n'; +import { LarkBotMenuCommand } from './commands' +import { ChatLarkContext, LarkMessage } from './types' import { LarkConversationService } from './conversation.service' @Injectable() @@ -39,6 +40,7 @@ export class LarkService { private readonly userService: UserService, private readonly roleService: RoleService, private readonly configService: ConfigService, + private readonly i18n: I18nService, private readonly commandBus: CommandBus, @Inject(CACHE_MANAGER) private readonly cacheManager: Cache @@ -154,21 +156,19 @@ export class LarkService { // return true // } - this.logger.debug('im.message.receive_v1:') - this.logger.debug(data) + this.logger.verbose('im.message.receive_v1:') + this.logger.verbose(data) try { const user = RequestContext.currentUser() - - // await this.getUser(client, tenant.id, data.sender.sender_id.union_id) const userQueue = await this.conversation.getUserQueue(user.id) - // 添加任务到队列 + // Adding task to user's queue await userQueue.add({ tenant, organizationId, integrationId: integration.id, - integration, - user, + // integration, + userId: user.id, message: data as any, chatId, chatType: data.message.chat_type, @@ -177,7 +177,7 @@ export class LarkService { console.error(err) } - this.logger.debug('Return for message:' + data.event_id) + this.logger.verbose('Return for message:' + data.event_id) return 'ok' }, 'application.bot.menu_v6': async (data) => { @@ -204,8 +204,8 @@ export class LarkService { * */ // const { event_key } = data - this.logger.debug('application.bot.menu_v6:') - this.logger.debug(data) + this.logger.verbose('application.bot.menu_v6:') + this.logger.verbose(data) // const organizationId = environment.larkConfig.organizationId // const tenant = await this.tenantService.findOne({ id: environment.larkConfig.tenantId }) const tenant = integration.tenant @@ -248,8 +248,8 @@ export class LarkService { [Symbol(event-type)]: 'card.action.trigger' } */ - this.logger.debug('card.action.trigger:') - this.logger.debug(data) + this.logger.verbose('card.action.trigger:') + this.logger.verbose(data) const messageId = data.context.open_message_id if (messageId && xpertId) { const user = RequestContext.currentUser() @@ -261,8 +261,8 @@ export class LarkService { tenant, organizationId, integrationId: integration.id, - integration, - user, + // integration, + userId: user.id, chatId: data.context.open_chat_id }, user.id, @@ -578,4 +578,7 @@ export class LarkService { }) } + async translate(key: string, options: TranslateOptions) { + return await this.i18n.t(key, options) + } } diff --git a/packages/server-ai/src/integration-lark/types.ts b/packages/server-ai/src/integration-lark/types.ts index b6ef4cb9..4441c901 100644 --- a/packages/server-ai/src/integration-lark/types.ts +++ b/packages/server-ai/src/integration-lark/types.ts @@ -16,11 +16,11 @@ export type ChatLarkContext = { tenant: ITenant organizationId: string integrationId: string - integration: IIntegration - user: IUser + userId: string chatId?: string chatType?: 'p2p' | 'group' | string message?: T + input?: string } export type TLarkEvent = { diff --git a/packages/server-ai/src/xpert-toolset/provider/builtin/builtin-toolset.ts b/packages/server-ai/src/xpert-toolset/provider/builtin/builtin-toolset.ts index 6451d0d9..1157f424 100644 --- a/packages/server-ai/src/xpert-toolset/provider/builtin/builtin-toolset.ts +++ b/packages/server-ai/src/xpert-toolset/provider/builtin/builtin-toolset.ts @@ -1,4 +1,4 @@ -import { IXpertToolset, TToolCredentials, XpertToolsetCategoryEnum } from '@metad/contracts' +import { IXpertToolset, TranslateOptions, TToolCredentials, XpertToolsetCategoryEnum } from '@metad/contracts' import { Logger } from '@nestjs/common' import { CommandBus, QueryBus } from '@nestjs/cqrs' import { BaseToolset } from '../../toolset' @@ -13,16 +13,6 @@ export type TBuiltinToolsetParams = { queryBus: QueryBus } -export type TranslateOptions = { - lang?: string; - args?: ({ - [k: string]: any; - } | string)[] | { - [k: string]: any; - }; - debug?: boolean; -} - export abstract class BuiltinToolset extends BaseToolset { static provider = '' protected logger = new Logger(this.constructor.name) diff --git a/packages/server/src/i18n/en/integration.json b/packages/server/src/i18n/en/integration.json new file mode 100644 index 00000000..5f980f85 --- /dev/null +++ b/packages/server/src/i18n/en/integration.json @@ -0,0 +1,15 @@ +{ + "Lark": { + "EndConversation": "End", + "HelpDoc": "Help", + "ConversationEnded": "This conversation has ended. If you have any other questions, please feel free to contact us.", + "Confirm": "Confirm", + "Reject": "Reject", + "Status_thinking": "Thinking...", + "Status_continuing": "Continuing...", + "Status_waiting": "Still thinking, please wait...", + "Status_interrupted": "Please confirm", + "Status_end": "End", + "ActionSessionTimedOut": "The action does not exist or the session has timed out!" + } +} \ No newline at end of file diff --git a/packages/server/src/i18n/zh/integration.json b/packages/server/src/i18n/zh/integration.json new file mode 100644 index 00000000..aa97b6af --- /dev/null +++ b/packages/server/src/i18n/zh/integration.json @@ -0,0 +1,15 @@ +{ + "Lark": { + "EndConversation": "结束对话", + "HelpDoc": "帮助文档", + "ConversationEnded": "对话已结束。如果您有其他问题,欢迎随时再来咨询。", + "Confirm": "确认", + "Reject": "拒绝", + "Status_thinking": "正在思考...", + "Status_continuing": "继续思考...", + "Status_waiting": "还在思考,请稍后...", + "Status_interrupted": "请确认", + "Status_end": "结束", + "ActionSessionTimedOut": "响应动作不存在或会话已超时!" + } +} \ No newline at end of file From 1d03555eaa0c0bc7619e7e1925da3fc7a7a8877a Mon Sep 17 00:00:00 2001 From: meta-d Date: Thu, 2 Jan 2025 17:57:57 +0800 Subject: [PATCH 15/19] feat: chatbi toolset --- .../prompt-editor/editor.component.html | 2 +- .../copilot/prompt-editor/editor.component.ts | 7 +- .../components/agent/agent.component.html | 2 +- .../components/toolset/toolset.component.html | 2 +- .../xpert/studio/header/header.component.html | 18 ++-- .../xpert/studio/header/header.component.scss | 4 + .../panel/xpert-agent/agent.component.ts | 14 ++- apps/cloud/src/assets/i18n/zh-Hans.json | 4 +- .../adapter/src/adapters/postgres/postgres.ts | 6 +- packages/adapter/src/helpers.ts | 6 +- packages/adapter/src/types.ts | 1 + .../builtin/chatbi-lark/chatbi-lark.ts | 54 +++++++++-- .../chatbi-lark/tools/answer_question.ts | 72 +++++++------- .../builtin/chatbi-lark/tools/welcome.ts | 2 +- .../toolset/builtin/chatbi/chatbi-toolset.ts | 51 ++++++---- .../src/ai/toolset/builtin/chatbi/chatbi.ts | 4 +- .../src/ai/toolset/builtin/chatbi/types.ts | 96 ++++++++++++++++--- packages/analytics/src/chatbi/README.md | 1 + packages/analytics/src/chatbi/types.ts | 8 +- packages/contracts/src/ai/xpert.model.ts | 4 +- packages/core/src/lib/models/property.ts | 1 - packages/server-ai/src/index.ts | 3 +- .../src/integration-lark/chat/message.ts | 5 +- .../commands/handlers/execute.handler.ts | 2 +- .../xpert-agent/commands/handlers/types.ts | 6 +- packages/server-ai/src/xpert-agent/index.ts | 3 +- .../queries/handlers/get-variables.handler.ts | 19 +++- packages/server/src/i18n/en/integration.json | 1 + packages/server/src/i18n/en/toolset.json | 12 ++- packages/server/src/i18n/zh/integration.json | 1 + packages/server/src/i18n/zh/toolset.json | 12 ++- packages/sql/src/lib/data-source.ts | 4 +- packages/sql/src/lib/types.ts | 1 + 33 files changed, 294 insertions(+), 134 deletions(-) create mode 100644 packages/analytics/src/chatbi/README.md diff --git a/apps/cloud/src/app/@shared/copilot/prompt-editor/editor.component.html b/apps/cloud/src/app/@shared/copilot/prompt-editor/editor.component.html index 6975e331..d9c11528 100644 --- a/apps/cloud/src/app/@shared/copilot/prompt-editor/editor.component.html +++ b/apps/cloud/src/app/@shared/copilot/prompt-editor/editor.component.html @@ -71,7 +71,7 @@
    @for (variable of variables(); track variable.name) {
  • - {{ variable.name }} {{variable.type}} {{variable.description}} + {{ variable.name }} {{variable.type}} {{variable.description | i18n}}
  • }
diff --git a/apps/cloud/src/app/@shared/copilot/prompt-editor/editor.component.ts b/apps/cloud/src/app/@shared/copilot/prompt-editor/editor.component.ts index b28a8ca4..cafd2094 100644 --- a/apps/cloud/src/app/@shared/copilot/prompt-editor/editor.component.ts +++ b/apps/cloud/src/app/@shared/copilot/prompt-editor/editor.component.ts @@ -13,7 +13,6 @@ import { input, model, numberAttribute, - signal, TemplateRef, ViewChild, ViewContainerRef @@ -23,6 +22,7 @@ import { MatDialog } from '@angular/material/dialog' import { MatTooltipModule } from '@angular/material/tooltip' import { NgmHighlightVarDirective } from '@metad/ocap-angular/common' import { TranslateModule } from '@ngx-translate/core' +import { NgmI18nPipe } from '@metad/ocap-angular/core' import { CopilotPromptGeneratorComponent } from '../prompt-generator/generator.component' import { TStateVariable } from '../../../@core' @@ -32,7 +32,7 @@ import { TStateVariable } from '../../../@core' styleUrls: ['./editor.component.scss'], standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, - imports: [CommonModule, CdkMenuModule, FormsModule, TranslateModule, MatTooltipModule, NgmHighlightVarDirective] + imports: [CommonModule, CdkMenuModule, FormsModule, TranslateModule, MatTooltipModule, NgmI18nPipe, NgmHighlightVarDirective] }) export class CopilotPromptEditorComponent { readonly #dialog = inject(MatDialog) @@ -82,9 +82,6 @@ export class CopilotPromptEditorComponent { } onPromptChange(editor: HTMLDivElement) { - // console.log(editor.children) - // console.log(editor.innerHTML) - // console.log(formatInnerHTML(editor.innerHTML)) this.prompt.set(formatInnerHTML(editor.innerHTML)) } diff --git a/apps/cloud/src/app/features/xpert/studio/components/agent/agent.component.html b/apps/cloud/src/app/features/xpert/studio/components/agent/agent.component.html index 4d651b35..db763c99 100644 --- a/apps/cloud/src/app/features/xpert/studio/components/agent/agent.component.html +++ b/apps/cloud/src/app/features/xpert/studio/components/agent/agent.component.html @@ -31,7 +31,7 @@ } @if(isEnd()) { } diff --git a/apps/cloud/src/app/features/xpert/studio/components/toolset/toolset.component.html b/apps/cloud/src/app/features/xpert/studio/components/toolset/toolset.component.html index 5da9b163..98d0373b 100644 --- a/apps/cloud/src/app/features/xpert/studio/components/toolset/toolset.component.html +++ b/apps/cloud/src/app/features/xpert/studio/components/toolset/toolset.component.html @@ -49,7 +49,7 @@ @if (isEnd(item.tool.name)) { }
diff --git a/apps/cloud/src/app/features/xpert/studio/header/header.component.html b/apps/cloud/src/app/features/xpert/studio/header/header.component.html index c8db765e..9c66da60 100644 --- a/apps/cloud/src/app/features/xpert/studio/header/header.component.html +++ b/apps/cloud/src/app/features/xpert/studio/header/header.component.html @@ -18,7 +18,7 @@
- - + @if (!(sidenav.opened && sidenavMode()==='side')) { + + } @if (!isMobile()) { @@ -69,9 +77,9 @@ - } @@ -101,7 +109,7 @@ -