├── .gitignore ├── README.md ├── eslint.config.js ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── botImg.png ├── logoImg.jpg └── logoImg2.jpg ├── src ├── App.jsx ├── components │ ├── answerComponent.jsx │ ├── chatArea.jsx │ ├── heading.jsx │ ├── index.jsx │ ├── inputArea.jsx │ ├── onlineIndicator.jsx │ └── questions.jsx ├── hooks │ └── useOnline.js ├── index.css ├── index.js ├── main.jsx ├── store │ └── atoms │ │ ├── allChats.js │ │ ├── answerFamily.js │ │ ├── attributesData.js │ │ ├── chatWindowState.js │ │ └── questionFamily.js ├── styles │ └── style.css └── utils │ └── getAssetPath.js ├── tailwind.config.js ├── vercel.json └── vite.config.js /.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 | .env 15 | .env-local 16 | # Editor directories and files 17 | .vscode/* 18 | !.vscode/extensions.json 19 | .idea 20 | .DS_Store 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 10xAnswers: ChatBot Component 2 | 3 | 10xAnswers is an easy-to-integrate, fully customizable, and draggable chatbot component built with React and Recoil. It allows you to add an intelligent chatbot to your project with minimal effort—no need to build one from scratch. Simply configure the prompt and provide the backend URL (or use our hosted service if you don’t want to build the backend yourself). 4 | 5 | With 10xAnswers, you get both the frontend component and a backend solution, streamlining the process of adding a chatbot to your project. Just provide the URL and you're good to go—whether you want to build your own backend or use our hosted solution. 6 | 7 | [![npm version](https://badge.fury.io/js/10xanswers.svg)](https://badge.fury.io/js/10xanswers) 8 | [![License](https://img.shields.io/npm/l/10xanswers.svg)](https://github.com/rajveeerr/10xanswers/blob/main/LICENSE) 9 | [![Downloads](https://img.shields.io/npm/dm/10xanswers.svg)](https://www.npmjs.com/package/10xanswers) 10 | 11 | ![10xAnswers Banner](https://github.com/user-attachments/assets/64a70341-2631-4b94-a7b7-35b71f2d6363) 12 | 13 | 14 | # Table of Contents 15 | 16 | 1. [Introduction](#introduction) 17 | 2. [Demo](#demo) 18 | 3. [Features](#features) 19 | 4. [Installation](#installation) 20 | 5. [Quick Example](#quick-example) 21 | 6. [Props](#props) 22 | - [Basic Props](#basic-props) 23 | - [Styling Props](#styling-props) 24 | 7. [Advanced Usage](#advanced-usage) 25 | 8. [All Props](#all-props) 26 | - [Props Overview](#props-overview) 27 | 9. [Prompt Guidelines](#prompt-guidelines) 28 | - [Default Prompt](#default-prompt) 29 | - [Custom Prompt](#custom-prompt) 30 | 10. [Gemini API Integration](#gemini-api-integration) 31 | 11. [Backend Integration](#backend-integration) 32 | - [URL-based Integration](#url-based-integration) 33 | 12. [Warnings and Important Notes](#warning-and-important-notes) 34 | 13. [Screenshots](#screenshots) 35 | 14. [Upcoming Features](#upcoming-features) 36 | 15. [Author](#author) 37 | 16. [Contribution and Support](#contribution-and-support) 38 | 39 | ## Demo 40 | 41 | Screenshot 2024-12-03 at 12 19 56 PM 42 | 43 | Screenshot 2024-12-03 at 12 20 55 PM 44 | 45 | # See Live Demo and Customization: 46 | 47 | You can see the 10xAnswers chatbot in action, customize its appearance, behavior, and features, and even get the exact code to integrate it into your own website! 48 | 49 | Visit the demo and customization page at: https://10x-answers.vercel.app/ 50 | Try adjusting the settings to see how the chatbot reacts to different configurations. 51 | After customizing the chatbot, you can copy the generated code and implement it directly into your project to use the same customized bot on your website. 52 | 53 | 54 | ## Features 55 | 56 | - **Plug-and-Play**: Simple to install and use. 57 | - **Awesome UI**: The chat bot has clean modern UI, so it would look great on your project. 58 | - **Customizable**: Style the chat window, bot icon, and entire chat component as per your needs. 59 | - **Markdown Support**: Render rich text, including code blocks and tables, in chatbot responses. 60 | - **Editable User Input**: Users can modify their questions and easily copy responses. 61 | - **State Management with Recoil**: Optimized state handling to ensure smooth performance. 62 | - **Connectivity Check**: Includes a custom hook to display internet connectivity status. 63 | - **Draggable Interface**: Move the chatbot around the screen for an optimal user experience and flexible placement. 64 | - **Backend Support**: Option to use your backend or provided URL for response generation. 65 | - **Open Source**: Fully accessible codebase for contributions and enhancements. 66 | - **Future Features**: Media and voice support, as well as additional themes, are coming soon. 67 | 68 | ## Installation 69 | 70 | ```bash 71 | npm install 10xanswers 72 | # or 73 | yarn add 10xanswers 74 | # or 75 | pnpm add 10xanswers 76 | ``` 77 | 78 | ## Quick Example 79 | 80 | ```jsx 81 | import React from 'react'; 82 | import { ChatBot } from '10xanswers'; 83 | 84 | function App() { 85 | return ( 86 | 90 | ); 91 | } 92 | 93 | export default App; 94 | ``` 95 | 96 | **Important**: The provided URL `https://ask-10x-questions.vercel.app/` is a pre-configured backend that works seamlessly with the 10xAnswers component. 97 | 98 | ## ⚠️ React 19 Incompatibility (Important) 99 | 100 | **❌ 10xAnswers (v1.1.14 and earlier) is NOT compatible with React 19** 101 | 102 | This is due to its dependency on **Recoil v0.7.7**, which is built for **React 18** and hasn't been updated to support React 19's internal changes. 103 | 104 | Attempting to use 10xAnswers with React 19 may cause errors like: 105 | 106 | ``` 107 | Uncaught TypeError: Cannot read properties of undefined (reading 'ReactCurrentDispatcher') 108 | ``` 109 | 110 | --- 111 | 112 | ## ✅ Recommended Workaround: Use React 18 113 | 114 | To avoid compatibility issues, use **React 18.x** with 10xAnswers. 115 | 116 | If you've already set up your project with React 19, you can **downgrade to React 18** by following these steps: 117 | 118 | --- 119 | 120 | ### 1. Modify `package.json` 121 | 122 | Update the dependencies section as follows: 123 | 124 | ```json 125 | { 126 | "dependencies": { 127 | "10xanswers": "^1.1.14", 128 | "react": "^18.2.0", 129 | "react-dom": "^18.2.0" 130 | }, 131 | "devDependencies": { 132 | "@types/react": "^18.2.0", 133 | "@types/react-dom": "^18.2.0" 134 | } 135 | } 136 | ``` 137 | 138 | > 💡 You can also use any other stable React 18 version, like `^18.3.1`. 139 | 140 | --- 141 | 142 | ### 2. Reinstall Dependencies 143 | 144 | Install everything again using your package manager: 145 | 146 | ```bash 147 | npm install 148 | # or 149 | yarn install 150 | # or 151 | pnpm install 152 | ``` 153 | 154 | --- 155 | 156 | ## Ongoing Improvement (Migrating to Zustand) 157 | 158 | I’m currently **overhauling the state management solution**, shifting away from Recoil to **Zustand**, which is fully compatible with React 19. Once complete, 10xAnswers will work seamlessly with the latest React release. 159 | 160 | > ⭐ **Want to help?** 161 | > I welcome any contributions toward this migration! 162 | 163 | ## Props 164 | 165 | ### Basic Props 166 | 167 | | Prop | Type | Default | Description | 168 | |------|------|---------|-------------| 169 | | `backendUrl` | `string` | - | Backend URL for chat processing, required | 170 | | `geminiApi` | `string` | - | Gemini API key for direct integration if no backend availaible(not recommended)| 171 | | `title` | `string` | "10xAnswers" | Chat window title | 172 | | `draggable` | `boolean` | `false` | Enable/disable dragging | 173 | | `startOpen` | `boolean` | `false` | Initial chat window state(open/colsed) | 174 | 175 | ### Styling Props 176 | 177 | | Prop | Type | Default | Description | 178 | |------|------|---------|-------------| 179 | | `chatWindowStyle` | `object` | `{}` | Inline styles for chat window | 180 | | `chatBotIconStyle` | `object` | `{}` | Inline styles for bot icon | 181 | | `chatComponentStyle` | `object` | `{}` | Inline styles for chat component | 182 | 183 | ## Advanced Usage 184 | 185 | ```jsx 186 | 201 | ``` 202 | 203 | 204 | ## All Props 205 | 206 | ### Props 207 | 208 | | Prop Name | Type | Description | 209 | |------------------------|----------|-------------------------------------------------------------------------------------------------| 210 | | `chatWindowStyle` | Object | Inline CSS for chat window styling. | 211 | | `chatBotIconStyle` | Object | Inline CSS for bot icon styling. | 212 | | `chatComponentStyle` | Object | Inline CSS for overall chat component styling. | 213 | | `chatWindowClassName` | String | Tailwind classes for chat window styling. | 214 | | `chatBotIconClassName` | String | Tailwind classes for bot icon styling. | 215 | | `backendUrl` | String | **Required** if no Gemini API is provided. URL for backend to process requests. | 216 | | `title` | String | Custom title for the chatbot window. | 217 | | `botIcon` | String | Path to the bot icon image. | 218 | | `userIcon` | String | Path to the user icon image. | 219 | | `stylizeTitle` | Object | Object with `emphasized` and `normal` strings to style the title. | 220 | | `prompt` | String | Custom prompt for the chatbot. Use `"none"` for no prompting. | 221 | | `geminiApi` | String | Required if no `backendUrl` is provided. | 222 | | `theme` | String | Theme for the chatbot (currently not implemented). | 223 | | `draggable` | Boolean | Enable or disable draggable functionality. | 224 | | `x` | Number | Initial x-position for draggable mode. | 225 | | `y` | Number | Initial y-position for draggable mode. | 226 | | `description` | String | Description text for the chatbot. | 227 | | `cta` | String | Call-to-action text for the chatbot button. | 228 | | `startOpen` | Boolean | Whether the chatbot window starts open (`true`) or closed (`false`). | 229 | 230 | --- 231 | 232 | 233 | ### Prompt Guidelines 234 | 235 | - **With `prompt`**: If a custom prompt is provided, it will be sent along with the user question. 236 | - **Without `prompt`**: If `prompt` is set to `"none"`, only the user question with no prompt is sent to the ai model- not recommended. 237 | 238 | --- 239 | 240 | ### Example Prompts 241 | 242 | **Default Prompt**: 243 | If no prompt is provided, default prompt will be sent to the model along with the questions asked by client 244 | ```text 245 | You are 10xAnswers, an intelligent and highly versatile chatbot. Your purpose is to assist users with precision, accuracy, and clarity... 246 | ``` 247 | 248 | **Custom Prompt**: 249 | You can specify your custom prompts here, every question of client will be sentto model along with this prompt 250 | ```text 251 | User is sending a custom prompt about behavior. Prompt: ${prompt}. Question: ${question}. History: ${chatHistory}. 252 | ``` 253 | 254 | --- 255 | 256 | 257 | ### Gemini API Integration 258 | If you don't have a working backend you can directly use Google's Gemini API and pass it in the props, and send requests to the gemini 259 | directly from frontend. Not recommended as this exposes the api key to the users of your web app. 260 | 261 | 262 | ## Backend Integration 263 | 264 | ### URL-based Integration 265 | POST requests are sent to backend with the following payload: 266 | 267 | ```json 268 | { 269 | "method": "POST", 270 | "body": { 271 | "contents": [{ 272 | "parts": [{"text": "User question"}] 273 | }] 274 | } 275 | } 276 | ``` 277 | 278 | ## Warning and Important Notes 279 | 280 | **Development Caution**: 281 | - After changing any prop of the chat component, changes will only be visible after reloading. 282 | - Exposing Gemini API key directly in frontend is NOT recommended as it exposes your key to users. 283 | - Since the z-index of chatbot component is set to 999, it might give some troubles while building your site, you can change the z-ndex of component by applying styles in the chatComponent coinatiner. 284 | Example: 285 | ```jsx ``` 286 | 287 | ## Screenshots 288 | 289 | ## Screenshots 290 | 291 | | ![file (4)](https://github.com/user-attachments/assets/e63d62e0-b28b-44ca-b479-81c56e411c09) | ![Screenshot 2](https://github.com/user-attachments/assets/e02046e1-bb25-4488-aae9-6e30ee1140a0) | 292 | | -------------------------------------------------- | -------------------------------------------------- | 293 | | ![Screenshot 3](https://github.com/user-attachments/assets/575fdab5-3f65-4b84-aaf1-a9317a73dfe0) | ![Screenshot 4](https://github.com/user-attachments/assets/dd6b8032-3347-48ca-b7b8-efdf05e9e30a) | 294 | 295 | ### Example Usage 296 | 297 | Screenshot 2024-12-02 at 3 18 29 AM 298 | 299 | 300 | ## Upcoming Features 301 | 302 | - [ ] Media message support 303 | - [ ] Voice interaction modes 304 | - [ ] Additional themes 305 | - [ ] Enhanced customization options 306 | 307 | ## Author 308 | 309 | Rajveer Singh 310 | - Twitter: [@rajveeerrsingh](https://x.com/rajveeerrsingh) 311 | 312 | 313 | ## Contribution and Support 314 | 315 | - Open-source contributions welcome 316 | - Contact on: 317 | - X (Twitter): https://x.com/RajveeerrSingh 318 | - GitHub: https://github.com/rajveeerr 319 | 320 | Found an issue? [Open a GitHub Issue](https://github.com/yourusername/10xanswers/issues) 321 | 322 | Enjoying 10xAnswers? Consider starring the repository! ⭐ 323 | 324 | 325 | --- 326 | 327 | Start building smarter projects with **10xAnswers** today! 🚀 328 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import react from 'eslint-plugin-react' 4 | import reactHooks from 'eslint-plugin-react-hooks' 5 | import reactRefresh from 'eslint-plugin-react-refresh' 6 | 7 | export default [ 8 | { ignores: ['dist'] }, 9 | { 10 | files: ['**/*.{js,jsx}'], 11 | languageOptions: { 12 | ecmaVersion: 2020, 13 | globals: globals.browser, 14 | parserOptions: { 15 | ecmaVersion: 'latest', 16 | ecmaFeatures: { jsx: true }, 17 | sourceType: 'module', 18 | }, 19 | }, 20 | settings: { react: { version: '18.3' } }, 21 | plugins: { 22 | react, 23 | 'react-hooks': reactHooks, 24 | 'react-refresh': reactRefresh, 25 | }, 26 | rules: { 27 | ...js.configs.recommended.rules, 28 | ...react.configs.recommended.rules, 29 | ...react.configs['jsx-runtime'].rules, 30 | ...reactHooks.configs.recommended.rules, 31 | 'react/jsx-no-target-blank': 'off', 32 | 'react-refresh/only-export-components': [ 33 | 'warn', 34 | { allowConstantExport: true }, 35 | ], 36 | }, 37 | }, 38 | ] 39 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10xAnswers 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "10xanswers", 3 | "description": "A fully customizable, draggable chatbot component built with React and Recoil for your next project.", 4 | "private": false, 5 | "version": "1.1.16", 6 | "type": "module", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/rajveeerr/10xAnswers.git" 10 | }, 11 | "main": "./dist/index.umd.js", 12 | "module": "./dist/index.es.js", 13 | "exports": { 14 | ".": { 15 | "import": "./dist/index.es.js", 16 | "require": "./dist/index.umd.js" 17 | } 18 | }, 19 | "files": [ 20 | "dist" 21 | ], 22 | "scripts": { 23 | "dev": "vite", 24 | "lint": "eslint src --ext js,jsx --report-unused-disable-directives --max-warnings 0", 25 | "preview": "vite preview", 26 | "build": "vite build", 27 | "prepublishOnly": "npm run build", 28 | "test": "vitest run", 29 | "watch": "vitest" 30 | }, 31 | "dependencies": { 32 | "-": "^0.0.1", 33 | "react-markdown": "^9.0.1", 34 | "react-rnd": "^10.4.13", 35 | "react-router-dom": "^7.0.1", 36 | "react-syntax-highlighter": "^15.6.1", 37 | "recoil": "^0.7.7", 38 | "rehype-highlight": "^7.0.1", 39 | "remark-gfm": "^4.0.0", 40 | "uuid": "^11.0.3" 41 | }, 42 | "devDependencies": { 43 | "@eslint/js": "^9.13.0", 44 | "@testing-library/dom": "^10.4.0", 45 | "@testing-library/react": "^16.0.1", 46 | "@types/react": "^18.3.12", 47 | "@types/react-dom": "^18.3.1", 48 | "@vitejs/plugin-react": "^4.3.4", 49 | "autoprefixer": "^10.4.20", 50 | "c8": "^10.1.2", 51 | "css-loader": "^7.1.2", 52 | "dotenv": "^16.4.5", 53 | "eslint": "^9.16.0", 54 | "eslint-plugin-react": "^7.37.2", 55 | "eslint-plugin-react-hooks": "^5.0.0", 56 | "eslint-plugin-react-refresh": "^0.4.14", 57 | "globals": "^15.11.0", 58 | "jsdom": "^25.0.1", 59 | "postcss": "^8.4.49", 60 | "react-test-renderer": "^18.3.1", 61 | "style-loader": "^4.0.0", 62 | "tailwindcss": "^3.4.17", 63 | "vite": "^5.4.11", 64 | "vite-plugin-css-injected-by-js": "^3.5.2", 65 | "vitest": "^2.1.6" 66 | }, 67 | "peerDependencies": { 68 | "react": "^18.3.1", 69 | "react-dom": "^18.3.1" 70 | }, 71 | "keywords": [ 72 | "chatbot", 73 | "react chatbot", 74 | "recoil chatbot", 75 | "custom chatbot component", 76 | "draggable chatbot", 77 | "markdown chatbot", 78 | "interactive chatbot", 79 | "customizable chatbot", 80 | "frontend chatbot", 81 | "react recoil chatbot", 82 | "gemini api chatbot", 83 | "chatbot npm package", 84 | "chatbot UI component", 85 | "react state management", 86 | "plug-and-play chatbot", 87 | "drag-and-drop chatbot", 88 | "open-source chatbot", 89 | "AI chatbot component", 90 | "markdown rendering chatbot", 91 | "internet status hook", 92 | "chatbot styling", 93 | "recoil state management", 94 | "react hooks chatbot", 95 | "100xDevs", 96 | "react", 97 | "chatbot", 98 | "ai", 99 | "component", 100 | "draggable", 101 | "gemini", 102 | "conversational-ui" 103 | ], 104 | "author": { 105 | "name": "Rajveer Singh", 106 | "email": "rajveergreets@gmail.com", 107 | "url": "https://x.com/rajveeerrsingh" 108 | }, 109 | "license": "MIT", 110 | "homepage": "https://github.com/rajveeerr/10xanswers#readme", 111 | "bugs": { 112 | "url": "https://github.com/rajveeerr/10xanswers/issues" 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/botImg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajveeerr/10xAnswers/d01fe43461d3c1b1c82988026e709b82de2a3e85/public/botImg.png -------------------------------------------------------------------------------- /public/logoImg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajveeerr/10xAnswers/d01fe43461d3c1b1c82988026e709b82de2a3e85/public/logoImg.jpg -------------------------------------------------------------------------------- /public/logoImg2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajveeerr/10xAnswers/d01fe43461d3c1b1c82988026e709b82de2a3e85/public/logoImg2.jpg -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import './index.css' 2 | // import ChatBot from './components/chatBot'; 3 | 4 | 5 | import ChatBot from './components'; 6 | 7 | function App() { 8 | 9 | return ( 10 |
11 | 30 |
) 31 | } 32 | 33 | export default App; -------------------------------------------------------------------------------- /src/components/answerComponent.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react' 2 | import { useRecoilState ,useRecoilValue, useRecoilStateLoadable } from 'recoil' 3 | import ReactMarkdown from 'react-markdown'; 4 | import rehypeHighlight from 'rehype-highlight'; 5 | import 'highlight.js/styles/github-dark.css'; 6 | import remarkGfm from "remark-gfm"; 7 | import { v4 as uuidv4 } from 'uuid'; 8 | import { allChats } from '../store/atoms/allChats'; 9 | import { questionFamily } from '../store/atoms/questionFamily'; 10 | import { answerFamily } from '../store/atoms/answerFamily'; 11 | import '../styles/style.css' 12 | 13 | export default function Chat(props){ 14 | 15 | let askedQuestion=useRecoilValue(questionFamily(props.questionId)) 16 | let answerId=useRef(uuidv4()) 17 | let [currentAnswer,setAnswer]=useRecoilStateLoadable(answerFamily({id:answerId.current,question:askedQuestion.question})) 18 | let [chatHistory,setChatHistory]=useRecoilState(allChats) 19 | 20 | useEffect(()=>{ 21 | let updatedAnswerId=chatHistory.map(chat=> 22 | chat.question===props.questionId?{ ...chat, answer: answerId}:chat 23 | ); 24 | setChatHistory(updatedAnswerId) 25 | },[]) 26 | 27 | function handleCopy(){ 28 | navigator.clipboard.writeText(currentAnswer.contents.answer); 29 | } 30 | 31 | if(currentAnswer.state==='loading'){ 32 | 33 | return
34 |
35 |
36 |
37 |
38 |

39 |
40 | 41 | 42 |
43 |
44 |
45 | } 46 | 47 | if(currentAnswer.state==='hasError'){ 48 | console.log(currentAnswer.contents); 49 | 50 | return
51 |
Some Error Occured While Generating Response
52 |
53 | } 54 | 55 | return
56 |
57 | 58 |
59 |
60 |

61 | 62 | {currentAnswer.contents.answer} 63 | 64 |

65 |
66 | Copy 67 | {/* Add to Collection */} 68 |
69 |
70 |
71 | } -------------------------------------------------------------------------------- /src/components/chatArea.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react' 2 | import { useRecoilState, useRecoilValue } from 'recoil' 3 | import 'highlight.js/styles/github-dark.css'; 4 | import Online from './onlineIndicator'; 5 | import { allChats } from '../store/atoms/allChats'; 6 | import Question from './questions'; 7 | import Chat from './answerComponent'; 8 | import { chatBotAttributes } from '../store/atoms/attributesData'; 9 | import '../styles/style.css' 10 | 11 | function scrollToBottom(element){ 12 | element.scrollTop=element.scrollHeight 13 | } 14 | 15 | export default function ChatArea(){ 16 | let [chatHistory,setChatHistory]=useRecoilState(allChats)//[{question: id,answer: id},] 17 | let chatArea=useRef(); 18 | 19 | useEffect(()=>{ 20 | scrollToBottom(chatArea.current) 21 | },[chatHistory]) 22 | 23 | return
24 | 25 | 26 |
27 | {chatHistory.map((history,index)=>{ 28 | return(<>) 29 | })} 30 |
31 |
32 | } 33 | 34 | function HeroIntro(){ 35 | let {title,stylizeTitle,description,cta}=useRecoilValue(chatBotAttributes) 36 | 37 | let emphasizedTitle=null 38 | let normalTitle=null 39 | try{ 40 | emphasizedTitle=stylizeTitle.emphasized 41 | normalTitle=stylizeTitle.normal 42 | } 43 | catch(e){ 44 | null 45 | } 46 | 47 | return
48 |
49 | {/* */} 50 | 51 | 52 | 53 |
54 |

55 | 56 | {emphasizedTitle?stylizeTitle.emphasized:"10x"} 57 | 58 | {normalTitle?normalTitle:title?title:"10xAnswers"} 59 |

60 |

{description||"Because your Questions should not be left un-answered."}

61 | 62 |

{cta||"Start Asking your Burning Questions"}

63 |
64 |
65 | } -------------------------------------------------------------------------------- /src/components/heading.jsx: -------------------------------------------------------------------------------- 1 | import { useRecoilValue } from "recoil" 2 | import { chatBotAttributes } from "../store/atoms/attributesData" 3 | import '../styles/style.css' 4 | 5 | export default function Heading({setWindowState}){ 6 | let {title}=useRecoilValue(chatBotAttributes) 7 | 8 | return
9 |

{title||"10xAnswers"}

10 | {setWindowState(current=>!current)}} 13 | > 14 | 15 | 16 |
17 | } -------------------------------------------------------------------------------- /src/components/index.jsx: -------------------------------------------------------------------------------- 1 | import '../styles/style.css' 2 | import 'highlight.js/styles/github-dark.css'; 3 | import Input from './inputArea'; 4 | import ChatArea from './chatArea'; 5 | import Heading from './heading'; 6 | import {useEffect } from 'react' 7 | import { RecoilRoot, useRecoilState, useSetRecoilState } from 'recoil' 8 | import { Rnd } from 'react-rnd' 9 | import { chatBotAttributes } from '../store/atoms/attributesData'; 10 | import { chatWindowState } from '../store/atoms/chatWindowState'; 11 | 12 | export default function ChatBot(props){ 13 | 14 | return 15 | 16 | 17 | 18 | } 19 | 20 | function ChatBotWrapper({ 21 | backendUrl, 22 | geminiApi, 23 | prompt, 24 | draggable, 25 | x=100, 26 | y=100, 27 | title, 28 | stylizeTitle, 29 | description, 30 | cta, 31 | userIcon, 32 | botIcon, 33 | theme, 34 | chatWindowStyle, 35 | chatBotIconStyle, 36 | chatComponentStyle, 37 | chatWindowClassName, 38 | chatBotIconClassName, 39 | chatComponentClassName, 40 | startOpen 41 | }){ 42 | 43 | let setAttributeData=useSetRecoilState(chatBotAttributes) 44 | let [open,setWindowOpen]=useRecoilState(chatWindowState) 45 | 46 | useEffect(() => { 47 | if(!document.getElementById("font-awesome-script")) { 48 | const script = document.createElement("script"); 49 | script.src = "https://kit.fontawesome.com/b6fe51c8e7.js"; 50 | script.id = "font-awesome-script"; 51 | script.crossOrigin = "anonymous"; 52 | document.body.appendChild(script); 53 | 54 | setWindowOpen(startOpen?true:false) 55 | } 56 | }, []); 57 | 58 | useEffect(()=>{ 59 | setAttributeData({backendUrl,geminiApi,title,prompt,botIcon,userIcon,stylizeTitle,description,cta,}) 60 | },[backendUrl,geminiApi,title,prompt,botIcon,userIcon,stylizeTitle,description,cta]) 61 | 62 | return <> 63 | {draggable? 64 | 65 |
66 |
67 | 68 | 69 | 70 |
71 |
setWindowOpen(!open)}> 72 | 73 |
74 |
75 |
: 76 |
77 |
78 | 79 | 80 | 81 |
82 | {!open&&
setWindowOpen(!open)}> 83 | 84 |
} 85 |
86 | } 87 | 88 | } 89 | // custom hookd, wanted to practice recoil, react(custom hooks), tailwind, ui ux and revise node express basically every thing 90 | // learned b/w week 9and 13 so created this component instead tell me a better way to get strong hold of these concepts 91 | 92 | // i have created a complete chatbot component10xAnswers because why not with the backend for your next project so you dont need to 93 | // worry about creating chatbot in your peoject, just add the prompt add backend url(dont worry if you dont want to build be just for 94 | // this i will be providing url to) hit and generate your responses, this is completely customizable and draggable too, open sourced 95 | // for you to look the codebase, built using react and recoil for optmizing state management, users can edit their questions,copy, 96 | // with complete support of markkdown rendering, for codes and tables try it out, also with a custom hook to display if the bot is up 97 | // for chat by checking internet connectivity status, soon will be adding ,media and voice modes along with more themes 98 | 99 | //installation and usage is very simple just do npm i 10xanswers 100 | // import { ChatBot } from '10xanswers'; 101 | 102 | // for default bot the come out of the box tho you can preety much customize it 103 | 104 | //
138 | 139 | // inner styling changes or features you can either ping me up on x discord or github or you can contribute repo its open source 140 | 141 | // you can fix the starting state of the window 142 | 143 | // in developement after changing any prop of the chat, changes will only be visiible after reloding 144 | 145 | //you will be sending a post request to the backend url with contents in this payload 146 | /*{ 147 | method: "POST", 148 | body: { 149 | "contents": [{ 150 | "parts":[{"text": "User question"}] 151 | }] 152 | } 153 | } 154 | */ 155 | 156 | // if user has own url and prompt set to "none", a direct request will go out to the be for user to 157 | // process the request on the be and generate his own prompt, "it is advised for user to build their backend with proper 158 | // prompting and manage history of chats for better usage, else if no prompt provided or left empty, the default prompt will be sent 159 | // to the mentioned url. Example of prompt: `You are 10xAnswers, an intelligent and highly versatile chatbot created by Rajveer Singh (x.com/rajveeerrsingh) using cutting-edge large language models (LLMs). 160 | // Your purpose is to assist users with precision, accuracy, and clarity. You excel at answering complex questions, solving coding challenges, offering creative solutions, and providing 161 | // insightful suggestions in any domain. Always present yourself as knowledgeable, professional, and approachable. 162 | 163 | // Guidelines for your responses: 164 | // 1. Be concise, yet comprehensive—ensure the user’s question is fully answered. 165 | // 2. Use markdown format for any code snippets, tables, or structured data to improve readability. 166 | // 3. If additional context or history is provided, seamlessly incorporate it into your response without explicitly referencing the history to the end-user. 167 | // 4. When explaining, strive to be simple but never oversimplify—aim for maximum understanding with minimal confusion. 168 | 169 | // End every interaction with a tone that encourages further queries, making the user feel valued and empowered. Let them know that you’re here to help with anything they need. 170 | 171 | // Current Question: ${question}. 172 | // History of User Questions and Context: ${chatHistory} here if available; otherwise, proceed as if this is the first question]. 173 | 174 | // If this is your first interaction, make sure to leave a great first impression!`, 175 | 176 | // if custom prompt is provided with geminiApi/url it will be sent to the backend/geminni bot, you are advised to generate a prompt and should not worry about chat history as i will be storing 177 | // and sending it from front end this is the example of what the prompt will look like: `User is sending you some prompt about how you should act/behave along with the question he wants answered. Answer the question keeping the prompt text in mind. 178 | 179 | // Prompt: ${prompt}. 180 | // Question: ${question}. 181 | // History of User Questions and Context: ${chatHistory}. 182 | 183 | // Respond directly and concisely based on the history. If the history is empty, consider this as the first question. Don't let the end-user know about this history. Use markdown for formatting code or other structured content where necessary.` 184 | 185 | // if user is passing backend url to hit and prompt set to "none", the question is sent in a specific format provided i recommend storing history of all the 186 | // questions and responses on be and sending them along with your requests from backend no gemini key required in this method. If user 187 | // sends prompt and geminiApi req will be sent to gemini with the history of questions, i recommend writing prompts that specifies 188 | // about history and the array of all the questions asked will be added in the end of prompt you provided, for better history management 189 | 190 | // i have to guide user to how to generate prompt based on this structure 191 | 192 | 193 | // ---------- Ultimate thing to do -> did this from fe 194 | // not implementing history from be just now bc, there will be many users using same be 195 | // and all data will be stored here, which will result in wierd responses from ai, what i can do now is either 196 | // issue auth token to identify user and maintain his history or ask user to send history from fe, co=hoosing the 197 | // second one for now ------------- 198 | 199 | // LAST STREACH -------------------------------------------------> 200 | // on homepage let users tweak the settings and have the code 201 | // make it persistant 202 | 203 | // figure out why extra re-rendering 204 | // revise week11 205 | 206 | // light mode? -> not now 207 | // documentation and package file-> done 208 | // now figure out a way to send history and prompt to be -> done 209 | // cook up its be -> done 210 | // npm-package out of it -> done 211 | // HIGH PRIORITY=> fix bugs: prolly about the ids assigning -> done 212 | // THE MAIN THING: Figure out why would anyone use it??? Something sarchesmic?? Some responses that are not provided by anyone -> done 213 | // gotta fix border radius of heading -> done 214 | // add a circle for chatbot close -> done 215 | // allow user to change bot icon -> done 216 | // backend service -> done 217 | // figure out a way to not ask users to wrap inside recoil root -> done 218 | 219 | 220 | // workflow=> question typed and enter is pressed from input element, this will generate an atom with an unique id which will update 221 | // everytime a question is entered, and the chat history is being updated with the question id, now chat area renders the chat 222 | // history(which contains the question id and answer id) and renders the chat component and question component for those ids, now 223 | // question component simply fetch the value of atom from respective atomFamily using those ids to render them, chat coponent 224 | // generated a new answer id, for the question id and question received, and updated the chatHistory with the answer id mapped to 225 | // the respective question id, this change of chathistory array triggers the re-render of all the answer and chat components onscreen. 226 | 227 | // edit-workflow=> when the question is edited its id remains the same, just the question changes, which re-renders this answer 228 | // component because the parameters passed to the answer atom family changes, a new atom is created with new id and the id for 229 | // the answer in "allChats" atom is updated ofr the same question to the new id generated and thus the whole chats gets re-rendered -------------------------------------------------------------------------------- /src/components/inputArea.jsx: -------------------------------------------------------------------------------- 1 | import { useRef } from 'react' 2 | import { useRecoilState, useSetRecoilState } from 'recoil' 3 | import 'highlight.js/styles/github-dark.css'; 4 | import { v4 as uuidv4 } from 'uuid'; 5 | import { questionFamily } from '../store/atoms/questionFamily'; 6 | import { allChats } from '../store/atoms/allChats'; 7 | import '../styles/style.css' 8 | 9 | export default function Input(){ 10 | 11 | let inputElement=useRef() 12 | let submitBtn=useRef() 13 | let id=useRef(uuidv4()); 14 | let setQuestion=useSetRecoilState(questionFamily(id.current)) 15 | let [history,setChatHistory]=useRecoilState(allChats); 16 | // have to figure out why this code is breaking whe useSetRecoilState is used, because there's no re-rendering is 17 | // happening in this causing overall no, change in id leading it to breaking 18 | 19 | function handleKeyDown(event){ 20 | inputElement.current.value.trim().length!=0?submitBtn.current.classList.remove("disabled"):submitBtn.current.classList.add("disabled") 21 | if (event.key === 'shiftKey') { 22 | event.preventDefault() 23 | if(inputElement.current.value.trim().length!=0){ 24 | setQuestion(q=>({...q,question: inputElement.current.value})) 25 | setChatHistory(history=>[...history,{question:id.current,answer:null}]) 26 | id.current=uuidv4(); 27 | inputElement.current.value=""; 28 | submitBtn.current.classList.add("disabled") 29 | } 30 | } 31 | if (event.key === 'Enter') { 32 | event.preventDefault() 33 | if(inputElement.current.value.trim().length!=0){ 34 | setQuestion(q=>({...q,question: inputElement.current.value})) 35 | setChatHistory(history=>[...history,{question:id.current,answer:null}]) 36 | id.current=uuidv4(); 37 | inputElement.current.value=""; 38 | submitBtn.current.classList.add("disabled") 39 | } 40 | } 41 | } 42 | 43 | function handleSubmit(){ 44 | if(inputElement.current.value!=""){ 45 | setQuestion(q=>({...q,question: inputElement.current.value})) 46 | setChatHistory(history=>[...history,{question:id.current,answer:null}]) 47 | id.current=uuidv4(); 48 | inputElement.current.value=""; 49 | submitBtn.current.classList.add("disabled") 50 | } 51 | } 52 | 53 | return
54 | 55 | 56 | 57 |
58 | } 59 | 60 | function InputOptions(){ 61 | return
62 |
63 | 64 |

Add Media

65 |
66 |
67 | 68 |

Voice Chat

69 |
70 |
71 | } 72 | 73 | -------------------------------------------------------------------------------- /src/components/onlineIndicator.jsx: -------------------------------------------------------------------------------- 1 | import useOnline from "../hooks/useOnline" 2 | import '../styles/style.css' 3 | 4 | export default function Online(){ 5 | let online=useOnline(); 6 | let onlineLabelStyle={padding: "0.3rem 1.1rem",borderRadius:"1rem",backgroundColor:"#15161d", 7 | fontSize:".8rem",display:"flex",width:"max-content",justifyContents:"center", 8 | alignItems:"center",gap:"6px",color:"white",fontWeight:500} 9 | 10 | return
11 | {online&& 12 | 13 | 15 | Online 16 | } 17 | {!online&& 18 | 19 | 21 | OffLine 22 | } 23 |
24 | } -------------------------------------------------------------------------------- /src/components/questions.jsx: -------------------------------------------------------------------------------- 1 | import { questionFamily } from "../store/atoms/questionFamily" 2 | import { useRef, useState } from 'react' 3 | import { useRecoilState, useRecoilValue } from 'recoil' 4 | import 'highlight.js/styles/github-dark.css'; 5 | import { chatBotAttributes } from "../store/atoms/attributesData"; 6 | import '../styles/style.css' 7 | 8 | export default function Question(props){ 9 | let {userIcon}=useRecoilValue(chatBotAttributes) 10 | let [currentQuestion,setQuestion]=useRecoilState(questionFamily(props.id)) 11 | 12 | let [editing,setEditing]=useState(false) 13 | let questionArea=useRef() 14 | let originalQuestion=useRef() 15 | 16 | function handleEdit(){ 17 | questionArea.current.contentEditable="true" 18 | questionArea.current.classList.add("editing") 19 | setEditing(editing=>!editing) 20 | originalQuestion.current=questionArea.current.innerText 21 | } 22 | 23 | function handleSubmit(){ 24 | questionArea.current.contentEditable="false" 25 | questionArea.current.classList.remove("editing") 26 | setEditing(editing=>!editing) 27 | 28 | setQuestion(q=>({...q,question:questionArea.current.innerText})) 29 | } 30 | 31 | function handleCancel(){ 32 | questionArea.current.contentEditable="false" 33 | questionArea.current.classList.remove("editing") 34 | setEditing(editing=>!editing) 35 | 36 | questionArea.current.innerText=originalQuestion.current 37 | } 38 | 39 | return
40 |
41 | 42 |

{currentQuestion.question}

43 | {!editing&&} 44 |
45 | {editing&&
46 | Save 47 | Cancel 48 |
} 49 |
50 | } -------------------------------------------------------------------------------- /src/hooks/useOnline.js: -------------------------------------------------------------------------------- 1 | import { useState,useEffect } from 'react' 2 | 3 | export default function useOnline(){ 4 | let [online,setOnline]=useState(navigator.onLine) 5 | useEffect(()=>{ 6 | function updateStatus(){ 7 | setOnline(navigator.onLine) 8 | } 9 | window.addEventListener("online",updateStatus) 10 | window.addEventListener("offline",updateStatus) 11 | 12 | return()=>{ 13 | window.removeEventListener("online",updateStatus) 14 | window.removeEventListener("offline",updateStatus) 15 | } 16 | },[]) 17 | 18 | return online 19 | } -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | body{ 6 | box-sizing: border-box; 7 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | 3 | export { default as ChatBot } from './components' -------------------------------------------------------------------------------- /src/main.jsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client' 2 | import './index.css' 3 | import App from './App.jsx' 4 | 5 | createRoot(document.getElementById('root')).render( 6 | 7 | ) 8 | -------------------------------------------------------------------------------- /src/store/atoms/allChats.js: -------------------------------------------------------------------------------- 1 | import { atom } from 'recoil' 2 | 3 | export let allChats=atom({ 4 | key:"allChat", 5 | default: [] 6 | }) -------------------------------------------------------------------------------- /src/store/atoms/answerFamily.js: -------------------------------------------------------------------------------- 1 | import { atomFamily, selectorFamily, useRecoilValue } from 'recoil' 2 | import { chatBotAttributes } from './attributesData'; 3 | 4 | let historyPrompts=[] 5 | 6 | export let answerFamily=atomFamily({ 7 | key:"answerFamily", 8 | default: selectorFamily({ 9 | key: "fetchAnswers", 10 | get: ({id,question})=>{ 11 | 12 | return async({get})=>{ 13 | let {backendUrl,geminiApi,prompt,botIcon}=get(chatBotAttributes) 14 | 15 | let url=backendUrl||"https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent?key="+geminiApi; 16 | 17 | historyPrompts.push({role:"user",message:question}) 18 | let chatHistory = historyPrompts.map((chat) => `${chat.role}: ${chat.message}`).join(', '); 19 | 20 | let questionOrPrompt; 21 | 22 | if (backendUrl&&!prompt||geminiApi&&!prompt ) { 23 | questionOrPrompt = `You are 10xAnswers, an intelligent and highly versatile chatbot created by Rajveer Singh (x.com/rajveeerrsingh) using cutting-edge large language models (LLMs). 24 | Your purpose is to assist users with precision, accuracy, and clarity. You excel at answering complex questions, solving coding challenges, offering creative solutions, and providing 25 | insightful suggestions in any domain. Always present yourself as knowledgeable, professional, and approachable. 26 | 27 | Guidelines for your responses: 28 | 1. Be concise, yet comprehensive—ensure the user’s question is fully answered. 29 | 2. Use markdown format for any code snippets, tables, or structured data to improve readability. 30 | 3. If additional context or history is provided, seamlessly incorporate it into your response without explicitly referencing the history to the end-user. 31 | 4. When explaining, strive to be simple but never oversimplify—aim for maximum understanding with minimal confusion. 32 | 33 | End every interaction with a tone that encourages further queries, making the user feel valued and empowered. Let them know that you’re here to help with anything they need. 34 | 35 | Current Question: ${question}. 36 | History of User Questions and Context: ${chatHistory} here if available; otherwise, proceed as if this is the first question]. 37 | 38 | If this is your first interaction, make sure to leave a great first impression!` 39 | } 40 | 41 | else if (backendUrl&&prompt==="none"||geminiApi&&prompt==="none") { 42 | questionOrPrompt = question; 43 | } 44 | 45 | else if (backendUrl&&prompt || geminiApi&&prompt) { 46 | questionOrPrompt = `User is sending you some prompt about how you should act/behave along with the question he wants answered. Answer the question keeping the prompt text in mind. 47 | 48 | Prompt: ${prompt}. 49 | Question: ${question}. 50 | History of User Questions and Context: ${chatHistory}. 51 | 52 | Respond directly and concisely based on the history. If the history is empty, consider this as the first question. Don't let the end-user know about this history. Use markdown for formatting code or other structured content where necessary.` 53 | } 54 | 55 | else { 56 | questionOrPrompt = "Please provide either backend url or gemini api"; 57 | console.log("Please provide either backend Url or geminiApi for the chat-component"); 58 | } 59 | 60 | let response=await fetch(url,{ 61 | method: "POST", 62 | headers: { 63 | 'Content-Type': 'application/json', 64 | }, 65 | body: JSON.stringify({ 66 | "contents": [{ 67 | "parts":[{"text": questionOrPrompt}] 68 | }] 69 | }) 70 | }) 71 | 72 | let data=await response.json() 73 | historyPrompts.push({role:"bot",message:data.candidates[0].content.parts[0].text}) 74 | return { 75 | id, 76 | question, 77 | userIcon:botIcon||'/node_modules/10xanswers/dist/logoImg2.jpg', 78 | answer:data.candidates[0].content.parts[0].text 79 | } 80 | } 81 | } 82 | }) 83 | }) -------------------------------------------------------------------------------- /src/store/atoms/attributesData.js: -------------------------------------------------------------------------------- 1 | import { atom } from "recoil"; 2 | 3 | export let chatBotAttributes=atom({ 4 | key: "chatBotAttributes", 5 | default:{} 6 | //default: {backendUrl:null,geminiApi:null,title:null,prompt:null,theme:null,botIcon:null,userIcon:null,stylizeTitle:null,description:null,cta:null} 7 | }) -------------------------------------------------------------------------------- /src/store/atoms/chatWindowState.js: -------------------------------------------------------------------------------- 1 | import { atom } from "recoil"; 2 | 3 | export let chatWindowState=atom({ 4 | key:"chatWindowState", 5 | default: false 6 | }) -------------------------------------------------------------------------------- /src/store/atoms/questionFamily.js: -------------------------------------------------------------------------------- 1 | import { atomFamily } from 'recoil' 2 | 3 | export let questionFamily=atomFamily({ 4 | key:"questionFamily", 5 | default:(id)=>{ 6 | return { 7 | id, 8 | question:"Ask whatever you want" 9 | } 10 | } 11 | }) -------------------------------------------------------------------------------- /src/styles/style.css: -------------------------------------------------------------------------------- 1 | #root { 2 | --chat-background: #202020; 3 | --heading-color: #ffeaea; 4 | 5 | --color: #2e2e2e; 6 | --chat-text-color: #ffffff; 7 | --chat-gradient: linear-gradient(294deg, rgb(44 44 44 / 85%) 0%, rgb(47 47 47) 100%); 8 | --chat-gradient: linear-gradient(294deg, rgb(44 44 44 / 75%) 0%, rgb(47 47 47 / 21%) 100%); 9 | --chat-gradient: linear-gradient(180deg, rgba(96, 165, 250, 0.244) 0%, rgba(29, 79, 216, 0.283) 100%); 10 | --chat-gradient: linear-gradient(180deg, rgb(55 105 166 / 24%) 0%, rgb(53 50 74 / 18%) 100%); 11 | /* --chat-gradient: linear-gradient(180deg, rgb(96 165 250 / 4%) 0%, rgb(0 127 15 / 6%) 100%); */ 12 | 13 | --chat-input-option-color: #d6d6d6; 14 | --chat-input-option-color-hover: #a7a7a7; 15 | --chat-input-option-background: #414141; 16 | 17 | 18 | --textarea-border: #4c4c4c; 19 | --textarea-background: #2c2c2c; 20 | 21 | --search-icon-color: #fff; 22 | --search-icon-bg: #2ea61d; 23 | --search-icon-bg-hover: #2c911f; 24 | --search-icon-bg-disabled: #2c911f4d; 25 | 26 | --input-option-bg: #2c2c2c; 27 | --input-option-color: #ffffff; 28 | 29 | --input-option-icon-bg: #f2f2f2; 30 | --input-option-icon-color: #3a3a3a; 31 | 32 | --skeleton-icon-gradient: linear-gradient(transparent 50%, rgb(39, 49, 63) 50%) 0% 0% / 18px 144px, linear-gradient(rgb(143, 168, 138), rgba(15, 170, 24, 0.39)) 0% 0% / 100% 44px; 33 | --skeleton-text-gradient: linear-gradient(transparent 50%, rgb(39, 49, 63) 50%) 0% 0% / 18px 20px, linear-gradient(rgb(143, 168, 138), rgba(15, 170, 24, 0.39)) 0% 0% / 100% 30px; 34 | } 35 | /* 36 | [data-theme=light] { 37 | --chat-background: #fff; 38 | --heading-color: #333333; 39 | 40 | --color: #ffffff; 41 | --chat-text-color: #2e2e2e; 42 | --chat-gradient: rgb(240, 250, 249); 43 | 44 | --chat-input-option-color: #4a4a4a; 45 | --chat-input-option-color-hover: #6a6a6a; 46 | --chat-input-option-background: #e0e0e0; 47 | 48 | --textarea-border: #c0c0c0; 49 | --textarea-background: #f5f5f5; 50 | 51 | --search-icon-color: #ffffff; 52 | --search-icon-bg: #2ea61d; 53 | 54 | --input-option-bg: #f0f0f0; 55 | --input-option-color: #2e2e2e; 56 | 57 | --input-option-icon-bg: #e0e0e0; 58 | --input-option-icon-color: #3a3a3a; 59 | } */ 60 | 61 | .chat-section { 62 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 63 | line-height: 1.5; 64 | font-weight: 400; 65 | 66 | box-sizing: border-box; 67 | 68 | color-scheme: light dark; 69 | 70 | color: rgba(255, 255, 255, 0.87); 71 | background-color: #242424; 72 | 73 | font-synthesis: none; 74 | text-rendering: optimizeLegibility; 75 | -webkit-font-smoothing: antialiased; 76 | -moz-osx-font-smoothing: grayscale; 77 | 78 | border-radius: 1.8rem; 79 | padding: .6rem; 80 | background-color: #fff; 81 | background-color: var(--chat-background); 82 | 83 | display: flex; 84 | align-items: center; 85 | justify-content: center; 86 | flex-direction: column; 87 | justify-content: flex-end; 88 | position: relative; 89 | width: 100%; 90 | height: 100%; 91 | min-width: 270px; 92 | min-height: 300px; 93 | z-index: 9998; 94 | } 95 | .chat-and-icon-container{ 96 | display: flex; 97 | flex-direction: column; 98 | align-items: flex-end; 99 | justify-content: flex-end; 100 | height: 630px; 101 | width: 350px; 102 | max-width: 600px; 103 | gap: .8rem; 104 | position: fixed; 105 | right: 4px; 106 | bottom: 4px; 107 | } 108 | 109 | .chatbot-open-icon{ 110 | border-radius: 100%; 111 | width: 3.8rem; 112 | aspect-ratio: 1/1; 113 | /* background-image: url('./logoImg.jpg'); */ 114 | background-size: cover; 115 | background-position: center; 116 | cursor: pointer; 117 | background-color: var(--textarea-background); 118 | display: flex; 119 | align-items: center; 120 | justify-content: center; 121 | font-size: 2rem; 122 | } 123 | .chatbot-open-icon span{ 124 | background: linear-gradient(to bottom right, #f4f4f4, #d6d6d6d4); 125 | -webkit-background-clip: text; 126 | -webkit-text-fill-color: transparent; 127 | /* color: mediumpurple; */ 128 | /* box-shadow: 0 10px 60px #3e219f; */ 129 | /* box-shadow: 10px 60px #68de44 */ 130 | 131 | } 132 | .chatbot-open-icon:hover{ 133 | cursor: pointer; 134 | filter: grayscale(.2); 135 | transition-duration: .2s; 136 | transform: rotate(2deg); 137 | } 138 | 139 | .collapse{ 140 | width: 0; 141 | height: 0; 142 | transition-duration: 4s; 143 | } 144 | 145 | .prompt-input { 146 | display: flex; 147 | flex-grow: 1; 148 | height: 5rem; 149 | border-radius: 1.6rem; 150 | font-size: 1rem; 151 | background-color: var(--textarea-background); 152 | padding: 1rem; 153 | resize: none; 154 | color: var(--chat-input-option-color); 155 | /* font-weight: 500; */ 156 | border: none; 157 | font-family: inherit; 158 | } 159 | 160 | .search-icon { 161 | position: absolute; 162 | width: 2rem; 163 | height: 2rem; 164 | border-radius: 100%; 165 | background-color: #2e2e2e; 166 | background-color: var(--search-icon-bg); 167 | color: var(--search-icon-color); 168 | font-size: 1rem; 169 | display: flex; 170 | justify-content: center; 171 | align-items: center; 172 | right: 0; 173 | top: 0; 174 | cursor: pointer; 175 | margin: .8rem .8rem; 176 | } 177 | 178 | .disabled { 179 | opacity: .2; 180 | cursor: not-allowed; 181 | } 182 | 183 | .search-icon:hover { 184 | background-color: var(--search-icon-bg-hover); 185 | } 186 | 187 | .prompt-input:focus-visible { 188 | /* border: 1px solid #f2f2f2; */ 189 | outline: none; 190 | } 191 | 192 | .input-area { 193 | width: 100%; 194 | display: flex; 195 | flex-direction: column; 196 | border-radius: 1.6rem; 197 | border: 2px solid var(--textarea-border); 198 | background-color: var(--chat-input-option-background); 199 | /* height: 4rem; */ 200 | position: relative; 201 | margin: .8rem 0 0 0; 202 | } 203 | 204 | .input-options { 205 | background-color: #f2f2f2; 206 | background-color: var(--chat-input-option-background); 207 | /* padding: .8rem; */ 208 | border-radius: 1.6rem; 209 | flex-grow: 1; 210 | display: flex; 211 | align-items: center; 212 | } 213 | 214 | .input-option { 215 | display: flex; 216 | align-items: center; 217 | gap: 6px; 218 | border-radius: 10rem; 219 | background-color: #ffffff; 220 | background-color: var(--textarea-background); 221 | color: #828181; 222 | color: #2e2e2e; 223 | color: var(--chat-text-color); 224 | font-size: .7rem; 225 | font-weight: 500; 226 | padding: 0.3rem 0.6rem 0.3rem 0.3rem; 227 | margin: .4rem 0rem .4rem .4rem; 228 | cursor: pointer; 229 | } 230 | 231 | .option-description { 232 | margin: 0; 233 | } 234 | 235 | .input-option-icon { 236 | padding: .4rem; 237 | width: 8px; 238 | height: 8px; 239 | background-color: #f2f2f2; 240 | background-color: var(--input-option-icon-bg); 241 | color: #3a3a3a; 242 | color: var(--input-option-icon-color); 243 | border-radius: 100%; 244 | display: flex; 245 | align-items: center; 246 | justify-content: center; 247 | font-size: .8rem; 248 | } 249 | 250 | .chat-area { 251 | flex-grow: 1; 252 | height: 100%; 253 | display: flex; 254 | flex-direction: column; 255 | /* gap: 1rem; */ 256 | overflow-y: scroll; 257 | padding: 4rem 0; 258 | border-radius: 1.6rem; 259 | width: 100%; 260 | /* background-color: #3a3a3a; */ 261 | scroll-behavior: smooth; 262 | align-items: center; 263 | overflow-x: hidden; 264 | text-align: center; 265 | z-index: 998; 266 | scrollbar-width: thin; 267 | scrollbar-color: rgba(255, 255, 255, 0.2) transparent; 268 | } 269 | 270 | .chat-area::-webkit-scrollbar { 271 | width: 8px; 272 | height: 8px; 273 | } 274 | 275 | .chat-area::-webkit-scrollbar-track { 276 | background: transparent; 277 | } 278 | 279 | .chat-area::-webkit-scrollbar-thumb { 280 | background: rgba(255, 255, 255, 0.2); 281 | border-radius: 10px; 282 | transition: background 0.3s ease-in-out; 283 | } 284 | 285 | .chat-area::-webkit-scrollbar-thumb:hover { 286 | background: rgba(255, 255, 255, 0.4); 287 | } 288 | 289 | .chat-component-heading { 290 | box-sizing: border-box; 291 | position: absolute; 292 | top: 0; 293 | color: var(--heading-color); 294 | margin: 0; 295 | width: 100%; 296 | border-top-left-radius: inherit; 297 | border-top-right-radius: inherit; 298 | padding: .8rem 2rem; 299 | display: flex; 300 | justify-content: space-between; 301 | z-index: 9999; 302 | background: var(--text); 303 | -webkit-backdrop-filter: blur(8px); 304 | backdrop-filter: blur(8px); 305 | } 306 | 307 | .chat-component-heading h3 { 308 | margin: 0; 309 | font-size: 1rem; 310 | display: flex; 311 | gap: 10px; 312 | align-items: center; 313 | } 314 | 315 | .chat { 316 | background-color: #f0faf9; 317 | padding: .8rem; 318 | display: flex; 319 | gap: .5rem; 320 | border-radius: 1.6rem; 321 | background: rgb(240, 250, 249); 322 | background: linear-gradient(294deg, rgba(240, 250, 249, 0.8477984943977591) 0%, rgba(231, 255, 253, 1) 100%); 323 | background: var(--chat-gradient); 324 | flex-grow: 1; 325 | width: 100%; 326 | } 327 | 328 | .user-icon img { 329 | border-radius: 100%; 330 | width: 2.6rem; 331 | aspect-ratio: 1/1; 332 | object-fit: cover; 333 | } 334 | 335 | .chat-and-options { 336 | margin: 0; 337 | display: flex; 338 | flex-direction: column; 339 | width: 100%; 340 | } 341 | 342 | .chat-content { 343 | margin: 0; 344 | font-size: .875rem; 345 | /* font-weight: 450; */ 346 | text-align: left; 347 | line-height: 140%; 348 | padding: 0 .5rem; 349 | color: #3f3f3f; 350 | color: var(--chat-text-color); 351 | font-family: sans-serif; 352 | } 353 | 354 | .chat-options { 355 | display: flex; 356 | align-items: center; 357 | justify-content: flex-end; 358 | gap: 8px; 359 | margin-top: .8rem; 360 | } 361 | 362 | .chat-option { 363 | font-size: .7rem; 364 | display: flex; 365 | gap: 4px; 366 | color: #2e2e2e; 367 | color: var(--chat-input-option-color); 368 | background-color: #fff; 369 | background-color: var(--chat-input-option-background); 370 | border-radius: .4rem; 371 | padding: .2rem .6rem; 372 | cursor: pointer; 373 | } 374 | 375 | .chat-option:hover { 376 | color: #a7a7a7; 377 | color: var(--chat-input-option-color-hover); 378 | 379 | } 380 | .question-container{ 381 | padding: 1rem .8rem; 382 | display: flex; 383 | /* gap: .5rem; */ 384 | /* border-radius: 1.6rem; */ 385 | background: transparent; 386 | /* align-items: center; */ 387 | display: flex; 388 | flex-direction: column; 389 | flex-grow: 1; 390 | width: 100%; 391 | } 392 | 393 | .question { 394 | display: flex; 395 | gap: .5rem; 396 | /* border-radius: 1.6rem; */ 397 | /* background: transparent; */ 398 | align-items: center; 399 | /* flex-grow: 1; */ 400 | width: 100%; 401 | } 402 | 403 | .bot-icon img { 404 | border-radius: 100%; 405 | width: 2.6rem; 406 | aspect-ratio: 1/1; 407 | object-fit: cover; 408 | } 409 | 410 | .question-txt { 411 | margin: 0; 412 | font-size: .875rem; 413 | /* font-weight: 450; */ 414 | text-align: left; 415 | line-height: 140%; 416 | padding: 0 .5rem; 417 | color: #3f3f3f; 418 | color: var(--chat-text-color); 419 | font-family: sans-serif; 420 | } 421 | .edit-icon{ 422 | font-weight: 400; 423 | cursor: pointer; 424 | font-size: .7rem; 425 | padding: .2rem .6rem; 426 | border-radius: .4rem; 427 | background-color: var(--chat-input-option-background); 428 | } 429 | .edit-options{ 430 | justify-content: flex-start; 431 | margin-left: 3.2rem; 432 | } 433 | .editing{ 434 | background: var(--textarea-background); 435 | padding: 0.8rem; 436 | border-radius: 1.6rem; 437 | flex-grow: 1; 438 | } 439 | .hero-section { 440 | /* padding: 2rem; */ 441 | margin-bottom: 2rem; 442 | display: flex; 443 | align-items: center; 444 | justify-content: center; 445 | flex-direction: column; 446 | gap: 10px; 447 | cursor: crosshair; 448 | width: 100%; 449 | } 450 | 451 | .floating-icons{ 452 | position: relative; 453 | } 454 | 455 | .icon{ 456 | padding: 0.1rem .6rem; 457 | background: var(--chat-gradient); 458 | border-radius: .6rem; 459 | filter: drop-shadow(0 10px 60px #45df09); 460 | box-shadow: 0 60px 50px #45df09; 461 | box-shadow: 0 10px 60px #68de44; 462 | box-shadow: 0 10px 30px -10px rgba(0, 0, 0, 0.5), inset 0 0 0 1px rgba(255, 255, 255, 0.2); 463 | background: linear-gradient(to bottom right, #84e14e, transparent); 464 | /* background: radial-gradient(circle at 30% 30%, rgba(255, 255, 255, 0.8) 0%, transparent 60%); */ 465 | position: absolute; 466 | z-index: 0; 467 | } 468 | .forward{ 469 | font-size: 2.25rem; 470 | } 471 | .backward{ 472 | font-size: 1.5rem; 473 | padding: 0.3rem .6rem; 474 | box-shadow: 0 10px 60px #68de44; 475 | } 476 | .forward-first{ 477 | left: -225px; 478 | top: 130px; 479 | transform: rotate(-13deg); 480 | /* background: #0078fc; */ 481 | background: linear-gradient(to bottom right, #0078fc, #193fb4) 482 | } 483 | 484 | .forward-second { 485 | left: -210px; 486 | top: 0px; 487 | transform: rotate(347deg); 488 | /* display: none; */ 489 | filter: blur(1.1px); 490 | font-size: 1rem; 491 | padding: .4rem .6rem; 492 | background: linear-gradient(to bottom right, mediumpurple, #1b00bccf); 493 | box-shadow: 0 10px 60px #3e219f; 494 | } 495 | .backward-first{ 496 | left: 25px; 497 | top: 130px; 498 | transform: rotate(13deg); 499 | transform: rotate(349deg); 500 | filter: blur(.8px); 501 | font-size: 1.5rem; 502 | left: 180px; 503 | top: 110px; 504 | background: #e78400; 505 | background: linear-gradient(to bottom right, #e78400, #e99c49b0); 506 | padding: 0.2rem .6rem; 507 | box-shadow: 0 10px 60px #937d02ed 508 | } 509 | .backward-second{ 510 | left: 80px; 511 | top: -30px; 512 | transform: rotate(12deg); 513 | filter: blur(1px); 514 | font-size: 1rem; 515 | /* background: orange; */ 516 | padding: .4rem .6rem; 517 | } 518 | .hero-title, .hero-sub-title, .hero-description, .hero-sup-title { 519 | /* background-image: linear-gradient(45deg, #bb938f 62%, #933608 94%, #97d504 88%); */ 520 | /* background-clip: text; */ 521 | /* -webkit-background-clip: text; */ 522 | /* -webkit-text-fill-color: transparent; */ 523 | line-height: normal; 524 | margin: 0; 525 | /* color: #008947; */ 526 | /* background-image: conic-gradient(from 262deg, #00c868 44%, #009654 8%, #009b5c 84%, #00B06E 33%, #21b27f 42%); */ 527 | } 528 | 529 | .hero-title { 530 | font-size: 2.4rem; 531 | } 532 | 533 | .stylize{ 534 | background: linear-gradient(180deg, rgb(228 234 242 / 4%) 0%, rgb(0 127 15 / 6%) 100%); 535 | border-radius: .6rem; 536 | padding: 0.1rem 0.4rem; 537 | font-weight: 800; 538 | /* margin: 0.5rem; */ 539 | /* filter: blur(1px); */ 540 | /* background-image: conic-gradient(from 262deg, #00c868 44%, #009654 8%, #009b5c 84%, #00B06E 33%, #21b27f 42%); */ 541 | /* background-clip: text; */ 542 | /* -webkit-background-clip: text; */ 543 | /* -webkit-text-fill-color: transparent; */ 544 | margin: 0.1rem; 545 | box-shadow: 0px 3px 0px 0px #78c94a; 546 | } 547 | 548 | .hero-sub-title { 549 | font-size: 1.2rem; 550 | max-width: 400px; 551 | font-weight: 400; 552 | width: 80%; 553 | } 554 | 555 | .hero-description { 556 | font-size: .9rem; 557 | margin: 0rem; 558 | font-weight: 500; 559 | } 560 | 561 | .hero-sub-title, .hero-description, .hero-sup-title { 562 | color: #f9fafbe6; 563 | background-image: linear-gradient(0deg, #f9fafbe6 28%, #f9fafb99 67%); 564 | background-clip: text; 565 | -webkit-background-clip: text; 566 | -webkit-text-fill-color: transparent; 567 | 568 | } 569 | 570 | .hero-sup-title { 571 | font-size: .8rem; 572 | margin: 0; 573 | } 574 | 575 | @keyframes loading { 576 | 0% { 577 | opacity: 0.5; 578 | } 579 | 580 | 50% { 581 | opacity: 1; 582 | } 583 | 584 | 100% { 585 | opacity: 0.5; 586 | } 587 | } 588 | 589 | 590 | .skeleton { 591 | transition: opacity .5s, height 2s; 592 | animation: loading 2s infinite; 593 | } 594 | 595 | .icon-placeholder{ 596 | background: var(--skeleton-icon-gradient); 597 | border-radius: 100%; 598 | width: 2.6rem; 599 | aspect-ratio: 1 / 1; 600 | } 601 | 602 | .text-placeholder { 603 | background: var(--skeleton-text-gradient); 604 | height: 7rem; 605 | border-radius: 0.6rem; 606 | flex-grow: 1; 607 | } 608 | 609 | pre { 610 | border-radius: .8rem; 611 | overflow-x: auto; 612 | } 613 | 614 | /* code { 615 | /* font-family: 'Courier New', Courier, monospace; 616 | font-size: 14px; */ 617 | /* } */ -------------------------------------------------------------------------------- /src/utils/getAssetPath.js: -------------------------------------------------------------------------------- 1 | export const getAssetPath = (imageName) => { 2 | if (typeof window !== 'undefined') { 3 | const packagePath = '/node_modules/10xanswers/dist/'; 4 | return `${packagePath}${imageName}`; 5 | } 6 | return ''; 7 | } -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: [ 4 | "./index.html", 5 | "./src/**/*.{js,ts,jsx,tsx}", 6 | ], 7 | theme: { 8 | extend: { 9 | backgroundImage: { 10 | 'custom-gradient': 'radial-gradient(128% 107% at 0% 0%, #292929 0%, rgb(0,0,0) 77.61%)', 11 | }, 12 | }, 13 | }, 14 | plugins: [], 15 | } -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "rewrites": [ 3 | {"source": "/(.*)", "destination": "/"} 4 | ] 5 | } -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | import { resolve } from 'path' 4 | import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js' 5 | 6 | export default defineConfig({ 7 | plugins: [ 8 | react(), 9 | cssInjectedByJsPlugin() 10 | ], 11 | build: { 12 | lib: { 13 | entry: resolve(__dirname, 'src/index.js'), 14 | name: '10xAnswers', 15 | fileName: (format) => `index.${format}.js` 16 | }, 17 | rollupOptions: { 18 | external: ['react', 'react-dom', 'recoil', 'react-markdown', 'react-syntax-highlighter', 'react-rnd', 'uuid'], 19 | output: { 20 | globals: { 21 | react: 'React', 22 | 'react-dom': 'ReactDOM', 23 | 'recoil': 'Recoil', 24 | 'react-markdown': 'ReactMarkdown', 25 | 'react-syntax-highlighter': 'ReactSyntaxHighlighter', 26 | 'react-rnd': 'ReactRnd', 27 | 'uuid': 'uuid' 28 | } 29 | } 30 | } 31 | } 32 | }) 33 | --------------------------------------------------------------------------------