├── .DS_Store ├── .eslintignore ├── .eslintrc.json ├── .github └── workflows │ └── deploy.yml ├── .gitignore ├── .vscode ├── launch.json └── settings.json ├── Dockerfile ├── README.md ├── docker-compose.prod.yml ├── docker-compose.yml ├── package.json ├── packages ├── chat-widget │ ├── .eslintrc.cjs │ ├── .gitignore │ ├── index.html │ ├── package.json │ ├── postcss.config.js │ ├── src │ │ ├── App.tsx │ │ ├── components │ │ │ └── Chat │ │ │ │ ├── Chat.tsx │ │ │ │ ├── ChatInput.tsx │ │ │ │ ├── ChatLoader.tsx │ │ │ │ ├── ChatMessage.tsx │ │ │ │ └── ResetChat.tsx │ │ ├── index.css │ │ ├── main.tsx │ │ ├── types │ │ │ └── index.ts │ │ └── vite-env.d.ts │ ├── tailwind.config.js │ ├── test │ │ └── index.html │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts ├── server │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc │ ├── nest-cli.json │ ├── nodemon.json │ ├── package.json │ ├── src │ │ ├── app.controller.spec.ts │ │ ├── app.controller.ts │ │ ├── app.module.ts │ │ ├── app.service.ts │ │ ├── bull-configuration │ │ │ ├── bull-configuration.module.ts │ │ │ └── runnode.processor.ts │ │ ├── chatbot │ │ │ ├── chatbot.controller.spec.ts │ │ │ ├── chatbot.controller.ts │ │ │ ├── chatbot.gateway.ts │ │ │ ├── chatbot.module.ts │ │ │ ├── chatbot.scheduler.ts │ │ │ ├── chatbot.service.spec.ts │ │ │ ├── chatbot.service.ts │ │ │ ├── dto │ │ │ │ ├── create-chatbot.dto.ts │ │ │ │ └── update-chatbot.dto.ts │ │ │ └── entities │ │ │ │ └── chatbot.entity.ts │ │ ├── main.ts │ │ ├── redis │ │ │ ├── redis.module.ts │ │ │ ├── redis.providers.ts │ │ │ └── redis.service.ts │ │ ├── state │ │ │ ├── state.module.ts │ │ │ ├── state.service.spec.ts │ │ │ └── state.service.ts │ │ └── supabase │ │ │ ├── supabase.service.spec.ts │ │ │ └── supabase.service.ts │ ├── test │ │ ├── app.e2e-spec.ts │ │ └── jest-e2e.json │ ├── tsconfig.build.json │ └── tsconfig.json ├── shared │ ├── .eslintignore │ ├── .eslintrc.json │ ├── .gitignore │ ├── nodemon.json │ ├── package.json │ ├── src │ │ ├── getNextNode.ts │ │ ├── getNodes.ts │ │ ├── getRootNodes.ts │ │ ├── index.ts │ │ ├── initializeFlowState.ts │ │ ├── runConditional │ │ │ ├── classifyCategories.ts │ │ │ ├── conditional.ts │ │ │ └── runConditional.ts │ │ ├── runNode │ │ │ ├── chatPrompt.ts │ │ │ ├── classify.ts │ │ │ ├── counter.ts │ │ │ ├── index.ts │ │ │ ├── inputText.ts │ │ │ ├── llmPrompt.ts │ │ │ ├── loop.ts │ │ │ ├── outputText.ts │ │ │ ├── runNode.ts │ │ │ ├── search.ts │ │ │ └── singleChatPrompt.ts │ │ ├── traversalStateType.ts │ │ ├── types │ │ │ ├── EdgeTypes.ts │ │ │ ├── Input.ts │ │ │ ├── MessageTypes.ts │ │ │ ├── NodeTypes.ts │ │ │ ├── SupabaseSettingsType.ts │ │ │ ├── dbTypes.ts │ │ │ └── schema.ts │ │ └── utils │ │ │ ├── env.ts │ │ │ ├── getAllChildren.ts │ │ │ ├── openai │ │ │ ├── models.ts │ │ │ └── openai.ts │ │ │ ├── parsePromptInput.ts │ │ │ ├── setupSupabaseClient.ts │ │ │ └── vectorStores │ │ │ └── SupabaseVectorStoreWithFilter.ts │ ├── tsconfig.json │ └── vite-env.d.ts ├── supabase │ ├── .gitignore │ ├── config.toml │ ├── functions │ │ ├── _shared │ │ │ ├── cors.ts │ │ │ ├── getOpenAiKey.ts │ │ │ ├── hex.ts │ │ │ └── supabaseClient.ts │ │ ├── embed │ │ │ └── index.ts │ │ ├── get-api-key │ │ │ └── index.ts │ │ ├── import_map.json │ │ ├── import_map_deploy.json │ │ ├── insert-api-key │ │ │ └── index.ts │ │ └── openai │ │ │ └── index.ts │ ├── package.json │ └── seed.sql └── web │ ├── .eslintignore │ ├── .eslintrc.json │ ├── .gitignore │ ├── .prettierrc │ ├── Dockerfile │ ├── README.md │ ├── add_node_settings.sh │ ├── add_node_type.sh │ ├── api │ └── getWorkflows.ts │ ├── docker-compose.yml │ ├── package.json │ ├── postcss.config.cjs │ ├── public │ └── promptsandbox.png │ ├── src │ ├── assets │ │ ├── icon.png │ │ ├── loading.svg │ │ ├── react.svg │ │ └── right-angle.svg │ ├── auth │ │ ├── getUrl.tsx │ │ └── supabaseClient.ts │ ├── auto-chatbot-creator.txt │ ├── backgroundTasks │ │ ├── dexieDb │ │ │ └── db.ts │ │ ├── langChainBrowser │ │ │ ├── base.ts │ │ │ ├── document.ts │ │ │ ├── embeddings │ │ │ │ ├── base.ts │ │ │ │ ├── fake.ts │ │ │ │ ├── index.ts │ │ │ │ └── openai.ts │ │ │ ├── schema │ │ │ │ └── index.ts │ │ │ ├── text.ts │ │ │ ├── text_splitter.ts │ │ │ ├── util │ │ │ │ ├── async_caller.ts │ │ │ │ ├── hub.ts │ │ │ │ └── index.ts │ │ │ └── vectorstores │ │ │ │ ├── base.ts │ │ │ │ ├── dexie.ts │ │ │ │ ├── index.ts │ │ │ │ └── supabase.ts │ │ └── processFile.ts │ ├── chatbot-advisor.txt │ ├── components │ │ ├── ApiKeySettings.tsx │ │ ├── ApiTab.tsx │ │ ├── DocumentSelector.tsx │ │ ├── EditableText.tsx │ │ ├── FullScreenEditor.tsx │ │ ├── LoadingOverlay.tsx │ │ ├── Navbar.tsx │ │ ├── Notification.tsx │ │ ├── RunButton.tsx │ │ └── RunFromStart.tsx │ ├── connection │ │ └── ConnectionLine.tsx │ ├── db │ │ ├── populateUserDocuments.ts │ │ ├── populateUserWorkflows.ts │ │ ├── selectWorkflow.ts │ │ └── syncToSupabase.ts │ ├── edges │ │ └── CustomEdgeType.tsx │ ├── nodes │ │ ├── ChatMessageNode.tsx │ │ ├── ChatPromptNode.tsx │ │ ├── ClassifyCategoriesNode.tsx │ │ ├── ClassifyNode.tsx │ │ ├── CombineNode.tsx │ │ ├── ConditionalNode.tsx │ │ ├── CounterNode.tsx │ │ ├── FileTextNode.tsx │ │ ├── InputTextNode.tsx │ │ ├── LLMPromptNode.tsx │ │ ├── LoopNode.tsx │ │ ├── OutputTextNode.tsx │ │ ├── PlaceholderNode.tsx │ │ ├── SearchNode.tsx │ │ ├── SingleChatPromptNode.tsx │ │ ├── TextNode.tsx │ │ ├── templates │ │ │ ├── InputNodesList.tsx │ │ │ ├── NodeTemplate.tsx │ │ │ ├── RunnableToolbarTemplate.tsx │ │ │ └── TextAreaTemplate.tsx │ │ └── types │ │ │ └── Input.tsx │ ├── openai │ │ └── models.ts │ ├── pages │ │ ├── Layout.tsx │ │ ├── app │ │ │ ├── App.tsx │ │ │ ├── AppEntrypoint.tsx │ │ │ ├── SandboxExecutionPanel.tsx │ │ │ ├── UsernamePrompt.tsx │ │ │ └── index.html │ │ ├── auth │ │ │ ├── Auth.tsx │ │ │ ├── AuthEntrypoint.tsx │ │ │ └── index.html │ │ ├── chat │ │ │ ├── Chat.tsx │ │ │ ├── ChatEntrypoint.tsx │ │ │ ├── ChatbotList.tsx │ │ │ ├── index.html │ │ │ └── layout │ │ │ │ └── SideNav.tsx │ │ ├── gallery │ │ │ ├── Gallery.tsx │ │ │ ├── GalleryEntrypoint.tsx │ │ │ └── index.html │ │ ├── index.css │ │ ├── index.html │ │ ├── overview │ │ │ ├── Overview.tsx │ │ │ ├── OverviewEntrypoint.tsx │ │ │ ├── components │ │ │ │ ├── Breadcrumbs.tsx │ │ │ │ ├── ChatbotDetails.tsx │ │ │ │ ├── ChatbotDetailsTabs.tsx │ │ │ │ ├── ChatbotMenu.tsx │ │ │ │ ├── ChatbotMenuPanel.tsx │ │ │ │ └── DocumentUploader.tsx │ │ │ └── utils │ │ │ │ ├── uploadFile.ts │ │ │ │ └── uploadWebsiteUrl.ts │ │ ├── public │ │ │ ├── favicon.ico │ │ │ └── promptsandbox.png │ │ └── settings │ │ │ ├── Settings.tsx │ │ │ ├── SettingsEntrypoint.tsx │ │ │ ├── components │ │ │ └── PlanSettings.tsx │ │ │ └── index.html │ ├── store │ │ ├── index.ts │ │ ├── initialEdges.ts │ │ ├── initialNodes.ts │ │ ├── onAdd.ts │ │ ├── onConnect.ts │ │ ├── onEdgesDelete.ts │ │ ├── onNodesChange.ts │ │ ├── onPlaceholderAdd.ts │ │ ├── storage.ts │ │ ├── updateNode.ts │ │ ├── useStore.ts │ │ └── useStoreSecret.ts │ ├── utils │ │ ├── classNames.ts │ │ ├── docLoaders │ │ │ ├── pdfLoader.ts │ │ │ └── pdfUrlLoader.ts │ │ ├── getChildren.ts │ │ ├── handleFormChange.ts │ │ ├── isWorkflowOwnedByUser.ts │ │ ├── parsePromptInputs.ts │ │ ├── runFlow.ts │ │ ├── useDebouncedCallback.ts │ │ ├── useDebouncedEffect.ts │ │ ├── useQueryParams.ts │ │ ├── useResize.ts │ │ ├── useSyncedRef.ts │ │ ├── useUnmountEffect.ts │ │ ├── userCredits.ts │ │ └── vectorStores │ │ │ └── SupabaseVectorStoreWithFilter.ts │ ├── vite-env.d.ts │ └── windows │ │ ├── ChatPanel │ │ ├── Chat │ │ │ ├── Chat.tsx │ │ │ ├── ChatInput.tsx │ │ │ ├── ChatLoader.tsx │ │ │ ├── ChatMessage.tsx │ │ │ ├── MessageTypes │ │ │ │ ├── SearchMessage.tsx │ │ │ │ └── SearchMessage │ │ │ │ │ └── SavedDocs.tsx │ │ │ ├── ResetChat.tsx │ │ │ └── types.ts │ │ ├── ChatPanel.tsx │ │ └── inputs │ │ │ ├── ChipsInput.tsx │ │ │ └── RangeInput.tsx │ │ ├── SettingsPanel │ │ ├── NodesList.tsx │ │ ├── SandboxSettings.tsx │ │ ├── SettingsPanel.tsx │ │ ├── TabsNavigator.tsx │ │ └── nodeSettings │ │ │ ├── DefaultTab.tsx │ │ │ ├── TabsTemplate.tsx │ │ │ ├── chatMessage │ │ │ └── tabs.tsx │ │ │ ├── chatPromptNode │ │ │ └── tabs.tsx │ │ │ ├── classifyNode │ │ │ └── tabs.tsx │ │ │ ├── llmPromptNode │ │ │ ├── tabs.tsx │ │ │ └── tabs │ │ │ │ ├── ApiTab.tsx │ │ │ │ └── TestTab.tsx │ │ │ ├── searchNode │ │ │ └── tabs.tsx │ │ │ ├── singleChatPromptNode │ │ │ └── tabs.tsx │ │ │ └── textNode │ │ │ └── tabs.tsx │ │ ├── Tutorial.tsx │ │ ├── Tutorials │ │ ├── Files │ │ │ └── Search.mdx │ │ ├── GptNodes │ │ │ ├── Chat.mdx │ │ │ └── GptNodes.mdx │ │ ├── Nodes.mdx │ │ ├── Overview.mdx │ │ ├── UseCases.mdx │ │ └── components │ │ │ └── VideoPlayer.tsx │ │ └── UserWorkflows.tsx │ ├── tailwind.config.cjs │ ├── tsconfig.json │ ├── tsconfig.node.json │ ├── users.csv │ ├── users_inserts.sql │ ├── vercel.json │ └── vite.config.ts ├── tsconfig.json └── yarn.lock /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eg9y/chatbutler/8db53cebf57e427483d10ebad1785e460a8b39a3/.DS_Store -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "es2020": true 5 | }, 6 | "parser": "@typescript-eslint/parser", 7 | "plugins": [ 8 | "@typescript-eslint" 9 | ], 10 | "extends": [ 11 | "eslint:recommended", 12 | "plugin:@typescript-eslint/recommended" 13 | ], 14 | "root": true 15 | } -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | deploy: 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - name: Execute remote ssh commands 12 | uses: appleboy/ssh-action@master 13 | with: 14 | host: chatserver.chatbutler.ai 15 | username: root 16 | key: ${{ secrets.SSH_PRIVATE_KEY }} 17 | script: | 18 | cd promptsandbox.io 19 | git pull 20 | yarn workspace @chatbutler/shared build 21 | if [ -d "packages/server/dist" ]; then rm -r packages/server/dist; fi 22 | yarn workspace @chatbutler/server build 23 | pm2 restart ecosystem.config.js 24 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "chrome", 9 | "request": "launch", 10 | "name": "Launch Chrome against localhost", 11 | "url": "http://localhost:5173", 12 | "webRoot": "${workspaceFolder}" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "editor.codeActionsOnSave": { 4 | "source.fixAll.eslint": true 5 | }, 6 | "eslint.validate": [ 7 | "typescript", 8 | "typescriptreact" 9 | ], 10 | "eslint.lintTask.enable": true, 11 | "prettier.enable": false, // Disable built-in Prettier extension if you have it installed 12 | "deno.enablePaths": ["./packages/supabase"], 13 | "deno.unstable": true, 14 | "deno.importMap": "./packages/supabase/functions/import_map.json" 15 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # First stage: build 2 | FROM node:18-buster as builder 3 | 4 | # Install necessary build tools 5 | RUN apt-get update && apt-get install -y build-essential python 6 | 7 | WORKDIR /app 8 | 9 | # Copy all directories 10 | COPY . . 11 | 12 | # Install all packages 13 | RUN yarn install 14 | 15 | # Build shared and server packages 16 | RUN yarn workspace @chatbutler/shared build 17 | RUN yarn workspace @chatbutler/server build 18 | 19 | # Second stage: runtime 20 | FROM node:18-buster 21 | 22 | WORKDIR /app 23 | 24 | # Copy over built application and production dependencies from the build stage 25 | COPY --from=builder /app . 26 | 27 | CMD [ "yarn", "workspace", "@chatbutler/server", "start:dev" ] 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ChatButler 2 | 3 | 💁‍♂️ Previously Promptsandbox.io. 4 | 5 | -- README WIP -- 6 | -------------------------------------------------------------------------------- /docker-compose.prod.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | app: 4 | build: 5 | context: . 6 | dockerfile: Dockerfile 7 | volumes: 8 | - .:/app 9 | command: yarn workspace @chatbutler/server start:prod 10 | environment: 11 | - SUPABASE_SERVICE_ROLE=${SUPABASE_SERVICE_ROLE} 12 | - SUPABASE_URL=${SUPABASE_URL} 13 | - OPENAI_API_KEY=${OPENAI_API_KEY} 14 | - ENV=prod 15 | - REDIS_HOST=${REDIS_HOST} 16 | - REDIS_PORT=${REDIS_PORT} 17 | - REDIS_PASSWORD=${REDIS_PASSWORD} 18 | ports: 19 | - 3000:3000 20 | depends_on: 21 | - redis 22 | 23 | redis: 24 | image: redis:latest 25 | restart: always 26 | ports: 27 | - 6379:6379 28 | volumes: 29 | - redis-data:/data 30 | 31 | volumes: 32 | redis-data: 33 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | app: 4 | build: 5 | context: . 6 | dockerfile: Dockerfile 7 | volumes: 8 | - .:/app 9 | command: yarn workspace @chatbutler/server start:dev 10 | environment: 11 | - SUPABASE_SERVICE_ROLE=${SUPABASE_SERVICE_ROLE} 12 | - SUPABASE_URL=${SUPABASE_URL} 13 | - OPENAI_API_KEY=${OPENAI_API_KEY} 14 | - ENV=dev 15 | - REDIS_HOST=redis 16 | - REDIS_PORT=6379 17 | ports: 18 | - 3000:3000 19 | depends_on: 20 | - redis 21 | 22 | redis: 23 | image: redis:latest 24 | restart: always 25 | ports: 26 | - 6379:6379 27 | volumes: 28 | - redis-data:/data 29 | 30 | volumes: 31 | redis-data: 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chatbutler", 3 | "version": "1.0.0", 4 | "description": "No Code AI Chatbot builder", 5 | "main": "index.js", 6 | "repository": "https://github.com/eg9y/promptsandbox.io.git", 7 | "author": "Egan Bisma ", 8 | "private": true, 9 | "workspaces": { 10 | "packages": [ 11 | "packages/*" 12 | ] 13 | }, 14 | "devDependencies": { 15 | "typescript": "^5.0.4" 16 | }, 17 | "packageManager": "yarn@3.5.1" 18 | } 19 | -------------------------------------------------------------------------------- /packages/chat-widget/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | export default { 2 | env: { browser: true, es2020: true }, 3 | extends: [ 4 | 'eslint:recommended', 5 | 'plugin:@typescript-eslint/recommended', 6 | 'plugin:react-hooks/recommended', 7 | ], 8 | parser: '@typescript-eslint/parser', 9 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, 10 | plugins: ['react-refresh'], 11 | rules: { 12 | 'react-refresh/only-export-components': 'warn', 13 | }, 14 | } 15 | -------------------------------------------------------------------------------- /packages/chat-widget/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /packages/chat-widget/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/chat-widget/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@chatbutler/chat-widget", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite --port 5174", 8 | "prod": "vite --port 5174 --mode production", 9 | "build": "tsc && vite build", 10 | "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 11 | "preview": "vite preview" 12 | }, 13 | "dependencies": { 14 | "@headlessui/react": "^1.7.13", 15 | "@heroicons/react": "^2.0.16", 16 | "@preact/preset-vite": "^2.5.0", 17 | "preact": "^10.15.0", 18 | "preact-compat": "^3.19.0", 19 | "react": "^18.2.0", 20 | "react-dom": "^18.2.0", 21 | "uuid": "^9.0.0" 22 | }, 23 | "devDependencies": { 24 | "@chatbutler/shared": "1.0.0", 25 | "@types/react": "^18.0.28", 26 | "@types/react-dom": "^18.0.11", 27 | "@typescript-eslint/eslint-plugin": "^5.57.1", 28 | "@typescript-eslint/parser": "^5.57.1", 29 | "@vitejs/plugin-react": "^4.0.0", 30 | "autoprefixer": "^10.4.14", 31 | "eslint": "^8.38.0", 32 | "eslint-plugin-react-hooks": "^4.6.0", 33 | "eslint-plugin-react-refresh": "^0.3.4", 34 | "postcss": "^8.4.21", 35 | "postcss-scope": "^1.5.0", 36 | "prettier-plugin-tailwindcss": "^0.2.8", 37 | "tailwindcss": "^3.2.7", 38 | "typescript": "^5.0.2", 39 | "vite": "^4.3.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/chat-widget/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | "postcss-import": {}, 4 | tailwindcss: {}, 5 | autoprefixer: {}, 6 | "postcss-scope": "#chatbot-root", 7 | }, 8 | } 9 | -------------------------------------------------------------------------------- /packages/chat-widget/src/components/Chat/Chat.tsx: -------------------------------------------------------------------------------- 1 | import { Message } from "../../types"; 2 | import { FC } from "react"; 3 | import { ChatInput } from "./ChatInput"; 4 | import { ChatLoader } from "./ChatLoader"; 5 | import { ChatMessage } from "./ChatMessage"; 6 | import { ResetChat } from "./ResetChat"; 7 | 8 | interface Props { 9 | messages: Message[]; 10 | loading: boolean; 11 | onSend: (message: Message) => void; 12 | onReset: () => void; 13 | } 14 | 15 | export const Chat: FC = ({ messages, loading, onSend, onReset }) => { 16 | return ( 17 | <> 18 |
19 | {messages.map((message, index) => ( 20 |
24 | 25 |
26 | ))} 27 | 28 | {loading && ( 29 |
30 | 31 |
32 | )} 33 | 34 |
35 | 36 | 37 |
38 |
39 | 40 | ); 41 | }; 42 | -------------------------------------------------------------------------------- /packages/chat-widget/src/components/Chat/ChatInput.tsx: -------------------------------------------------------------------------------- 1 | import { JSX } from 'preact'; 2 | import { useRef, useState, useEffect } from 'preact/hooks'; 3 | import { ArrowUpIcon } from "@heroicons/react/20/solid"; 4 | import { Message } from "../../types"; 5 | 6 | interface Props { 7 | onSend: (message: Message) => void; 8 | } 9 | 10 | export const ChatInput = ({ onSend }: Props) => { 11 | const [content, setContent] = useState(); 12 | 13 | const textareaRef = useRef(null); 14 | 15 | const handleChange = (e: JSX.TargetedEvent) => { 16 | const value = e.currentTarget.value; 17 | if (value.length > 4000) { 18 | alert("Message limit is 4000 characters"); 19 | return; 20 | } 21 | 22 | setContent(value); 23 | }; 24 | 25 | const handleSend = async () => { 26 | if (!content) { 27 | alert("Please enter a message"); 28 | return; 29 | } 30 | setContent(""); 31 | await onSend({ role: "user", content }); 32 | }; 33 | 34 | const handleKeyDown = (e: JSX.TargetedKeyboardEvent) => { 35 | if (e.key === "Enter" && !e.shiftKey) { 36 | e.preventDefault(); 37 | handleSend(); 38 | } 39 | }; 40 | 41 | useEffect(() => { 42 | if (textareaRef && textareaRef.current) { 43 | textareaRef.current.style.height = "inherit"; 44 | textareaRef.current.style.height = `${textareaRef.current?.scrollHeight}px`; 45 | } 46 | }, [content]); 47 | 48 | return ( 49 |
50 |