├── .dockerignore ├── .env.local.example ├── .eslintrc.json ├── .github └── workflows │ ├── deploy-docker-image.yaml │ └── run-test-suite.yml ├── .gitignore ├── CONTRIBUTING.md ├── Dockerfile ├── Makefile ├── README.md ├── SECURITY.md ├── __tests__ └── utils │ └── app │ └── importExports.test.ts ├── components ├── Buttons │ └── SidebarActionButton │ │ ├── SidebarActionButton.tsx │ │ └── index.ts ├── Chat │ ├── Chat.tsx │ ├── ChatInput.tsx │ ├── ChatLoader.tsx │ ├── ChatMessage.tsx │ ├── ErrorMessageDiv.tsx │ ├── MemoizedChatMessage.tsx │ ├── ModelSelect.tsx │ ├── PluginSelect.tsx │ ├── PromptList.tsx │ ├── Regenerate.tsx │ ├── SystemPrompt.tsx │ ├── Temperature.tsx │ └── VariableModal.tsx ├── Chatbar │ ├── Chatbar.context.tsx │ ├── Chatbar.state.tsx │ ├── Chatbar.tsx │ └── components │ │ ├── ChatFolders.tsx │ │ ├── ChatbarSettings.tsx │ │ ├── ClearConversations.tsx │ │ ├── Conversation.tsx │ │ ├── Conversations.tsx │ │ └── PluginKeys.tsx ├── Folder │ ├── Folder.tsx │ └── index.ts ├── Markdown │ ├── CodeBlock.tsx │ └── MemoizedReactMarkdown.tsx ├── Mobile │ └── Navbar.tsx ├── Promptbar │ ├── PromptBar.context.tsx │ ├── Promptbar.state.tsx │ ├── Promptbar.tsx │ ├── components │ │ ├── Prompt.tsx │ │ ├── PromptFolders.tsx │ │ ├── PromptModal.tsx │ │ ├── PromptbarSettings.tsx │ │ └── Prompts.tsx │ └── index.ts ├── Search │ ├── Search.tsx │ └── index.ts ├── Settings │ ├── Import.tsx │ ├── Key.tsx │ └── SettingDialog.tsx ├── Sidebar │ ├── Sidebar.tsx │ ├── SidebarButton.tsx │ ├── components │ │ └── OpenCloseButton.tsx │ └── index.ts └── Spinner │ ├── Spinner.tsx │ └── index.ts ├── docker-compose.yml ├── docs └── google_search.md ├── hooks ├── useCreateReducer.ts └── useFetch.ts ├── k8s └── chatbot-ui.yaml ├── license ├── next-i18next.config.js ├── next.config.js ├── package-lock.json ├── package.json ├── pages ├── _app.tsx ├── _document.tsx ├── api │ ├── chat.ts │ ├── google.ts │ ├── home │ │ ├── home.context.tsx │ │ ├── home.state.tsx │ │ ├── home.tsx │ │ └── index.ts │ └── models.ts └── index.tsx ├── postcss.config.js ├── prettier.config.js ├── public ├── favicon.ico ├── locales │ ├── ar │ │ ├── chat.json │ │ ├── common.json │ │ ├── markdown.json │ │ ├── promptbar.json │ │ ├── settings.json │ │ └── sidebar.json │ ├── bn │ │ ├── chat.json │ │ ├── common.json │ │ ├── markdown.json │ │ ├── promptbar.json │ │ ├── settings.json │ │ └── sidebar.json │ ├── ca │ │ ├── chat.json │ │ ├── common.json │ │ ├── markdown.json │ │ ├── promptbar.json │ │ └── sidebar.json │ ├── de │ │ ├── chat.json │ │ ├── common.json │ │ ├── markdown.json │ │ ├── promptbar.json │ │ ├── settings.json │ │ └── sidebar.json │ ├── en │ │ └── common.json │ ├── es │ │ ├── chat.json │ │ ├── common.json │ │ ├── markdown.json │ │ ├── promptbar.json │ │ ├── settings.json │ │ └── sidebar.json │ ├── fi │ │ ├── chat.json │ │ ├── common.json │ │ ├── markdown.json │ │ ├── promptbar.json │ │ ├── settings.json │ │ └── sidebar.json │ ├── fr │ │ ├── chat.json │ │ ├── common.json │ │ ├── markdown.json │ │ ├── promptbar.json │ │ ├── settings.json │ │ └── sidebar.json │ ├── he │ │ ├── chat.json │ │ ├── common.json │ │ ├── markdown.json │ │ ├── promptbar.json │ │ ├── settings.json │ │ └── sidebar.json │ ├── id │ │ ├── chat.json │ │ ├── common.json │ │ ├── markdown.json │ │ ├── promptbar.json │ │ ├── settings.json │ │ └── sidebar.json │ ├── it │ │ ├── chat.json │ │ ├── common.json │ │ ├── markdown.json │ │ ├── promptbar.json │ │ ├── settings.json │ │ └── sidebar.json │ ├── ja │ │ ├── chat.json │ │ ├── common.json │ │ ├── markdown.json │ │ ├── promptbar.json │ │ ├── settings.json │ │ └── sidebar.json │ ├── ko │ │ ├── chat.json │ │ ├── common.json │ │ ├── markdown.json │ │ ├── promptbar.json │ │ ├── settings.json │ │ └── sidebar.json │ ├── pl │ │ ├── chat.json │ │ ├── common.json │ │ ├── markdown.json │ │ ├── promptbar.json │ │ ├── settings.json │ │ └── sidebar.json │ ├── pt │ │ ├── chat.json │ │ ├── common.json │ │ ├── markdown.json │ │ ├── promptbar.json │ │ ├── settings.json │ │ └── sidebar.json │ ├── ro │ │ ├── chat.json │ │ ├── common.json │ │ ├── markdown.json │ │ ├── promptbar.json │ │ ├── settings.json │ │ └── sidebar.json │ ├── ru │ │ ├── chat.json │ │ ├── common.json │ │ ├── markdown.json │ │ ├── promptbar.json │ │ ├── settings.json │ │ └── sidebar.json │ ├── si │ │ ├── chat.json │ │ ├── common.json │ │ ├── markdown.json │ │ ├── promptbar.json │ │ ├── settings.json │ │ └── sidebar.json │ ├── sv │ │ ├── chat.json │ │ ├── common.json │ │ ├── markdown.json │ │ ├── promptbar.json │ │ ├── settings.json │ │ └── sidebar.json │ ├── te │ │ ├── chat.json │ │ ├── common.json │ │ ├── markdown.json │ │ ├── promptbar.json │ │ ├── settings.json │ │ └── sidebar.json │ ├── tr │ │ ├── chat.json │ │ ├── common.json │ │ ├── markdown.json │ │ ├── promptbar.json │ │ └── sidebar.json │ ├── vi │ │ ├── chat.json │ │ ├── common.json │ │ ├── markdown.json │ │ ├── promptbar.json │ │ ├── settings.json │ │ └── sidebar.json │ └── zh │ │ ├── chat.json │ │ ├── common.json │ │ ├── markdown.json │ │ ├── promptbar.json │ │ ├── settings.json │ │ └── sidebar.json ├── screenshot.png └── screenshots │ └── screenshot-0402023.jpg ├── run.sh ├── services ├── errorService.ts └── useApiService.ts ├── styles └── globals.css ├── tailwind.config.js ├── tsconfig.json ├── types ├── chat.ts ├── data.ts ├── env.ts ├── error.ts ├── export.ts ├── folder.ts ├── google.ts ├── index.ts ├── openai.ts ├── plugin.ts ├── prompt.ts ├── settings.ts └── storage.ts ├── utils ├── app │ ├── api.ts │ ├── clean.ts │ ├── codeblock.ts │ ├── const.ts │ ├── conversation.ts │ ├── folders.ts │ ├── importExport.ts │ ├── prompts.ts │ └── settings.ts ├── data │ └── throttle.ts └── server │ ├── google.ts │ └── index.ts └── vitest.config.ts /.dockerignore: -------------------------------------------------------------------------------- 1 | .env 2 | .env.local 3 | node_modules 4 | test-results 5 | -------------------------------------------------------------------------------- /.env.local.example: -------------------------------------------------------------------------------- 1 | # Chatbot UI 2 | DEFAULT_MODEL=gpt-3.5-turbo 3 | NEXT_PUBLIC_DEFAULT_SYSTEM_PROMPT=You are ChatGPT, a large language model trained by OpenAI. Follow the user's instructions carefully. Respond using markdown. 4 | OPENAI_API_KEY=YOUR_KEY 5 | 6 | # Google 7 | GOOGLE_API_KEY=YOUR_API_KEY 8 | GOOGLE_CSE_ID=YOUR_ENGINE_ID 9 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.github/workflows/deploy-docker-image.yaml: -------------------------------------------------------------------------------- 1 | name: Docker 2 | 3 | # This workflow uses actions that are not certified by GitHub. 4 | # They are provided by a third-party and are governed by 5 | # separate terms of service, privacy policy, and support 6 | # documentation. 7 | 8 | on: 9 | push: 10 | branches: ['main'] 11 | 12 | env: 13 | # Use docker.io for Docker Hub if empty 14 | REGISTRY: ghcr.io 15 | # github.repository as / 16 | IMAGE_NAME: ${{ github.repository }} 17 | 18 | jobs: 19 | build: 20 | runs-on: ubuntu-latest 21 | permissions: 22 | contents: read 23 | packages: write 24 | # This is used to complete the identity challenge 25 | # with sigstore/fulcio when running outside of PRs. 26 | id-token: write 27 | 28 | steps: 29 | - name: Checkout repository 30 | uses: actions/checkout@v3 31 | 32 | - name: Set up QEMU 33 | uses: docker/setup-qemu-action@v2.1.0 34 | 35 | # Workaround: https://github.com/docker/build-push-action/issues/461 36 | - name: Setup Docker buildx 37 | uses: docker/setup-buildx-action@79abd3f86f79a9d68a23c75a09a9a85889262adf 38 | 39 | # Login against a Docker registry except on PR 40 | # https://github.com/docker/login-action 41 | - name: Log into registry ${{ env.REGISTRY }} 42 | if: github.event_name != 'pull_request' 43 | uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c 44 | with: 45 | registry: ${{ env.REGISTRY }} 46 | username: ${{ github.actor }} 47 | password: ${{ secrets.GITHUB_TOKEN }} 48 | 49 | # Extract metadata (tags, labels) for Docker 50 | # https://github.com/docker/metadata-action 51 | - name: Extract Docker metadata 52 | id: meta 53 | uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 54 | with: 55 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 56 | 57 | # Build and push Docker image with Buildx (don't push on PR) 58 | # https://github.com/docker/build-push-action 59 | - name: Build and push Docker image 60 | id: build-and-push 61 | uses: docker/build-push-action@ac9327eae2b366085ac7f6a2d02df8aa8ead720a 62 | with: 63 | context: . 64 | platforms: "linux/amd64,linux/arm64" 65 | push: ${{ github.event_name != 'pull_request' }} 66 | tags: ${{ steps.meta.outputs.tags }} 67 | labels: ${{ steps.meta.outputs.labels }} 68 | cache-from: type=gha 69 | cache-to: type=gha,mode=max 70 | -------------------------------------------------------------------------------- /.github/workflows/run-test-suite.yml: -------------------------------------------------------------------------------- 1 | name: Run Unit Tests 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | container: 14 | image: node:16 15 | 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@v2 19 | 20 | - name: Install dependencies 21 | run: npm ci 22 | 23 | - name: Run Vitest Suite 24 | run: npm test 25 | -------------------------------------------------------------------------------- /.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 | /test-results 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | /dist 16 | 17 | # production 18 | /build 19 | 20 | # misc 21 | .DS_Store 22 | *.pem 23 | 24 | # debug 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | .pnpm-debug.log* 29 | 30 | # local env files 31 | .env*.local 32 | 33 | # vercel 34 | .vercel 35 | 36 | # typescript 37 | *.tsbuildinfo 38 | next-env.d.ts 39 | .idea 40 | pnpm-lock.yaml 41 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | **Welcome to Chatbot UI!** 4 | 5 | We appreciate your interest in contributing to our project. 6 | 7 | Before you get started, please read our guidelines for contributing. 8 | 9 | ## Types of Contributions 10 | 11 | We welcome the following types of contributions: 12 | 13 | - Bug fixes 14 | - New features 15 | - Documentation improvements 16 | - Code optimizations 17 | - Translations 18 | - Tests 19 | 20 | ## Getting Started 21 | 22 | To get started, fork the project on GitHub and clone it locally on your machine. Then, create a new branch to work on your changes. 23 | 24 | ``` 25 | git clone https://github.com/mckaywrigley/chatbot-ui.git 26 | cd chatbot-ui 27 | git checkout -b my-branch-name 28 | 29 | ``` 30 | 31 | Before submitting your pull request, please make sure your changes pass our automated tests and adhere to our code style guidelines. 32 | 33 | ## Pull Request Process 34 | 35 | 1. Fork the project on GitHub. 36 | 2. Clone your forked repository locally on your machine. 37 | 3. Create a new branch from the main branch. 38 | 4. Make your changes on the new branch. 39 | 5. Ensure that your changes adhere to our code style guidelines and pass our automated tests. 40 | 6. Commit your changes and push them to your forked repository. 41 | 7. Submit a pull request to the main branch of the main repository. 42 | 43 | ## Contact 44 | 45 | If you have any questions or need help getting started, feel free to reach out to me on [Twitter](https://twitter.com/mckaywrigley). 46 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # ---- Base Node ---- 2 | FROM node:19-alpine AS base 3 | WORKDIR /app 4 | COPY package*.json ./ 5 | 6 | # ---- Dependencies ---- 7 | FROM base AS dependencies 8 | RUN npm ci 9 | 10 | # ---- Build ---- 11 | FROM dependencies AS build 12 | COPY . . 13 | RUN npm run build 14 | 15 | # ---- Production ---- 16 | FROM node:19-alpine AS production 17 | WORKDIR /app 18 | COPY --from=dependencies /app/node_modules ./node_modules 19 | COPY --from=build /app/.next ./.next 20 | COPY --from=build /app/public ./public 21 | COPY --from=build /app/package*.json ./ 22 | COPY --from=build /app/next.config.js ./next.config.js 23 | COPY --from=build /app/next-i18next.config.js ./next-i18next.config.js 24 | 25 | # Expose the port the app will run on 26 | EXPOSE 3000 27 | 28 | # Start the application 29 | CMD ["npm", "start"] 30 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | include .env 2 | 3 | .PHONY: all 4 | 5 | build: 6 | docker build -t chatbot-ui . 7 | 8 | run: 9 | export $(cat .env | xargs) 10 | docker stop chatbot-ui || true && docker rm chatbot-ui || true 11 | docker run --name chatbot-ui --rm -e OPENAI_API_KEY=${OPENAI_API_KEY} -p 3000:3000 chatbot-ui 12 | 13 | logs: 14 | docker logs -f chatbot-ui 15 | 16 | push: 17 | docker tag chatbot-ui:latest ${DOCKER_USER}/chatbot-ui:${DOCKER_TAG} 18 | docker push ${DOCKER_USER}/chatbot-ui:${DOCKER_TAG} -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | 4 | This security policy outlines the process for reporting vulnerabilities and secrets found within this GitHub repository. It is essential that all contributors and users adhere to this policy in order to maintain a secure and stable environment. 5 | 6 | ## Reporting a Vulnerability 7 | 8 | If you discover a vulnerability within the code, dependencies, or any other component of this repository, please follow these steps: 9 | 10 | 1. **Do not disclose the vulnerability publicly.** Publicly disclosing a vulnerability may put the project at risk and could potentially harm other users. 11 | 12 | 2. **Contact the repository maintainer(s) privately.** Send a private message or email to the maintainer(s) with a detailed description of the vulnerability. Include the following information: 13 | 14 | - The affected component(s) 15 | - Steps to reproduce the issue 16 | - Potential impact of the vulnerability 17 | - Any possible mitigations or workarounds 18 | 19 | 3. **Wait for a response from the maintainer(s).** Please be patient, as they may need time to investigate and verify the issue. The maintainer(s) should acknowledge receipt of your report and provide an estimated time frame for addressing the vulnerability. 20 | 21 | 4. **Cooperate with the maintainer(s).** If requested, provide additional information or assistance to help resolve the issue. 22 | 23 | 5. **Do not disclose the vulnerability until the maintainer(s) have addressed it.** Once the issue has been resolved, the maintainer(s) may choose to publicly disclose the vulnerability and credit you for the discovery. 24 | 25 | ## Reporting Secrets 26 | 27 | If you discover any secrets, such as API keys or passwords, within the repository, follow these steps: 28 | 29 | 1. **Do not share the secret or use it for unauthorized purposes.** Misusing a secret could have severe consequences for the project and its users. 30 | 31 | 2. **Contact the repository maintainer(s) privately.** Notify them of the discovered secret, its location, and any potential risks associated with it. 32 | 33 | 3. **Wait for a response and further instructions.** 34 | 35 | ## Responsible Disclosure 36 | 37 | We encourage responsible disclosure of vulnerabilities and secrets. If you follow the steps outlined in this policy, we will work with you to understand and address the issue. We will not take legal action against individuals who discover and report vulnerabilities or secrets in accordance with this policy. 38 | 39 | ## Patching and Updates 40 | 41 | We are committed to maintaining the security of our project. When vulnerabilities are reported and confirmed, we will: 42 | 43 | 1. Work diligently to develop and apply a patch or implement a mitigation strategy. 44 | 2. Keep the reporter informed about the progress of the fix. 45 | 3. Update the repository with the necessary patches and document the changes in the release notes or changelog. 46 | 4. Credit the reporter for the discovery, if they wish to be acknowledged. 47 | 48 | ## Contributing to Security 49 | 50 | We welcome contributions that help improve the security of our project. If you have suggestions or want to contribute code to address security issues, please follow the standard contribution guidelines for this repository. When submitting a pull request related to security, please mention that it addresses a security issue and provide any necessary context. 51 | 52 | By adhering to this security policy, you contribute to the overall security and stability of the project. Thank you for your cooperation and responsible handling of vulnerabilities and secrets. 53 | 54 | -------------------------------------------------------------------------------- /components/Buttons/SidebarActionButton/SidebarActionButton.tsx: -------------------------------------------------------------------------------- 1 | import { MouseEventHandler, ReactElement } from 'react'; 2 | 3 | interface Props { 4 | handleClick: MouseEventHandler; 5 | children: ReactElement; 6 | } 7 | 8 | const SidebarActionButton = ({ handleClick, children }: Props) => ( 9 | 15 | ); 16 | 17 | export default SidebarActionButton; 18 | -------------------------------------------------------------------------------- /components/Buttons/SidebarActionButton/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './SidebarActionButton'; 2 | -------------------------------------------------------------------------------- /components/Chat/ChatLoader.tsx: -------------------------------------------------------------------------------- 1 | import { IconRobot } from '@tabler/icons-react'; 2 | import { FC } from 'react'; 3 | 4 | interface Props { } 5 | 6 | export const ChatLoader: FC = () => { 7 | return ( 8 |
12 |
13 |
14 | 15 |
16 | 17 |
18 |
19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /components/Chat/ErrorMessageDiv.tsx: -------------------------------------------------------------------------------- 1 | import { IconCircleX } from '@tabler/icons-react'; 2 | import { FC } from 'react'; 3 | 4 | import { ErrorMessage } from '@/types/error'; 5 | 6 | interface Props { 7 | error: ErrorMessage; 8 | } 9 | 10 | export const ErrorMessageDiv: FC = ({ error }) => { 11 | return ( 12 |
13 |
14 | 15 |
16 |
{error.title}
17 | {error.messageLines.map((line, index) => ( 18 |
19 | {' '} 20 | {line}{' '} 21 |
22 | ))} 23 |
24 | {error.code ? Code: {error.code} : ''} 25 |
26 |
27 | ); 28 | }; 29 | -------------------------------------------------------------------------------- /components/Chat/MemoizedChatMessage.tsx: -------------------------------------------------------------------------------- 1 | import { FC, memo } from "react"; 2 | import { ChatMessage, Props } from "./ChatMessage"; 3 | 4 | export const MemoizedChatMessage: FC = memo( 5 | ChatMessage, 6 | (prevProps, nextProps) => ( 7 | prevProps.message.content === nextProps.message.content 8 | ) 9 | ); 10 | -------------------------------------------------------------------------------- /components/Chat/ModelSelect.tsx: -------------------------------------------------------------------------------- 1 | import { IconExternalLink } from '@tabler/icons-react'; 2 | import { useContext } from 'react'; 3 | 4 | import { useTranslation } from 'next-i18next'; 5 | 6 | import { OpenAIModel } from '@/types/openai'; 7 | 8 | import HomeContext from '@/pages/api/home/home.context'; 9 | 10 | export const ModelSelect = () => { 11 | const { t } = useTranslation('chat'); 12 | 13 | const { 14 | state: { selectedConversation, models, defaultModelId }, 15 | handleUpdateConversation, 16 | dispatch: homeDispatch, 17 | } = useContext(HomeContext); 18 | 19 | const handleChange = (e: React.ChangeEvent) => { 20 | selectedConversation && 21 | handleUpdateConversation(selectedConversation, { 22 | key: 'model', 23 | value: models.find( 24 | (model) => model.id === e.target.value, 25 | ) as OpenAIModel, 26 | }); 27 | }; 28 | 29 | return ( 30 |
31 | 34 |
35 | 53 |
54 | 64 |
65 | ); 66 | }; 67 | -------------------------------------------------------------------------------- /components/Chat/PluginSelect.tsx: -------------------------------------------------------------------------------- 1 | import { FC, useEffect, useRef } from 'react'; 2 | 3 | import { useTranslation } from 'next-i18next'; 4 | 5 | import { Plugin, PluginList } from '@/types/plugin'; 6 | 7 | interface Props { 8 | plugin: Plugin | null; 9 | onPluginChange: (plugin: Plugin) => void; 10 | onKeyDown: (e: React.KeyboardEvent) => void; 11 | } 12 | 13 | export const PluginSelect: FC = ({ 14 | plugin, 15 | onPluginChange, 16 | onKeyDown, 17 | }) => { 18 | const { t } = useTranslation('chat'); 19 | 20 | const selectRef = useRef(null); 21 | 22 | const handleKeyDown = (e: React.KeyboardEvent) => { 23 | const selectElement = selectRef.current; 24 | const optionCount = selectElement?.options.length || 0; 25 | 26 | if (e.key === '/' && e.metaKey) { 27 | e.preventDefault(); 28 | if (selectElement) { 29 | selectElement.selectedIndex = 30 | (selectElement.selectedIndex + 1) % optionCount; 31 | selectElement.dispatchEvent(new Event('change')); 32 | } 33 | } else if (e.key === '/' && e.shiftKey && e.metaKey) { 34 | e.preventDefault(); 35 | if (selectElement) { 36 | selectElement.selectedIndex = 37 | (selectElement.selectedIndex - 1 + optionCount) % optionCount; 38 | selectElement.dispatchEvent(new Event('change')); 39 | } 40 | } else if (e.key === 'Enter') { 41 | e.preventDefault(); 42 | if (selectElement) { 43 | selectElement.dispatchEvent(new Event('change')); 44 | } 45 | 46 | onPluginChange( 47 | PluginList.find( 48 | (plugin) => 49 | plugin.name === selectElement?.selectedOptions[0].innerText, 50 | ) as Plugin, 51 | ); 52 | } else { 53 | onKeyDown(e); 54 | } 55 | }; 56 | 57 | useEffect(() => { 58 | if (selectRef.current) { 59 | selectRef.current.focus(); 60 | } 61 | }, []); 62 | 63 | return ( 64 |
65 |
66 | 100 |
101 |
102 | ); 103 | }; 104 | -------------------------------------------------------------------------------- /components/Chat/PromptList.tsx: -------------------------------------------------------------------------------- 1 | import { FC, MutableRefObject } from 'react'; 2 | 3 | import { Prompt } from '@/types/prompt'; 4 | 5 | interface Props { 6 | prompts: Prompt[]; 7 | activePromptIndex: number; 8 | onSelect: () => void; 9 | onMouseOver: (index: number) => void; 10 | promptListRef: MutableRefObject; 11 | } 12 | 13 | export const PromptList: FC = ({ 14 | prompts, 15 | activePromptIndex, 16 | onSelect, 17 | onMouseOver, 18 | promptListRef, 19 | }) => { 20 | return ( 21 |
    25 | {prompts.map((prompt, index) => ( 26 |
  • { 34 | e.preventDefault(); 35 | e.stopPropagation(); 36 | onSelect(); 37 | }} 38 | onMouseEnter={() => onMouseOver(index)} 39 | > 40 | {prompt.name} 41 |
  • 42 | ))} 43 |
44 | ); 45 | }; 46 | -------------------------------------------------------------------------------- /components/Chat/Regenerate.tsx: -------------------------------------------------------------------------------- 1 | import { IconRefresh } from '@tabler/icons-react'; 2 | import { FC } from 'react'; 3 | 4 | import { useTranslation } from 'next-i18next'; 5 | 6 | interface Props { 7 | onRegenerate: () => void; 8 | } 9 | 10 | export const Regenerate: FC = ({ onRegenerate }) => { 11 | const { t } = useTranslation('chat'); 12 | return ( 13 |
14 |
15 | {t('Sorry, there was an error.')} 16 |
17 | 24 |
25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /components/Chat/Temperature.tsx: -------------------------------------------------------------------------------- 1 | import { FC, useContext, useState } from 'react'; 2 | 3 | import { useTranslation } from 'next-i18next'; 4 | 5 | import { DEFAULT_TEMPERATURE } from '@/utils/app/const'; 6 | 7 | import HomeContext from '@/pages/api/home/home.context'; 8 | 9 | interface Props { 10 | label: string; 11 | onChangeTemperature: (temperature: number) => void; 12 | } 13 | 14 | export const TemperatureSlider: FC = ({ 15 | label, 16 | onChangeTemperature, 17 | }) => { 18 | const { 19 | state: { conversations }, 20 | } = useContext(HomeContext); 21 | const lastConversation = conversations[conversations.length - 1]; 22 | const [temperature, setTemperature] = useState( 23 | lastConversation?.temperature ?? DEFAULT_TEMPERATURE, 24 | ); 25 | const { t } = useTranslation('chat'); 26 | const handleChange = (event: React.ChangeEvent) => { 27 | const newValue = parseFloat(event.target.value); 28 | setTemperature(newValue); 29 | onChangeTemperature(newValue); 30 | }; 31 | 32 | return ( 33 |
34 | 37 | 38 | {t( 39 | 'Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.', 40 | )} 41 | 42 | 43 | {temperature.toFixed(1)} 44 | 45 | 54 |
    55 |
  • 56 | {t('Precise')} 57 |
  • 58 |
  • 59 | {t('Neutral')} 60 |
  • 61 |
  • 62 | {t('Creative')} 63 |
  • 64 |
65 |
66 | ); 67 | }; 68 | -------------------------------------------------------------------------------- /components/Chat/VariableModal.tsx: -------------------------------------------------------------------------------- 1 | import { FC, KeyboardEvent, useEffect, useRef, useState } from 'react'; 2 | 3 | import { Prompt } from '@/types/prompt'; 4 | 5 | interface Props { 6 | prompt: Prompt; 7 | variables: string[]; 8 | onSubmit: (updatedVariables: string[]) => void; 9 | onClose: () => void; 10 | } 11 | 12 | export const VariableModal: FC = ({ 13 | prompt, 14 | variables, 15 | onSubmit, 16 | onClose, 17 | }) => { 18 | const [updatedVariables, setUpdatedVariables] = useState< 19 | { key: string; value: string }[] 20 | >( 21 | variables 22 | .map((variable) => ({ key: variable, value: '' })) 23 | .filter( 24 | (item, index, array) => 25 | array.findIndex((t) => t.key === item.key) === index, 26 | ), 27 | ); 28 | 29 | const modalRef = useRef(null); 30 | const nameInputRef = useRef(null); 31 | 32 | const handleChange = (index: number, value: string) => { 33 | setUpdatedVariables((prev) => { 34 | const updated = [...prev]; 35 | updated[index].value = value; 36 | return updated; 37 | }); 38 | }; 39 | 40 | const handleSubmit = () => { 41 | if (updatedVariables.some((variable) => variable.value === '')) { 42 | alert('Please fill out all variables'); 43 | return; 44 | } 45 | 46 | onSubmit(updatedVariables.map((variable) => variable.value)); 47 | onClose(); 48 | }; 49 | 50 | const handleKeyDown = (e: KeyboardEvent) => { 51 | if (e.key === 'Enter' && !e.shiftKey) { 52 | e.preventDefault(); 53 | handleSubmit(); 54 | } else if (e.key === 'Escape') { 55 | onClose(); 56 | } 57 | }; 58 | 59 | useEffect(() => { 60 | const handleOutsideClick = (e: MouseEvent) => { 61 | if (modalRef.current && !modalRef.current.contains(e.target as Node)) { 62 | onClose(); 63 | } 64 | }; 65 | 66 | window.addEventListener('click', handleOutsideClick); 67 | 68 | return () => { 69 | window.removeEventListener('click', handleOutsideClick); 70 | }; 71 | }, [onClose]); 72 | 73 | useEffect(() => { 74 | if (nameInputRef.current) { 75 | nameInputRef.current.focus(); 76 | } 77 | }, []); 78 | 79 | return ( 80 |
84 |
89 |
90 | {prompt.name} 91 |
92 | 93 |
94 | {prompt.description} 95 |
96 | 97 | {updatedVariables.map((variable, index) => ( 98 |
99 |
100 | {variable.key} 101 |
102 | 103 |