diff --git a/.bumpversion.cfg b/.bumpversion.cfg new file mode 100644 index 0000000..86a2ebd --- /dev/null +++ b/.bumpversion.cfg @@ -0,0 +1,6 @@ +[bumpversion] +current_version = 0.1.5 +commit = True +tag = True + +[bumpversion:file:pyproject.toml] diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 8da4d7f..015c31a 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -10,9 +10,11 @@ on: branches: [ "main" ] pull_request: branches: [ "main" ] + types: [closed] # Run after PR is merged jobs: build: + if: ${{ github.event.pull_request.merged == true }} runs-on: ubuntu-latest environment: release permissions: @@ -30,23 +32,55 @@ jobs: uses: actions/setup-python@v3 with: python-version: ${{ matrix.python-version }} + - name: Install dependencies run: | python -m pip install --upgrade pip python -m pip install flake8 pytest pip install poetry poetry install --with dev + + - name: Set up Git credentials for pushing + run: git remote set-url origin https://x-access-token:${GITHUB_TOKEN}@github.com/${{ github.repository }}.git + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Configure Git user + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "GitHub Actions" + + - name: Auto-increment version + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "GitHub Actions" + bump2version patch --allow-dirty + poetry lock --no-update + cat pyproject.toml | grep version + + - name: Commit and Push changes + run: | + git add pyproject.toml + git add poetry.lock + git commit -m "Increment version [skip ci]" || echo "No changes to commit" + git push origin main + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Test with pytest env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} run: | poetry run pytest + - name: Build package run: | poetry build diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..5ab4637 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,44 @@ +name: Tests + +on: + push: + branches: [ "main", "dev" ] + pull_request: + branches: [ "main", "dev" ] + +jobs: + build: + runs-on: ubuntu-latest + environment: release + permissions: + # IMPORTANT: this permission is mandatory for trusted publishing + id-token: write + + strategy: + fail-fast: false + matrix: + python-version: ["3.11"] + + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install flake8 pytest + pip install poetry + poetry install --with dev + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Test with pytest + env: + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + run: | + poetry run pytest diff --git a/.gitignore b/.gitignore index 4f07ec0..be66196 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ /celai_chatwoot/telegram woot_test.py /.vscode -__pycache__ \ No newline at end of file +__pycache__ +DEV.md \ No newline at end of file diff --git a/celai_chatwoot/connector/bot_utils.py b/celai_chatwoot/connector/bot_utils.py index 391be26..fbab5b7 100644 --- a/celai_chatwoot/connector/bot_utils.py +++ b/celai_chatwoot/connector/bot_utils.py @@ -107,12 +107,26 @@ async def upsert_bot(self, if bot: bot_id = bot["id"] - log.debug(f"Bot {name} id:{bot_id} found. Updating bot with webhook url {outgoing_url}") + log.debug(f"Bot '{name}' id:{bot_id} found. Updating bot with webhook url {outgoing_url}") return await self.update_agent_bot(bot_id, name, description, outgoing_url) - log.warning(f"Bot {name} not found. Creating bot with webhook url {outgoing_url}") + log.warning(f"Bot '{name}' not found. Creating bot with webhook url {outgoing_url}") return await self.create_agent_bot(name, description, outgoing_url) + + # add bot to inbox https://app.chatwoot.com/api/v1/accounts/{account_id}/inboxes/{id}/set_agent_bot + async def assign_bot_to_inbox(self, inbox_id: str | int, agent_bot_id: str | int) -> Dict[str, Any]: + url = f"{self.base_url}/api/v1/accounts/{self.account_id}/inboxes/{inbox_id}/set_agent_bot" + log.debug(f"Adding bot to inbox at Chatwoot url: {url}") + + payload = { + 'agent_bot': agent_bot_id + } + + async with aiohttp.ClientSession() as session: + async with session.post(url, json=payload, headers=self.headers) as response: + response_data = await response.json() + return response_data @@ -148,13 +162,21 @@ async def main(): # find bot by name log.debug(f"Bot found: {bot}") + bot_id = bot["id"] + # list bots + bots = await client.list_agent_bots() + log.debug(f"Bots: {bots}") - - - - - + for b in bots: + # show id, name, description + log.debug(f"Bot: {b['id']} {b['name']} {b['description']}") + + # assign bot to inbox + inbox_id = 1 + bot_id = 1 + await client.assign_bot_to_inbox(inbox_id, bot_id) + print("Done!") asyncio.run(main()) diff --git a/celai_chatwoot/connector/woo_connector.py b/celai_chatwoot/connector/woo_connector.py index cd85532..8efd2cc 100644 --- a/celai_chatwoot/connector/woo_connector.py +++ b/celai_chatwoot/connector/woo_connector.py @@ -32,6 +32,7 @@ def __init__(self, account_id: str, access_key: str, chatwoot_url: str, + inbox_id: str, bot_description: str = "Celai Bot", stream_mode: StreamMode = StreamMode.SENTENCE): log.debug("Creating Chatwoot connector") @@ -48,8 +49,9 @@ def __init__(self, self.bot_name = bot_name self.account_id = account_id self.access_key = access_key + self.inbox_id = inbox_id self.chatwoot_url = chatwoot_url - self.bot_description = bot_description + self.bot_description = bot_description or "Celai generated Bot" def __create_routes(self, router: APIRouter): @@ -243,30 +245,35 @@ def startup(self, context: MessageGatewayContext): # verify if the webhook_url is set and is HTTPS assert context.webhook_url, "webhook_url must be set in the context" assert context.webhook_url.startswith("https"),\ - f"webhook_url must be HTTPS, got: {context.webhook_url}.\ - Be sure that your url is public and has a valid SSL certificate." + (f"webhook_url must be HTTPS, got: {context.webhook_url}" + "Be sure that your url is public and has a valid SSL certificate.") async def update_bot(): - + try: + base_url = f"{context.webhook_url}" + webhook_url = urljoin(base_url, f"{self.router.prefix}/webhook/{self.security_token}") + + log.debug(f"Updating Chatwoot Bot webhook url to: {webhook_url}") - base_url = f"{context.webhook_url}" - webhook_url = urljoin(base_url, f"{self.router.prefix}/webhook/{self.security_token}") - - # webhook_url = f"{context.webhook_url}/{self.router.prefix}/webhook/{self.security_token}" - log.debug(f"Updating Chatwoot Bot webhook url to: {webhook_url}") - # TODO: update chatwoot webhook url by bot name - client = ChatwootAgentsBots( - base_url=self.chatwoot_url, - account_id=self.account_id, - access_key=self.access_key - ) - - res = await client.upsert_bot(name=self.bot_name, - outgoing_url=webhook_url, - description=self.bot_description) - - log.debug(f"Chatwoot Bot updated: {res}") - + client = ChatwootAgentsBots( + base_url=self.chatwoot_url, + account_id=self.account_id, + access_key=self.access_key + ) + + bot = await client.upsert_bot(name=self.bot_name, + outgoing_url=webhook_url, + description=self.bot_description) + + bot_id = bot["id"] + log.debug(f"Chatwoot Bot '{self.bot_name}' id:{bot_id} updated. Adding bot to inbox {self.inbox_id}") + await client.assign_bot_to_inbox(inbox_id=self.inbox_id, agent_bot_id=bot_id) + log.debug(f"Chatwoot Bot '{self.bot_name}' assigned to inbox {self.inbox_id}") + + except Exception as e: + log.error(f"Error updating Chatwoot bot: {e}") + log.exception(e) + raise e try: loop = asyncio.get_running_loop() loop.create_task(update_bot()) diff --git a/examples/test.py b/examples/test.py index e24aeb4..0cf7815 100644 --- a/examples/test.py +++ b/examples/test.py @@ -20,7 +20,7 @@ from cel.gateway.message_gateway import MessageGateway, StreamMode from cel.assistants.macaw.macaw_assistant import MacawAssistant from cel.prompt.prompt_template import PromptTemplate -from cel.gateway.request_context import RequestContext +from cel.assistants.request_context import RequestContext # Setup prompt @@ -39,6 +39,7 @@ @ast.event('message') async def handle_message(session, ctx: RequestContext): log.critical(f"Got message event with message!") + assert isinstance(ctx.connector, WootConnector) if ctx.message.text == "img": await ctx.connector.send_image_message( @@ -46,21 +47,20 @@ async def handle_message(session, ctx: RequestContext): "https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_92x30dp.png", "This is an image" ) - return RequestContext.cancel_response() + return RequestContext.cancel_ai_response() if ctx.message.text == "audio": await ctx.connector.send_audio_message( ctx.lead, "https://www.w3schools.com/html/horse.mp3" ) - return RequestContext.cancel_response() + return RequestContext.cancel_ai_response() # Create the Message Gateway - This component is the core of the assistant # It handles the communication between the assistant and the connectors gateway = MessageGateway( - webhook_url=os.environ.get("WEBHOOK_URL"), assistant=ast, host="127.0.0.1", port=8000 ) @@ -70,6 +70,7 @@ async def handle_message(session, ctx: RequestContext): bot_name="Testing Ale Bot", access_key=os.environ.get("CHATWOOT_ACCESS_KEY"), account_id=os.environ.get("CHATWOOT_ACCOUNT_ID"), + inbox_id=os.environ.get("CHATWOOT_INBOX_ID"), chatwoot_url=os.environ.get("CHATWOOT_URL"), bot_description="This is a test bot", stream_mode=StreamMode.FULL