├── .gitignore ├── LICENSE ├── README.md └── slack-ai ├── .gitignore ├── README.md ├── api ├── .env.example ├── Dockerfile ├── main.py ├── requirements.txt ├── routes │ ├── __init__.py │ ├── admin.py │ └── api.py └── utils │ ├── __init__.py │ ├── app.py │ ├── slack_chunker.py │ └── slack_loader.py ├── docker-compose.yml └── ui ├── .env.local ├── .eslintrc.json ├── .gitignore ├── Dockerfile ├── README.md ├── app ├── admin │ ├── chromadb │ │ └── collections │ │ │ └── [collection_name] │ │ │ └── page.tsx │ └── page.tsx ├── favicon.ico ├── globals.css ├── layout.tsx └── page.tsx ├── components.json ├── components ├── chat.tsx ├── main-nav.tsx ├── slack-channel.tsx └── ui │ ├── accordion.tsx │ ├── avatar.tsx │ ├── button.tsx │ ├── card.tsx │ ├── drawer.tsx │ ├── input.tsx │ ├── scroll-area.tsx │ ├── select.tsx │ ├── separator.tsx │ ├── switch.tsx │ ├── table.tsx │ ├── toast.tsx │ ├── toaster.tsx │ ├── tooltip.tsx │ └── use-toast.ts ├── next.config.js ├── package.json ├── postcss.config.js ├── public ├── slack-ai.png └── slack.png ├── tailwind.config.js ├── tailwind.config.ts ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Embedchain 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Embedchain examples 2 | 3 | This repository contains the list of RAG applications created using Embedchain. 4 | 5 | ## ⚡⚡ Latest 6 | 7 | - Checkout the latest [Slack AI](https://github.com/embedchain/examples/tree/main/slack-ai) -------------------------------------------------------------------------------- /slack-ai/.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | wheels/ 22 | share/python-wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .nox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *.cover 48 | *.py,cover 49 | .hypothesis/ 50 | .pytest_cache/ 51 | cover/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | db.sqlite3 61 | db.sqlite3-journal 62 | 63 | # Flask stuff: 64 | instance/ 65 | .webassets-cache 66 | 67 | # Scrapy stuff: 68 | .scrapy 69 | 70 | # Sphinx documentation 71 | docs/_build/ 72 | 73 | # PyBuilder 74 | .pybuilder/ 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | # For a library or package, you might want to ignore these files since the code is 86 | # intended to run in multiple environments; otherwise, check them in: 87 | # .python-version 88 | 89 | # pipenv 90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 93 | # install all needed dependencies. 94 | #Pipfile.lock 95 | 96 | # poetry 97 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 98 | # This is especially recommended for binary packages to ensure reproducibility, and is more 99 | # commonly ignored for libraries. 100 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 101 | #poetry.lock 102 | 103 | # pdm 104 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 105 | #pdm.lock 106 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 107 | # in version control. 108 | # https://pdm.fming.dev/#use-with-ide 109 | .pdm.toml 110 | 111 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 112 | __pypackages__/ 113 | 114 | # Celery stuff 115 | celerybeat-schedule 116 | celerybeat.pid 117 | 118 | # SageMath parsed files 119 | *.sage.py 120 | 121 | # Environments 122 | .env 123 | .venv 124 | env/ 125 | venv/ 126 | ENV/ 127 | env.bak/ 128 | venv.bak/ 129 | 130 | # Spyder project settings 131 | .spyderproject 132 | .spyproject 133 | 134 | # Rope project settings 135 | .ropeproject 136 | 137 | # mkdocs documentation 138 | /site 139 | 140 | # mypy 141 | .mypy_cache/ 142 | .dmypy.json 143 | dmypy.json 144 | 145 | # Pyre type checker 146 | .pyre/ 147 | 148 | # pytype static type analyzer 149 | .pytype/ 150 | 151 | # Cython debug symbols 152 | cython_debug/ 153 | 154 | .DS_Store 155 | db/ 156 | backend/static 157 | backend/media 158 | backend/db.sqlite3 159 | 160 | .terraform 161 | 162 | db/ -------------------------------------------------------------------------------- /slack-ai/README.md: -------------------------------------------------------------------------------- 1 | # Slack AI 2 | 3 | This directory contains code on how to build your own Slack AI to chat with the unstructured data lying in your slack channels. 4 | 5 | slack ai demo 6 | 7 | 8 | ## Getting started 9 | 10 | Create a slack bot involves 3 steps 11 | 12 | * Create slack user 13 | * Set environment variables 14 | * Run the app locally 15 | 16 | ### Step 1: Create slack user token 17 | 18 | Follow the steps given below to fetch your slack user token to get data through Slack APIs: 19 | 20 | 1. Create a workspace on Slack if you don’t have one already by clicking [here](https://slack.com/intl/en-in/). 21 | 2. Create a new App on your Slack account by going [here](https://api.slack.com/apps). 22 | 3. Select `From Scratch`, then enter the App Name and select your workspace. 23 | 4. Navigate to `OAuth & Permissions` tab from the left sidebar and go to the `scopes` section. Add the following scopes under `User Token Scopes`: 24 | 25 | ``` 26 | # Following scopes are needed for reading channel history 27 | channels:history 28 | channels:read 29 | 30 | # Following scopes are needed to fetch list of channels from slack 31 | groups:read 32 | mpim:read 33 | im:read 34 | ``` 35 | 36 | 5. Click on the `Install to Workspace` button under `OAuth Tokens for Your Workspace` section in the same page and install the app in your slack workspace. 37 | 6. After installing the app you will see the `User OAuth Token`, save that token as you will need to configure it as `SLACK_USER_TOKEN` for this demo. 38 | 39 | ### Step 2: Set environment variables 40 | 41 | Navigate to `api` folder and set your `HUGGINGFACE_ACCESS_TOKEN` and `SLACK_USER_TOKEN` in `.env.example` file. Then rename the `.env.example` file to `.env`. 42 | 43 | 44 | ### Step 3: Run app locally 45 | 46 | Follow the instructions given below to run app locally based on your development setup (with docker or without docker): 47 | 48 | #### With docker 49 | 50 | ```bash 51 | docker-compose build 52 | ec start --docker 53 | ``` 54 | 55 | #### Without docker 56 | 57 | ```bash 58 | ec install-reqs 59 | ec start 60 | ``` 61 | 62 | Finally, you will have the Slack AI frontend running on http://localhost:3000. You can also access the REST APIs on http://localhost:8000. 63 | 64 | ## Credits 65 | 66 | This demo was built using the Embedchain's [full stack demo template](https://docs.embedchain.ai/get-started/full-stack). Follow the instructions [given here](https://docs.embedchain.ai/get-started/full-stack) to create your own full stack RAG application. 67 | -------------------------------------------------------------------------------- /slack-ai/api/.env.example: -------------------------------------------------------------------------------- 1 | SLACK_USER_TOKEN= 2 | # set your openai token if you want to use openai LLMs 3 | # OPENAI_API_KEY= 4 | # Set your HuggingFace access token if you want to use open source LLMs 5 | HUGGINGFACE_ACCESS_TOKEN= 6 | -------------------------------------------------------------------------------- /slack-ai/api/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11-slim 2 | 3 | WORKDIR /app 4 | 5 | COPY requirements.txt /app/ 6 | 7 | RUN pip install -r requirements.txt 8 | 9 | COPY . /app 10 | 11 | EXPOSE 8000 12 | 13 | CMD ["python", "-m", "main"] 14 | -------------------------------------------------------------------------------- /slack-ai/api/main.py: -------------------------------------------------------------------------------- 1 | import uvicorn 2 | from dotenv import load_dotenv 3 | from fastapi import FastAPI 4 | 5 | from routes import admin, api 6 | 7 | load_dotenv(".env") 8 | 9 | app = FastAPI(title="Embedchain API") 10 | 11 | app.include_router(api.router) 12 | app.include_router(admin.router) 13 | 14 | if __name__ == "__main__": 15 | uvicorn.run("main:app", host="0.0.0.0", port=8000, log_level="info", 16 | reload=True, timeout_keep_alive=600) 17 | -------------------------------------------------------------------------------- /slack-ai/api/requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi==0.108.0 2 | uvicorn==0.25.0 3 | embedchain[slack] 4 | beautifulsoup4 5 | sentence-transformers 6 | -------------------------------------------------------------------------------- /slack-ai/api/routes/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mem0ai/examples/f33f9c347abdab3a18d833f8392cbb4f7643d2ab/slack-ai/api/routes/__init__.py -------------------------------------------------------------------------------- /slack-ai/api/routes/admin.py: -------------------------------------------------------------------------------- 1 | import chromadb 2 | from chromadb.config import Settings 3 | from fastapi import APIRouter 4 | 5 | router = APIRouter() 6 | 7 | 8 | chroma_settings = Settings( 9 | anonymized_telemetry=False, 10 | persist_directory="db", 11 | allow_reset=False, 12 | is_persistent=True, 13 | ) 14 | client = chromadb.Client(chroma_settings) 15 | 16 | 17 | @router.get("/api/v1/admin/collections") 18 | async def get_all_collections(): 19 | # Currently only works for ChromaDB but can be extended easily 20 | # for other vector stores as well 21 | collections = client.list_collections() 22 | responses = [c.dict() for c in collections] 23 | return responses 24 | 25 | 26 | # TODO(deshraj): Add pagination and make this endpoint agnostic to the vector store 27 | @router.get("/api/v1/admin/collections/chromadb/{collection_name}") 28 | async def get_collection_details(collection_name: str): 29 | collection = client.get_collection(collection_name) 30 | collection_data = collection.get() 31 | metadatas, documents = collection_data['metadatas'], collection_data['documents'] 32 | collated_data = [] 33 | for i in zip(metadatas, documents): 34 | collated_data.append({ 35 | "metadata": i[0], 36 | "document": i[1] 37 | }) 38 | response = {"details": collection.dict(), "data": collated_data} 39 | return response 40 | -------------------------------------------------------------------------------- /slack-ai/api/routes/api.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from fastapi import APIRouter, Query, responses 4 | from pydantic import BaseModel 5 | 6 | from utils.app import chunker, ec_app, loader 7 | 8 | router = APIRouter() 9 | 10 | 11 | class SourceModel(BaseModel): 12 | channel: str 13 | 14 | 15 | class QuestionModel(BaseModel): 16 | question: str 17 | session_id: str 18 | 19 | 20 | @router.post("/api/v1/add") 21 | async def add_channel_data(source_model: SourceModel): 22 | """ 23 | Adds a new source to the Embedchain app. 24 | Expects a JSON with a "channel" key. 25 | """ 26 | channel = source_model.channel.replace("#", "").strip() 27 | try: 28 | ec_app.add(f"in:{channel}", data_type="slack", loader=loader, chunker=chunker) 29 | return {"message": f"Data for '{channel}' added successfully."} 30 | except Exception as e: 31 | response = f"An error occurred: Error message: {str(e)}. Contact Embedchain founders on Slack: https://embedchain.com/slack or Discord: https://embedchain.com/discord" # noqa:E501 32 | return {"message": response} 33 | 34 | 35 | @router.get("/api/v1/chat") 36 | async def handle_chat(query: str, session_id: str = Query(None)): 37 | """ 38 | Handles a chat request to the Embedchain app. 39 | Accepts 'query' and 'session_id' as query parameters. 40 | """ 41 | try: 42 | answer, metadata = ec_app.chat(query, session_id=session_id, citations=True) 43 | citations = [] 44 | for i in metadata: 45 | citations.append(i[1]["url"]) 46 | response = {"answer": answer, "citations": list(set(citations))} 47 | except Exception as e: 48 | response = f"An error occurred: Error message: {str(e)}. Contact Embedchain founders on Slack: https://embedchain.com/slack or Discord: https://embedchain.com/discord" # noqa:E501 49 | return {"response": response} 50 | 51 | 52 | @router.get("/api/v1/channels") 53 | async def get_channels(): 54 | """ 55 | Handles a channel list request to the Embedchain app. 56 | Accepts 'user_token' as a query parameter. 57 | """ 58 | try: 59 | # List all channels in slack: https://api.slack.com/methods/conversations.list 60 | response = loader.get_channels(os.environ["SLACK_USER_TOKEN"]) 61 | return {"message": response} 62 | except Exception as e: 63 | response = f"An error occurred: Error message: {str(e)}. Contact Embedchain founders on Slack: https://embedchain.com/slack or Discord: https://embedchain.com/discord" # noqa:E501 64 | return {"message": response} 65 | 66 | 67 | @router.get("/api/v1/reset") 68 | async def reset_app(): 69 | """ 70 | Resets the Embedchain app. 71 | """ 72 | try: 73 | ec_app.reset() 74 | return {"message": "Embedchain app reset successfully."} 75 | except Exception: 76 | response = "An error occurred: Error message: {str(e)}. Contact Embedchain founders on Slack: https://embedchain.com/slack or Discord: https://embedchain.com/discord" # noqa:E501 77 | return {"message": response} 78 | 79 | 80 | @router.get("/") 81 | async def root(): 82 | return responses.RedirectResponse(url="/docs") 83 | -------------------------------------------------------------------------------- /slack-ai/api/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mem0ai/examples/f33f9c347abdab3a18d833f8392cbb4f7643d2ab/slack-ai/api/utils/__init__.py -------------------------------------------------------------------------------- /slack-ai/api/utils/app.py: -------------------------------------------------------------------------------- 1 | from dotenv import load_dotenv 2 | from embedchain import App 3 | from embedchain.models.data_type import DataType 4 | 5 | from .slack_chunker import SlackChunker 6 | from .slack_loader import SlackLoader 7 | 8 | load_dotenv(".env") 9 | 10 | loader = SlackLoader() 11 | chunker = SlackChunker() 12 | chunker.set_data_type(DataType.SLACK) 13 | 14 | # Uncomment this line if you want to use HuggingFace LLMs 15 | app_config = { 16 | "app": { 17 | "config": { 18 | "name": "slack-ai" 19 | } 20 | }, 21 | "llm": { 22 | "provider": "huggingface", 23 | "config": { 24 | "model": "mistralai/Mixtral-8x7B-Instruct-v0.1", 25 | "temperature": 0.1, 26 | "max_tokens": 250, 27 | "top_p": 0.1 28 | } 29 | }, 30 | "embedder": { 31 | "provider": "huggingface", 32 | "config": { 33 | "model": "sentence-transformers/all-mpnet-base-v2" 34 | } 35 | } 36 | } 37 | 38 | # app_config = { 39 | # "app": { 40 | # "config": { 41 | # "id": "slack-ai-app", 42 | # } 43 | # }, 44 | # "llm": { 45 | # "provider": "openai", 46 | # "config": { 47 | # "model": "gpt-3.5-turbo-1106", 48 | # }, 49 | # }, 50 | # } 51 | 52 | 53 | ec_app = App.from_config(config=app_config) 54 | -------------------------------------------------------------------------------- /slack-ai/api/utils/slack_chunker.py: -------------------------------------------------------------------------------- 1 | from embedchain.chunkers.base_chunker import BaseChunker 2 | 3 | 4 | class SlackChunker(BaseChunker): 5 | def __init__(self): 6 | text_splitter = lambda x: x # noqa: E731 7 | super().__init__(text_splitter) 8 | 9 | def get_chunks(self, content): 10 | return [content] -------------------------------------------------------------------------------- /slack-ai/api/utils/slack_loader.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import logging 3 | import os 4 | import ssl 5 | from typing import Any, Dict, Optional 6 | from urllib.parse import parse_qs, urlparse 7 | 8 | import certifi 9 | from embedchain.loaders.base_loader import BaseLoader 10 | from embedchain.utils.misc import clean_string 11 | 12 | 13 | def get_thread_ts(url, parent_ts): 14 | parsed_url = urlparse(url) 15 | query_params = parse_qs(parsed_url.query) 16 | thread_ts = query_params.get("thread_ts", [None])[0] 17 | return thread_ts if thread_ts else parent_ts 18 | 19 | 20 | SLACK_API_BASE_URL = "https://www.slack.com/api/" 21 | 22 | 23 | class SlackLoader(BaseLoader): 24 | def __init__(self, config: Optional[Dict[str, Any]] = None): 25 | super().__init__() 26 | 27 | self.config = config if config else {} 28 | 29 | if "base_url" not in self.config: 30 | self.config["base_url"] = SLACK_API_BASE_URL 31 | 32 | self.client = None 33 | self._setup_loader(self.config) 34 | 35 | def _setup_loader(self, config: Dict[str, Any]): 36 | try: 37 | from slack_sdk import WebClient 38 | except ImportError as e: 39 | raise ImportError( 40 | "Slack loader requires extra dependencies. \ 41 | Install with `pip install --upgrade embedchain[slack]`" 42 | ) from e 43 | 44 | if os.getenv("SLACK_USER_TOKEN") is None: 45 | raise ValueError( 46 | "SLACK_USER_TOKEN environment variables not provided. Check `https://docs.embedchain.ai/data-sources/slack` to learn more." # noqa:E501 47 | ) 48 | 49 | logging.info(f"Creating Slack Loader with config: {config}") 50 | # get slack client config params 51 | slack_bot_token = os.getenv("SLACK_USER_TOKEN") 52 | ssl_cert = ssl.create_default_context(cafile=certifi.where()) 53 | base_url = config.get("base_url", SLACK_API_BASE_URL) 54 | headers = config.get("headers") 55 | # for Org-Wide App 56 | team_id = config.get("team_id") 57 | 58 | self.client = WebClient( 59 | token=slack_bot_token, 60 | base_url=base_url, 61 | ssl=ssl_cert, 62 | headers=headers, 63 | team_id=team_id, 64 | ) 65 | logging.info("Slack Loader setup successful!") 66 | 67 | def _check_query(self, query): 68 | if not isinstance(query, str): 69 | raise ValueError( 70 | f"Invalid query passed to Slack loader, found: {query}. Check `https://docs.embedchain.ai/data-sources/slack` to learn more." # noqa:E501 71 | ) 72 | 73 | def load_data(self, query): 74 | self._check_query(query) 75 | try: 76 | message_data = [] 77 | data = [] 78 | data_content = [] 79 | 80 | logging.info(f"Searching slack conversations for {query=}") 81 | results = self.client.search_messages( 82 | query=query, 83 | sort="timestamp", 84 | sort_dir="desc", 85 | count=self.config.get("count", 100), 86 | ) 87 | messages = results.get("messages") 88 | num_message = messages.get("total") 89 | total_pages = messages.get("pagination").get("page_count") 90 | current_page = messages.get("pagination").get("page") 91 | print(f"Collecting {num_message} messages for {query=}, from {total_pages=}") 92 | message_data.extend(messages.get("matches", [])) 93 | for page in range(current_page + 1, total_pages + 1): 94 | results = self.client.search_messages( 95 | query=query, sort="timestamp", sort_dir="desc", count=self.config.get("count", 100), page=page 96 | ) 97 | messages = results.get("messages") 98 | message_data.extend(messages.get("matches", [])) 99 | 100 | # group thread messages 101 | print("Grouping messages in threads...") 102 | message_threads = {} 103 | for message in message_data: 104 | url = message.get("permalink") 105 | text = message.get("text") 106 | content = clean_string(text) 107 | 108 | message_meta_data_keys = ["iid", "team", "ts", "type", "user", "username"] 109 | meta_data = {} 110 | for key in message.keys(): 111 | if key in message_meta_data_keys: 112 | meta_data[key] = message.get(key) 113 | meta_data.update({"url": url}) 114 | thread_ts = get_thread_ts(url, meta_data.get("ts")) 115 | if thread_ts not in message_threads: 116 | message_threads[thread_ts] = [(content, meta_data, meta_data.get("ts"))] 117 | else: 118 | message_threads[thread_ts].append((content, meta_data, meta_data.get("ts"))) 119 | 120 | for url, messages in message_threads.items(): 121 | messages = sorted(messages, key=lambda x: x[2]) 122 | content = "\n".join([f"@{message[1].get('username')}: {message[0]}" for message in messages]) 123 | meta_data = messages[0][1] 124 | data.append( 125 | { 126 | "content": content, 127 | "meta_data": meta_data, 128 | } 129 | ) 130 | data_content.append(content) 131 | 132 | doc_id = hashlib.md5((query + ", ".join(data_content)).encode()).hexdigest() 133 | return { 134 | "doc_id": doc_id, 135 | "data": data, 136 | } 137 | except Exception as e: 138 | logging.warning(f"Error in loading slack data: {e}") 139 | raise ValueError( 140 | f"Error in loading slack data: {e}. Check `https://docs.embedchain.ai/components/data-sources/slack` to learn more." # noqa:E501 141 | ) from e 142 | 143 | def get_channels(self, user_token): 144 | try: 145 | results = self.client.conversations_list( 146 | token=user_token, 147 | type="public_channel", 148 | limit=100) 149 | channels = results.get("channels") 150 | public_channels = [] 151 | for channel in channels: 152 | if channel.get("is_channel", False): 153 | public_channels.append(channel) 154 | return public_channels 155 | except Exception as e: 156 | logging.warning(f"Error in loading slack channels: {e}") 157 | raise ValueError( 158 | f"Error in loading slack channels: {e}. Check `https://docs.embedchain.ai/components/data-sources/slack` to learn more." # noqa:E501 159 | ) from e 160 | -------------------------------------------------------------------------------- /slack-ai/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | 3 | services: 4 | frontend: 5 | build: 6 | context: ui/ 7 | dockerfile: Dockerfile 8 | volumes: 9 | - ./ui:/app 10 | - /app/node_modules 11 | ports: 12 | - "3000:3000" 13 | environment: 14 | - NODE_ENV=development 15 | - NEXT_PUBLIC_API_ENDPOINT=http://backend:8000 16 | depends_on: 17 | - backend 18 | 19 | backend: 20 | build: 21 | context: api/ 22 | dockerfile: Dockerfile 23 | volumes: 24 | - ./api:/app 25 | env_file: 26 | - api/.env 27 | ports: 28 | - "8000:8000" 29 | environment: 30 | - PYTHONUNBUFFERED=1 31 | -------------------------------------------------------------------------------- /slack-ai/ui/.env.local: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_API_ENDPOINT=http://localhost:8000 2 | -------------------------------------------------------------------------------- /slack-ai/ui/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /slack-ai/ui/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # vercel 28 | .vercel 29 | 30 | # typescript 31 | *.tsbuildinfo 32 | next-env.d.ts 33 | -------------------------------------------------------------------------------- /slack-ai/ui/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:latest 2 | 3 | WORKDIR /app 4 | 5 | COPY package.json yarn.lock ./ 6 | 7 | RUN yarn install 8 | 9 | COPY . . 10 | 11 | EXPOSE 3000 12 | 13 | CMD ["yarn", "dev"] 14 | -------------------------------------------------------------------------------- /slack-ai/ui/README.md: -------------------------------------------------------------------------------- 1 | This is Embedchain Admin repository. 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | ``` 14 | 15 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 16 | 17 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 18 | 19 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 20 | 21 | ## Learn More 22 | 23 | To learn more about Next.js, take a look at the following resources: 24 | 25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 27 | 28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 29 | 30 | ## Deploy on Vercel 31 | 32 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 33 | 34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 35 | -------------------------------------------------------------------------------- /slack-ai/ui/app/admin/chromadb/collections/[collection_name]/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useState, useEffect } from "react"; 4 | import { Maximize } from "lucide-react"; 5 | import { Separator } from "@/components/ui/separator"; 6 | 7 | import { 8 | Drawer, 9 | DrawerContent, 10 | DrawerHeader, 11 | DrawerTitle, 12 | DrawerTrigger, 13 | } from "@/components/ui/drawer"; 14 | 15 | import { 16 | Table, 17 | TableBody, 18 | TableCell, 19 | TableHead, 20 | TableHeader, 21 | TableRow, 22 | } from "@/components/ui/table"; 23 | import Link from "next/link"; 24 | 25 | export default function Page({ 26 | params, 27 | }: { 28 | params: { collection_name: string }; 29 | }) { 30 | const [collectionData, setCollectionData] = useState([]); 31 | console.log(params); 32 | 33 | useEffect(() => { 34 | fetch(`/api/v1/admin/collections/chromadb/${params.collection_name}`) 35 | .then((response) => response.json()) 36 | .then((data) => { 37 | setCollectionData(data); 38 | }); 39 | }, []); 40 | 41 | return ( 42 |
43 |
44 |
45 |
46 |
47 | {" "} 48 |

49 | Collection: {params.collection_name} 50 |

51 |
52 |
53 |

54 | List of document chunks present in your collection:{" "} 55 | '{params.collection_name}' in 56 | chromadb vector store. 57 |

58 | 59 | 60 | 61 | 62 | app id 63 | url 64 | data type 65 | document hash 66 | document chunk 67 | 68 | 69 | 70 | {collectionData?.data && 71 | collectionData.data.map((c, index) => ( 72 | 73 | 74 | {c.metadata.app_id} 75 | 76 | 77 | 78 | {c.metadata.url} 79 | 80 | 81 | 82 | {c.metadata.data_type} 83 | 84 | {c.metadata.hash} 85 | 86 | 87 | 88 | {`${c.document.substring(0, 50)} ....`}{" "} 89 | 94 | 95 | 96 | 97 |
98 | 99 | Document chunk 100 | 101 |
102 |
103 |
104 |
105 | {c.document} 106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 | ))} 116 |
117 |
118 |
119 |
120 |
121 | ); 122 | } 123 | -------------------------------------------------------------------------------- /slack-ai/ui/app/admin/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useState, useEffect } from "react"; 4 | import { Separator } from "@/components/ui/separator"; 5 | import { 6 | Table, 7 | TableBody, 8 | TableCell, 9 | TableHead, 10 | TableHeader, 11 | TableRow, 12 | } from "@/components/ui/table"; 13 | import Link from "next/link"; 14 | 15 | export default function Page() { 16 | const [collections, setCollections] = useState([]); 17 | 18 | useEffect(() => { 19 | fetch("/api/v1/admin/collections") 20 | .then((response) => response.json()) 21 | .then((data) => { 22 | setCollections(data); 23 | }); 24 | }, []); 25 | 26 | return ( 27 |
28 |
29 |
30 |
31 |
32 | {" "} 33 | {/* Right-align heading and tooltip */} 34 |

35 | Vector store collections 36 |

37 |
38 |
39 |

40 | See list of collections/indices present in your vector store. 41 |

42 | 43 | 44 | 45 | 46 | {/* Id */} 47 | Name 48 | Tenant 49 | Database 50 | 51 | 52 | 53 | {collections.map((collection) => ( 54 | 55 | 56 | 59 | {collection.name} 60 | 61 | 62 | {/* {collection.name} */} 63 | {collection?.tenant} 64 | {collection?.database} 65 | 66 | ))} 67 | 68 |
69 |
70 |
71 |
72 | ); 73 | } 74 | -------------------------------------------------------------------------------- /slack-ai/ui/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mem0ai/examples/f33f9c347abdab3a18d833f8392cbb4f7643d2ab/slack-ai/ui/app/favicon.ico -------------------------------------------------------------------------------- /slack-ai/ui/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 100%; 8 | --foreground: 222.2 84% 4.9%; 9 | 10 | --card: 0 0% 100%; 11 | --card-foreground: 222.2 84% 4.9%; 12 | 13 | --popover: 0 0% 100%; 14 | --popover-foreground: 222.2 84% 4.9%; 15 | 16 | --primary: 222.2 47.4% 11.2%; 17 | --primary-foreground: 210 40% 98%; 18 | 19 | --secondary: 210 40% 96.1%; 20 | --secondary-foreground: 222.2 47.4% 11.2%; 21 | 22 | --muted: 210 40% 96.1%; 23 | --muted-foreground: 215.4 16.3% 46.9%; 24 | 25 | --accent: 210 40% 96.1%; 26 | --accent-foreground: 222.2 47.4% 11.2%; 27 | 28 | --destructive: 0 84.2% 60.2%; 29 | --destructive-foreground: 210 40% 98%; 30 | 31 | --border: 214.3 31.8% 91.4%; 32 | --input: 214.3 31.8% 91.4%; 33 | --ring: 222.2 84% 4.9%; 34 | 35 | --radius: 0.5rem; 36 | } 37 | 38 | .dark { 39 | --background: 222.2 84% 4.9%; 40 | --foreground: 210 40% 98%; 41 | 42 | --card: 222.2 84% 4.9%; 43 | --card-foreground: 210 40% 98%; 44 | 45 | --popover: 222.2 84% 4.9%; 46 | --popover-foreground: 210 40% 98%; 47 | 48 | --primary: 210 40% 98%; 49 | --primary-foreground: 222.2 47.4% 11.2%; 50 | 51 | --secondary: 217.2 32.6% 17.5%; 52 | --secondary-foreground: 210 40% 98%; 53 | 54 | --muted: 217.2 32.6% 17.5%; 55 | --muted-foreground: 215 20.2% 65.1%; 56 | 57 | --accent: 217.2 32.6% 17.5%; 58 | --accent-foreground: 210 40% 98%; 59 | 60 | --destructive: 0 62.8% 30.6%; 61 | --destructive-foreground: 210 40% 98%; 62 | 63 | --border: 217.2 32.6% 17.5%; 64 | --input: 217.2 32.6% 17.5%; 65 | --ring: 212.7 26.8% 83.9%; 66 | } 67 | } 68 | 69 | @layer base { 70 | * { 71 | @apply border-border; 72 | } 73 | body { 74 | @apply bg-background text-foreground; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /slack-ai/ui/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import "./globals.css"; 2 | import type { Metadata } from "next"; 3 | import { Inter } from "next/font/google"; 4 | import { MainNav } from "@/components/main-nav"; 5 | import { Toaster } from "@/components/ui/toaster"; 6 | 7 | const inter = Inter({ subsets: ["latin"] }); 8 | 9 | export const metadata: Metadata = { 10 | title: "Embedchain chat", 11 | description: 12 | "Embedchain is an Open Source RAG Framework that makes it easy to create and deploy AI apps", 13 | }; 14 | 15 | export default function RootLayout({ 16 | children, 17 | }: { 18 | children: React.ReactNode; 19 | }) { 20 | return ( 21 | 22 | 23 |
24 |
25 |
26 | 27 |
28 |
29 |
30 |
{children}
31 | 32 | 33 | 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /slack-ai/ui/app/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useState, useEffect } from "react"; 4 | import { Separator } from "@/components/ui/separator"; 5 | import { ChatCard } from "@/components/chat"; 6 | import Link from "next/link"; 7 | import Image from "next/image"; 8 | import { SelectSlackChannel } from "@/components/slack-channel"; 9 | 10 | function generateSessionId() { 11 | return Date.now().toString(); 12 | } 13 | 14 | export default function Page() { 15 | const [sessionId, setSessionId] = useState(null); 16 | 17 | useEffect(() => { 18 | if (!sessionId) { 19 | // Generate a new session ID (you can use any method to generate an ID) 20 | const newSessionId = generateSessionId(); // Replace this with your own logic 21 | setSessionId(newSessionId); 22 | } 23 | }, [sessionId]); 24 | 25 | return ( 26 |
27 |
28 |
29 |
30 |
31 | {" "} 32 | Slack logo 33 |

34 | Slack AI 35 |

36 |
37 |
38 |

39 | Chat with your slack data. Code on{" "} 40 | 44 | GitHub 45 | 46 | . 47 |
48 | Built using{" "} 49 | 54 | Embedchain 55 | {" "} 56 | ❤️ 57 |

58 | 59 | 60 |
61 | {sessionId !== null && } 62 |
63 |
64 |
65 |
66 | ); 67 | } 68 | -------------------------------------------------------------------------------- /slack-ai/ui/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "app/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true 11 | }, 12 | "aliases": { 13 | "components": "@/components", 14 | "utils": "@/lib/utils" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /slack-ai/ui/components/chat.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | 5 | import { Send } from "lucide-react"; 6 | 7 | import { cn } from "@/lib/utils"; 8 | import { Button } from "@/components/ui/button"; 9 | import { Card, CardContent, CardFooter } from "@/components/ui/card"; 10 | import { Input } from "@/components/ui/input"; 11 | import axios from "axios"; 12 | import { ScrollArea } from "@/components/ui/scroll-area"; 13 | import Link from "next/link"; 14 | 15 | interface ChatCardProps { 16 | sessionId: string; 17 | } 18 | 19 | export function ChatCard({ sessionId }: ChatCardProps) { 20 | const [loading, setLoading] = React.useState(false); 21 | const [messages, setMessages] = React.useState([ 22 | { 23 | role: "agent", 24 | content: { 25 | question: 26 | "Hi, I am your Slack AI Assistant. Start asking questions related to your slack channel.", 27 | }, 28 | }, 29 | ]); 30 | 31 | const sendQuery = async (query: string, sessionId: string) => { 32 | try { 33 | const response = await axios.get( 34 | "/api/v1/chat?query=" + query + "&session_id=" + sessionId, 35 | ); 36 | setMessages((prevMessages) => [ 37 | ...prevMessages, 38 | { 39 | role: "agent", 40 | content: { 41 | answer: response?.data?.response?.answer, 42 | citations: response?.data?.response?.citations, 43 | }, 44 | }, 45 | ]); 46 | } catch (error) { 47 | console.log("Error getting response from bot. Please try again."); 48 | } 49 | }; 50 | 51 | const sendChatMessage = async (query: string, sessionId: string) => { 52 | await sendQuery(query, sessionId); 53 | }; 54 | 55 | return ( 56 | <> 57 | 58 | 59 | 60 |
61 | {messages.length && 62 | messages.map((message, index) => ( 63 |
72 | {message.content.question 73 | ? message.content.question 74 | : message.content.answer} 75 |
76 | {message.content?.citations && ( 77 |
78 |

Sources:

79 | {message.content.citations.map((citation, index) => ( 80 | 86 | {citation} 87 | 88 | ))} 89 |
90 | )} 91 |
92 | ))} 93 |
94 |
95 |
96 | 97 |
{ 99 | event.preventDefault(); 100 | const currentMessage = event.currentTarget.message.value; 101 | event.target.message.value = ""; 102 | if (currentMessage === "") { 103 | return; 104 | } 105 | setMessages([ 106 | ...messages, 107 | { 108 | role: "user", 109 | content: { question: currentMessage }, 110 | }, 111 | ]); 112 | await sendChatMessage(currentMessage, sessionId); 113 | }} 114 | className="flex w-full items-center space-x-2" 115 | > 116 | 123 | 131 |
132 |
133 |
134 | 135 | ); 136 | } 137 | -------------------------------------------------------------------------------- /slack-ai/ui/components/main-nav.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import Link from "next/link"; 3 | 4 | import { cn } from "@/lib/utils"; 5 | 6 | export function MainNav({ 7 | className, 8 | ...props 9 | }: React.HTMLAttributes) { 10 | return ( 11 | 37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /slack-ai/ui/components/slack-channel.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import axios from "axios"; 5 | import { useState, useEffect } from "react"; 6 | import { Button } from "@/components/ui/button"; 7 | import { toast } from "@/components/ui/use-toast"; 8 | import { ResetIcon } from "@radix-ui/react-icons"; 9 | 10 | import { 11 | Select, 12 | SelectContent, 13 | SelectItem, 14 | SelectTrigger, 15 | SelectValue, 16 | } from "@/components/ui/select"; 17 | 18 | export function SelectSlackChannel() { 19 | const [channels, setChannels] = useState([]); 20 | const [selectedChannel, setSelectedChannel] = useState(""); 21 | const [isLoading, setIsLoading] = useState(false); 22 | 23 | const addChannel = async () => { 24 | setIsLoading(true); 25 | const payload = { 26 | channel: selectedChannel, 27 | }; 28 | 29 | try { 30 | await axios.post("/api/v1/add", payload); 31 | setIsLoading(false); 32 | toast({ 33 | title: "Success", 34 | description: "Added data from channel " + selectedChannel, 35 | }); 36 | } catch (error) { 37 | setIsLoading(false); 38 | toast({ 39 | title: "Failed", 40 | description: "Failed to add data from channel " + selectedChannel, 41 | }); 42 | } 43 | }; 44 | 45 | const resetApp = async () => { 46 | try { 47 | await axios.get("/api/v1/reset"); 48 | toast({ 49 | title: "Success", 50 | description: "Successfully reset the app", 51 | }); 52 | } catch (error) { 53 | toast({ 54 | title: "Failed", 55 | description: "Failed to reset the app", 56 | }); 57 | } 58 | }; 59 | useEffect(() => { 60 | const getChannels = async () => { 61 | try { 62 | const response = await axios.get("/api/v1/channels"); 63 | const channelNames = response?.data?.message?.map((m) => "# " + m.name); 64 | setChannels(channelNames); 65 | } catch (error) { 66 | console.log("Error getting channels from Slack. Please try again."); 67 | } 68 | }; 69 | 70 | getChannels(); 71 | }, []); 72 | 73 | return ( 74 |
75 | 87 | 94 | 97 |
98 | ); 99 | } 100 | -------------------------------------------------------------------------------- /slack-ai/ui/components/ui/accordion.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as AccordionPrimitive from "@radix-ui/react-accordion"; 5 | import { ChevronDownIcon } from "@radix-ui/react-icons"; 6 | 7 | import { cn } from "@/lib/utils"; 8 | 9 | const Accordion = AccordionPrimitive.Root; 10 | 11 | const AccordionItem = React.forwardRef< 12 | React.ElementRef, 13 | React.ComponentPropsWithoutRef 14 | >(({ className, ...props }, ref) => ( 15 | 20 | )); 21 | AccordionItem.displayName = "AccordionItem"; 22 | 23 | const AccordionTrigger = React.forwardRef< 24 | React.ElementRef, 25 | React.ComponentPropsWithoutRef 26 | >(({ className, children, ...props }, ref) => ( 27 | 28 | svg]:rotate-180", 32 | className, 33 | )} 34 | {...props} 35 | > 36 | {children} 37 | 38 | 39 | 40 | )); 41 | AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName; 42 | 43 | const AccordionContent = React.forwardRef< 44 | React.ElementRef, 45 | React.ComponentPropsWithoutRef 46 | >(({ className, children, ...props }, ref) => ( 47 | 52 |
{children}
53 |
54 | )); 55 | AccordionContent.displayName = AccordionPrimitive.Content.displayName; 56 | 57 | export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }; 58 | -------------------------------------------------------------------------------- /slack-ai/ui/components/ui/avatar.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as AvatarPrimitive from "@radix-ui/react-avatar"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | 8 | const Avatar = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, ...props }, ref) => ( 12 | 20 | )); 21 | Avatar.displayName = AvatarPrimitive.Root.displayName; 22 | 23 | const AvatarImage = React.forwardRef< 24 | React.ElementRef, 25 | React.ComponentPropsWithoutRef 26 | >(({ className, ...props }, ref) => ( 27 | 32 | )); 33 | AvatarImage.displayName = AvatarPrimitive.Image.displayName; 34 | 35 | const AvatarFallback = React.forwardRef< 36 | React.ElementRef, 37 | React.ComponentPropsWithoutRef 38 | >(({ className, ...props }, ref) => ( 39 | 47 | )); 48 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName; 49 | 50 | export { Avatar, AvatarImage, AvatarFallback }; 51 | -------------------------------------------------------------------------------- /slack-ai/ui/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Slot } from "@radix-ui/react-slot"; 3 | import { cva, type VariantProps } from "class-variance-authority"; 4 | 5 | import { cn } from "@/lib/utils"; 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50", 9 | { 10 | variants: { 11 | variant: { 12 | default: 13 | "bg-primary text-primary-foreground shadow hover:bg-primary/90", 14 | destructive: 15 | "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", 16 | outline: 17 | "border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground", 18 | secondary: 19 | "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", 20 | ghost: "hover:bg-accent hover:text-accent-foreground", 21 | link: "text-primary underline-offset-4 hover:underline", 22 | }, 23 | size: { 24 | default: "h-9 px-4 py-2", 25 | sm: "h-8 rounded-md px-3 text-xs", 26 | lg: "h-10 rounded-md px-8", 27 | icon: "h-9 w-9", 28 | }, 29 | }, 30 | defaultVariants: { 31 | variant: "default", 32 | size: "default", 33 | }, 34 | }, 35 | ); 36 | 37 | export interface ButtonProps 38 | extends React.ButtonHTMLAttributes, 39 | VariantProps { 40 | asChild?: boolean; 41 | } 42 | 43 | const Button = React.forwardRef( 44 | ({ className, variant, size, asChild = false, ...props }, ref) => { 45 | const Comp = asChild ? Slot : "button"; 46 | return ( 47 | 52 | ); 53 | }, 54 | ); 55 | Button.displayName = "Button"; 56 | 57 | export { Button, buttonVariants }; 58 | -------------------------------------------------------------------------------- /slack-ai/ui/components/ui/card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | const Card = React.forwardRef< 6 | HTMLDivElement, 7 | React.HTMLAttributes 8 | >(({ className, ...props }, ref) => ( 9 |
17 | )); 18 | Card.displayName = "Card"; 19 | 20 | const CardHeader = React.forwardRef< 21 | HTMLDivElement, 22 | React.HTMLAttributes 23 | >(({ className, ...props }, ref) => ( 24 |
29 | )); 30 | CardHeader.displayName = "CardHeader"; 31 | 32 | const CardTitle = React.forwardRef< 33 | HTMLParagraphElement, 34 | React.HTMLAttributes 35 | >(({ className, ...props }, ref) => ( 36 |

41 | )); 42 | CardTitle.displayName = "CardTitle"; 43 | 44 | const CardDescription = React.forwardRef< 45 | HTMLParagraphElement, 46 | React.HTMLAttributes 47 | >(({ className, ...props }, ref) => ( 48 |

53 | )); 54 | CardDescription.displayName = "CardDescription"; 55 | 56 | const CardContent = React.forwardRef< 57 | HTMLDivElement, 58 | React.HTMLAttributes 59 | >(({ className, ...props }, ref) => ( 60 |

61 | )); 62 | CardContent.displayName = "CardContent"; 63 | 64 | const CardFooter = React.forwardRef< 65 | HTMLDivElement, 66 | React.HTMLAttributes 67 | >(({ className, ...props }, ref) => ( 68 |
73 | )); 74 | CardFooter.displayName = "CardFooter"; 75 | 76 | export { 77 | Card, 78 | CardHeader, 79 | CardFooter, 80 | CardTitle, 81 | CardDescription, 82 | CardContent, 83 | }; 84 | -------------------------------------------------------------------------------- /slack-ai/ui/components/ui/drawer.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import { Drawer as DrawerPrimitive } from "vaul"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | 8 | const Drawer = ({ 9 | shouldScaleBackground = true, 10 | ...props 11 | }: React.ComponentProps) => ( 12 | 16 | ); 17 | Drawer.displayName = "Drawer"; 18 | 19 | const DrawerTrigger = DrawerPrimitive.Trigger; 20 | 21 | const DrawerPortal = DrawerPrimitive.Portal; 22 | 23 | const DrawerClose = DrawerPrimitive.Close; 24 | 25 | const DrawerOverlay = React.forwardRef< 26 | React.ElementRef, 27 | React.ComponentPropsWithoutRef 28 | >(({ className, ...props }, ref) => ( 29 | 34 | )); 35 | DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName; 36 | 37 | const DrawerContent = React.forwardRef< 38 | React.ElementRef, 39 | React.ComponentPropsWithoutRef 40 | >(({ className, children, ...props }, ref) => ( 41 | 42 | 43 | 51 |
52 | {children} 53 | 54 | 55 | )); 56 | DrawerContent.displayName = "DrawerContent"; 57 | 58 | const DrawerHeader = ({ 59 | className, 60 | ...props 61 | }: React.HTMLAttributes) => ( 62 |
66 | ); 67 | DrawerHeader.displayName = "DrawerHeader"; 68 | 69 | const DrawerFooter = ({ 70 | className, 71 | ...props 72 | }: React.HTMLAttributes) => ( 73 |
77 | ); 78 | DrawerFooter.displayName = "DrawerFooter"; 79 | 80 | const DrawerTitle = React.forwardRef< 81 | React.ElementRef, 82 | React.ComponentPropsWithoutRef 83 | >(({ className, ...props }, ref) => ( 84 | 92 | )); 93 | DrawerTitle.displayName = DrawerPrimitive.Title.displayName; 94 | 95 | const DrawerDescription = React.forwardRef< 96 | React.ElementRef, 97 | React.ComponentPropsWithoutRef 98 | >(({ className, ...props }, ref) => ( 99 | 104 | )); 105 | DrawerDescription.displayName = DrawerPrimitive.Description.displayName; 106 | 107 | export { 108 | Drawer, 109 | DrawerPortal, 110 | DrawerOverlay, 111 | DrawerTrigger, 112 | DrawerClose, 113 | DrawerContent, 114 | DrawerHeader, 115 | DrawerFooter, 116 | DrawerTitle, 117 | DrawerDescription, 118 | }; 119 | -------------------------------------------------------------------------------- /slack-ai/ui/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | export interface InputProps 6 | extends React.InputHTMLAttributes {} 7 | 8 | const Input = React.forwardRef( 9 | ({ className, type, ...props }, ref) => { 10 | return ( 11 | 20 | ); 21 | }, 22 | ); 23 | Input.displayName = "Input"; 24 | 25 | export { Input }; 26 | -------------------------------------------------------------------------------- /slack-ai/ui/components/ui/scroll-area.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | 8 | const ScrollArea = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, children, ...props }, ref) => ( 12 | 17 | 18 | {children} 19 | 20 | 21 | 22 | 23 | )); 24 | ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName; 25 | 26 | const ScrollBar = React.forwardRef< 27 | React.ElementRef, 28 | React.ComponentPropsWithoutRef 29 | >(({ className, orientation = "vertical", ...props }, ref) => ( 30 | 43 | 44 | 45 | )); 46 | ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName; 47 | 48 | export { ScrollArea, ScrollBar }; 49 | -------------------------------------------------------------------------------- /slack-ai/ui/components/ui/select.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import { 5 | CaretSortIcon, 6 | CheckIcon, 7 | ChevronDownIcon, 8 | ChevronUpIcon, 9 | } from "@radix-ui/react-icons"; 10 | import * as SelectPrimitive from "@radix-ui/react-select"; 11 | 12 | import { cn } from "@/lib/utils"; 13 | 14 | const Select = SelectPrimitive.Root; 15 | 16 | const SelectGroup = SelectPrimitive.Group; 17 | 18 | const SelectValue = SelectPrimitive.Value; 19 | 20 | const SelectTrigger = React.forwardRef< 21 | React.ElementRef, 22 | React.ComponentPropsWithoutRef 23 | >(({ className, children, ...props }, ref) => ( 24 | span]:line-clamp-1", 28 | className, 29 | )} 30 | {...props} 31 | > 32 | {children} 33 | 34 | 35 | 36 | 37 | )); 38 | SelectTrigger.displayName = SelectPrimitive.Trigger.displayName; 39 | 40 | const SelectScrollUpButton = React.forwardRef< 41 | React.ElementRef, 42 | React.ComponentPropsWithoutRef 43 | >(({ className, ...props }, ref) => ( 44 | 52 | 53 | 54 | )); 55 | SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName; 56 | 57 | const SelectScrollDownButton = React.forwardRef< 58 | React.ElementRef, 59 | React.ComponentPropsWithoutRef 60 | >(({ className, ...props }, ref) => ( 61 | 69 | 70 | 71 | )); 72 | SelectScrollDownButton.displayName = 73 | SelectPrimitive.ScrollDownButton.displayName; 74 | 75 | const SelectContent = React.forwardRef< 76 | React.ElementRef, 77 | React.ComponentPropsWithoutRef 78 | >(({ className, children, position = "popper", ...props }, ref) => ( 79 | 80 | 91 | 92 | 99 | {children} 100 | 101 | 102 | 103 | 104 | )); 105 | SelectContent.displayName = SelectPrimitive.Content.displayName; 106 | 107 | const SelectLabel = React.forwardRef< 108 | React.ElementRef, 109 | React.ComponentPropsWithoutRef 110 | >(({ className, ...props }, ref) => ( 111 | 116 | )); 117 | SelectLabel.displayName = SelectPrimitive.Label.displayName; 118 | 119 | const SelectItem = React.forwardRef< 120 | React.ElementRef, 121 | React.ComponentPropsWithoutRef 122 | >(({ className, children, ...props }, ref) => ( 123 | 131 | 132 | 133 | 134 | 135 | 136 | {children} 137 | 138 | )); 139 | SelectItem.displayName = SelectPrimitive.Item.displayName; 140 | 141 | const SelectSeparator = React.forwardRef< 142 | React.ElementRef, 143 | React.ComponentPropsWithoutRef 144 | >(({ className, ...props }, ref) => ( 145 | 150 | )); 151 | SelectSeparator.displayName = SelectPrimitive.Separator.displayName; 152 | 153 | export { 154 | Select, 155 | SelectGroup, 156 | SelectValue, 157 | SelectTrigger, 158 | SelectContent, 159 | SelectLabel, 160 | SelectItem, 161 | SelectSeparator, 162 | SelectScrollUpButton, 163 | SelectScrollDownButton, 164 | }; 165 | -------------------------------------------------------------------------------- /slack-ai/ui/components/ui/separator.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as SeparatorPrimitive from "@radix-ui/react-separator"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | 8 | const Separator = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >( 12 | ( 13 | { className, orientation = "horizontal", decorative = true, ...props }, 14 | ref, 15 | ) => ( 16 | 27 | ), 28 | ); 29 | Separator.displayName = SeparatorPrimitive.Root.displayName; 30 | 31 | export { Separator }; 32 | -------------------------------------------------------------------------------- /slack-ai/ui/components/ui/switch.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as SwitchPrimitives from "@radix-ui/react-switch"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | 8 | const Switch = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, ...props }, ref) => ( 12 | 20 | 25 | 26 | )); 27 | Switch.displayName = SwitchPrimitives.Root.displayName; 28 | 29 | export { Switch }; 30 | -------------------------------------------------------------------------------- /slack-ai/ui/components/ui/table.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | const Table = React.forwardRef< 6 | HTMLTableElement, 7 | React.HTMLAttributes 8 | >(({ className, ...props }, ref) => ( 9 |
10 | 15 | 16 | )); 17 | Table.displayName = "Table"; 18 | 19 | const TableHeader = React.forwardRef< 20 | HTMLTableSectionElement, 21 | React.HTMLAttributes 22 | >(({ className, ...props }, ref) => ( 23 | 24 | )); 25 | TableHeader.displayName = "TableHeader"; 26 | 27 | const TableBody = React.forwardRef< 28 | HTMLTableSectionElement, 29 | React.HTMLAttributes 30 | >(({ className, ...props }, ref) => ( 31 | 36 | )); 37 | TableBody.displayName = "TableBody"; 38 | 39 | const TableFooter = React.forwardRef< 40 | HTMLTableSectionElement, 41 | React.HTMLAttributes 42 | >(({ className, ...props }, ref) => ( 43 | tr]:last:border-b-0", 47 | className, 48 | )} 49 | {...props} 50 | /> 51 | )); 52 | TableFooter.displayName = "TableFooter"; 53 | 54 | const TableRow = React.forwardRef< 55 | HTMLTableRowElement, 56 | React.HTMLAttributes 57 | >(({ className, ...props }, ref) => ( 58 | 66 | )); 67 | TableRow.displayName = "TableRow"; 68 | 69 | const TableHead = React.forwardRef< 70 | HTMLTableCellElement, 71 | React.ThHTMLAttributes 72 | >(({ className, ...props }, ref) => ( 73 |
[role=checkbox]]:translate-y-[2px]", 77 | className, 78 | )} 79 | {...props} 80 | /> 81 | )); 82 | TableHead.displayName = "TableHead"; 83 | 84 | const TableCell = React.forwardRef< 85 | HTMLTableCellElement, 86 | React.TdHTMLAttributes 87 | >(({ className, ...props }, ref) => ( 88 | [role=checkbox]]:translate-y-[2px]", 92 | className, 93 | )} 94 | {...props} 95 | /> 96 | )); 97 | TableCell.displayName = "TableCell"; 98 | 99 | const TableCaption = React.forwardRef< 100 | HTMLTableCaptionElement, 101 | React.HTMLAttributes 102 | >(({ className, ...props }, ref) => ( 103 |
108 | )); 109 | TableCaption.displayName = "TableCaption"; 110 | 111 | export { 112 | Table, 113 | TableHeader, 114 | TableBody, 115 | TableFooter, 116 | TableHead, 117 | TableRow, 118 | TableCell, 119 | TableCaption, 120 | }; 121 | -------------------------------------------------------------------------------- /slack-ai/ui/components/ui/toast.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Cross2Icon } from "@radix-ui/react-icons"; 3 | import * as ToastPrimitives from "@radix-ui/react-toast"; 4 | import { cva, type VariantProps } from "class-variance-authority"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | 8 | const ToastProvider = ToastPrimitives.Provider; 9 | 10 | const ToastViewport = React.forwardRef< 11 | React.ElementRef, 12 | React.ComponentPropsWithoutRef 13 | >(({ className, ...props }, ref) => ( 14 | 22 | )); 23 | ToastViewport.displayName = ToastPrimitives.Viewport.displayName; 24 | 25 | const toastVariants = cva( 26 | "group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full", 27 | { 28 | variants: { 29 | variant: { 30 | default: "border bg-background text-foreground", 31 | destructive: 32 | "destructive group border-destructive bg-destructive text-destructive-foreground", 33 | }, 34 | }, 35 | defaultVariants: { 36 | variant: "default", 37 | }, 38 | }, 39 | ); 40 | 41 | const Toast = React.forwardRef< 42 | React.ElementRef, 43 | React.ComponentPropsWithoutRef & 44 | VariantProps 45 | >(({ className, variant, ...props }, ref) => { 46 | return ( 47 | 52 | ); 53 | }); 54 | Toast.displayName = ToastPrimitives.Root.displayName; 55 | 56 | const ToastAction = React.forwardRef< 57 | React.ElementRef, 58 | React.ComponentPropsWithoutRef 59 | >(({ className, ...props }, ref) => ( 60 | 68 | )); 69 | ToastAction.displayName = ToastPrimitives.Action.displayName; 70 | 71 | const ToastClose = React.forwardRef< 72 | React.ElementRef, 73 | React.ComponentPropsWithoutRef 74 | >(({ className, ...props }, ref) => ( 75 | 84 | 85 | 86 | )); 87 | ToastClose.displayName = ToastPrimitives.Close.displayName; 88 | 89 | const ToastTitle = React.forwardRef< 90 | React.ElementRef, 91 | React.ComponentPropsWithoutRef 92 | >(({ className, ...props }, ref) => ( 93 | 98 | )); 99 | ToastTitle.displayName = ToastPrimitives.Title.displayName; 100 | 101 | const ToastDescription = React.forwardRef< 102 | React.ElementRef, 103 | React.ComponentPropsWithoutRef 104 | >(({ className, ...props }, ref) => ( 105 | 110 | )); 111 | ToastDescription.displayName = ToastPrimitives.Description.displayName; 112 | 113 | type ToastProps = React.ComponentPropsWithoutRef; 114 | 115 | type ToastActionElement = React.ReactElement; 116 | 117 | export { 118 | type ToastProps, 119 | type ToastActionElement, 120 | ToastProvider, 121 | ToastViewport, 122 | Toast, 123 | ToastTitle, 124 | ToastDescription, 125 | ToastClose, 126 | ToastAction, 127 | }; 128 | -------------------------------------------------------------------------------- /slack-ai/ui/components/ui/toaster.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { 4 | Toast, 5 | ToastClose, 6 | ToastDescription, 7 | ToastProvider, 8 | ToastTitle, 9 | ToastViewport, 10 | } from "@/components/ui/toast"; 11 | import { useToast } from "@/components/ui/use-toast"; 12 | 13 | export function Toaster() { 14 | const { toasts } = useToast(); 15 | 16 | return ( 17 | 18 | {toasts.map(function ({ id, title, description, action, ...props }) { 19 | return ( 20 | 21 |
22 | {title && {title}} 23 | {description && ( 24 | {description} 25 | )} 26 |
27 | {action} 28 | 29 |
30 | ); 31 | })} 32 | 33 |
34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /slack-ai/ui/components/ui/tooltip.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as TooltipPrimitive from "@radix-ui/react-tooltip"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | 8 | const TooltipProvider = TooltipPrimitive.Provider; 9 | 10 | const Tooltip = TooltipPrimitive.Root; 11 | 12 | const TooltipTrigger = TooltipPrimitive.Trigger; 13 | 14 | const TooltipContent = React.forwardRef< 15 | React.ElementRef, 16 | React.ComponentPropsWithoutRef 17 | >(({ className, sideOffset = 4, ...props }, ref) => ( 18 | 27 | )); 28 | TooltipContent.displayName = TooltipPrimitive.Content.displayName; 29 | 30 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }; 31 | -------------------------------------------------------------------------------- /slack-ai/ui/components/ui/use-toast.ts: -------------------------------------------------------------------------------- 1 | // Inspired by react-hot-toast library 2 | import * as React from "react"; 3 | 4 | import type { ToastActionElement, ToastProps } from "@/components/ui/toast"; 5 | 6 | const TOAST_LIMIT = 1; 7 | const TOAST_REMOVE_DELAY = 1000000; 8 | 9 | type ToasterToast = ToastProps & { 10 | id: string; 11 | title?: React.ReactNode; 12 | description?: React.ReactNode; 13 | action?: ToastActionElement; 14 | }; 15 | 16 | const actionTypes = { 17 | ADD_TOAST: "ADD_TOAST", 18 | UPDATE_TOAST: "UPDATE_TOAST", 19 | DISMISS_TOAST: "DISMISS_TOAST", 20 | REMOVE_TOAST: "REMOVE_TOAST", 21 | } as const; 22 | 23 | let count = 0; 24 | 25 | function genId() { 26 | count = (count + 1) % Number.MAX_SAFE_INTEGER; 27 | return count.toString(); 28 | } 29 | 30 | type ActionType = typeof actionTypes; 31 | 32 | type Action = 33 | | { 34 | type: ActionType["ADD_TOAST"]; 35 | toast: ToasterToast; 36 | } 37 | | { 38 | type: ActionType["UPDATE_TOAST"]; 39 | toast: Partial; 40 | } 41 | | { 42 | type: ActionType["DISMISS_TOAST"]; 43 | toastId?: ToasterToast["id"]; 44 | } 45 | | { 46 | type: ActionType["REMOVE_TOAST"]; 47 | toastId?: ToasterToast["id"]; 48 | }; 49 | 50 | interface State { 51 | toasts: ToasterToast[]; 52 | } 53 | 54 | const toastTimeouts = new Map>(); 55 | 56 | const addToRemoveQueue = (toastId: string) => { 57 | if (toastTimeouts.has(toastId)) { 58 | return; 59 | } 60 | 61 | const timeout = setTimeout(() => { 62 | toastTimeouts.delete(toastId); 63 | dispatch({ 64 | type: "REMOVE_TOAST", 65 | toastId: toastId, 66 | }); 67 | }, TOAST_REMOVE_DELAY); 68 | 69 | toastTimeouts.set(toastId, timeout); 70 | }; 71 | 72 | export const reducer = (state: State, action: Action): State => { 73 | switch (action.type) { 74 | case "ADD_TOAST": 75 | return { 76 | ...state, 77 | toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), 78 | }; 79 | 80 | case "UPDATE_TOAST": 81 | return { 82 | ...state, 83 | toasts: state.toasts.map((t) => 84 | t.id === action.toast.id ? { ...t, ...action.toast } : t, 85 | ), 86 | }; 87 | 88 | case "DISMISS_TOAST": { 89 | const { toastId } = action; 90 | 91 | // ! Side effects ! - This could be extracted into a dismissToast() action, 92 | // but I'll keep it here for simplicity 93 | if (toastId) { 94 | addToRemoveQueue(toastId); 95 | } else { 96 | state.toasts.forEach((toast) => { 97 | addToRemoveQueue(toast.id); 98 | }); 99 | } 100 | 101 | return { 102 | ...state, 103 | toasts: state.toasts.map((t) => 104 | t.id === toastId || toastId === undefined 105 | ? { 106 | ...t, 107 | open: false, 108 | } 109 | : t, 110 | ), 111 | }; 112 | } 113 | case "REMOVE_TOAST": 114 | if (action.toastId === undefined) { 115 | return { 116 | ...state, 117 | toasts: [], 118 | }; 119 | } 120 | return { 121 | ...state, 122 | toasts: state.toasts.filter((t) => t.id !== action.toastId), 123 | }; 124 | } 125 | }; 126 | 127 | const listeners: Array<(state: State) => void> = []; 128 | 129 | let memoryState: State = { toasts: [] }; 130 | 131 | function dispatch(action: Action) { 132 | memoryState = reducer(memoryState, action); 133 | listeners.forEach((listener) => { 134 | listener(memoryState); 135 | }); 136 | } 137 | 138 | type Toast = Omit; 139 | 140 | function toast({ ...props }: Toast) { 141 | const id = genId(); 142 | 143 | const update = (props: ToasterToast) => 144 | dispatch({ 145 | type: "UPDATE_TOAST", 146 | toast: { ...props, id }, 147 | }); 148 | const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }); 149 | 150 | dispatch({ 151 | type: "ADD_TOAST", 152 | toast: { 153 | ...props, 154 | id, 155 | open: true, 156 | onOpenChange: (open) => { 157 | if (!open) dismiss(); 158 | }, 159 | }, 160 | }); 161 | 162 | return { 163 | id: id, 164 | dismiss, 165 | update, 166 | }; 167 | } 168 | 169 | function useToast() { 170 | const [state, setState] = React.useState(memoryState); 171 | 172 | React.useEffect(() => { 173 | listeners.push(setState); 174 | return () => { 175 | const index = listeners.indexOf(setState); 176 | if (index > -1) { 177 | listeners.splice(index, 1); 178 | } 179 | }; 180 | }, [state]); 181 | 182 | return { 183 | ...state, 184 | toast, 185 | dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), 186 | }; 187 | } 188 | 189 | export { useToast, toast }; 190 | -------------------------------------------------------------------------------- /slack-ai/ui/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | swcMinify: true, 5 | images: { 6 | domains: ["lh3.googleusercontent.com"], 7 | }, 8 | trailingSlash: true, 9 | async rewrites() { 10 | return [ 11 | { 12 | source: "/api/:path*", 13 | destination: `${process.env.NEXT_PUBLIC_API_ENDPOINT}/api/:path*`, 14 | }, 15 | ]; 16 | }, 17 | }; 18 | 19 | module.exports = nextConfig; 20 | -------------------------------------------------------------------------------- /slack-ai/ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "embedchain-admin", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@radix-ui/react-accordion": "^1.1.2", 13 | "@radix-ui/react-avatar": "^1.0.3", 14 | "@radix-ui/react-dialog": "^1.0.5", 15 | "@radix-ui/react-dropdown-menu": "^2.0.5", 16 | "@radix-ui/react-icons": "^1.3.0", 17 | "@radix-ui/react-scroll-area": "^1.0.4", 18 | "@radix-ui/react-select": "^2.0.0", 19 | "@radix-ui/react-separator": "^1.0.3", 20 | "@radix-ui/react-slot": "^1.0.2", 21 | "@radix-ui/react-switch": "^1.0.3", 22 | "@radix-ui/react-tabs": "^1.0.4", 23 | "@radix-ui/react-toast": "^1.1.5", 24 | "@radix-ui/react-tooltip": "^1.0.6", 25 | "@types/node": "20.5.1", 26 | "@types/react": "18.2.20", 27 | "@types/react-dom": "18.2.7", 28 | "autoprefixer": "10.4.15", 29 | "axios": "^1.4.0", 30 | "class-variance-authority": "^0.7.0", 31 | "clsx": "^2.0.0", 32 | "cmdk": "^0.2.0", 33 | "eslint": "8.47.0", 34 | "eslint-config-next": "13.4.19", 35 | "lucide-react": "^0.268.0", 36 | "next": "13.4.19", 37 | "postcss": "8.4.28", 38 | "react": "18.2.0", 39 | "react-dom": "18.2.0", 40 | "tailwind-merge": "^1.14.0", 41 | "tailwindcss": "3.3.3", 42 | "tailwindcss-animate": "^1.0.6", 43 | "typescript": "5.1.6", 44 | "vaul": "^0.8.0" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /slack-ai/ui/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /slack-ai/ui/public/slack-ai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mem0ai/examples/f33f9c347abdab3a18d833f8392cbb4f7643d2ab/slack-ai/ui/public/slack-ai.png -------------------------------------------------------------------------------- /slack-ai/ui/public/slack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mem0ai/examples/f33f9c347abdab3a18d833f8392cbb4f7643d2ab/slack-ai/ui/public/slack.png -------------------------------------------------------------------------------- /slack-ai/ui/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | darkMode: ["class"], 4 | content: [ 5 | "./pages/**/*.{ts,tsx}", 6 | "./components/**/*.{ts,tsx}", 7 | "./app/**/*.{ts,tsx}", 8 | "./src/**/*.{ts,tsx}", 9 | ], 10 | theme: { 11 | container: { 12 | center: true, 13 | padding: "2rem", 14 | screens: { 15 | "2xl": "1400px", 16 | }, 17 | }, 18 | extend: { 19 | colors: { 20 | border: "hsl(var(--border))", 21 | input: "hsl(var(--input))", 22 | ring: "hsl(var(--ring))", 23 | background: "hsl(var(--background))", 24 | foreground: "hsl(var(--foreground))", 25 | primary: { 26 | DEFAULT: "hsl(var(--primary))", 27 | foreground: "hsl(var(--primary-foreground))", 28 | }, 29 | secondary: { 30 | DEFAULT: "hsl(var(--secondary))", 31 | foreground: "hsl(var(--secondary-foreground))", 32 | }, 33 | destructive: { 34 | DEFAULT: "hsl(var(--destructive))", 35 | foreground: "hsl(var(--destructive-foreground))", 36 | }, 37 | muted: { 38 | DEFAULT: "hsl(var(--muted))", 39 | foreground: "hsl(var(--muted-foreground))", 40 | }, 41 | accent: { 42 | DEFAULT: "hsl(var(--accent))", 43 | foreground: "hsl(var(--accent-foreground))", 44 | }, 45 | popover: { 46 | DEFAULT: "hsl(var(--popover))", 47 | foreground: "hsl(var(--popover-foreground))", 48 | }, 49 | card: { 50 | DEFAULT: "hsl(var(--card))", 51 | foreground: "hsl(var(--card-foreground))", 52 | }, 53 | }, 54 | borderRadius: { 55 | lg: "var(--radius)", 56 | md: "calc(var(--radius) - 2px)", 57 | sm: "calc(var(--radius) - 4px)", 58 | }, 59 | keyframes: { 60 | "accordion-down": { 61 | from: { height: 0 }, 62 | to: { height: "var(--radix-accordion-content-height)" }, 63 | }, 64 | "accordion-up": { 65 | from: { height: "var(--radix-accordion-content-height)" }, 66 | to: { height: 0 }, 67 | }, 68 | }, 69 | animation: { 70 | "accordion-down": "accordion-down 0.2s ease-out", 71 | "accordion-up": "accordion-up 0.2s ease-out", 72 | }, 73 | }, 74 | }, 75 | plugins: [require("tailwindcss-animate")], 76 | }; 77 | -------------------------------------------------------------------------------- /slack-ai/ui/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | const config: Config = { 4 | content: [ 5 | "./pages/**/*.{js,ts,jsx,tsx,mdx}", 6 | "./components/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./app/**/*.{js,ts,jsx,tsx,mdx}", 8 | ], 9 | theme: { 10 | extend: { 11 | backgroundImage: { 12 | "gradient-radial": "radial-gradient(var(--tw-gradient-stops))", 13 | "gradient-conic": 14 | "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))", 15 | }, 16 | }, 17 | }, 18 | plugins: [], 19 | }; 20 | export default config; 21 | -------------------------------------------------------------------------------- /slack-ai/ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | --------------------------------------------------------------------------------