From bfafab239dcb4467dc8ba26d909e0796ac9db017 Mon Sep 17 00:00:00 2001 From: meta-d Date: Sat, 30 Nov 2024 17:43:17 +0800 Subject: [PATCH 1/2] feat: fix api base url for chat --- .../app/@core/interceptors/api.interceptor.ts | 11 +- apps/cloud/src/app/@core/providers/index.ts | 3 +- apps/cloud/src/app/@core/providers/url.ts | 15 ++ .../src/app/@core/services/chat.service.ts | 4 +- .../app/@core/services/xpert-agent.service.ts | 12 +- .../src/app/@core/services/xpert.service.ts | 4 +- .../panel/preview/preview.component.html | 194 ++++++++++-------- .../studio/panel/preview/preview.component.ts | 3 + apps/cloud/src/assets/i18n/zh-Hans.json | 3 +- apps/cloud/src/assets/i18n/zh-Hant.json | 27 ++- 10 files changed, 169 insertions(+), 107 deletions(-) create mode 100644 apps/cloud/src/app/@core/providers/url.ts diff --git a/apps/cloud/src/app/@core/interceptors/api.interceptor.ts b/apps/cloud/src/app/@core/interceptors/api.interceptor.ts index 2ebab228d..874b5f7a7 100644 --- a/apps/cloud/src/app/@core/interceptors/api.interceptor.ts +++ b/apps/cloud/src/app/@core/interceptors/api.interceptor.ts @@ -2,16 +2,15 @@ import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/c import { Injectable } from '@angular/core' import { API_PREFIX } from '@metad/cloud/state' import { Observable } from 'rxjs' -import { environment } from '../../../environments/environment' - -const baseUrl = environment.API_BASE_URL +import { injectApiBaseUrl } from '../providers' @Injectable() export class APIInterceptor implements HttpInterceptor { + readonly baseUrl = injectApiBaseUrl() + intercept(request: HttpRequest, next: HttpHandler): Observable> { - if (baseUrl && request.url.startsWith(`${API_PREFIX}`)) { - const url = baseUrl + request.url - // console.log(`API Request: ${request.url} -> ${url}`); + if (this.baseUrl && request.url.startsWith(`${API_PREFIX}`)) { + const url = this.baseUrl + request.url request = request.clone({ url: url }) diff --git a/apps/cloud/src/app/@core/providers/index.ts b/apps/cloud/src/app/@core/providers/index.ts index 6a8c6e20d..88cca6c1c 100644 --- a/apps/cloud/src/app/@core/providers/index.ts +++ b/apps/cloud/src/app/@core/providers/index.ts @@ -1,3 +1,4 @@ export * from './translate' export * from './logger' -export * from './user' \ No newline at end of file +export * from './user' +export * from './url' \ No newline at end of file diff --git a/apps/cloud/src/app/@core/providers/url.ts b/apps/cloud/src/app/@core/providers/url.ts new file mode 100644 index 000000000..6fd8cda92 --- /dev/null +++ b/apps/cloud/src/app/@core/providers/url.ts @@ -0,0 +1,15 @@ +import { environment } from '../../../environments/environment' + +const baseUrl = environment.API_BASE_URL + +/** + * Inject the base url of api server + * + * @returns url + */ +export function injectApiBaseUrl() { + if (!baseUrl) { + return '' + } + return baseUrl?.endsWith('/') ? baseUrl.slice(0, baseUrl.length - 1) : baseUrl +} diff --git a/apps/cloud/src/app/@core/services/chat.service.ts b/apps/cloud/src/app/@core/services/chat.service.ts index 3eb127ece..50df6e11d 100644 --- a/apps/cloud/src/app/@core/services/chat.service.ts +++ b/apps/cloud/src/app/@core/services/chat.service.ts @@ -5,11 +5,13 @@ import { EventSourceMessage, fetchEventSource } from '@microsoft/fetch-event-sou import { Observable } from 'rxjs' import { AuthStrategy } from '../auth' import { API_CHAT } from '../constants/app.constants' +import { injectApiBaseUrl } from '../providers' @Injectable({ providedIn: 'root' }) export class ChatService { readonly #store = inject(Store) readonly #auth = inject(AuthStrategy) + readonly baseUrl = injectApiBaseUrl() chat(request: TChatRequest, options: TChatOptions): Observable { const token = this.#store.token @@ -17,7 +19,7 @@ export class ChatService { return new Observable((subscriber) => { const ctrl = new AbortController() - fetchEventSource(API_CHAT, { + fetchEventSource(this.baseUrl + API_CHAT, { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/apps/cloud/src/app/@core/services/xpert-agent.service.ts b/apps/cloud/src/app/@core/services/xpert-agent.service.ts index ee63253a5..63d267d63 100644 --- a/apps/cloud/src/app/@core/services/xpert-agent.service.ts +++ b/apps/cloud/src/app/@core/services/xpert-agent.service.ts @@ -1,17 +1,19 @@ import { inject, Injectable } from '@angular/core' import { EventSourceMessage, fetchEventSource } from '@microsoft/fetch-event-source' +import { pick } from 'lodash-es' import { NGXLogger } from 'ngx-logger' import { BehaviorSubject, Observable } from 'rxjs' import { API_XPERT_AGENT } from '../constants/app.constants' +import { injectApiBaseUrl } from '../providers' import { IXpertAgent, TChatAgentParams } from '../types' -import { XpertWorkspaceBaseCrudService } from './xpert-workspace.service' import { Store } from './store.service' -import { pick } from 'lodash-es' +import { XpertWorkspaceBaseCrudService } from './xpert-workspace.service' @Injectable({ providedIn: 'root' }) export class XpertAgentService extends XpertWorkspaceBaseCrudService { readonly #logger = inject(NGXLogger) readonly #store = inject(Store) + readonly baseUrl = injectApiBaseUrl() readonly #refresh = new BehaviorSubject(null) @@ -21,10 +23,10 @@ export class XpertAgentService extends XpertWorkspaceBaseCrudService { const token = this.#store.token - const organization = this.store.selectedOrganization ?? {id: null} + const organization = this.store.selectedOrganization ?? { id: null } return new Observable((subscriber) => { const ctrl = new AbortController() - fetchEventSource(this.apiBaseUrl + `/chat`, { + fetchEventSource(this.baseUrl + this.apiBaseUrl + `/chat`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -44,7 +46,7 @@ export class XpertAgentService extends XpertWorkspaceBaseCrudService { readonly #logger = inject(NGXLogger) readonly #store = inject(Store) + readonly baseUrl = injectApiBaseUrl() readonly #refresh = new BehaviorSubject(null) @@ -70,7 +72,7 @@ export class XpertService extends XpertWorkspaceBaseCrudService { const organization = this.store.selectedOrganization ?? { id: null } return new Observable((subscriber) => { const ctrl = new AbortController() - fetchEventSource(this.apiBaseUrl + `/${id}/chat`, { + fetchEventSource(this.baseUrl + this.apiBaseUrl + `/${id}/chat`, { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/apps/cloud/src/app/features/xpert/studio/panel/preview/preview.component.html b/apps/cloud/src/app/features/xpert/studio/panel/preview/preview.component.html index d4cf54c94..03b773ef8 100644 --- a/apps/cloud/src/app/features/xpert/studio/panel/preview/preview.component.html +++ b/apps/cloud/src/app/features/xpert/studio/panel/preview/preview.component.html @@ -38,114 +38,130 @@ } - @for (message of messages(); track message.id) { - @switch (message.role) { - @case ('human') { -
-
- -
-
-

{{message.content}}

-
+ @for (message of messages(); track message.id) { + @switch (message.role) { + @case ('human') { +
+
+ +
+
+

{{message.content}}

-
-
-
- -
+
+
+
+
+
- } +
+ } - @case ('ai') { -
- -
-
- -
- -
+ @case ('ai') { +
+ +
+
+ +
+ +
-
+
- - } - } - } @empty { -
-
- - - - - - -
- -
- {{ 'PAC.Xpert.StartDebuggingDigitalExpert' | translate: {Default: 'Enter your content in the box below to start debugging Digital Expert'} }} + } + } + } @empty { +
+
+ + + + + + + +
+ +
+ {{ 'PAC.Xpert.StartDebuggingDigitalExpert' | translate: {Default: 'Enter your content in the box below to start debugging Digital Expert'} }} +
+ +
+
+
+
{{ 'PAC.Xpert.YouMightAsk' | translate: {Default: 'You might want to ask'} }}
+
- } - -
-
+
+ @for (starter of starters(); track starter) { +
{{starter}}
+ } +
+
+ } + + +
+
diff --git a/apps/cloud/src/app/features/xpert/studio/panel/preview/preview.component.ts b/apps/cloud/src/app/features/xpert/studio/panel/preview/preview.component.ts index aec856741..2acb657a1 100644 --- a/apps/cloud/src/app/features/xpert/studio/panel/preview/preview.component.ts +++ b/apps/cloud/src/app/features/xpert/studio/panel/preview/preview.component.ts @@ -54,6 +54,8 @@ export class XpertStudioPreviewComponent { readonly xpert = this.studioComponent.xpert readonly parameters = computed(() => this.apiService.primaryAgent()?.parameters) readonly avatar = computed(() => this.xpert()?.avatar) + readonly starters = computed(() => this.xpert()?.starters) + readonly input = model() readonly inputLength = computed(() => this.input()?.length) readonly loading = signal(false) @@ -170,4 +172,5 @@ export class XpertStudioPreviewComponent { event.preventDefault() } } + } diff --git a/apps/cloud/src/assets/i18n/zh-Hans.json b/apps/cloud/src/assets/i18n/zh-Hans.json index d7d1a0a1b..1b1e0a002 100644 --- a/apps/cloud/src/assets/i18n/zh-Hans.json +++ b/apps/cloud/src/assets/i18n/zh-Hans.json @@ -1995,7 +1995,8 @@ "Command": "命令", "Input": "输入", "Retrieval": "检索", - "Examples": "示例" + "Examples": "示例", + "YouMightAsk": "你可能想问" }, "title": { "short": "Xpert AI" diff --git a/apps/cloud/src/assets/i18n/zh-Hant.json b/apps/cloud/src/assets/i18n/zh-Hant.json index 1fa144ecd..141108388 100644 --- a/apps/cloud/src/assets/i18n/zh-Hant.json +++ b/apps/cloud/src/assets/i18n/zh-Hant.json @@ -1380,9 +1380,10 @@ "APIHost": "API 主機", "AskAICopilot": "向 AI 智能助理詢問問題", "EnableSecondaryProvider": "啟用副提供商", - "Prompt": "提示", + "Prompt": "提示詞", "SendPrompt": "發送提示", "CommandChartDesc": "描述圖形的業務邏輯", + "TokenLimitPerUser": "代幣限額/用戶", "TokenBalancePerUser": "代幣余額/用戶", "Provider_Alibaba Tongyi": "阿裏雲百煉", "Provider_Zhipu": "智譜", @@ -1426,6 +1427,16 @@ "OurSupport": "聯系我們,獲取優先支持", "DelCopilotModelUseInherit": "刪除當前配置,如果可以將使用繼承配置", "ChooseRightCopilot": "請選擇合適的智能助理", + "FreeTrialTokenQuota": "免費試用的 Token 額度,在啟用組織自定義 Copilot 後失效", + "Tokens": "代幣", + "Quota": "額度", + "FreeQuota": "免費額度", + "CustomCopilots": "自定義智能助理", + "Remain": "剩余", + "WelcomeTitle": "Xpert 智能助理", + "WelcomeSubTitle": "你的數字業務專家智能助理", + "UploadFile": "上傳文件", + "NewExample": "新建示例", "Prompts": { "Set story theme dark": "設置故事暗黑主題", "Set story gradient background color sense of technology": "設置故事背景為科技感漸變色", @@ -1456,7 +1467,9 @@ "ConfirmOptionsForUploadExample": "請確認上傳示例的選項", "UpdatedBy": "更新人", "UpdatedAt": "更新時間", - "ExpertRole": "專家角色" + "ExpertRole": "專家角色", + "Test": "測試", + "RetrievalTesting": "召回測試" }, "Roles": { "Name": "名稱", @@ -1474,6 +1487,7 @@ } }, "Chat": { + "EnableCopilot": "啟用智能助理", "DeleteConversation": "刪除會話", "RetrievingDocuments": "檢索文檔", "RetrievedDocuments": "檢索到{{value}}個相關文檔塊", @@ -1921,6 +1935,7 @@ "ToolsetType_All": "所有", "ToolsetType_Builtin": "內置", "ToolsetType_Custom": "自定義", + "ChatHistory": "聊天記錄", "NoChatHistory": "暫無聊天記錄", "ReleaseNewVersion": "發布為新版本", "Reset": "恢復", @@ -1975,7 +1990,13 @@ "ChangeXpertName": "更改專家名稱", "ImportDSL": "導入 DSL 文件", "ExportDSLVersion": "導出 DSL (當前版本)", - "ExportDSLDraft": "導出 DSL (草稿)" + "ExportDSLDraft": "導出 DSL (草稿)", + "CommandExamples": "命令示例", + "Command": "命令", + "Input": "輸入", + "Retrieval": "檢索", + "Examples": "示例", + "YouMightAsk": "你可能想問" }, "title": { "short": "Xpert AI" From 94a7d312f7ca5d4e5b40b9db2ee301a40e1cb759 Mon Sep 17 00:00:00 2001 From: meta-d Date: Sat, 30 Nov 2024 17:51:53 +0800 Subject: [PATCH 2/2] feat: filter disabled copilots of tenant --- packages/copilot-angular/src/lib/chat/chat.component.ts | 6 +++--- .../copilot-provider/dto/copilot-provider-public.dto.ts | 7 ++++++- packages/server-ai/src/copilot/copilot.service.ts | 9 +++++++-- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/packages/copilot-angular/src/lib/chat/chat.component.ts b/packages/copilot-angular/src/lib/chat/chat.component.ts index bfd7aee09..08faca501 100644 --- a/packages/copilot-angular/src/lib/chat/chat.component.ts +++ b/packages/copilot-angular/src/lib/chat/chat.component.ts @@ -481,9 +481,9 @@ export class NgmCopilotChatComponent { { allowSignalWrites: true } ) - effect(() => { - console.log(`isContextTrigger:`, this.isContextTrigger(), this.filteredContextItems()) - }) + // effect(() => { + // console.log(`isContextTrigger:`, this.isContextTrigger(), this.filteredContextItems()) + // }) } trackByKey(index: number, item) { diff --git a/packages/server-ai/src/copilot-provider/dto/copilot-provider-public.dto.ts b/packages/server-ai/src/copilot-provider/dto/copilot-provider-public.dto.ts index 97298ab76..cbeb53a01 100644 --- a/packages/server-ai/src/copilot-provider/dto/copilot-provider-public.dto.ts +++ b/packages/server-ai/src/copilot-provider/dto/copilot-provider-public.dto.ts @@ -13,8 +13,13 @@ export class CopilotProviderPublicDto { @Transform(({ value, obj }) => value && new CopilotDto(value, obj.baseUrl)) copilot: ICopilot + + @Exclude() + baseUrl: string - constructor(partial: Partial, private baseUrl: string) { + constructor(partial: Partial, baseUrl: string) { Object.assign(this, partial) + + this.baseUrl = baseUrl } } diff --git a/packages/server-ai/src/copilot/copilot.service.ts b/packages/server-ai/src/copilot/copilot.service.ts index 235311bf0..49ace5c4f 100644 --- a/packages/server-ai/src/copilot/copilot.service.ts +++ b/packages/server-ai/src/copilot/copilot.service.ts @@ -29,16 +29,21 @@ export class CopilotService extends TenantOrganizationAwareCrudService * @param filter */ async findAvailables(filter?: PaginationParams) { - const copilots = await this.findAllCopilots() + const tenantId = RequestContext.currentTenantId() + const organizationId = RequestContext.getOrganizationId() + let copilots = await this.findAllCopilots() + // Filter the tenant enabled copilots for organization user + copilots = copilots.filter((copilot) => (organizationId && !copilot.organizationId) ? copilot.enabled : true) for await (const copilot of copilots) { if (!copilot.organizationId) { - const usage = await this.queryBus.execute(new GetCopilotOrgUsageQuery(RequestContext.currentTenantId(), RequestContext.getOrganizationId(), copilot.id)) + const usage = await this.queryBus.execute(new GetCopilotOrgUsageQuery(tenantId, organizationId, copilot.id)) copilot.usage = usage ?? { tokenLimit: copilot.tokenBalance, tokenUsed: 0 } } } + return copilots }