From 183b0387c4f52a4e38e92acdbed23c2e7aa73d25 Mon Sep 17 00:00:00 2001 From: meta-d Date: Tue, 24 Dec 2024 14:59:48 +0800 Subject: [PATCH 1/8] feat: xAI support --- packages/server-ai/package.json | 1 + .../ai-model/model_providers/_position.yaml | 1 + .../src/ai-model/model_providers/index.ts | 4 +- .../model_providers/x/_assets/x-ai-logo.svg | 1 + .../model_providers/x/llm/grok-2-1212.yaml | 66 ++++++++++++++++++ .../x/llm/grok-2-vision-1212.yaml | 64 ++++++++++++++++++ .../model_providers/x/llm/grok-beta.yaml | 66 ++++++++++++++++++ .../x/llm/grok-vision-beta.yaml | 64 ++++++++++++++++++ .../src/ai-model/model_providers/x/llm/llm.ts | 67 +++++++++++++++++++ .../src/ai-model/model_providers/x/types.ts | 15 +++++ .../src/ai-model/model_providers/x/x.ts | 51 ++++++++++++++ .../src/ai-model/model_providers/x/x.yaml | 38 +++++++++++ yarn.lock | 7 ++ 13 files changed, 444 insertions(+), 1 deletion(-) create mode 100644 packages/server-ai/src/ai-model/model_providers/x/_assets/x-ai-logo.svg create mode 100644 packages/server-ai/src/ai-model/model_providers/x/llm/grok-2-1212.yaml create mode 100644 packages/server-ai/src/ai-model/model_providers/x/llm/grok-2-vision-1212.yaml create mode 100644 packages/server-ai/src/ai-model/model_providers/x/llm/grok-beta.yaml create mode 100644 packages/server-ai/src/ai-model/model_providers/x/llm/grok-vision-beta.yaml create mode 100644 packages/server-ai/src/ai-model/model_providers/x/llm/llm.ts create mode 100644 packages/server-ai/src/ai-model/model_providers/x/types.ts create mode 100644 packages/server-ai/src/ai-model/model_providers/x/x.ts create mode 100644 packages/server-ai/src/ai-model/model_providers/x/x.yaml diff --git a/packages/server-ai/package.json b/packages/server-ai/package.json index 8a341f445..934913367 100644 --- a/packages/server-ai/package.json +++ b/packages/server-ai/package.json @@ -14,6 +14,7 @@ "@langchain/community": "0.3.11", "@langchain/langgraph": "0.2.33", "@langchain/google-genai": "0.1.6", + "@langchain/xai": "0.0.1", "@nestjs/schedule": "4.1.2", "@openapi-contrib/openapi-schema-to-json-schema": "^5.1.0", "@sap_oss/odata-library": "2.4.0", diff --git a/packages/server-ai/src/ai-model/model_providers/_position.yaml b/packages/server-ai/src/ai-model/model_providers/_position.yaml index 89fccef65..f23ed979e 100644 --- a/packages/server-ai/src/ai-model/model_providers/_position.yaml +++ b/packages/server-ai/src/ai-model/model_providers/_position.yaml @@ -1,6 +1,7 @@ - openai - anthropic - azure_openai +- x - google - vertex_ai - nvidia diff --git a/packages/server-ai/src/ai-model/model_providers/index.ts b/packages/server-ai/src/ai-model/model_providers/index.ts index b5a0591a6..bc5636f45 100644 --- a/packages/server-ai/src/ai-model/model_providers/index.ts +++ b/packages/server-ai/src/ai-model/model_providers/index.ts @@ -15,6 +15,7 @@ import { OllamaProviderModule } from './ollama/ollama' import { OpenAIProviderModule } from './openai/openai' import { TogetherAIProvider } from './togetherai/togetherai' import { TongyiProviderModule } from './tongyi/tongyi' +import { XAIProviderModule } from './x/x' import { ZhipuaiProviderModule } from './zhipuai/zhipuai' export const ProviderModules = [ @@ -33,5 +34,6 @@ export const ProviderModules = [ MistralAIProviderModule, MoonshotProviderModule, TongyiProviderModule, - HuggingfaceHubProviderModule + HuggingfaceHubProviderModule, + XAIProviderModule ] \ No newline at end of file diff --git a/packages/server-ai/src/ai-model/model_providers/x/_assets/x-ai-logo.svg b/packages/server-ai/src/ai-model/model_providers/x/_assets/x-ai-logo.svg new file mode 100644 index 000000000..f8b745cb1 --- /dev/null +++ b/packages/server-ai/src/ai-model/model_providers/x/_assets/x-ai-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/server-ai/src/ai-model/model_providers/x/llm/grok-2-1212.yaml b/packages/server-ai/src/ai-model/model_providers/x/llm/grok-2-1212.yaml new file mode 100644 index 000000000..24ea716a9 --- /dev/null +++ b/packages/server-ai/src/ai-model/model_providers/x/llm/grok-2-1212.yaml @@ -0,0 +1,66 @@ +model: grok-2-1212 +label: + en_US: grok-2-1212 +model_type: llm +features: + - agent-thought + - tool-call + - multi-tool-call + - stream-tool-call +model_properties: + mode: chat + context_size: 131072 +parameter_rules: + - name: temperature + label: + en_US: "Temperature" + zh_Hans: "采样温度" + type: float + default: 0.7 + min: 0.0 + max: 2.0 + precision: 1 + required: true + help: + en_US: "The randomness of the sampling temperature control output. The temperature value is within the range of [0.0, 1.0]. The higher the value, the more random and creative the output; the lower the value, the more stable it is. It is recommended to adjust either top_p or temperature parameters according to your needs to avoid adjusting both at the same time." + zh_Hans: "采样温度控制输出的随机性。温度值在 [0.0, 1.0] 范围内,值越高,输出越随机和创造性;值越低,输出越稳定。建议根据需求调整 top_p 或 temperature 参数,避免同时调整两者。" + + - name: top_p + label: + en_US: "Top P" + zh_Hans: "Top P" + type: float + default: 0.7 + min: 0.0 + max: 1.0 + precision: 1 + required: true + help: + en_US: "The value range of the sampling method is [0.0, 1.0]. The top_p value determines that the model selects tokens from the top p% of candidate words with the highest probability; when top_p is 0, this parameter is invalid. It is recommended to adjust either top_p or temperature parameters according to your needs to avoid adjusting both at the same time." + zh_Hans: "采样方法的取值范围为 [0.0,1.0]。top_p 值确定模型从概率最高的前p%的候选词中选取 tokens;当 top_p 为 0 时,此参数无效。建议根据需求调整 top_p 或 temperature 参数,避免同时调整两者。" + + - name: frequency_penalty + use_template: frequency_penalty + label: + en_US: "Frequency Penalty" + zh_Hans: "频率惩罚" + type: float + default: 0 + min: 0 + max: 2.0 + precision: 1 + required: false + help: + en_US: "Number between 0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim." + zh_Hans: "介于0和2.0之间的数字。正值会根据新标记在文本中迄今为止的现有频率来惩罚它们,从而降低模型一字不差地重复同一句话的可能性。" + + - name: user + use_template: text + label: + en_US: "User" + zh_Hans: "用户" + type: string + required: false + help: + en_US: "Used to track and differentiate conversation requests from different users." + zh_Hans: "用于追踪和区分不同用户的对话请求。" diff --git a/packages/server-ai/src/ai-model/model_providers/x/llm/grok-2-vision-1212.yaml b/packages/server-ai/src/ai-model/model_providers/x/llm/grok-2-vision-1212.yaml new file mode 100644 index 000000000..f224fa575 --- /dev/null +++ b/packages/server-ai/src/ai-model/model_providers/x/llm/grok-2-vision-1212.yaml @@ -0,0 +1,64 @@ +model: grok-2-vision-1212 +label: + en_US: grok-2-vision-1212 +model_type: llm +features: + - agent-thought + - vision +model_properties: + mode: chat + context_size: 8192 +parameter_rules: + - name: temperature + label: + en_US: "Temperature" + zh_Hans: "采样温度" + type: float + default: 0.7 + min: 0.0 + max: 2.0 + precision: 1 + required: true + help: + en_US: "The randomness of the sampling temperature control output. The temperature value is within the range of [0.0, 1.0]. The higher the value, the more random and creative the output; the lower the value, the more stable it is. It is recommended to adjust either top_p or temperature parameters according to your needs to avoid adjusting both at the same time." + zh_Hans: "采样温度控制输出的随机性。温度值在 [0.0, 1.0] 范围内,值越高,输出越随机和创造性;值越低,输出越稳定。建议根据需求调整 top_p 或 temperature 参数,避免同时调整两者。" + + - name: top_p + label: + en_US: "Top P" + zh_Hans: "Top P" + type: float + default: 0.7 + min: 0.0 + max: 1.0 + precision: 1 + required: true + help: + en_US: "The value range of the sampling method is [0.0, 1.0]. The top_p value determines that the model selects tokens from the top p% of candidate words with the highest probability; when top_p is 0, this parameter is invalid. It is recommended to adjust either top_p or temperature parameters according to your needs to avoid adjusting both at the same time." + zh_Hans: "采样方法的取值范围为 [0.0,1.0]。top_p 值确定模型从概率最高的前p%的候选词中选取 tokens;当 top_p 为 0 时,此参数无效。建议根据需求调整 top_p 或 temperature 参数,避免同时调整两者。" + + - name: frequency_penalty + use_template: frequency_penalty + label: + en_US: "Frequency Penalty" + zh_Hans: "频率惩罚" + type: float + default: 0 + min: 0 + max: 2.0 + precision: 1 + required: false + help: + en_US: "Number between 0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim." + zh_Hans: "介于0和2.0之间的数字。正值会根据新标记在文本中迄今为止的现有频率来惩罚它们,从而降低模型一字不差地重复同一句话的可能性。" + + - name: user + use_template: text + label: + en_US: "User" + zh_Hans: "用户" + type: string + required: false + help: + en_US: "Used to track and differentiate conversation requests from different users." + zh_Hans: "用于追踪和区分不同用户的对话请求。" diff --git a/packages/server-ai/src/ai-model/model_providers/x/llm/grok-beta.yaml b/packages/server-ai/src/ai-model/model_providers/x/llm/grok-beta.yaml new file mode 100644 index 000000000..7f722539d --- /dev/null +++ b/packages/server-ai/src/ai-model/model_providers/x/llm/grok-beta.yaml @@ -0,0 +1,66 @@ +model: grok-beta +label: + en_US: grok-beta +model_type: llm +features: + - agent-thought + - tool-call + - multi-tool-call + - stream-tool-call +model_properties: + mode: chat + context_size: 131072 +parameter_rules: + - name: temperature + label: + en_US: "Temperature" + zh_Hans: "采样温度" + type: float + default: 0.7 + min: 0.0 + max: 2.0 + precision: 1 + required: true + help: + en_US: "The randomness of the sampling temperature control output. The temperature value is within the range of [0.0, 1.0]. The higher the value, the more random and creative the output; the lower the value, the more stable it is. It is recommended to adjust either top_p or temperature parameters according to your needs to avoid adjusting both at the same time." + zh_Hans: "采样温度控制输出的随机性。温度值在 [0.0, 1.0] 范围内,值越高,输出越随机和创造性;值越低,输出越稳定。建议根据需求调整 top_p 或 temperature 参数,避免同时调整两者。" + + - name: top_p + label: + en_US: "Top P" + zh_Hans: "Top P" + type: float + default: 0.7 + min: 0.0 + max: 1.0 + precision: 1 + required: true + help: + en_US: "The value range of the sampling method is [0.0, 1.0]. The top_p value determines that the model selects tokens from the top p% of candidate words with the highest probability; when top_p is 0, this parameter is invalid. It is recommended to adjust either top_p or temperature parameters according to your needs to avoid adjusting both at the same time." + zh_Hans: "采样方法的取值范围为 [0.0,1.0]。top_p 值确定模型从概率最高的前p%的候选词中选取 tokens;当 top_p 为 0 时,此参数无效。建议根据需求调整 top_p 或 temperature 参数,避免同时调整两者。" + + - name: frequency_penalty + use_template: frequency_penalty + label: + en_US: "Frequency Penalty" + zh_Hans: "频率惩罚" + type: float + default: 0 + min: 0 + max: 2.0 + precision: 1 + required: false + help: + en_US: "Number between 0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim." + zh_Hans: "介于0和2.0之间的数字。正值会根据新标记在文本中迄今为止的现有频率来惩罚它们,从而降低模型一字不差地重复同一句话的可能性。" + + - name: user + use_template: text + label: + en_US: "User" + zh_Hans: "用户" + type: string + required: false + help: + en_US: "Used to track and differentiate conversation requests from different users." + zh_Hans: "用于追踪和区分不同用户的对话请求。" diff --git a/packages/server-ai/src/ai-model/model_providers/x/llm/grok-vision-beta.yaml b/packages/server-ai/src/ai-model/model_providers/x/llm/grok-vision-beta.yaml new file mode 100644 index 000000000..1d8128253 --- /dev/null +++ b/packages/server-ai/src/ai-model/model_providers/x/llm/grok-vision-beta.yaml @@ -0,0 +1,64 @@ +model: grok-vision-beta +label: + en_US: grok-vision-beta +model_type: llm +features: + - agent-thought + - vision +model_properties: + mode: chat + context_size: 8192 +parameter_rules: + - name: temperature + label: + en_US: "Temperature" + zh_Hans: "采样温度" + type: float + default: 0.7 + min: 0.0 + max: 2.0 + precision: 1 + required: true + help: + en_US: "The randomness of the sampling temperature control output. The temperature value is within the range of [0.0, 1.0]. The higher the value, the more random and creative the output; the lower the value, the more stable it is. It is recommended to adjust either top_p or temperature parameters according to your needs to avoid adjusting both at the same time." + zh_Hans: "采样温度控制输出的随机性。温度值在 [0.0, 1.0] 范围内,值越高,输出越随机和创造性;值越低,输出越稳定。建议根据需求调整 top_p 或 temperature 参数,避免同时调整两者。" + + - name: top_p + label: + en_US: "Top P" + zh_Hans: "Top P" + type: float + default: 0.7 + min: 0.0 + max: 1.0 + precision: 1 + required: true + help: + en_US: "The value range of the sampling method is [0.0, 1.0]. The top_p value determines that the model selects tokens from the top p% of candidate words with the highest probability; when top_p is 0, this parameter is invalid. It is recommended to adjust either top_p or temperature parameters according to your needs to avoid adjusting both at the same time." + zh_Hans: "采样方法的取值范围为 [0.0,1.0]。top_p 值确定模型从概率最高的前p%的候选词中选取 tokens;当 top_p 为 0 时,此参数无效。建议根据需求调整 top_p 或 temperature 参数,避免同时调整两者。" + + - name: frequency_penalty + use_template: frequency_penalty + label: + en_US: "Frequency Penalty" + zh_Hans: "频率惩罚" + type: float + default: 0 + min: 0 + max: 2.0 + precision: 1 + required: false + help: + en_US: "Number between 0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim." + zh_Hans: "介于0和2.0之间的数字。正值会根据新标记在文本中迄今为止的现有频率来惩罚它们,从而降低模型一字不差地重复同一句话的可能性。" + + - name: user + use_template: text + label: + en_US: "User" + zh_Hans: "用户" + type: string + required: false + help: + en_US: "Used to track and differentiate conversation requests from different users." + zh_Hans: "用于追踪和区分不同用户的对话请求。" diff --git a/packages/server-ai/src/ai-model/model_providers/x/llm/llm.ts b/packages/server-ai/src/ai-model/model_providers/x/llm/llm.ts new file mode 100644 index 000000000..8637da50f --- /dev/null +++ b/packages/server-ai/src/ai-model/model_providers/x/llm/llm.ts @@ -0,0 +1,67 @@ +import { ChatXAI } from '@langchain/xai' +import { AiModelTypeEnum, ICopilotModel } from '@metad/contracts' +import { sumTokenUsage } from '@metad/copilot' +import { getErrorMessage } from '@metad/server-common' +import { Injectable } from '@nestjs/common' +import { AIModel } from '../../../ai-model' +import { ModelProvider } from '../../../ai-provider' +import { TChatModelOptions } from '../../../types/types' +import { CredentialsValidateFailedError } from '../../errors' +import { XAICredentials, toCredentialKwargs } from '../types' + +@Injectable() +export class XAILargeLanguageModel extends AIModel { + constructor(readonly modelProvider: ModelProvider) { + super(modelProvider, AiModelTypeEnum.LLM) + } + + async validateCredentials(model: string, credentials: XAICredentials): Promise { + const params = toCredentialKwargs(credentials) + + const chatModel = new ChatXAI({ + ...params, + model + }) + + try { + await chatModel.invoke([ + { + role: 'human', + content: `Hi` + } + ]) + } catch (err) { + throw new CredentialsValidateFailedError(getErrorMessage(err)) + } + } + + override getChatModel(copilotModel: ICopilotModel, options?: TChatModelOptions) { + const { copilot } = copilotModel + const { modelProvider } = copilot + const params = toCredentialKwargs(modelProvider.credentials as XAICredentials) + + const { handleLLMTokens } = options ?? {} + return new ChatXAI({ + ...params, + model: copilotModel.model, + streaming: copilotModel.options?.streaming ?? true, + temperature: copilotModel.options?.temperature ?? 0, + callbacks: [ + { + handleLLMEnd(output) { + if (handleLLMTokens) { + let totalTokens = output.llmOutput?.totalTokens ?? output.llmOutput?.tokenUsage?.totalTokens + if (isNaN(totalTokens)) { + totalTokens = sumTokenUsage(output) + } + handleLLMTokens({ + copilot, + tokenUsed: isNaN(totalTokens) ? 0 : (totalTokens ?? 0) + }) + } + } + } + ] + }) + } +} diff --git a/packages/server-ai/src/ai-model/model_providers/x/types.ts b/packages/server-ai/src/ai-model/model_providers/x/types.ts new file mode 100644 index 000000000..c25007102 --- /dev/null +++ b/packages/server-ai/src/ai-model/model_providers/x/types.ts @@ -0,0 +1,15 @@ +import { ChatXAIInput } from '@langchain/xai' + +export interface XAICredentials { + api_key: string + endpoint_url: string +} + +export function toCredentialKwargs(credentials: XAICredentials) { + const credentialsKwargs: ChatXAIInput = { + apiKey: credentials.api_key + // baseUrl: credentials.endpoint_url + } + + return credentialsKwargs +} diff --git a/packages/server-ai/src/ai-model/model_providers/x/x.ts b/packages/server-ai/src/ai-model/model_providers/x/x.ts new file mode 100644 index 000000000..7157c4b3a --- /dev/null +++ b/packages/server-ai/src/ai-model/model_providers/x/x.ts @@ -0,0 +1,51 @@ +import { AiModelTypeEnum } from '@metad/contracts' +import { Injectable, Module } from '@nestjs/common' +import { ModelProvider } from '../../ai-provider' +import { CredentialsValidateFailedError } from '../errors' +import { XAILargeLanguageModel } from './llm/llm' +import { XAICredentials, toCredentialKwargs } from './types' + +@Injectable() +export class XAIProvider extends ModelProvider { + constructor() { + super('x') + } + + getBaseUrl(credentials: XAICredentials): string { + // const kwags = toCredentialKwargs(credentials) + return `https://api.x.ai` + } + + getAuthorization(credentials: XAICredentials): string { + const kwags = toCredentialKwargs(credentials) + return `Bearer ${kwags.apiKey}` + } + + async validateProviderCredentials(credentials: XAICredentials): Promise { + try { + const modelInstance = this.getModelManager(AiModelTypeEnum.LLM) + + await modelInstance.validateCredentials('grok-beta', credentials) + } catch (ex) { + if (ex instanceof CredentialsValidateFailedError) { + throw ex + } else { + this.logger.error(`${this.getProviderSchema().provider}: credentials verification failed`, ex.stack) + throw ex + } + } + } +} + +@Module({ + providers: [ + XAIProvider, + { + provide: ModelProvider, + useExisting: XAIProvider + }, + XAILargeLanguageModel + ], + exports: [XAIProvider] +}) +export class XAIProviderModule {} diff --git a/packages/server-ai/src/ai-model/model_providers/x/x.yaml b/packages/server-ai/src/ai-model/model_providers/x/x.yaml new file mode 100644 index 000000000..90d1cbfe7 --- /dev/null +++ b/packages/server-ai/src/ai-model/model_providers/x/x.yaml @@ -0,0 +1,38 @@ +provider: x +label: + en_US: xAI +description: + en_US: xAI is a company working on building artificial intelligence to accelerate human scientific discovery. We are guided by our mission to advance our collective understanding of the universe. +icon_small: + en_US: x-ai-logo.svg +icon_large: + en_US: x-ai-logo.svg +help: + title: + en_US: Get your token from xAI + zh_Hans: 从 xAI 获取 token + url: + en_US: https://x.ai/api +supported_model_types: + - llm +configurate_methods: + - predefined-model +provider_credential_schema: + credential_form_schemas: + - variable: api_key + label: + en_US: API Key + type: secret-input + required: true + placeholder: + zh_Hans: 在此输入您的 API Key + en_US: Enter your API Key + - variable: endpoint_url + label: + en_US: API Base + type: text-input + required: false + default: https://api.x.ai/v1 + placeholder: + zh_Hans: 在此输入您的 API Base + en_US: Enter your API Base diff --git a/yarn.lock b/yarn.lock index 7c3ad1558..b0902ae9f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5037,6 +5037,13 @@ dependencies: js-tiktoken "^1.0.12" +"@langchain/xai@0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@langchain/xai/-/xai-0.0.1.tgz#e9dfbae3e64f3bfab2c5236b311f090f71a0cace" + integrity sha512-F1/btq7+DzvyBFsCsShkt1MVUXIo52b4f6Ti2Eea0o/Oth/D2jfpnQmZLZ4rZHSGjxI0bRkS5zLyYveTbr+7yA== + dependencies: + "@langchain/openai" "~0.3.0" + "@larksuiteoapi/node-sdk@^1.30.0": version "1.33.0" resolved "https://registry.yarnpkg.com/@larksuiteoapi/node-sdk/-/node-sdk-1.33.0.tgz#33d22ac10a11171d5161e9e1bd82a248c56b2540" From 4e21f87f85c31d4d290ec3dd09f15adca502babb Mon Sep 17 00:00:00 2001 From: meta-d Date: Tue, 24 Dec 2024 15:05:32 +0800 Subject: [PATCH 2/8] fix: xmla error catch logic --- packages/adapter/src/adapters/xmla.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/adapter/src/adapters/xmla.ts b/packages/adapter/src/adapters/xmla.ts index 748b66c08..ea2bd285d 100644 --- a/packages/adapter/src/adapters/xmla.ts +++ b/packages/adapter/src/adapters/xmla.ts @@ -121,7 +121,7 @@ export class XMLA extends BaseHTTPQueryRunner { return response } catch (error: any) { - if (error.response.status === 401) { + if (error.response?.status === 401) { // If unauthorized, re-authenticate // Retry the request with auth response = await this.post(query, { From db26e8087147ee1545366fc7dc4789fc1de92d4f Mon Sep 17 00:00:00 2001 From: meta-d Date: Tue, 24 Dec 2024 15:55:35 +0800 Subject: [PATCH 3/8] fix: command tool message event --- .../studio/services/execution.service.ts | 2 +- .../auth/lark-token.strategy.ts | 45 +++++++++++-------- .../commands/handlers/chat-xpert.handler.ts | 3 ++ .../commands/handlers/execute.handler.ts | 15 +++++-- 4 files changed, 42 insertions(+), 23 deletions(-) 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 389ae5176..5454acf67 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 @@ -136,7 +136,7 @@ export class XpertExecutionService { this.toolExecutions.update((state) => { return Object.keys(state).reduce((acc, name) => { - acc[name] = Object.keys(acc[name]).reduce((executions, id) => { + acc[name] = Object.keys(acc[name] ?? {}).reduce((executions, id) => { executions[id] = acc[name][id].status === XpertAgentExecutionStatusEnum.RUNNING ? { ...acc[name][id], status: XpertAgentExecutionStatusEnum.ERROR, diff --git a/packages/server-ai/src/integration-lark/auth/lark-token.strategy.ts b/packages/server-ai/src/integration-lark/auth/lark-token.strategy.ts index 1bec84403..3ec092fb3 100644 --- a/packages/server-ai/src/integration-lark/auth/lark-token.strategy.ts +++ b/packages/server-ai/src/integration-lark/auth/lark-token.strategy.ts @@ -22,30 +22,37 @@ export class LarkTokenStrategy extends PassportStrategy(Strategy, 'lark-token') ;(async () => { try { const integration = await this.integrationService.findOne(integrationId, { relations: ['tenant'] }) + req.headers['organization-id'] = integration.organizationId + const integrationClient = this.larkService.getOrCreateLarkClient(integration) - let union_id = null - switch (data.header?.event_type) { - case 'card.action.trigger': { - union_id = data.event.operator?.union_id - break + console.log(data) + if (data.type === 'url_verification') { + this.success({}) + } else { + let union_id = null + switch (data.header?.event_type) { + case 'card.action.trigger': { + union_id = data.event.operator?.union_id + break + } + case 'im.message.receive_v1': { + union_id = data.event.sender?.sender_id.union_id + break + } } - case 'im.message.receive_v1': { - union_id = data.event.sender?.sender_id.union_id - break + + if (!union_id) { + throw new Error(`Can't get union_id from event of lark message`) } - } - if (!union_id) { - throw new Error(`Can't get union_id from event of lark message`) + const user = await this.larkService.getUser( + integrationClient.client, + integration.tenantId, + union_id + ) + this.success(user) } - const user = await this.larkService.getUser( - integrationClient.client, - integration.tenantId, - union_id - ) - - req.headers['organization-id'] = integration.organizationId - this.success(user) + } catch (err) { console.error(err, integrationId, data) this.fail(new UnauthorizedException('Invalid user', err.message)) 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 b31124430..b53803aac 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 @@ -35,6 +35,9 @@ export class LarkChatXpertHandler implements ICommandHandler { 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 f4ffec16c..9e01df633 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,9 +1,9 @@ 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, LangGraphRunnableConfig, NodeInterrupt } from '@langchain/langgraph' +import { Annotation, CompiledStateGraph, isCommand, LangGraphRunnableConfig, NodeInterrupt } from '@langchain/langgraph' import { agentLabel, ChatMessageEventTypeEnum, ChatMessageTypeEnum, convertToUrlPath, IXpert, IXpertAgent, ToolCall, TSensitiveOperation, TStateVariable, XpertAgentExecutionStatusEnum } from '@metad/contracts' import { AgentRecursionLimit, isNil } from '@metad/copilot' import { RequestContext } from '@metad/server-core' @@ -334,12 +334,21 @@ export class XpertAgentExecuteHandler implements ICommandHandleroutput.update)?.messages + if (Array.isArray(messages)) { + const toolMessage = messages[messages.length - 1] + output = toolMessage + } + } subscriber.next({ data: { type: ChatMessageTypeEnum.EVENT, event: ChatMessageEventTypeEnum.ON_TOOL_END, data: { - data, + data: {...data, output}, tags, ...rest, } From 5d1c55b3c65db1a0fa59563daae1a338090b1092 Mon Sep 17 00:00:00 2001 From: meta-d Date: Tue, 24 Dec 2024 21:21:54 +0800 Subject: [PATCH 4/8] fix: production env --- apps/api/src/plugin-config.ts | 32 +++++++++---------- docker/.env.example | 1 + packages/analytics/src/core/features.ts | 4 +-- packages/config/src/config.service.ts | 5 ++- .../src/environments/environment.prod.ts | 4 +-- .../config/src/environments/environment.ts | 4 +-- packages/config/src/environments/index.ts | 5 +++ packages/config/src/index.ts | 2 +- packages/server-ai/src/core/features.ts | 4 +-- .../server/src/feature/default-features.ts | 4 +-- 10 files changed, 37 insertions(+), 28 deletions(-) create mode 100644 packages/config/src/environments/index.ts diff --git a/apps/api/src/plugin-config.ts b/apps/api/src/plugin-config.ts index f6bc4c148..a6ccd374a 100644 --- a/apps/api/src/plugin-config.ts +++ b/apps/api/src/plugin-config.ts @@ -82,22 +82,22 @@ function getDbConfig(): ConnectionOptions { }; } - case 'sqlite': { - const sqlitePath = - process.env.DB_PATH || - path.join( - path.resolve('.', ...['apps', 'api', 'data']), - 'gauzy.sqlite3' - ); + // case 'sqlite': { + // const sqlitePath = + // process.env.DB_PATH || + // path.join( + // path.resolve('.', ...['apps', 'api', 'data']), + // 'xxxx.sqlite3' + // ); - return { - type: dbType, - database: sqlitePath, - logging: true, - // Removes console logging, instead logs all queries in a file ormlogs.log - logger: 'file', - synchronize: true - }; - } + // return { + // type: dbType, + // database: sqlitePath, + // logging: true, + // // Removes console logging, instead logs all queries in a file ormlogs.log + // logger: 'file', + // synchronize: true + // }; + // } } } diff --git a/docker/.env.example b/docker/.env.example index 58be80928..9a022173b 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -10,6 +10,7 @@ INSTALLATION_MODE=standalone ALLOW_SUPER_ADMIN_ROLE=true # set to API base URL +# WEB_PORT=80 API_BASE_URL=//localhost:3000 CLIENT_BASE_URL=//localhost diff --git a/packages/analytics/src/core/features.ts b/packages/analytics/src/core/features.ts index f0466cbd5..bcc8762dd 100644 --- a/packages/analytics/src/core/features.ts +++ b/packages/analytics/src/core/features.ts @@ -1,7 +1,7 @@ import { AnalyticsFeatures, FeatureEnum, AiFeatureEnum, IFeatureCreateInput } from '@metad/contracts' -import { pacToggleFeatures } from '@metad/server-config' +import { toggleFeatures } from '@metad/server-config' -const features = pacToggleFeatures +const features = toggleFeatures export const DEFAULT_FEATURES: Partial[] = [ { diff --git a/packages/config/src/config.service.ts b/packages/config/src/config.service.ts index 4815e842e..9708603b7 100644 --- a/packages/config/src/config.service.ts +++ b/packages/config/src/config.service.ts @@ -1,9 +1,12 @@ +import "dotenv/config"; + import { DynamicModule, Injectable, Type, Logger } from '@nestjs/common'; import { IPluginConfig, IApiServerOptions, IAssetOptions } from '@metad/server-common'; import { TypeOrmModuleOptions } from '@nestjs/typeorm'; import { getConfig } from './config-manager'; -import { environment } from './environments/environment'; import { IEnvironment } from './environments/ienvironment'; +import { environment } from "./environments"; + @Injectable() export class ConfigService { diff --git a/packages/config/src/environments/environment.prod.ts b/packages/config/src/environments/environment.prod.ts index 6e71fef78..aa47eb621 100644 --- a/packages/config/src/environments/environment.prod.ts +++ b/packages/config/src/environments/environment.prod.ts @@ -5,7 +5,7 @@ import { IEnvironment, IPACFeatures } from './ienvironment'; const API_BASE_URL = process.env.API_BASE_URL || 'http://localhost:3000' -export const environment: IEnvironment = { +export const prodEnvironment: IEnvironment = { port: process.env.PORT || 3000, host: process.env.HOST || 'http://localhost', baseUrl: API_BASE_URL, @@ -188,7 +188,7 @@ export const environment: IEnvironment = { demo: process.env.DEMO === 'true' ? true : false }; -export const pacToggleFeatures: IPACFeatures = { +export const prodToggleFeatures: IPACFeatures = { FEATURE_DASHBOARD: process.env.FEATURE_DASHBOARD === 'false' ? false : true, FEATURE_TIME_TRACKING: process.env.FEATURE_TIME_TRACKING === 'false' ? false : true, diff --git a/packages/config/src/environments/environment.ts b/packages/config/src/environments/environment.ts index b19fa4f25..99558768f 100644 --- a/packages/config/src/environments/environment.ts +++ b/packages/config/src/environments/environment.ts @@ -9,7 +9,7 @@ import { IEnvironment, IPACFeatures } from './ienvironment'; const API_BASE_URL = process.env.API_BASE_URL || 'http://localhost:3000' -export const environment: IEnvironment = { +export const devEnvironment: IEnvironment = { port: process.env.PORT || 3000, host: process.env.HOST || 'http://localhost', baseUrl: API_BASE_URL, @@ -192,7 +192,7 @@ export const environment: IEnvironment = { demo: process.env.DEMO === 'true' ? true : false }; -export const pacToggleFeatures: IPACFeatures = { +export const devToggleFeatures: IPACFeatures = { FEATURE_DASHBOARD: process.env.FEATURE_DASHBOARD === 'false' ? false : true, FEATURE_TIME_TRACKING: process.env.FEATURE_TIME_TRACKING === 'false' ? false : true, diff --git a/packages/config/src/environments/index.ts b/packages/config/src/environments/index.ts new file mode 100644 index 000000000..f134f6c9a --- /dev/null +++ b/packages/config/src/environments/index.ts @@ -0,0 +1,5 @@ +import { devEnvironment, devToggleFeatures } from "./environment"; +import { prodEnvironment, prodToggleFeatures } from "./environment.prod"; + +export const environment = process.env.NODE_ENV === 'production' ? prodEnvironment : devEnvironment +export const toggleFeatures = process.env.NODE_ENV === 'production' ? prodToggleFeatures : devToggleFeatures diff --git a/packages/config/src/index.ts b/packages/config/src/index.ts index f57d4524b..326bbc71f 100644 --- a/packages/config/src/index.ts +++ b/packages/config/src/index.ts @@ -5,6 +5,6 @@ export * from './config-manager'; export * from './config.module'; export * from './config.service'; -export { environment, pacToggleFeatures } from './environments/environment'; +export { environment, toggleFeatures } from './environments/'; export * from './environments/ienvironment'; export * from './database-helpers' \ No newline at end of file diff --git a/packages/server-ai/src/core/features.ts b/packages/server-ai/src/core/features.ts index 661fe58e7..5e7fa431e 100644 --- a/packages/server-ai/src/core/features.ts +++ b/packages/server-ai/src/core/features.ts @@ -1,7 +1,7 @@ import { AiFeatureEnum, IFeatureCreateInput } from '@metad/contracts' -import { pacToggleFeatures } from '@metad/server-config' +import { toggleFeatures } from '@metad/server-config' -const features = pacToggleFeatures +const features = toggleFeatures export const DEFAULT_FEATURES: Partial[] = [ { diff --git a/packages/server/src/feature/default-features.ts b/packages/server/src/feature/default-features.ts index 7d6377630..543f3a317 100644 --- a/packages/server/src/feature/default-features.ts +++ b/packages/server/src/feature/default-features.ts @@ -1,7 +1,7 @@ -import { pacToggleFeatures } from '@metad/server-config'; +import { toggleFeatures } from '@metad/server-config'; import { FeatureEnum, IFeatureCreateInput } from '@metad/contracts'; -const features = pacToggleFeatures; +const features = toggleFeatures; export let DEFAULT_FEATURES: IFeatureCreateInput[] = [ { From 33439a94137183cf16271d55a0021d521e497963 Mon Sep 17 00:00:00 2001 From: meta-d Date: Wed, 25 Dec 2024 00:55:41 +0800 Subject: [PATCH 5/8] feat: xpert publish to lark --- .../src/app/@core/services/xpert.service.ts | 8 +- .../avatar/emoji-avatar/avatar.component.ts | 10 +- .../src/app/@shared/integration/index.ts | 3 +- .../integration.component.html | 42 +++++ .../integration.component.scss | 3 + .../integration-form/integration.component.ts | 142 ++++++++++++++ apps/cloud/src/app/@shared/xpert/index.ts | 3 +- .../xpert/publish/publish.component.html | 94 ++++++++++ .../xpert/publish/publish.component.scss | 7 + .../xpert/publish/publish.component.ts | 177 ++++++++++++++++++ .../integration/integration.component.ts | 2 + .../features/memory/memory.component.ts | 2 - .../xpert/studio/header/header.component.html | 22 ++- .../xpert/studio/header/header.component.ts | 12 +- apps/cloud/src/assets/i18n/zh-Hans.json | 14 +- .../src/assets/images/destinations/lark.png | Bin 0 -> 6500 bytes .../src/assets/images/destinations/wecom.png | Bin 0 -> 2098 bytes .../src/assets/images/illustrations/pro.svg | 4 + apps/cloud/src/styles/components/index.scss | 3 +- apps/cloud/src/styles/components/pro.scss | 17 ++ .../common/radio-select/select.component.html | 4 +- .../common/radio-select/select.component.ts | 9 +- packages/contracts/src/ai/xpert.model.ts | 7 +- packages/contracts/src/integration.model.ts | 15 +- .../contracts/src/integration/dingtalk.ts | 11 ++ packages/contracts/src/integration/index.ts | 6 +- packages/contracts/src/integration/lark.ts | 4 +- packages/contracts/src/integration/wecom.ts | 11 ++ .../xpert/commands/del-integration.command.ts | 10 + .../handlers/del-integration.handler.ts | 25 +++ .../src/xpert/commands/handlers/index.ts | 6 +- .../handlers/publish-integration.handler.ts | 31 +++ .../server-ai/src/xpert/commands/index.ts | 4 +- .../commands/publish-integration.command.ts | 11 ++ .../server-ai/src/xpert/xpert.controller.ts | 14 +- packages/server-ai/src/xpert/xpert.entity.ts | 13 +- .../integration/commands/delete.command.ts | 7 + .../commands/handlers/delete.handler.ts | 16 ++ .../integration/commands/handlers/index.ts | 7 + .../commands/handlers/upsert.handler.ts | 23 +++ .../server/src/integration/commands/index.ts | 2 + .../integration/commands/upsert.command.ts | 8 + packages/server/src/integration/index.ts | 3 +- .../src/integration/integration.module.ts | 3 +- 44 files changed, 784 insertions(+), 31 deletions(-) create mode 100644 apps/cloud/src/app/@shared/integration/integration-form/integration.component.html create mode 100644 apps/cloud/src/app/@shared/integration/integration-form/integration.component.scss create mode 100644 apps/cloud/src/app/@shared/integration/integration-form/integration.component.ts create mode 100644 apps/cloud/src/app/@shared/xpert/publish/publish.component.html create mode 100644 apps/cloud/src/app/@shared/xpert/publish/publish.component.scss create mode 100644 apps/cloud/src/app/@shared/xpert/publish/publish.component.ts create mode 100644 apps/cloud/src/assets/images/destinations/lark.png create mode 100644 apps/cloud/src/assets/images/destinations/wecom.png create mode 100644 apps/cloud/src/assets/images/illustrations/pro.svg create mode 100644 apps/cloud/src/styles/components/pro.scss create mode 100644 packages/contracts/src/integration/dingtalk.ts create mode 100644 packages/contracts/src/integration/wecom.ts create mode 100644 packages/server-ai/src/xpert/commands/del-integration.command.ts create mode 100644 packages/server-ai/src/xpert/commands/handlers/del-integration.handler.ts create mode 100644 packages/server-ai/src/xpert/commands/handlers/publish-integration.handler.ts create mode 100644 packages/server-ai/src/xpert/commands/publish-integration.command.ts create mode 100644 packages/server/src/integration/commands/delete.command.ts create mode 100644 packages/server/src/integration/commands/handlers/delete.handler.ts create mode 100644 packages/server/src/integration/commands/handlers/index.ts create mode 100644 packages/server/src/integration/commands/handlers/upsert.handler.ts create mode 100644 packages/server/src/integration/commands/index.ts create mode 100644 packages/server/src/integration/commands/upsert.command.ts diff --git a/apps/cloud/src/app/@core/services/xpert.service.ts b/apps/cloud/src/app/@core/services/xpert.service.ts index 0cb91c5a3..64ad410d1 100644 --- a/apps/cloud/src/app/@core/services/xpert.service.ts +++ b/apps/cloud/src/app/@core/services/xpert.service.ts @@ -4,7 +4,7 @@ import { toParams } from '@metad/ocap-angular/core' import { NGXLogger } from 'ngx-logger' import { BehaviorSubject, tap } from 'rxjs' import { API_XPERT_ROLE } from '../constants/app.constants' -import { ICopilotStore, IUser, IXpert, IXpertAgentExecution, OrderTypeEnum, TChatRequest, TDeleteResult, TXpertTeamDraft, XpertTypeEnum } from '../types' +import { ICopilotStore, IIntegration, IUser, IXpert, IXpertAgentExecution, OrderTypeEnum, TChatRequest, TDeleteResult, TXpertTeamDraft, XpertTypeEnum } from '../types' import { XpertWorkspaceBaseCrudService } from './xpert-workspace.service' import { injectApiBaseUrl } from '../providers' import { injectFetchEventSource } from './fetch-event-source' @@ -57,6 +57,12 @@ export class XpertService extends XpertWorkspaceBaseCrudService { publish(id: string) { return this.httpClient.post(this.apiBaseUrl + `/${id}/publish`, {}) } + publishIntegration(id: string, integration: Partial) { + return this.httpClient.post(this.apiBaseUrl + `/${id}/publish/integration`, integration) + } + removeIntegration(xpertId: string, id: string) { + return this.httpClient.delete(this.apiBaseUrl + `/${xpertId}/publish/integration/${id}`,) + } validateTitle(title: string) { return this.httpClient.get(this.apiBaseUrl + `/validate`, { diff --git a/apps/cloud/src/app/@shared/avatar/emoji-avatar/avatar.component.ts b/apps/cloud/src/app/@shared/avatar/emoji-avatar/avatar.component.ts index 7d2d59178..2d2f70a55 100644 --- a/apps/cloud/src/app/@shared/avatar/emoji-avatar/avatar.component.ts +++ b/apps/cloud/src/app/@shared/avatar/emoji-avatar/avatar.component.ts @@ -1,5 +1,5 @@ import { CommonModule } from '@angular/common' -import { booleanAttribute, Component, computed, HostListener, HostBinding, inject, input, model } from '@angular/core' +import { booleanAttribute, Component, computed, HostListener, HostBinding, inject, input, model, effect } from '@angular/core' import { MatDialog } from '@angular/material/dialog' import { EmojiComponent } from '@ctrl/ngx-emoji-mart/ngx-emoji' import { NgxControlValueAccessor } from 'ngxtension/control-value-accessor' @@ -70,6 +70,14 @@ export class EmojiAvatarComponent { @HostBinding('class.focused') focused = false; + constructor() { + effect(() => { + if (this.cva.value$()) { + this.avatar.set(this.cva.value$()) + } + }, { allowSignalWrites: true }) + } + @HostListener('click') onClick() { if (this.editable()) { diff --git a/apps/cloud/src/app/@shared/integration/index.ts b/apps/cloud/src/app/@shared/integration/index.ts index d77506cb1..ff97b1dc9 100644 --- a/apps/cloud/src/app/@shared/integration/index.ts +++ b/apps/cloud/src/app/@shared/integration/index.ts @@ -1 +1,2 @@ -export * from './integration-list/list.component' \ No newline at end of file +export * from './integration-list/list.component' +export * from './integration-form/integration.component' \ No newline at end of file diff --git a/apps/cloud/src/app/@shared/integration/integration-form/integration.component.html b/apps/cloud/src/app/@shared/integration/integration-form/integration.component.html new file mode 100644 index 000000000..0e72bcc90 --- /dev/null +++ b/apps/cloud/src/app/@shared/integration/integration-form/integration.component.html @@ -0,0 +1,42 @@ +
+ + + + +
+ + +
+ + @if (schema(); as fields) { + + } @else if (integrationProvider()) { + + } + + @if(webhookUrl()) { +
+

{{ 'PAC.Integration.WebhookUrl' | translate: {Default: 'Webhook url'} }}:

+
{{webhookUrl()}}
+
+ } + +
+ +
+ + +@if (loading()) { + +} \ No newline at end of file diff --git a/apps/cloud/src/app/@shared/integration/integration-form/integration.component.scss b/apps/cloud/src/app/@shared/integration/integration-form/integration.component.scss new file mode 100644 index 000000000..719142dd4 --- /dev/null +++ b/apps/cloud/src/app/@shared/integration/integration-form/integration.component.scss @@ -0,0 +1,3 @@ +:host { + @apply relative; +} \ No newline at end of file diff --git a/apps/cloud/src/app/@shared/integration/integration-form/integration.component.ts b/apps/cloud/src/app/@shared/integration/integration-form/integration.component.ts new file mode 100644 index 000000000..6b9860ebf --- /dev/null +++ b/apps/cloud/src/app/@shared/integration/integration-form/integration.component.ts @@ -0,0 +1,142 @@ +import { CdkListboxModule } from '@angular/cdk/listbox' +import { TextFieldModule } from '@angular/cdk/text-field' +import { CommonModule } from '@angular/common' +import { Component, computed, effect, inject, model, signal } from '@angular/core' +import { toSignal } from '@angular/core/rxjs-interop' +import { FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms' +import { MatInputModule } from '@angular/material/input' +import { NgmInputComponent, NgmSpinComponent } from '@metad/ocap-angular/common' +import { NgmI18nPipe } from '@metad/ocap-angular/core' +import { ContentLoaderModule } from '@ngneat/content-loader' +import { FormlyModule } from '@ngx-formly/core' +import { TranslateModule } from '@ngx-translate/core' +import { assign, omit } from 'lodash-es' +import { map, startWith } from 'rxjs' +import { injectApiBaseUrl, injectToastr, IntegrationService, toFormlySchema } from '../../../@core' +import { getErrorMessage, IIntegration, INTEGRATION_PROVIDERS } from '../../../@core/types' +import { EmojiAvatarComponent } from '../../avatar' + +@Component({ + standalone: true, + imports: [ + CommonModule, + FormsModule, + ReactiveFormsModule, + CdkListboxModule, + TextFieldModule, + TranslateModule, + ContentLoaderModule, + FormlyModule, + MatInputModule, + EmojiAvatarComponent, + NgmInputComponent, + NgmSpinComponent + ], + selector: 'pac-integration-form', + templateUrl: 'integration.component.html', + styleUrls: ['integration.component.scss'] +}) +export class IntegrationFormComponent { + readonly integrationService = inject(IntegrationService) + readonly #toastr = injectToastr() + readonly apiBaseUrl = injectApiBaseUrl() + readonly i18n = new NgmI18nPipe() + + readonly integration = model() + + readonly formGroup = new FormGroup({ + id: new FormControl(null), + name: new FormControl(null, [Validators.required]), + avatar: new FormControl(null), + description: new FormControl(null), + slug: new FormControl(null), + provider: new FormControl(null), + options: new FormGroup({}) + }) + + get optionsForm() { + return this.formGroup.get('options') as FormGroup + } + + optionsModel = {} + formOptions = {} + + readonly providers = signal( + Object.keys(INTEGRATION_PROVIDERS).map((name) => ({ + key: name, + caption: this.i18n.transform(INTEGRATION_PROVIDERS[name].label) + })) + ) + readonly provider = this.formGroup.get('provider') + readonly integrationProvider = toSignal( + this.provider.valueChanges.pipe( + startWith(this.provider.value), + map((provider) => INTEGRATION_PROVIDERS[provider]) + ) + ) + + readonly schema = computed(() => { + const schema = this.integrationProvider()?.schema + return schema + ? toFormlySchema( + { + ...schema, + properties: omit(schema.properties, 'xpertId') + }, + this.i18n + ) + : null + }) + + readonly webhookUrl = computed(() => + this.integration() ? this.integrationProvider()?.webhookUrl(this.integration(), this.apiBaseUrl) : null + ) + + readonly loading = signal(false) + + constructor() { + effect( + () => { + if (this.integration()) { + this.formGroup.patchValue(this.integration()) + assign(this.optionsModel, this.integration().options) + if (this.integration().id) { + this.formGroup.markAsPristine() + } else { + this.formGroup.markAsDirty() + } + } + }, + { allowSignalWrites: true } + ) + } + + compareId(a: IIntegration, b: IIntegration): boolean { + return a?.id === b?.id + } + + getProvider(integration?: IIntegration) { + return INTEGRATION_PROVIDERS[integration.name] + } + + onModelChange(model) { + console.log(model) + this.integration.set(model) + } + + test() { + this.loading.set(true) + this.integrationService.test({ ...this.formGroup.value }).subscribe({ + next: (result) => { + this.formGroup.patchValue(result) + this.formGroup.markAsDirty() + this.loading.set(false) + this.#toastr.success('PAC.Messages.Successfully', { Default: 'Successfully!' }) + }, + error: (error) => { + this.#toastr.danger(getErrorMessage(error)) + this.loading.set(false) + } + }) + } +} diff --git a/apps/cloud/src/app/@shared/xpert/index.ts b/apps/cloud/src/app/@shared/xpert/index.ts index 357a6a5d5..a20fe847e 100644 --- a/apps/cloud/src/app/@shared/xpert/index.ts +++ b/apps/cloud/src/app/@shared/xpert/index.ts @@ -11,4 +11,5 @@ export * from './tool-name-input/input.component' export * from './xpert-card/xpert-card.component' export * from './execution-status/execution.component' export * from './execution-accordion/execution.component' -export * from './tool-call-confirm/confirm.component' \ No newline at end of file +export * from './tool-call-confirm/confirm.component' +export * from './publish/publish.component' \ No newline at end of file diff --git a/apps/cloud/src/app/@shared/xpert/publish/publish.component.html b/apps/cloud/src/app/@shared/xpert/publish/publish.component.html new file mode 100644 index 000000000..1eeabbf34 --- /dev/null +++ b/apps/cloud/src/app/@shared/xpert/publish/publish.component.html @@ -0,0 +1,94 @@ +
+
+ + {{ 'PAC.Xpert.PublishThirdPlatforms' | translate: { Default: 'Publish to third-party platforms' } }} + +
+ +
    + @for (item of integrations(); track item.id) { +
  • + + {{ item.name }} +
  • + } +
+ +
+ +
+
+ + +
+ @for (provider of providers(); track provider.name) { +
+ + {{ provider.label | i18n }} + + @if (provider.pro) { + + } +
+ } +
+
+ +
+
+
+
{{ 'PAC.Xpert.PublishTo' | translate: { Default: 'Publish to ' } }} {{integration()?.provider || '?'}}
+
+
+ +
+
+
+
+ + @for (integration of selectedIntegrations(); track integration) { + + +
+ + @if (integration.id) { + + } + + + +
+ + + +
+
+ } + + @if (loading()) { + + } +
\ No newline at end of file diff --git a/apps/cloud/src/app/@shared/xpert/publish/publish.component.scss b/apps/cloud/src/app/@shared/xpert/publish/publish.component.scss new file mode 100644 index 000000000..793ffae09 --- /dev/null +++ b/apps/cloud/src/app/@shared/xpert/publish/publish.component.scss @@ -0,0 +1,7 @@ +:host { + @apply flex h-[80vh] rounded-2xl overflow-hidden shadow-xl bg-components-card-bg; +} + +.integration.active { + @apply bg-white/30; +} \ No newline at end of file diff --git a/apps/cloud/src/app/@shared/xpert/publish/publish.component.ts b/apps/cloud/src/app/@shared/xpert/publish/publish.component.ts new file mode 100644 index 000000000..1401cc97c --- /dev/null +++ b/apps/cloud/src/app/@shared/xpert/publish/publish.component.ts @@ -0,0 +1,177 @@ +import { Dialog, DIALOG_DATA, DialogRef } from '@angular/cdk/dialog' +import { DragDropModule } from '@angular/cdk/drag-drop' +import { CdkMenuModule } from '@angular/cdk/menu' +import { TextFieldModule } from '@angular/cdk/text-field' +import { CommonModule } from '@angular/common' +import { ChangeDetectionStrategy, Component, effect, inject, model, signal } from '@angular/core' +import { FormsModule, ReactiveFormsModule } from '@angular/forms' +import { MatTooltipModule } from '@angular/material/tooltip' +import { CdkConfirmDeleteComponent, NgmSpinComponent } from '@metad/ocap-angular/common' +import { NgmI18nPipe } from '@metad/ocap-angular/core' +import { TranslateModule } from '@ngx-translate/core' +import { derivedAsync } from 'ngxtension/derived-async' +import { EMPTY, of, switchMap } from 'rxjs' +import { + getErrorMessage, + IIntegration, + injectToastr, + INTEGRATION_PROVIDERS, + IntegrationEnum, + IntegrationService, + IXpert, + TIntegrationProvider, + XpertService +} from '../../../@core' +import { EmojiAvatarComponent } from '../../avatar' +import { IntegrationFormComponent } from '../../integration' + +@Component({ + standalone: true, + selector: 'xpert-publish-dialog', + templateUrl: './publish.component.html', + styleUrls: ['publish.component.scss'], + imports: [ + CommonModule, + FormsModule, + ReactiveFormsModule, + DragDropModule, + TextFieldModule, + CdkMenuModule, + TranslateModule, + MatTooltipModule, + NgmSpinComponent, + EmojiAvatarComponent, + IntegrationFormComponent, + NgmI18nPipe + ], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class XpertPublishComponent { + eIntegrationEnum = IntegrationEnum + readonly #dialogRef = inject(DialogRef) + readonly #dialog = inject(Dialog) + readonly #data = inject<{ xpert: IXpert }>(DIALOG_DATA) + readonly #toastr = injectToastr() + readonly integrationService = inject(IntegrationService) + readonly xpertService = inject(XpertService) + readonly i18n = new NgmI18nPipe() + + readonly xpertId = signal(this.#data.xpert.id) + + readonly loading = signal(false) + + readonly providers = signal(Object.keys(INTEGRATION_PROVIDERS).map((name) => INTEGRATION_PROVIDERS[name])) + + readonly xpert = derivedAsync(() => { + return this.xpertId() ? this.xpertService.getById(this.xpertId(), { relations: ['integrations'] }) : of(null) + }) + readonly integrations = signal([]) + + readonly selectedIntegrations = signal([]) + readonly integration = model() + + constructor() { + effect( + () => { + if (this.xpert()) { + this.integrations.set(this.xpert().integrations) + } + }, + { allowSignalWrites: true } + ) + + effect( + () => { + if (this.selectedIntegrations()) { + this.integration.set(this.selectedIntegrations()[0]) + } + }, + { allowSignalWrites: true } + ) + } + + onStart(statement: string): void { + this.#dialogRef.close(statement) + } + + close() { + this.#dialogRef.close() + } + + selectIntegration(integration: IIntegration) { + this.selectedIntegrations.set([integration]) + } + + addIntegration(provider: TIntegrationProvider) { + if (!provider.pro) { + const integration = { + provider: provider.name, + name: this.#data.xpert.name, + description: this.#data.xpert.description, + avatar: this.#data.xpert.avatar + } as IIntegration + + this.selectedIntegrations.set([integration]) + this.integrations.update((state) => [...state, integration]) + } + } + + updateIntegration(integration: IIntegration) { + this.selectedIntegrations.set([integration]) + } + + cancel() { + this.selectedIntegrations.set([]) + } + + save(value: Partial) { + this.loading.set(true) + this.xpertService + .publishIntegration(this.#data.xpert.id, { + ...value, + options: { + ...value.options, + xpertId: this.#data.xpert.id + } + }) + .subscribe({ + next: (response) => { + this.#toastr.success('PAC.Xpert.XpertPublished', { Default: 'Xpert published successfully!' }) + this.loading.set(false) + this.selectedIntegrations.set([response]) + }, + error: (error) => { + this.#toastr.danger(getErrorMessage(error)) + this.loading.set(false) + } + }) + } + + remove(integration: IIntegration) { + this.#dialog + .open(CdkConfirmDeleteComponent, { + data: { + value: integration.name, + information: integration.description + } + }) + .closed.pipe(switchMap((confirm) => (confirm ? this.delete(integration.id) : EMPTY))) + .subscribe({ + next: () => { + this.#toastr.success('PAC.Xpert.XpertPublishDeleted', { Default: 'Xpert publish deleted!' }) + this.loading.set(false) + this.selectedIntegrations.set([]) + this.integrations.update((state) => state.filter((_) => _.id !== integration.id)) + }, + error: (error) => { + this.#toastr.danger(getErrorMessage(error)) + this.loading.set(false) + } + }) + } + + delete(id: string) { + this.loading.set(true) + return this.xpertService.removeIntegration(this.#data.xpert.id, id) + } +} diff --git a/apps/cloud/src/app/features/setting/integration/integration/integration.component.ts b/apps/cloud/src/app/features/setting/integration/integration/integration.component.ts index c3461f66b..2cd92cb03 100644 --- a/apps/cloud/src/app/features/setting/integration/integration/integration.component.ts +++ b/apps/cloud/src/app/features/setting/integration/integration/integration.component.ts @@ -30,6 +30,7 @@ import { ToastrService, toFormlySchema } from '../../../../@core' +import { TextFieldModule } from '@angular/cdk/text-field' @Component({ standalone: true, @@ -42,6 +43,7 @@ import { FormsModule, ReactiveFormsModule, FormlyModule, + TextFieldModule, MatTooltipModule, MatIconModule, MatInputModule, diff --git a/apps/cloud/src/app/features/xpert/studio/features/memory/memory.component.ts b/apps/cloud/src/app/features/xpert/studio/features/memory/memory.component.ts index e910b3e5f..ccb7aa4a5 100644 --- a/apps/cloud/src/app/features/xpert/studio/features/memory/memory.component.ts +++ b/apps/cloud/src/app/features/xpert/studio/features/memory/memory.component.ts @@ -6,7 +6,6 @@ import { MatSliderModule } from '@angular/material/slider' import { MatTooltipModule } from '@angular/material/tooltip' import { LongTermMemoryTypeEnum, TLongTermMemory, TLongTermMemoryConfig } from '@metad/contracts' import { IfAnimations, OverlayAnimations } from '@metad/core' -import { NgmRadioSelectComponent } from '@metad/ocap-angular/common' import { TranslateModule } from '@ngx-translate/core' import { CopilotPromptEditorComponent } from '../../../../../@shared/copilot' import { XpertStudioApiService } from '../../domain' @@ -26,7 +25,6 @@ import { NgmTooltipDirective } from '@metad/ocap-angular/core' MatSliderModule, MatTooltipModule, MatCheckboxModule, - NgmRadioSelectComponent, NgmTooltipDirective, CopilotPromptEditorComponent, 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 9654f7838..0c13baa75 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 @@ -171,7 +171,7 @@ {{ 'PAC.Xpert.Features' | translate: {Default: 'Features'} }} - + +
+
+
+ + + + + +
+ {{ 'PAC.Xpert.PublishToLarketc' | translate: {Default: 'Publish to Lark, etc.'} }} +
+ + {{ 'PAC.Xpert.ConfigurationRequired' | translate: {Default: 'Configuration required'} }} + +
+
+
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 5b86a3a1e..978276f89 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,6 +10,7 @@ import { ChatConversationService, getErrorMessage, IChatConversation, + IntegrationEnum, OrderTypeEnum, ToastrService, XpertService @@ -25,6 +26,7 @@ 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' @Component({ selector: 'xpert-studio-header', @@ -57,6 +59,7 @@ export class XpertStudioHeaderComponent { readonly sidePanel = model<'preview' | 'variables'>(null) readonly team = computed(() => this.xpertStudioComponent.team()) + readonly xpert = this.xpertStudioComponent.xpert readonly version = computed(() => this.team()?.version) readonly latest = computed(() => this.team()?.latest) readonly versions = computed(() => { @@ -164,6 +167,13 @@ export class XpertStudioHeaderComponent { // const draft = isDraft ? this.apiService.store.getValue().draft : this.apiService.getInitialDraft() // const result = stringify(instanceToPlain(new XpertDraftDslDTO(draft))) - + } + + publishToIntegration() { + this.#dialog.open(XpertPublishComponent, { + data: { + xpert: this.xpert(), + } + }).closed.subscribe({}) } } diff --git a/apps/cloud/src/assets/i18n/zh-Hans.json b/apps/cloud/src/assets/i18n/zh-Hans.json index f418a0083..1a9431b60 100644 --- a/apps/cloud/src/assets/i18n/zh-Hans.json +++ b/apps/cloud/src/assets/i18n/zh-Hans.json @@ -268,7 +268,8 @@ "InService": "运行中", "SecretKey": "密钥", "LastUsed": "最后使用", - "Never": "从未" + "Never": "从未", + "AvailablePro": "企业版中可用" }, "MENU": { "Certification": "认证", @@ -548,7 +549,8 @@ "ArchivedSuccessfully": "已成功归档", "NoRecordsFoundinFile": "文件中未找到记录", "UnsavedChanges": "未保存的更改", - "SavedDraft": "草稿已保存!" + "SavedDraft": "草稿已保存!", + "Successfully": "成功" }, "NOTES": { "ORGANIZATIONS": { @@ -2073,7 +2075,13 @@ "QAMemoryPromptTooltip": "提示用于通过指令和约束引导 AI 总结对话问题和答案的成功经验", "ClearMemory": "清空记忆", "ClearAllMemoryOfXpert": "清空跟此专家相关的所有记忆", - "SemanticSearchTest": "语义搜索测试" + "SemanticSearchTest": "语义搜索测试", + "PublishThirdPlatforms": "发布到第三方平台", + "PublishTo": "发布到", + "PublishToLarketc": "发布到飞书等.", + "ConfigurationRequired": "需要进行配置", + "XpertPublished": "专家已发布成功", + "XpertPublishDeleted": "专家发布已删除" }, "title": { "short": "Xpert AI" diff --git a/apps/cloud/src/assets/images/destinations/lark.png b/apps/cloud/src/assets/images/destinations/lark.png new file mode 100644 index 0000000000000000000000000000000000000000..a865b457f9b59f38ed4ee11c343d43bbf08952c0 GIT binary patch literal 6500 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 V-WI#L*gY@MwABsNpek>p{ttUDdi4MR literal 0 HcmV?d00001 diff --git a/apps/cloud/src/assets/images/destinations/wecom.png b/apps/cloud/src/assets/images/destinations/wecom.png new file mode 100644 index 0000000000000000000000000000000000000000..1d92c953d77905469ee9f48d50a6950cdce896a1 GIT binary patch literal 2098 zcmXw)c{mhY8^$LxcB5pMWQmEfCdBe6M{gvQwB4C6awCjU_}O z!z;36&%TVM!b|gwx9_{I=iJYIe$Rc*b^bev<_H)&ivSA%0APn3>08qJ3~k+DqNnKy zCd8T6&b#ZF=>PyVsjMf?47B~ZAWN7opz5>G8f`-`H?=l^`I`c8RKNo&08c$Lv1fWI zd;fosMjrmlX#raON&eA4oaV#+JA)&7JEf%$nTC#a?$Yvm=`{CDP7ynq*2;o-~n7;$J%4&o5Ms%i7KZX zmx~iy?jSv;&gWx0E(+v4HTmX`#qydbp6$Ij3hreK2bn7dmQ}kPx5qI zPZ9IoWDR!r>}$-A?oOStdaNjfEt9s9I5sU#{dTm4n)bvbC;s#pw9V}Zj*`vVVIUjS zaOZ)~Dd^}yN|!Sj7Jx15BAW;MhAmfH=f9JzWc8Yd+l+A7mgkBnx3^9EnXCluditE& zXVv+?v{i~-cjV$V=*awww*J0R8CjfRpS+ZH<<+4>aSD?6Gf8^IER86s!nNgA!Dx`A z7y%EpCV?44MQ+1f(aWDgdi1~n_$UY#^R#pPq$viiA;|lI+|SLiMKVeBn9{WgAwnnr zwy)2O@)M`1=4~AHci`*zzSKQRu19)v^;*y)9A+dxytU>b>PQq< z$kdvVJeKyPuKl`$hFm_rTX~UcEZ7qy6Mxk+d)rrqy@rCUZq4M%P}#w3H)D+ z=HutHR8Q%O>ll{9xYJ5xS`_~d3o97oB{_j7Tm(Fz#_5l?Gt$w3{Upnm)=@x;A|6=qAoU#yLud~g2QaE$>6ZRQO~h|_MC zGFw7savFPsVi`qbS@#K}M?fmwgbUjw7Mde#OT30(k5C_lZQm zI(7~um%$ZNdyyY8W0G#rTPFCFcZ%12j1{1bK7zj?Y9I3s#5CEqH%_Nc(_-dw#+?}T zYz5$NQemO*vt*=xz2EC#1oKz0&xPHe0DzRb#uuRxr$2AL8S-9S+0dI__F8=TlxNfY z^(fyf9VOzYY_|KJ4-~7I^g<8b3KYtdMv8(E1j zkmNFPyNn;F+?2{D%{4|r*dv6$aDi~uD%_}}UeW0&SM;KE`H^*T25W;!^+ak`GB6yf z5#{&gAw|hB6!fKNdGbV_Cr@fu`O-?tU&Qhh96`)>^Tr;=_`l z*MIDKK!$n`-Cc)wZff$hd^7VET;PKQ7zgfN7FD7XT%uQ?tD|+<4-;;+}j@U>eIK5Fz(TEn9H48ISLdlEsD0aUH?n=#3 z-GAnD_7^V?1@@o?VzW&?@<{Rp=)f;Fq&GI*(h~+JqD4}t57`@fl_`9#oAQ2#`k17y zNa4Amqmwm$m@c}hsQyZvj;zURPnc)!yu3Q1X-}WGZ?71f?(bG;<>oV!R5FWo$iIe; z0O$ILHDv6VA?7H}(kg==WWK?bw|95LHMQEN?HeBkfu3g3sXF%hhZ=5hHIW##4XO8> zS_FeSD4#>ew%(k^1&l~8Xl}SxwWhM~P;hli)7x#S>+kG^P$=I|p;S5vr + + + diff --git a/apps/cloud/src/styles/components/index.scss b/apps/cloud/src/styles/components/index.scss index 042544d9b..a87d8be8e 100644 --- a/apps/cloud/src/styles/components/index.scss +++ b/apps/cloud/src/styles/components/index.scss @@ -11,4 +11,5 @@ @import 'list'; @import 'tag'; @import 'card'; -@import 'radio'; \ No newline at end of file +@import 'radio'; +@import 'pro'; \ No newline at end of file diff --git a/apps/cloud/src/styles/components/pro.scss b/apps/cloud/src/styles/components/pro.scss new file mode 100644 index 000000000..019e76ed5 --- /dev/null +++ b/apps/cloud/src/styles/components/pro.scss @@ -0,0 +1,17 @@ +.enterprise-pro { + background: linear-gradient(99deg, hsla(0, 0%, 100%, 0.12) 7.16%, hsla(0, 0%, 100%, 0) 85.47%), + linear-gradient(280deg, #00b2ff 12.96%, #132bff 90.95%); + box-shadow: + 0 2px 4px -2px rgba(16, 24, 40, 0.06), + 0 4px 8px -2px rgba(0, 162, 253, 0.12); + + @apply px-3 rounded-2xl border border-solid border-[#0096EA] text-white; + + &:hover { + background: linear-gradient(99deg, hsla(0, 0%, 100%, 0.12) 7.16%, hsla(0, 0%, 100%, 0) 85.47%), + linear-gradient(280deg, #02c2ff 12.96%, #001aff 90.95%); + box-shadow: + 0 4px 6px -2px rgba(16, 18, 40, 0.08), + 0 12px 16px -4px rgba(0, 209, 255, 0.08); + } +} diff --git a/packages/angular/common/radio-select/select.component.html b/packages/angular/common/radio-select/select.component.html index de6edc73c..7047ba9c2 100644 --- a/packages/angular/common/radio-select/select.component.html +++ b/packages/angular/common/radio-select/select.component.html @@ -1,8 +1,8 @@
    @for (option of options(); track option.key) { -
  • +
  • -
    {{option.caption}}
    +
    {{option.label | i18n}}
  • }
diff --git a/packages/angular/common/radio-select/select.component.ts b/packages/angular/common/radio-select/select.component.ts index a25d21304..0d9c3c48a 100644 --- a/packages/angular/common/radio-select/select.component.ts +++ b/packages/angular/common/radio-select/select.component.ts @@ -1,7 +1,7 @@ import { CommonModule } from '@angular/common' -import { ChangeDetectionStrategy, Component, effect, inject, input, model } from '@angular/core' +import { ChangeDetectionStrategy, Component, computed, effect, inject, input, model } from '@angular/core' import { FormsModule, ReactiveFormsModule } from '@angular/forms' -import { ISelectOption, NgmDensityDirective } from '@metad/ocap-angular/core' +import { TSelectOption, NgmDensityDirective, NgmI18nPipe } from '@metad/ocap-angular/core' import { NgxControlValueAccessor } from 'ngxtension/control-value-accessor' import { CdkListboxModule } from '@angular/cdk/listbox' @@ -22,13 +22,14 @@ import { CdkListboxModule } from '@angular/cdk/listbox' }, NgxControlValueAccessor ], - imports: [CommonModule, FormsModule, ReactiveFormsModule, CdkListboxModule] + imports: [CommonModule, FormsModule, ReactiveFormsModule, CdkListboxModule, NgmI18nPipe] }) export class NgmRadioSelectComponent { protected cva = inject>(NgxControlValueAccessor) - readonly options = input() + readonly selectOptions = input() + readonly options = computed(() => this.selectOptions()?.map((option) => ({...option, key: option.key ?? option.value}))) readonly value = model(null) diff --git a/packages/contracts/src/ai/xpert.model.ts b/packages/contracts/src/ai/xpert.model.ts index 8487d70e2..315924ebd 100644 --- a/packages/contracts/src/ai/xpert.model.ts +++ b/packages/contracts/src/ai/xpert.model.ts @@ -7,6 +7,7 @@ import { TAvatar } from '../types' import { IXpertAgent } from './xpert-agent.model' import { IXpertToolset } from './xpert-toolset.model' import { IBasePerWorkspaceEntityModel } from './xpert-workspace.model' +import { IIntegration } from '../integration.model' export type ToolCall = LToolCall @@ -85,7 +86,11 @@ export type TXpert = { * The corresponding person in charge, whose has the authority to execute this digital expert */ managers?: IUser[] - + /** + * Integrations for this xpert + */ + integrations?: IIntegration[] + tags?: ITag[] } diff --git a/packages/contracts/src/integration.model.ts b/packages/contracts/src/integration.model.ts index 2259c0d0b..671f1045f 100644 --- a/packages/contracts/src/integration.model.ts +++ b/packages/contracts/src/integration.model.ts @@ -4,7 +4,7 @@ import { import { IOrganizationProjectsUpdateInput } from './organization-projects.model'; import { IOrganizationUpdateInput } from './organization.model'; import { ITag } from './tag-entity.model'; -import { TAvatar } from './types'; +import { I18nObject, TAvatar } from './types'; // export interface IIntegrationSetting // extends IBasePerTenantAndOrganizationEntityModel { @@ -93,7 +93,9 @@ export interface IIntegrationMapSyncOrganization export enum IntegrationEnum { UPWORK = 'Upwork', HUBSTAFF = 'Hubstaff', - LARK = 'Lark' + LARK = 'Lark', + DINGTALK = 'DingTalk', + WECOM = 'WeCom' } // export enum IntegrationEntity { @@ -189,4 +191,13 @@ export const DEFAULT_INTEGRATIONS = [ export interface IDateRangeActivityFilter { start: Date; end: Date; +} + +export type TIntegrationProvider = { + name: string + label: I18nObject + avatar: string + schema?: any + webhookUrl?: (integration: IIntegration, baseUrl: string) => string + pro?: boolean } \ No newline at end of file diff --git a/packages/contracts/src/integration/dingtalk.ts b/packages/contracts/src/integration/dingtalk.ts new file mode 100644 index 000000000..a7e0120ea --- /dev/null +++ b/packages/contracts/src/integration/dingtalk.ts @@ -0,0 +1,11 @@ +import { IntegrationEnum, TIntegrationProvider } from '../integration.model' + +export const IntegrationDingTalkProvider: TIntegrationProvider = { + name: IntegrationEnum.DINGTALK, + label: { + en_US: 'DingTalk', + zh_Hans: '钉钉' + }, + avatar: 'dingtalk.png', + pro: true +} diff --git a/packages/contracts/src/integration/index.ts b/packages/contracts/src/integration/index.ts index 9af2fbf6e..6c26a887d 100644 --- a/packages/contracts/src/integration/index.ts +++ b/packages/contracts/src/integration/index.ts @@ -1,6 +1,10 @@ import { IntegrationEnum } from '../integration.model' +import { IntegrationDingTalkProvider } from './dingtalk' import { IntegrationLarkProvider } from './lark' +import { IntegrationWeComProvider } from './wecom' export const INTEGRATION_PROVIDERS = { - [IntegrationEnum.LARK]: IntegrationLarkProvider + [IntegrationEnum.LARK]: IntegrationLarkProvider, + [IntegrationEnum.DINGTALK]: IntegrationDingTalkProvider, + [IntegrationEnum.WECOM]: IntegrationWeComProvider, } diff --git a/packages/contracts/src/integration/lark.ts b/packages/contracts/src/integration/lark.ts index 2d817d834..4cd0b3cf8 100644 --- a/packages/contracts/src/integration/lark.ts +++ b/packages/contracts/src/integration/lark.ts @@ -1,6 +1,6 @@ -import { IIntegration, IntegrationEnum } from '../integration.model' +import { IIntegration, IntegrationEnum, TIntegrationProvider } from '../integration.model' -export const IntegrationLarkProvider = { +export const IntegrationLarkProvider: TIntegrationProvider = { name: IntegrationEnum.LARK, label: { en_US: 'Lark', diff --git a/packages/contracts/src/integration/wecom.ts b/packages/contracts/src/integration/wecom.ts new file mode 100644 index 000000000..90c364ee7 --- /dev/null +++ b/packages/contracts/src/integration/wecom.ts @@ -0,0 +1,11 @@ +import { IntegrationEnum, TIntegrationProvider } from '../integration.model' + +export const IntegrationWeComProvider: TIntegrationProvider = { + name: IntegrationEnum.WECOM, + label: { + en_US: 'WeCom', + zh_Hans: '企业微信' + }, + avatar: 'wecom.png', + pro: true +} diff --git a/packages/server-ai/src/xpert/commands/del-integration.command.ts b/packages/server-ai/src/xpert/commands/del-integration.command.ts new file mode 100644 index 000000000..bbb59358e --- /dev/null +++ b/packages/server-ai/src/xpert/commands/del-integration.command.ts @@ -0,0 +1,10 @@ +import { ICommand } from '@nestjs/cqrs' + +export class XpertDelIntegrationCommand implements ICommand { + static readonly type = '[Xpert] Delete integration' + + constructor( + public readonly id: string, + public readonly integration: string + ) { } +} diff --git a/packages/server-ai/src/xpert/commands/handlers/del-integration.handler.ts b/packages/server-ai/src/xpert/commands/handlers/del-integration.handler.ts new file mode 100644 index 000000000..617c90289 --- /dev/null +++ b/packages/server-ai/src/xpert/commands/handlers/del-integration.handler.ts @@ -0,0 +1,25 @@ +import { IntegrationDelCommand } from '@metad/server-core' +import { Logger } from '@nestjs/common' +import { CommandBus, CommandHandler, ICommandHandler } from '@nestjs/cqrs' +import { XpertService } from '../../xpert.service' +import { XpertDelIntegrationCommand } from '../del-integration.command' + +@CommandHandler(XpertDelIntegrationCommand) +export class XpertDelIntegrationHandler implements ICommandHandler { + readonly #logger = new Logger(XpertDelIntegrationHandler.name) + + constructor( + private readonly xpertService: XpertService, + private readonly commandBus: CommandBus + ) {} + + public async execute(command: XpertDelIntegrationCommand): Promise { + const { id, integration } = command + const xpert = await this.xpertService.findOne(id, { relations: ['integrations'] }) + + await this.commandBus.execute(new IntegrationDelCommand(integration)) + + xpert.integrations = xpert.integrations.filter((_) => _.id !== integration) + await this.xpertService.save(xpert) + } +} diff --git a/packages/server-ai/src/xpert/commands/handlers/index.ts b/packages/server-ai/src/xpert/commands/handlers/index.ts index ac51529fb..ede7f319e 100644 --- a/packages/server-ai/src/xpert/commands/handlers/index.ts +++ b/packages/server-ai/src/xpert/commands/handlers/index.ts @@ -1,8 +1,10 @@ import { XpertChatHandler } from './chat.handler' import { XpertCreateHandler } from './create.handler' +import { XpertDelIntegrationHandler } from './del-integration.handler' import { XpertExecuteHandler } from './execute.handler' import { XpertExportHandler } from './export.handler' import { XpertImportHandler } from './import.handler' +import { XpertPublishIntegrationHandler } from './publish-integration.handler' import { XpertPublishHandler } from './publish.handler' import { XpertSummarizeMemoryHandler } from './summarize-memory.handler' @@ -13,5 +15,7 @@ export const CommandHandlers = [ XpertExecuteHandler, XpertImportHandler, XpertExportHandler, - XpertSummarizeMemoryHandler + XpertSummarizeMemoryHandler, + XpertPublishIntegrationHandler, + XpertDelIntegrationHandler ] diff --git a/packages/server-ai/src/xpert/commands/handlers/publish-integration.handler.ts b/packages/server-ai/src/xpert/commands/handlers/publish-integration.handler.ts new file mode 100644 index 000000000..f3cc28635 --- /dev/null +++ b/packages/server-ai/src/xpert/commands/handlers/publish-integration.handler.ts @@ -0,0 +1,31 @@ +import { IntegrationUpsertCommand } from '@metad/server-core' +import { Logger } from '@nestjs/common' +import { CommandBus, CommandHandler, ICommandHandler } from '@nestjs/cqrs' +import { Xpert } from '../../xpert.entity' +import { XpertService } from '../../xpert.service' +import { XpertPublishIntegrationCommand } from '../publish-integration.command' + +@CommandHandler(XpertPublishIntegrationCommand) +export class XpertPublishIntegrationHandler implements ICommandHandler { + readonly #logger = new Logger(XpertPublishIntegrationHandler.name) + + constructor( + private readonly xpertService: XpertService, + private readonly commandBus: CommandBus + ) {} + + public async execute(command: XpertPublishIntegrationCommand): Promise { + const { id, integration } = command + + const _integration = await this.commandBus.execute(new IntegrationUpsertCommand(integration)) + + const xpert = await this.xpertService.findOne(id, { relations: ['integrations'] }) + + xpert.integrations = xpert.integrations.filter((_) => _.id !== _integration.id) + xpert.integrations.push(_integration) + + await this.xpertService.save(xpert) + + return _integration + } +} diff --git a/packages/server-ai/src/xpert/commands/index.ts b/packages/server-ai/src/xpert/commands/index.ts index ed79a72ba..18e1e9bad 100644 --- a/packages/server-ai/src/xpert/commands/index.ts +++ b/packages/server-ai/src/xpert/commands/index.ts @@ -4,4 +4,6 @@ export * from './execute.command' export * from './chat.command' export * from './import.command' export * from './export.command' -export * from './summarize-memory.command' \ No newline at end of file +export * from './summarize-memory.command' +export * from './publish-integration.command' +export * from './del-integration.command' \ No newline at end of file diff --git a/packages/server-ai/src/xpert/commands/publish-integration.command.ts b/packages/server-ai/src/xpert/commands/publish-integration.command.ts new file mode 100644 index 000000000..fa8470e04 --- /dev/null +++ b/packages/server-ai/src/xpert/commands/publish-integration.command.ts @@ -0,0 +1,11 @@ +import { IIntegration } from '@metad/contracts' +import { ICommand } from '@nestjs/cqrs' + +export class XpertPublishIntegrationCommand implements ICommand { + static readonly type = '[Xpert Role] Publish to integration' + + constructor( + public readonly id: string, + public readonly integration: Partial + ) { } +} diff --git a/packages/server-ai/src/xpert/xpert.controller.ts b/packages/server-ai/src/xpert/xpert.controller.ts index f3b543577..9c4177be2 100644 --- a/packages/server-ai/src/xpert/xpert.controller.ts +++ b/packages/server-ai/src/xpert/xpert.controller.ts @@ -1,4 +1,4 @@ -import { ICopilotStore, OrderTypeEnum, RolesEnum, TChatRequest, TXpertTeamDraft, xpertLabel } from '@metad/contracts' +import { ICopilotStore, IIntegration, OrderTypeEnum, RolesEnum, TChatRequest, TXpertTeamDraft, xpertLabel } from '@metad/contracts' import { CrudController, OptionParams, @@ -36,7 +36,7 @@ import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagg import { DeleteResult, FindConditions, In, IsNull, Like, Not } from 'typeorm' import { XpertAgentExecution } from '../core/entities/internal' import { FindExecutionsByXpertQuery } from '../xpert-agent-execution/queries' -import { XpertChatCommand, XpertExportCommand, XpertImportCommand } from './commands' +import { XpertChatCommand, XpertDelIntegrationCommand, XpertExportCommand, XpertImportCommand, XpertPublishIntegrationCommand } from './commands' import { XpertDraftDslDTO, XpertPublicDTO } from './dto' import { Xpert } from './xpert.entity' import { XpertService } from './xpert.service' @@ -162,6 +162,16 @@ export class XpertController extends CrudController { return this.service.publish(id) } + @Post(':id/publish/integration') + async publishIntegration(@Param('id') id: string, @Body() integration: Partial) { + return this.commandBus.execute(new XpertPublishIntegrationCommand(id, integration)) + } + + @Delete(':id/publish/integration/:integration') + async deleteIntegration(@Param('id') id: string, @Param('integration') integration: string,) { + return this.commandBus.execute(new XpertDelIntegrationCommand(id, integration)) + } + @Get(':id/executions') async getExecutions( @Param('id') id: string, diff --git a/packages/server-ai/src/xpert/xpert.entity.ts b/packages/server-ai/src/xpert/xpert.entity.ts index 3aae45f0c..e41993a2d 100644 --- a/packages/server-ai/src/xpert/xpert.entity.ts +++ b/packages/server-ai/src/xpert/xpert.entity.ts @@ -1,6 +1,7 @@ import { AiBusinessRole, ICopilotModel, + IIntegration, IKnowledgebase, ITag, IUser, @@ -15,7 +16,7 @@ import { TXpertTeamDraft, XpertTypeEnum } from '@metad/contracts' -import { Tag, User } from '@metad/server-core' +import { Integration, Tag, User } from '@metad/server-core' import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger' import { IsBoolean, IsJSON, IsOptional, IsString } from 'class-validator' import { Column, Entity, Index, JoinColumn, JoinTable, ManyToMany, OneToMany, OneToOne, RelationId } from 'typeorm' @@ -219,4 +220,14 @@ export class Xpert extends WorkspaceBaseEntity implements IXpert { name: 'tag_xpert' }) tags?: ITag[] + + @ApiProperty({ type: () => Integration, isArray: true }) + @ManyToMany(() => Integration, { + onUpdate: 'CASCADE', + onDelete: 'CASCADE' + }) + @JoinTable({ + name: 'xpert_to_integration', + }) + integrations?: IIntegration[] } diff --git a/packages/server/src/integration/commands/delete.command.ts b/packages/server/src/integration/commands/delete.command.ts new file mode 100644 index 000000000..51af2e42d --- /dev/null +++ b/packages/server/src/integration/commands/delete.command.ts @@ -0,0 +1,7 @@ +import { ICommand } from '@nestjs/cqrs'; + +export class IntegrationDelCommand implements ICommand { + static readonly type = '[Integration] Delete'; + + constructor(public readonly id: string) {} +} diff --git a/packages/server/src/integration/commands/handlers/delete.handler.ts b/packages/server/src/integration/commands/handlers/delete.handler.ts new file mode 100644 index 000000000..3d93b61c9 --- /dev/null +++ b/packages/server/src/integration/commands/handlers/delete.handler.ts @@ -0,0 +1,16 @@ +import { CommandBus, CommandHandler, ICommandHandler } from '@nestjs/cqrs' +import { IntegrationService } from '../../integration.service' +import { IntegrationDelCommand } from '../delete.command' + +@CommandHandler(IntegrationDelCommand) +export class IntegrationDelHandler implements ICommandHandler { + constructor( + private readonly commandBus: CommandBus, + private readonly service: IntegrationService + ) {} + + public async execute(command: IntegrationDelCommand): Promise { + const { id } = command + await this.service.delete(id) + } +} diff --git a/packages/server/src/integration/commands/handlers/index.ts b/packages/server/src/integration/commands/handlers/index.ts new file mode 100644 index 000000000..94d7736dc --- /dev/null +++ b/packages/server/src/integration/commands/handlers/index.ts @@ -0,0 +1,7 @@ +import { IntegrationDelHandler } from "./delete.handler"; +import { IntegrationUpsertHandler } from "./upsert.handler"; + +export const CommandHandlers = [ + IntegrationUpsertHandler, + IntegrationDelHandler +] diff --git a/packages/server/src/integration/commands/handlers/upsert.handler.ts b/packages/server/src/integration/commands/handlers/upsert.handler.ts new file mode 100644 index 000000000..435584cf5 --- /dev/null +++ b/packages/server/src/integration/commands/handlers/upsert.handler.ts @@ -0,0 +1,23 @@ +import { IIntegration } from '@metad/contracts' +import { omit } from '@metad/server-common' +import { CommandBus, CommandHandler, ICommandHandler } from '@nestjs/cqrs' +import { IntegrationService } from '../../integration.service' +import { IntegrationUpsertCommand } from '../upsert.command' + +@CommandHandler(IntegrationUpsertCommand) +export class IntegrationUpsertHandler implements ICommandHandler { + constructor( + private readonly commandBus: CommandBus, + private readonly service: IntegrationService + ) {} + + public async execute(command: IntegrationUpsertCommand): Promise { + const { input } = command + if (input.id) { + await this.service.update(input.id, omit(input, 'id')) + return await this.service.findOne(input.id) + } else { + return await this.service.create(omit(input, 'id')) + } + } +} diff --git a/packages/server/src/integration/commands/index.ts b/packages/server/src/integration/commands/index.ts new file mode 100644 index 000000000..dd8e08a86 --- /dev/null +++ b/packages/server/src/integration/commands/index.ts @@ -0,0 +1,2 @@ +export * from './upsert.command' +export * from './delete.command' \ No newline at end of file diff --git a/packages/server/src/integration/commands/upsert.command.ts b/packages/server/src/integration/commands/upsert.command.ts new file mode 100644 index 000000000..63452331f --- /dev/null +++ b/packages/server/src/integration/commands/upsert.command.ts @@ -0,0 +1,8 @@ +import { IIntegration } from '@metad/contracts'; +import { ICommand } from '@nestjs/cqrs'; + +export class IntegrationUpsertCommand implements ICommand { + static readonly type = '[Integration] Upsert'; + + constructor(public readonly input: Partial) {} +} diff --git a/packages/server/src/integration/index.ts b/packages/server/src/integration/index.ts index 423680c3b..c66fc99c0 100644 --- a/packages/server/src/integration/index.ts +++ b/packages/server/src/integration/index.ts @@ -1,2 +1,3 @@ export * from './integration.module' -export * from './integration.service' \ No newline at end of file +export * from './integration.service' +export * from './commands/index' \ No newline at end of file diff --git a/packages/server/src/integration/integration.module.ts b/packages/server/src/integration/integration.module.ts index d3f88dd22..ceeff44bc 100644 --- a/packages/server/src/integration/integration.module.ts +++ b/packages/server/src/integration/integration.module.ts @@ -6,6 +6,7 @@ import { TenantModule } from '../tenant/tenant.module' import { IntegrationController } from './integration.controller' import { Integration } from './integration.entity' import { IntegrationService } from './integration.service' +import { CommandHandlers } from './commands/handlers' @Module({ imports: [ @@ -15,7 +16,7 @@ import { IntegrationService } from './integration.service' CqrsModule ], controllers: [IntegrationController], - providers: [IntegrationService], + providers: [IntegrationService, ...CommandHandlers], exports: [IntegrationService] }) export class IntegrationModule {} From 9bcc7d572bfe391ac80a10b9dffad353f624fbd3 Mon Sep 17 00:00:00 2001 From: meta-d Date: Wed, 25 Dec 2024 00:55:58 +0800 Subject: [PATCH 6/8] fix: sqlite --- packages/config/src/database.ts | 26 +++++++++---------- .../src/ai-model/model_providers/index.ts | 4 +-- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/config/src/database.ts b/packages/config/src/database.ts index 6c150b0eb..f6e7d22e2 100644 --- a/packages/config/src/database.ts +++ b/packages/config/src/database.ts @@ -1,4 +1,4 @@ -import * as path from 'path'; +// import * as path from 'path'; import { TypeOrmModuleOptions } from '@nestjs/typeorm'; import { ConnectionOptions } from 'typeorm'; @@ -7,11 +7,11 @@ const defaultConnection = ? 'postgres' : 'sqlite'; -const dbPath = - process.env.DB_PATH || - path.join(process.cwd(), ...['apps', 'api', 'data'], 'metad.sqlite3'); +// const dbPath = +// process.env.DB_PATH || +// path.join(process.cwd(), ...['apps', 'api', 'data'], 'metad.sqlite3'); -console.log('Sqlite DB Path: ' + dbPath); +// console.log('Sqlite DB Path: ' + dbPath); const ssl = process.env.DB_SSL_MODE === 'true' ? true : undefined; @@ -29,17 +29,17 @@ const postgreSQLConfig: ConnectionOptions = { uuidExtension: 'pgcrypto' }; -const sqliteConfig: ConnectionOptions = { - type: 'sqlite', - database: dbPath, - logging: true, - logger: 'file', //Removes console logging, instead logs all queries in a file ormlogs.log - synchronize: true -}; +// const sqliteConfig: ConnectionOptions = { +// type: 'sqlite', +// database: dbPath, +// logging: true, +// logger: 'file', //Removes console logging, instead logs all queries in a file ormlogs.log +// synchronize: true +// }; const connections = { postgres: postgreSQLConfig, - sqlite: sqliteConfig + // sqlite: sqliteConfig }; export function getConnectionOptions(connection: string) { diff --git a/packages/server-ai/src/ai-model/model_providers/index.ts b/packages/server-ai/src/ai-model/model_providers/index.ts index bc5636f45..3d76c5604 100644 --- a/packages/server-ai/src/ai-model/model_providers/index.ts +++ b/packages/server-ai/src/ai-model/model_providers/index.ts @@ -13,7 +13,7 @@ import { MistralAIProviderModule } from './mistralai/mistralai' import { MoonshotProviderModule } from './moonshot/moonshot' import { OllamaProviderModule } from './ollama/ollama' import { OpenAIProviderModule } from './openai/openai' -import { TogetherAIProvider } from './togetherai/togetherai' +import { TogetherAIProviderModule } from './togetherai/togetherai' import { TongyiProviderModule } from './tongyi/tongyi' import { XAIProviderModule } from './x/x' import { ZhipuaiProviderModule } from './zhipuai/zhipuai' @@ -26,7 +26,7 @@ export const ProviderModules = [ BaichuanProviderModule, AzureAIStudioProviderModule, ZhipuaiProviderModule, - TogetherAIProvider, + TogetherAIProviderModule, CohereProviderModule, GoogleProviderModule, GroqProviderModule, From 830c6e9900cb3bca0d78df970c7099d7b4e2393e Mon Sep 17 00:00:00 2001 From: meta-d Date: Wed, 25 Dec 2024 00:56:48 +0800 Subject: [PATCH 7/8] version 3.0.7 --- 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 1379fecb6..ad4e9f399 100644 --- a/packages/adapter/package.json +++ b/packages/adapter/package.json @@ -1,6 +1,6 @@ { "name": "@metad/adapter", - "version": "3.0.6", + "version": "3.0.7", "dependencies": { "axios": "^0.21.4", "clickhouse": "^2.4.1", diff --git a/packages/analytics/package.json b/packages/analytics/package.json index fcc85094e..1e540e6f6 100644 --- a/packages/analytics/package.json +++ b/packages/analytics/package.json @@ -1,6 +1,6 @@ { "name": "@metad/analytics", - "version": "3.0.6", + "version": "3.0.7", "type": "commonjs", "license": "MIT", "scripts": { diff --git a/packages/angular/package.json b/packages/angular/package.json index 93f413fa5..6d84f9b42 100644 --- a/packages/angular/package.json +++ b/packages/angular/package.json @@ -1,6 +1,6 @@ { "name": "@metad/ocap-angular", - "version": "3.0.6", + "version": "3.0.7", "keywords": [ "metad", "ocap", diff --git a/packages/auth/package.json b/packages/auth/package.json index 156e92193..a2652a54a 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -1,6 +1,6 @@ { "name": "@metad/server-auth", - "version": "3.0.6", + "version": "3.0.7", "type": "commonjs", "dependencies": { "@nestjs/common": "^8.0.0", diff --git a/packages/common/package.json b/packages/common/package.json index a0d7996e4..9a167d6c0 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -1,6 +1,6 @@ { "name": "@metad/server-common", - "version": "3.0.6", + "version": "3.0.7", "type": "commonjs", "dependencies": { "@nestjs/typeorm": "^8.0.3", diff --git a/packages/config/package.json b/packages/config/package.json index f31105aaa..ff8d8d764 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -1,6 +1,6 @@ { "name": "@metad/server-config", - "version": "3.0.6", + "version": "3.0.7", "type": "commonjs", "dependencies": { "@nestjs/common": "^8.0.0", diff --git a/packages/contracts/package.json b/packages/contracts/package.json index be0297e61..79c536f5a 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -1,6 +1,6 @@ { "name": "@metad/contracts", - "version": "3.0.6", + "version": "3.0.7", "scripts": { "docs": "yarn typedoc --out ./.docs" } diff --git a/packages/copilot-angular/package.json b/packages/copilot-angular/package.json index b88758030..6eea35a1f 100644 --- a/packages/copilot-angular/package.json +++ b/packages/copilot-angular/package.json @@ -1,6 +1,6 @@ { "name": "@metad/copilot-angular", - "version": "3.0.6", + "version": "3.0.7", "peerDependencies": { "@angular/common": "^17.3.0", "@angular/core": "^17.3.0", diff --git a/packages/copilot/package.json b/packages/copilot/package.json index 873164e16..1d760a355 100644 --- a/packages/copilot/package.json +++ b/packages/copilot/package.json @@ -1,6 +1,6 @@ { "name": "@metad/copilot", - "version": "3.0.6", + "version": "3.0.7", "scripts": { "docs": "yarn typedoc --out ./.docs" }, diff --git a/packages/core/package.json b/packages/core/package.json index 1f47f9fa8..4dbaef8bb 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@metad/ocap-core", - "version": "3.0.6", + "version": "3.0.7", "dependencies": { "tslib": "^2.3.0" }, diff --git a/packages/duckdb/package.json b/packages/duckdb/package.json index a2fc4b34a..e9349a8ef 100644 --- a/packages/duckdb/package.json +++ b/packages/duckdb/package.json @@ -1,6 +1,6 @@ { "name": "@metad/ocap-duckdb", - "version": "3.0.6", + "version": "3.0.7", "keywords": [ "metad", "ocap", diff --git a/packages/echarts/package.json b/packages/echarts/package.json index ccc7de169..43e9b15e9 100644 --- a/packages/echarts/package.json +++ b/packages/echarts/package.json @@ -1,6 +1,6 @@ { "name": "@metad/ocap-echarts", - "version": "3.0.6", + "version": "3.0.7", "keywords": [ "metad", "ocap", diff --git a/packages/server-ai/package.json b/packages/server-ai/package.json index 934913367..130763cc2 100644 --- a/packages/server-ai/package.json +++ b/packages/server-ai/package.json @@ -1,6 +1,6 @@ { "name": "@metad/server-ai", - "version": "3.0.6", + "version": "3.0.7", "type": "commonjs", "dependencies": { "@apidevtools/swagger-parser": "^10.1.0", diff --git a/packages/server/package.json b/packages/server/package.json index b3cfb230f..21874530d 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@metad/server-core", - "version": "3.0.6", + "version": "3.0.7", "type": "commonjs", "dependencies": { "@casl/ability": "^5.4.3", diff --git a/packages/sql/package.json b/packages/sql/package.json index 8497b61cf..51a9df3e4 100644 --- a/packages/sql/package.json +++ b/packages/sql/package.json @@ -1,6 +1,6 @@ { "name": "@metad/ocap-sql", - "version": "3.0.6", + "version": "3.0.7", "keywords": [ "metad", "ocap", diff --git a/packages/store/package.json b/packages/store/package.json index 6c654529a..79ee9f590 100644 --- a/packages/store/package.json +++ b/packages/store/package.json @@ -1,6 +1,6 @@ { "name": "@metad/store", - "version": "3.0.6", + "version": "3.0.7", "keywords": [ "metad", "ocap", diff --git a/packages/xmla/package.json b/packages/xmla/package.json index ec7e20415..ba7851fa2 100644 --- a/packages/xmla/package.json +++ b/packages/xmla/package.json @@ -1,6 +1,6 @@ { "name": "@metad/ocap-xmla", - "version": "3.0.6", + "version": "3.0.7", "scripts": { "docs": "yarn typedoc --out ./.docs" }, From 97c67c1f4c5528e906db8f4b1ae42da8c1bb6e09 Mon Sep 17 00:00:00 2001 From: meta-d Date: Wed, 25 Dec 2024 01:07:58 +0800 Subject: [PATCH 8/8] fix: production env --- docker/docker-compose.yml | 4 +- .../server/src/feature/feature.subscriber.ts | 54 +++++++++---------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index bff1521a5..f15652fd6 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -58,7 +58,7 @@ services: environment: HOST: ${API_HOST:-api} PORT: ${API_PORT:-3000} - NODE_ENV: ${NODE_ENV:-development} + NODE_ENV: ${NODE_ENV:-production} DB_HOST: db REDIS_HOST: redis REDIS_PORT: 6379 @@ -86,7 +86,7 @@ services: image: ghcr.io/xpert-ai/xpert-webapp:latest environment: HOST: ${WEB_HOST:-webapp} - NODE_ENV: ${NODE_ENV:-development} + NODE_ENV: ${NODE_ENV:-production} API_BASE_URL: ${API_BASE_URL:-http://localhost:3000} CLIENT_BASE_URL: ${CLIENT_BASE_URL:-http://localhost:4200} SENTRY_DSN: ${SENTRY_DSN:-} diff --git a/packages/server/src/feature/feature.subscriber.ts b/packages/server/src/feature/feature.subscriber.ts index 0b5f0d508..cf5c6214f 100644 --- a/packages/server/src/feature/feature.subscriber.ts +++ b/packages/server/src/feature/feature.subscriber.ts @@ -1,37 +1,37 @@ -import { pacToggleFeatures } from "@metad/server-config"; -import { FeatureStatusEnum } from "@metad/contracts"; -import { EntitySubscriberInterface, EventSubscriber } from "typeorm"; -import { shuffle } from 'underscore'; -import { FileStorage } from "./../core/file-storage"; -import { Feature } from "./feature.entity"; +import { FeatureStatusEnum } from '@metad/contracts' +import { toggleFeatures } from '@metad/server-config' +import { isNil } from 'lodash' +import { EntitySubscriberInterface, EventSubscriber } from 'typeorm' +import { shuffle } from 'underscore' +import { FileStorage } from './../core/file-storage' +import { Feature } from './feature.entity' @EventSubscriber() export class FeatureSubscriber implements EntitySubscriberInterface { + /** + * Indicates that this subscriber only listen to Feature events. + */ + listenTo() { + return Feature + } - /** - * Indicates that this subscriber only listen to Feature events. - */ - listenTo() { - return Feature; - } - - /** - * Called after entity is loaded. - */ - afterLoad(entity: any) { - if (!entity.status) { - entity.status = shuffle(Object.values(FeatureStatusEnum))[0]; + /** + * Called after entity is loaded. + */ + afterLoad(entity: any) { + if (!entity.status) { + entity.status = shuffle(Object.values(FeatureStatusEnum))[0] } - if (pacToggleFeatures.hasOwnProperty(entity.code)) { - const feature = pacToggleFeatures[entity.code]; - entity.isEnabled = feature; + if (!isNil(toggleFeatures[entity.code])) { + const feature = toggleFeatures[entity.code] + entity.isEnabled = feature } else { - entity.isEnabled = true; + entity.isEnabled = true } - if (entity.image) { - entity.imageUrl = new FileStorage().getProvider().url(entity.image); + if (entity.image) { + entity.imageUrl = new FileStorage().getProvider().url(entity.image) } - } -} \ No newline at end of file + } +}