diff --git a/.github/workflows/deploy-integrations-production.yml b/.github/workflows/deploy-integrations-production.yml index 746a2a326c1..128d96bf524 100644 --- a/.github/workflows/deploy-integrations-production.yml +++ b/.github/workflows/deploy-integrations-production.yml @@ -40,7 +40,7 @@ jobs: uses: ./.github/actions/deploy-plugins with: environment: 'production' - extra_filter: "-F '!analytics' -F '!logger' -F '!personality' -F '!synchronizer' -F '!knowledge'" + extra_filter: "-F '!analytics' -F '!logger' -F '!personality' -F '!synchronizer' -F '!knowledge' -F '!conversation-insights'" force: ${{ github.event.inputs.force == 'true' }} token_cloud_ops_account: ${{ secrets.PRODUCTION_TOKEN_CLOUD_OPS_ACCOUNT }} cloud_ops_workspace_id: ${{ secrets.PRODUCTION_CLOUD_OPS_WORKSPACE_ID }} diff --git a/eslint.config.mjs b/eslint.config.mjs index 69a90f6e8a2..a680bf2a2e7 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -6,6 +6,9 @@ import tsParser from "@typescript-eslint/parser"; import tseslint from 'typescript-eslint'; import stylistic from '@stylistic/eslint-plugin' import oxlint from 'eslint-plugin-oxlint'; +import path from "path" + +const oxlintFile = path.join(import.meta.dirname, '.oxlintrc.json'); const ignores = [ ".git/", @@ -77,7 +80,9 @@ export default [{ avoidEscape: true, }], - "@typescript-eslint/no-floating-promises": "error", + "@typescript-eslint/no-floating-promises": ["error", { + checkThenables: true + }], "@typescript-eslint/no-misused-promises": "error", "@stylistic/semi": ["error", "never"], "@stylistic/type-annotation-spacing": "error", @@ -112,7 +117,7 @@ export default [{ "@typescript-eslint/explicit-member-accessibility": "warn", // Disable every rule already covered by oxlint: - ...oxlint.buildFromOxlintConfigFile('./.oxlintrc.json') + ...oxlint.buildFromOxlintConfigFile(oxlintFile) .map(config => config.rules) .reduce((acc, rules) => ({ ...acc, ...rules }), {}), }, diff --git a/integrations/email/src/setup.ts b/integrations/email/src/setup.ts index f6ee3bc1fe0..2d666f4841e 100644 --- a/integrations/email/src/setup.ts +++ b/integrations/email/src/setup.ts @@ -18,7 +18,6 @@ export const register: bp.IntegrationProps['register'] = async (props) => { await getMessages({ page: 0, perPage: 1 }, props) } catch (thrown: unknown) { const err = thrown instanceof Error ? thrown : new Error(`${thrown}`) - console.log(err.message) throw new sdk.RuntimeError( `An error occured when registering the integration: ${err.message} Verify your configuration.` ) diff --git a/packages/cli/src/command-implementations/init-command.ts b/packages/cli/src/command-implementations/init-command.ts index 4d6a2e9c397..26527cf6c73 100644 --- a/packages/cli/src/command-implementations/init-command.ts +++ b/packages/cli/src/command-implementations/init-command.ts @@ -194,6 +194,7 @@ export class InitCommand extends GlobalCommand { if (!destinationCanBeUsed) { throw new errors.AbortedOperationError() } + await fs.promises.rm(destination, { recursive: true, force: true }) await fs.promises.cp(srcDir, destination, { recursive: true }) diff --git a/packages/zai/src/response.ts b/packages/zai/src/response.ts index 7cacfa17ea3..7cd0b1d71c5 100644 --- a/packages/zai/src/response.ts +++ b/packages/zai/src/response.ts @@ -68,8 +68,8 @@ export class Response implements PromiseLike { signal.addEventListener('abort', () => signalAbort()) - this.once('complete', () => signal.removeEventListener('abort', signalAbort)) - this.once('error', () => signal.removeEventListener('abort', signalAbort)) + void this.once('complete', () => signal.removeEventListener('abort', signalAbort)) + void this.once('error', () => signal.removeEventListener('abort', signalAbort)) return this } @@ -78,6 +78,7 @@ export class Response implements PromiseLike { this._context.controller.abort(reason) } + // oxlint-disable-next-line no-thenable public then( onfulfilled?: ((value: S) => TResult1 | PromiseLike) | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | null diff --git a/plugins/analytics/tsconfig.json b/plugins/analytics/tsconfig.json index df65383c598..da5cae6f64b 100644 --- a/plugins/analytics/tsconfig.json +++ b/plugins/analytics/tsconfig.json @@ -4,5 +4,5 @@ "baseUrl": ".", "outDir": "dist" }, - "include": [".botpress/**/*", "src/**/*"] + "include": [".botpress/**/*", "definitions/**/*", "src/**/*", "plugin.definition.ts"] } diff --git a/plugins/conversation-insights/package.json b/plugins/conversation-insights/package.json new file mode 100644 index 00000000000..c610f823546 --- /dev/null +++ b/plugins/conversation-insights/package.json @@ -0,0 +1,11 @@ +{ + "name": "@botpresshub/conversation-insights", + "scripts": { + "check:type": "tsc --noEmit", + "build": "bp build" + }, + "private": true, + "dependencies": { + "@botpress/sdk": "workspace:*" + } +} diff --git a/plugins/conversation-insights/plugin.definition.ts b/plugins/conversation-insights/plugin.definition.ts new file mode 100644 index 00000000000..528f3f6a8ad --- /dev/null +++ b/plugins/conversation-insights/plugin.definition.ts @@ -0,0 +1,29 @@ +import { PluginDefinition, z } from '@botpress/sdk' + +export default new PluginDefinition({ + name: 'conversation-insights', + version: '0.1.3', + conversation: { + tags: { + title: { title: 'Title', description: 'The title of the conversation.' }, + summary: { + title: 'Summary', + description: 'A summary of the current conversation. ', + }, + message_count: { + title: 'Message count', + description: 'The count of messages sent in the conversation by both the bot and user(s). Type: int', + }, + participant_count: { + title: 'Participant count', + description: 'The count of users having participated in the conversation, including the bot. Type: int', + }, + isDirty: { + title: 'Dirty', + description: 'Signifies whether the conversation has had a new message since last refresh', + }, + }, + }, + // TODO: replace this event with a workflow + events: { updateTitleAndSummary: { schema: z.object({}) } }, +}) diff --git a/plugins/conversation-insights/src/index.ts b/plugins/conversation-insights/src/index.ts new file mode 100644 index 00000000000..e4aaefbb9b3 --- /dev/null +++ b/plugins/conversation-insights/src/index.ts @@ -0,0 +1,74 @@ +import * as bp from '.botpress' + +const plugin = new bp.Plugin({ + actions: {}, +}) + +// TODO: generate a type for CommonProps in the CLI / SDK +type CommonProps = + | bp.HookHandlerProps['after_incoming_message'] + | bp.HookHandlerProps['after_outgoing_message'] + | bp.EventHandlerProps + +plugin.on.afterIncomingMessage('*', async (props) => { + const { conversation } = await props.client.getConversation({ id: props.data.conversationId }) + await _onNewMessage({ ...props, conversation, isDirty: true }) + return undefined +}) + +plugin.on.afterOutgoingMessage('*', async (props) => { + const { conversation } = await props.client.getConversation({ id: props.data.message.conversationId }) + await _onNewMessage({ ...props, conversation, isDirty: false }) + return undefined +}) + +plugin.on.event('updateTitleAndSummary', async (props) => { + const conversations = await props.client.listConversations({ tags: { isDirty: 'true' } }) + + for (const conversation of conversations.conversations) { + const messages = await props.client.listMessages({ conversationId: conversation.id }) + const newMessages = messages.messages.map((message) => message.payload.text) + await _updateTitleAndSummary({ ...props, conversationId: conversation.id, messages: newMessages }) + } +}) + +type OnNewMessageProps = CommonProps & { + conversation: bp.ClientOutputs['getConversation']['conversation'] + isDirty: boolean +} +const _onNewMessage = async (props: OnNewMessageProps) => { + const message_count = props.conversation.tags.message_count ? parseInt(props.conversation.tags.message_count) + 1 : 1 + + const participant_count = await props.client + .listParticipants({ id: props.conversation.id }) + .then(({ participants }) => participants.length) + + const tags = { + message_count: message_count.toString(), + participant_count: participant_count.toString(), + isDirty: props.isDirty ? 'true' : 'false', + } + + await props.client.updateConversation({ + id: props.conversation.id, + tags, + }) +} + +type UpdateTitleAndSummaryProps = CommonProps & { + conversationId: string + messages: string[] +} +const _updateTitleAndSummary = async (props: UpdateTitleAndSummaryProps) => { + await props.client.updateConversation({ + id: props.conversationId, + tags: { + // TODO: use the cognitive client / service to generate a title and summary + title: 'The conversation title!', + summary: 'This is normally where the conversation summary would be.', + isDirty: 'false', + }, + }) +} + +export default plugin diff --git a/plugins/conversation-insights/tsconfig.json b/plugins/conversation-insights/tsconfig.json new file mode 100644 index 00000000000..da5cae6f64b --- /dev/null +++ b/plugins/conversation-insights/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "baseUrl": ".", + "outDir": "dist" + }, + "include": [".botpress/**/*", "definitions/**/*", "src/**/*", "plugin.definition.ts"] +} diff --git a/plugins/file-synchronizer/tsconfig.json b/plugins/file-synchronizer/tsconfig.json index df65383c598..da5cae6f64b 100644 --- a/plugins/file-synchronizer/tsconfig.json +++ b/plugins/file-synchronizer/tsconfig.json @@ -4,5 +4,5 @@ "baseUrl": ".", "outDir": "dist" }, - "include": [".botpress/**/*", "src/**/*"] + "include": [".botpress/**/*", "definitions/**/*", "src/**/*", "plugin.definition.ts"] } diff --git a/plugins/hitl/tsconfig.json b/plugins/hitl/tsconfig.json index df65383c598..da5cae6f64b 100644 --- a/plugins/hitl/tsconfig.json +++ b/plugins/hitl/tsconfig.json @@ -4,5 +4,5 @@ "baseUrl": ".", "outDir": "dist" }, - "include": [".botpress/**/*", "src/**/*"] + "include": [".botpress/**/*", "definitions/**/*", "src/**/*", "plugin.definition.ts"] } diff --git a/plugins/knowledge/tsconfig.json b/plugins/knowledge/tsconfig.json index df65383c598..da5cae6f64b 100644 --- a/plugins/knowledge/tsconfig.json +++ b/plugins/knowledge/tsconfig.json @@ -4,5 +4,5 @@ "baseUrl": ".", "outDir": "dist" }, - "include": [".botpress/**/*", "src/**/*"] + "include": [".botpress/**/*", "definitions/**/*", "src/**/*", "plugin.definition.ts"] } diff --git a/plugins/logger/tsconfig.json b/plugins/logger/tsconfig.json index df65383c598..da5cae6f64b 100644 --- a/plugins/logger/tsconfig.json +++ b/plugins/logger/tsconfig.json @@ -4,5 +4,5 @@ "baseUrl": ".", "outDir": "dist" }, - "include": [".botpress/**/*", "src/**/*"] + "include": [".botpress/**/*", "definitions/**/*", "src/**/*", "plugin.definition.ts"] } diff --git a/plugins/personality/tsconfig.json b/plugins/personality/tsconfig.json index df8f1eeaa6f..da5cae6f64b 100644 --- a/plugins/personality/tsconfig.json +++ b/plugins/personality/tsconfig.json @@ -4,5 +4,5 @@ "baseUrl": ".", "outDir": "dist" }, - "include": [".botpress/**/*", "src/**/*", "plugin.definition.ts"] + "include": [".botpress/**/*", "definitions/**/*", "src/**/*", "plugin.definition.ts"] } diff --git a/plugins/synchronizer/tsconfig.json b/plugins/synchronizer/tsconfig.json index df65383c598..da5cae6f64b 100644 --- a/plugins/synchronizer/tsconfig.json +++ b/plugins/synchronizer/tsconfig.json @@ -4,5 +4,5 @@ "baseUrl": ".", "outDir": "dist" }, - "include": [".botpress/**/*", "src/**/*"] + "include": [".botpress/**/*", "definitions/**/*", "src/**/*", "plugin.definition.ts"] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5cf0d117bc7..675d1d5d5e0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2584,6 +2584,12 @@ importers: specifier: 0.0.1 version: 0.0.1 + plugins/conversation-insights: + dependencies: + '@botpress/sdk': + specifier: workspace:* + version: link:../../packages/sdk + plugins/file-synchronizer: dependencies: '@botpress/client':