Skip to content

Commit 4fdf462

Browse files
committed
feat: chatbi verbose
1 parent 65a04a8 commit 4fdf462

File tree

15 files changed

+144
-141
lines changed

15 files changed

+144
-141
lines changed

apps/cloud/src/app/features/semantic-model/model/entity/preview/preview.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
<div class="flex flex-col p-4">
2323
<label>{{ 'PAC.KEY_WORDS.Variables' | translate: {Default: "Variables"} }}</label>
2424
@for (variable of variableList(); track variable.name) {
25-
<ngm-variable [label]="variable.caption" displayDensity="compact"
25+
<ngm-variable [label]="variable.caption" displayDensity="cosy"
2626
[dataSettings]="dataSettings()"
2727
[variable]="variable"
2828
[ngModel]="variables()[variable.name]"

apps/cloud/src/app/features/setting/knowledgebase/knowledgebase/configuration/configuration.component.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@
6363
>💡</span>
6464
</ngm-slider-input>
6565

66-
<div class="flex justify-end">
67-
<button mat-flat-button (click)="cancel()">
66+
<div class="flex justify-end gap-2">
67+
<button mat-flat-button type="button" (click)="cancel()">
6868
{{ 'PAC.KEY_WORDS.Cancel' | translate: {Default: 'Cancel'} }}
6969
</button>
7070
<button mat-raised-button color="primary" [disabled]="loading() || formGroup.invalid || formGroup.pristine" (click)="save()">

apps/cloud/src/app/features/setting/knowledgebase/knowledgebase/configuration/configuration.component.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angula
55
import { ActivatedRoute, Router, RouterModule } from '@angular/router'
66
import { AI_PROVIDERS, AiModelCapability, AiProviderRole, isNil } from '@metad/copilot'
77
import { NgmCommonModule } from '@metad/ocap-angular/common'
8+
import { DisplayBehaviour } from '@metad/ocap-core'
89
import { TranslateModule } from '@ngx-translate/core'
10+
import { upperFirst } from 'lodash-es'
911
import { startWith } from 'rxjs'
1012
import {
1113
IKnowledgebase,
@@ -19,8 +21,6 @@ import {
1921
} from '../../../../../@core'
2022
import { AvatarEditorComponent, MaterialModule, TranslationBaseComponent } from '../../../../../@shared'
2123
import { KnowledgebaseComponent } from '../knowledgebase.component'
22-
import { DisplayBehaviour } from '@metad/ocap-core'
23-
import { upperFirst } from 'lodash-es'
2424

2525
@Component({
2626
standalone: true,
@@ -105,11 +105,15 @@ export class KnowledgeConfigurationComponent extends TranslationBaseComponent {
105105
})
106106
items.push(
107107
...models
108-
.filter((_) => _.id !== copilot.defaultModel && (isNil(_.capabilities) || _.capabilities.includes(AiModelCapability.Embed)))
109-
.map((item) => ({
110-
key: item.id,
111-
caption: item.name
112-
}))
108+
.filter(
109+
(_) =>
110+
_.id !== copilot.defaultModel &&
111+
(isNil(_.capabilities) || _.capabilities.includes(AiModelCapability.Embed))
112+
)
113+
.map((item) => ({
114+
key: item.id,
115+
caption: item.name
116+
}))
113117
)
114118
}
115119
return items
@@ -161,6 +165,6 @@ export class KnowledgeConfigurationComponent extends TranslationBaseComponent {
161165
}
162166

163167
cancel() {
164-
this.#router.navigate(['..', '..'], { relativeTo: this.#route })
168+
this.#router.navigate(['../..'], { relativeTo: this.#route })
165169
}
166170
}

libs/story-angular/designer/settings-panel/settings-panel.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<mat-drawer-container class="h-full" autoresize waResizeBox="content-box" (waResizeObserver)="onResize($event)">
1+
<mat-drawer-container class="h-full" displayDensity="compact" autoresize waResizeBox="content-box" (waResizeObserver)="onResize($event)">
22
<mat-drawer #drawer mode="over" position="end" [(opened)]="drawerOpened" class="ngm-settings-panel__drawer">
33
<mat-toolbar class="ngm-settings-panel__drawer-drawer flex justify-start items-center gap-2">
44
<button mat-icon-button type="button" displayDensity="cosy" (click)="closeDrawer()">

packages/analytics/src/chatbi/commands/handlers/chatbi.handler.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,12 @@ export class ChatBIHandler implements ICommandHandler<ChatBICommand> {
7070
}
7171

7272
// Ask
73-
conversation.ask(input.text).then()
73+
conversation.ask(input.text).then().catch((err) => {
74+
larkService.errorMessage(
75+
input,
76+
new Error(`Internal Error!`)
77+
).catch((err) => console.error(err))
78+
})
7479

7580
return
7681
}

packages/analytics/src/chatbi/conversation.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import { createIndicatorTool } from './tools/indicator'
5252
*/
5353
export class ChatBIConversation implements IChatBIConversation {
5454
private readonly logger = new Logger(ChatBIConversation.name)
55+
static readonly toolCallTimeout = 10000
5556
readonly commandName = 'chatbi'
5657

5758
public id: string = null
@@ -96,6 +97,7 @@ export class ChatBIConversation implements IChatBIConversation {
9697

9798
private status: 'init' | 'idle' | 'running' | 'error' = 'init'
9899
private chatStack: ChatStack[] = []
100+
99101
constructor(
100102
private readonly chatContext: ChatBILarkContext,
101103
private readonly chatModel: BaseChatModel,
@@ -388,7 +390,9 @@ ${createAgentStepsInstructions(
388390
}
389391

390392
// Few-shot prompt
393+
this.logger.verbose(`ExampleFewShot start for user input: ${text}`)
391394
const content = await this.exampleFewShotPrompt.format({ input: text })
395+
this.logger.verbose(`ExampleFewShot got content: ${content}`)
392396

393397
const streamResults = await this.graph.stream(
394398
{
@@ -466,7 +470,7 @@ ${createAgentStepsInstructions(
466470
end = true
467471
} else {
468472
// larkService.errorMessage(input, err)
469-
errorWithEndMessage(this.chatContext, err.message, this)
473+
await errorWithEndMessage(this.chatContext, err.message, this)
470474
}
471475
}
472476

packages/analytics/src/chatbi/tools/answer.ts

Lines changed: 42 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ import {
3131
tryFixVariableSlicer,
3232
workOutTimeRangeSlicers
3333
} from '@metad/ocap-core'
34+
import { race } from '@metad/server-common'
3435
import { firstValueFrom, Subject, takeUntil } from 'rxjs'
36+
import { ChatBIConversation } from '../conversation'
3537
import { ChatLarkMessage } from '../message'
3638
import { ChatAnswerSchema, ChatBILarkContext, ChatContext, IChatBIConversation } from '../types'
3739
import { createBaseChart } from './charts/chart'
@@ -55,41 +57,55 @@ export type ChatAnswer = {
5557
orders: OrderBy[]
5658
}
5759

58-
5960
export function createChatAnswerTool(context: ChatContext, larkContext: ChatBILarkContext) {
6061
const { chatId, logger, dsCoreService, conversation } = context
6162
return tool(
6263
async (answer): Promise<string> => {
6364
logger.debug(`Execute copilot action 'answerQuestion':`, JSON.stringify(answer, null, 2))
6465
try {
65-
let entityType = null
66-
if (answer.dataSettings) {
67-
// Make sure datasource exists
68-
const _dataSource = await dsCoreService._getDataSource(answer.dataSettings.dataSource)
69-
const entity = await firstValueFrom(
70-
dsCoreService.selectEntitySet(answer.dataSettings.dataSource, answer.dataSettings.entitySet)
71-
)
72-
entityType = entity.entityType
73-
}
74-
75-
// Fetch data for chart or table or kpi
76-
if (answer.dimensions?.length || answer.measures?.length) {
77-
const { categoryMembers } = await drawChartMessage(
78-
{ ...context, entityType: entityType || context.entityType },
79-
conversation,
80-
answer as ChatAnswer
81-
)
82-
// Max limit 20 members
83-
const members = categoryMembers
84-
? JSON.stringify(Object.values(categoryMembers).slice(0, 20))
85-
: 'Empty'
86-
87-
return `The analysis data has been displayed to the user. The dimension members involved in this data analysis are:
66+
try {
67+
// 限制总体超时时间
68+
return await race(
69+
ChatBIConversation.toolCallTimeout,
70+
(async () => {
71+
let entityType = null
72+
if (answer.dataSettings) {
73+
// Make sure datasource exists
74+
const _dataSource = await dsCoreService._getDataSource(answer.dataSettings.dataSource)
75+
const entity = await firstValueFrom(
76+
dsCoreService.selectEntitySet(
77+
answer.dataSettings.dataSource,
78+
answer.dataSettings.entitySet
79+
)
80+
)
81+
entityType = entity.entityType
82+
}
83+
84+
// Fetch data for chart or table or kpi
85+
if (answer.dimensions?.length || answer.measures?.length) {
86+
const { categoryMembers } = await drawChartMessage(
87+
{ ...context, entityType: entityType || context.entityType },
88+
conversation,
89+
answer as ChatAnswer
90+
)
91+
// Max limit 20 members
92+
const members = categoryMembers
93+
? JSON.stringify(Object.values(categoryMembers).slice(0, 20))
94+
: 'Empty'
95+
96+
return `The analysis data has been displayed to the user. The dimension members involved in this data analysis are:
8897
${members}
8998
Please give more analysis suggestions about other dimensions or filter by dimensioin members, 3 will be enough.`
90-
}
99+
}
91100

92-
return `图表答案已经回复给用户了,请不要重复回答了。`
101+
return `图表答案已经回复给用户了,请不要重复回答了。`
102+
})()
103+
)
104+
} catch (err) {
105+
throw new Error(
106+
`Timeout in getting cube context (dataSource=${answer.dataSettings.dataSource}, cube=${answer.dataSettings.entitySet})`
107+
)
108+
}
93109
} catch (err) {
94110
logger.error(err)
95111
return `Error: ${err}。如果需要用户提供更多信息,请直接提醒用户。`
@@ -333,63 +349,6 @@ function createLineChart(
333349
unit = shortUnit
334350
} else {
335351
throw Error(`图形配置错误`)
336-
// let categoryProperty: PropertyHierarchy = null
337-
// const fields = []
338-
// if (chartAnnotation.dimensions?.length > 1) {
339-
// const dimensions = chartAnnotation.dimensions.filter((d) => d.role !== ChartDimensionRoleType.Time)
340-
// const series = getChartSeries(chartAnnotation) || dimensions[1] || dimensions[0]
341-
// if (!series) {
342-
// throw new Error(
343-
// `Cannot find series dimension in chart dimensions: '${JSON.stringify(chartAnnotation.dimensions)}'`
344-
// )
345-
// }
346-
// const seriesName = getPropertyHierarchy(series)
347-
// const property = getEntityHierarchy(entityType, seriesName)
348-
// if (!property) {
349-
// throw new Error(`Cannot find hierarchy for series dimension '${JSON.stringify(series)}'`)
350-
// }
351-
// const seriesCaption = property.memberCaption
352-
// chart_spec.seriesField = seriesCaption
353-
// fields.push(seriesCaption)
354-
// categoryProperty = getEntityHierarchy(
355-
// entityType,
356-
// chartAnnotation.dimensions.filter((d) => d.dimension !== series.dimension)[0]
357-
// )
358-
// const categoryCaption = categoryProperty.memberCaption
359-
// chart_spec[categoryField] = categoryCaption
360-
// fields.push(categoryCaption)
361-
// } else if (chartAnnotation.dimensions?.length) {
362-
// categoryProperty = getEntityHierarchy(entityType, chartAnnotation.dimensions[0])
363-
// if (!categoryProperty) {
364-
// throw new Error(`Not found dimension '${chartAnnotation.dimensions[0].dimension}'`)
365-
// }
366-
// const categoryCaption = categoryProperty.memberCaption
367-
// chart_spec[categoryField] = categoryCaption
368-
// fields.push(categoryCaption)
369-
// }
370-
371-
// chartAnnotation.measures?.forEach((measure) => {
372-
// const property = getEntityProperty<PropertyMeasure>(entityType, measure)
373-
// // // Type: measure
374-
// // _data.forEach((item, index) => {
375-
// // item['type'] = property.caption || property.name
376-
// // })
377-
// if (property.formatting?.unit === '%') {
378-
// _data.forEach((item, index) => {
379-
// item[property.name] = isNil(data[index][property.name])
380-
// ? null
381-
// : (data[index][property.name] * 100).toFixed(1)
382-
// })
383-
// } else {
384-
// const result = formatDataValues(data, _data, property.name)
385-
// _data = result.values
386-
// unit = result.unit
387-
// }
388-
// })
389-
390-
// chart_spec.data = {
391-
// values: _data // 此处传入数据。
392-
// }
393352
}
394353

395354
categoryMembers = {}

packages/analytics/src/chatbi/tools/cube-context.ts

Lines changed: 43 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,60 @@
11
import { tool } from '@langchain/core/tools'
22
import { isEntitySet, markdownModelCube } from '@metad/ocap-core'
3+
import { race } from '@metad/server-common'
34
import { firstValueFrom, switchMap } from 'rxjs'
4-
import { z } from 'zod'
5-
import { ChatContext, GetCubesContextSchema } from '../types'
65
import { ChatBIModelService } from '../../chatbi-model/'
6+
import { ChatContext, GetCubesContextSchema } from '../types'
7+
import { ChatBIConversation } from '../conversation'
78

89
export function createCubeContextTool(context: ChatContext, modelService: ChatBIModelService) {
910
const { logger, dsCoreService, conversation } = context
1011
return tool(
1112
async ({ cubes }): Promise<string> => {
1213
logger.debug(`Tool 'getCubeContext' params:`, JSON.stringify(cubes))
13-
let context = ''
14-
for await (const item of cubes) {
15-
logger.debug(` get context for:`, item.modelId, item.name)
14+
try {
15+
// 限制总体超时时间
16+
return await race(ChatBIConversation.toolCallTimeout,
17+
(async () => {
18+
let context = ''
19+
for await (const item of cubes) {
20+
logger.debug(` get context for:`, item.modelId, item.name)
1621

17-
let entityType = await conversation.getCubeCache(item.modelId, item.name)
18-
if (!entityType) {
19-
const entitySet = await firstValueFrom(
20-
dsCoreService.getDataSource(item.modelId).pipe(
21-
switchMap((dataSource) => dataSource.selectEntitySet(item.name)),
22-
)
23-
)
24-
if (isEntitySet(entitySet)) {
25-
entityType = entitySet.entityType
26-
await conversation.setCubeCache(item.modelId, item.name, entityType)
27-
} else {
28-
logger.error(` get context error: `, entitySet.message)
29-
}
30-
}
31-
if (entityType) {
32-
if (context) {
33-
context += '\n'
34-
}
22+
let entityType = await conversation.getCubeCache(item.modelId, item.name)
23+
if (!entityType) {
24+
const entitySet = await firstValueFrom(
25+
dsCoreService
26+
.getDataSource(item.modelId)
27+
.pipe(switchMap((dataSource) => dataSource.selectEntitySet(item.name)))
28+
)
29+
if (isEntitySet(entitySet)) {
30+
entityType = entitySet.entityType
31+
await conversation.setCubeCache(item.modelId, item.name, entityType)
32+
} else {
33+
logger.error(` get context error: `, entitySet.message)
34+
}
35+
}
36+
if (entityType) {
37+
if (context) {
38+
context += '\n'
39+
}
3540

36-
context += markdownModelCube({
37-
modelId: item.modelId,
38-
dataSource: item.modelId,
39-
cube: entityType
40-
})
41+
context += markdownModelCube({
42+
modelId: item.modelId,
43+
dataSource: item.modelId,
44+
cube: entityType
45+
})
4146

42-
// Record visit
43-
await modelService.visit(item.modelId, item.name)
44-
}
45-
}
47+
// Record visit
48+
await modelService.visit(item.modelId, item.name)
49+
}
50+
}
4651

47-
return context
52+
return context
53+
})()
54+
)
55+
} catch (err) {
56+
return 'Error:' + err.message
57+
}
4858
},
4959
{
5060
name: 'getCubeContext',

packages/angular/common/input/_input.component.scss

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ $prefix: (ngm, color);
6969
.ngm-input__suffix {
7070
@apply absolute right-1;
7171
}
72+
.ngm-input-element__clear {
73+
@apply w-6 h-6;
74+
.material-icons {
75+
font-size: 20px;
76+
}
77+
}
7278
}
7379

7480
.ngm-density__compact {

packages/angular/common/select/select/select.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
} @else {
3333
<div class="relative">
3434
<input class="ngm-input-element w-full relative"
35-
type="text"
35+
#searInput type="text"
3636
[placeholder]="placeholder()"
3737
matInput
3838
[matAutocomplete]="auto"

0 commit comments

Comments
 (0)