├── .gitignore ├── requirements.txt ├── chainlit.md ├── data_store.py ├── ice_cream_store_app.py ├── api_docs.py ├── prompts.py ├── chatbot.py ├── README.md └── .chainlit ├── config.toml └── translations ├── en-US.json └── pt-BR.json /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | *.png 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | langchain==0.1.1 2 | openai==1.9.0 3 | nbconvert 4 | python-dotenv==1.0.0 5 | langchain-openai 6 | chainlit 7 | uvicorn==0.25.0 8 | Flask==3.0.1 -------------------------------------------------------------------------------- /chainlit.md: -------------------------------------------------------------------------------- 1 | # 🍨 Welcome to Scoopsie! 🍦 2 | 3 | Hi there! 👋 I am Scoopsie and I am built to assist you with all your ice-cream related queries. You can begin by asking me anything related to ice-creams in the chatbox below. 4 | 5 | 6 | -------------------------------------------------------------------------------- /data_store.py: -------------------------------------------------------------------------------- 1 | # Example menu, special offers, customer reviews, and customizations 2 | 3 | menu = { 4 | "flavors": [ 5 | {"flavorName": "Strawberry", "count": 50}, 6 | {"flavorName": "Chocolate", "count": 75} 7 | ], 8 | "toppings": [ 9 | {"toppingName": "Hot Fudge", "count": 50}, 10 | {"toppingName": "Sprinkles", "count": 2000}, 11 | {"toppingName": "Whipped Cream", "count": 50} 12 | ] 13 | } 14 | 15 | special_offers = { 16 | "offers": [ 17 | {"offerName": "Two for Tuesday", "details": "Buy one get one free on all ice cream flavors every Tuesday."}, 18 | {"offerName": "Winter Wonderland Discount", "details": "25% off on all orders above $20 during the winter season."} 19 | ] 20 | } 21 | 22 | customer_reviews = { 23 | "reviews": [ 24 | {"userName": "andrew_1", "rating": 5, "comment": "Loved the chocolate flavor!"}, 25 | {"userName": "john", "rating": 4, "comment": "Great place, but always crowded."}, 26 | {"userName": "allison", "rating": 5, "comment": "Love the ice-creams and Scoopsie is super helpful!"} 27 | ] 28 | } 29 | 30 | customizations = { 31 | "options": [ 32 | {"customizationName": "Sugar-Free", "details": "Available for most flavors."}, 33 | {"customizationName": "Extra Toppings", "details": "Choose as many toppings as you want for an extra $5!"} 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /ice_cream_store_app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, jsonify 2 | from data_store import menu, special_offers, customer_reviews, customizations 3 | 4 | app = Flask(__name__) 5 | 6 | 7 | @app.route('/menu', methods=['GET']) 8 | def get_menu(): 9 | """ 10 | Retrieves the menu data. 11 | 12 | Returns: 13 | A tuple containing the menu data as JSON and the HTTP status code. 14 | """ 15 | return jsonify(menu), 200 16 | 17 | 18 | @app.route('/special-offers', methods=['GET']) 19 | def get_special_offers(): 20 | """ 21 | Retrieves the special offers data. 22 | 23 | Returns: 24 | A tuple containing the special offers data as JSON and the HTTP status code. 25 | """ 26 | return jsonify(special_offers), 200 27 | 28 | 29 | @app.route('/customer-reviews', methods=['GET']) 30 | def get_customer_reviews(): 31 | """ 32 | Retrieves customer reviews data. 33 | 34 | Returns: 35 | A tuple containing the customer reviews data as JSON and the HTTP status code. 36 | """ 37 | return jsonify(customer_reviews), 200 38 | 39 | 40 | @app.route('/customizations', methods=['GET']) 41 | def get_customizations(): 42 | """ 43 | Retrieves the customizations data. 44 | 45 | Returns: 46 | A tuple containing the customizations data as JSON and the HTTP status code. 47 | """ 48 | return jsonify(customizations), 200 49 | 50 | 51 | if __name__ == '__main__': 52 | app.run(debug=True) 53 | -------------------------------------------------------------------------------- /api_docs.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | scoopsie_api_docs = { 4 | "base_url": "http://127.0.0.1:5000/", 5 | "endpoints": { 6 | "/menu": { 7 | "method": "GET", 8 | "description": "Retrieve the menu of flavors and customizations.", 9 | "parameters": None, 10 | "response": { 11 | "description": "A JSON object containing available flavors and toppings along with their counts.", 12 | "content_type": "application/json" 13 | } 14 | }, 15 | "/special-offers": { 16 | "method": "GET", 17 | "description": "Retrieve current special offers and discounts.", 18 | "parameters": None, 19 | "response": { 20 | "description": "A JSON object listing the current special offers and discounts.", 21 | "content_type": "application/json" 22 | } 23 | }, 24 | "/customer-reviews": { 25 | "method": "GET", 26 | "description": "Retrieve customer reviews for the ice cream store.", 27 | "parameters": None, 28 | "response": { 29 | "description": "A JSON object containing customer reviews, ratings, and comments.", 30 | "content_type": "application/json" 31 | } 32 | }, 33 | "/customizations": { 34 | "method": "GET", 35 | "description": "Retrieve available ice cream customizations.", 36 | "parameters": None, 37 | "response": { 38 | "description": "A JSON object listing available customizations like toppings and sugar-free options.", 39 | "content_type": "application/json" 40 | } 41 | } 42 | } 43 | } 44 | 45 | scoopsie_api_docs = json.dumps(scoopsie_api_docs, indent=2) 46 | -------------------------------------------------------------------------------- /prompts.py: -------------------------------------------------------------------------------- 1 | from langchain.prompts import PromptTemplate 2 | 3 | ice_cream_assistant_template = """ 4 | You are an ice cream assistant chatbot named "Scoopsie". Your expertise is exclusively in providing information and 5 | advice about anything related to ice creams. This includes flavor combinations, ice cream recipes, and general 6 | ice cream-related queries. You do not provide information outside of this scope. If a question is not about ice cream, 7 | respond with, "I specialize only in ice cream related queries." 8 | Chat History: {chat_history} 9 | Question: {question} 10 | Answer:""" 11 | 12 | ice_cream_assistant_prompt = PromptTemplate( 13 | input_variables=["chat_history", "question"], 14 | template=ice_cream_assistant_template 15 | ) 16 | 17 | api_url_template = """ 18 | Given the following API Documentation for Scoopsie's official ice cream store API: {api_docs} 19 | Your task is to construct the most efficient API URL to answer the user's question, ensuring the 20 | call is optimized to include only necessary information. 21 | Question: {question} 22 | API URL: 23 | """ 24 | api_url_prompt = PromptTemplate(input_variables=['api_docs', 'question'], 25 | template=api_url_template) 26 | 27 | api_response_template = """" 28 | With the API Documentation for Scoopsie's official API: {api_docs} and the specific user question: {question} in mind, 29 | and given this API URL: {api_url} for querying, here is the response from Scoopsie's API: {api_response}. 30 | Please provide a summary that directly addresses the user's question, 31 | omitting technical details like response format, and focusing on delivering the answer with clarity and conciseness, 32 | as if Scoopsie itself is providing this information. 33 | Summary: 34 | """ 35 | api_response_prompt = PromptTemplate(input_variables=['api_docs', 'question', 'api_url', 36 | 'api_response'], 37 | template=api_response_template) 38 | -------------------------------------------------------------------------------- /chatbot.py: -------------------------------------------------------------------------------- 1 | from langchain_openai import OpenAI 2 | from langchain.chains import LLMChain, APIChain 3 | from prompts import ice_cream_assistant_prompt, api_response_prompt, api_url_prompt 4 | from langchain.memory.buffer import ConversationBufferMemory 5 | from api_docs import scoopsie_api_docs 6 | 7 | from dotenv import load_dotenv 8 | 9 | import chainlit as cl 10 | 11 | load_dotenv() 12 | 13 | 14 | @cl.on_chat_start 15 | def setup_multiple_chains(): 16 | llm = OpenAI(model='gpt-3.5-turbo-instruct', 17 | temperature=0) 18 | conversation_memory = ConversationBufferMemory(memory_key="chat_history", 19 | max_len=200, 20 | return_messages=True, 21 | ) 22 | llm_chain = LLMChain(llm=llm, prompt=ice_cream_assistant_prompt, memory=conversation_memory) 23 | cl.user_session.set("llm_chain", llm_chain) 24 | 25 | api_chain = APIChain.from_llm_and_api_docs( 26 | llm=llm, 27 | api_docs=scoopsie_api_docs, 28 | api_url_prompt=api_url_prompt, 29 | api_response_prompt=api_response_prompt, 30 | verbose=True, 31 | limit_to_domains=["http://127.0.0.1:5000/"] 32 | ) 33 | cl.user_session.set("api_chain", api_chain) 34 | 35 | 36 | @cl.on_message 37 | async def handle_message(message: cl.Message): 38 | user_message = message.content.lower() 39 | llm_chain = cl.user_session.get("llm_chain") 40 | api_chain = cl.user_session.get("api_chain") 41 | 42 | if any(keyword in user_message for keyword in ["menu", "customization", 43 | "offer", "review"]): 44 | # If any of the keywords are in the user_message, use api_chain 45 | response = await api_chain.acall(user_message, 46 | callbacks=[cl.AsyncLangchainCallbackHandler()]) 47 | else: 48 | # Default to llm_chain for handling general queries 49 | response = await llm_chain.acall(user_message, 50 | callbacks=[cl.AsyncLangchainCallbackHandler()]) 51 | 52 | response_key = "output" if "output" in response else "text" 53 | await cl.Message(response.get(response_key, "")).send() 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Custom Chatbot using LangChain, OpenAI and Chainlit 2 | 3 | You can read further [here](https://medium.com/@tahreemrasul/how-to-build-your-own-chatbot-with-langchain-and-openai-f092822b6ba6), [here](https://medium.com/@tahreemrasul/building-a-chatbot-application-with-chainlit-and-langchain-3e86da0099a6) and [here](https://medium.com/@tahreemrasul/integrating-an-external-api-with-a-chatbot-application-using-langchain-and-chainlit-b687bb1efe58). 4 | 5 | ## Overview 6 | ## Overview 7 | This repo contains the code for Scoopsie, a custom chatbot that answers ice-cream-related questions and fetches information from a fictional ice-cream store's API. Using LangChain and OpenAI's text model, alongside a Flask web service, Scoopsie can provide users with details on flavors, toppings, and store offers. Everything is put together with Chainlit for easy web application integration. 8 | 9 | ## Getting Started 10 | 11 | ### Prerequisites 12 | - Python 3.8 or later 13 | - An OpenAI API key 14 | 15 | ### Installation 16 | 17 | 1. **Clone the Repository** 18 | ```bash 19 | git clone git@github.com:tahreemrasul/simple_chatbot_langchain.git 20 | cd ./simple_chatbot_langchain 21 | 22 | 2. **Set Up a Conda Environment (Recommended)** 23 | * If you don't have Conda, install it first. 24 | * Create a new Conda environment: 25 | ```bash 26 | conda create -n chatbot_langchain python=3.8 27 | * Activate the environment: 28 | ```bash 29 | conda activate chatbot_langchain 30 | 31 | 3. **Install Dependencies** 32 | * Install the required packages using the `requirements.txt` file: 33 | ```bash 34 | pip install -r requirements.txt 35 | 36 | 4. **Set Up Your OpenAI API Key** 37 | * Create a .env file in the root directory of the project. 38 | * Add your OpenAI API key to the `.env` file: 39 | ```bash 40 | OPENAI_API_KEY='Your-OpenAI-API-Key-Here' 41 | 42 | ### Usage 43 | To run the fictional store's API, execute the following command: 44 | ```bash 45 | python ice_cream_store_app.py 46 | ``` 47 | The fictional store's API will be accessible at http://localhost:5000/{endpoint_name} 48 | 49 | Make sure the fictional store's application is running before running the chatbot. To run Scoopsie, 50 | simply execute the `chatbot.py` script: 51 | ```bash 52 | chainlit run chatbot.py -w --port 8000 53 | ``` 54 | 55 | To run the chatbot application, navigate to http://localhost:8000 56 | 57 | -------------------------------------------------------------------------------- /.chainlit/config.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | # Whether to enable telemetry (default: true). No personal data is collected. 3 | enable_telemetry = true 4 | 5 | 6 | # List of environment variables to be provided by each user to use the app. 7 | user_env = [] 8 | 9 | # Duration (in seconds) during which the session is saved when the connection is lost 10 | session_timeout = 3600 11 | 12 | # Enable third parties caching (e.g LangChain cache) 13 | cache = false 14 | 15 | # Authorized origins 16 | allow_origins = ["*"] 17 | 18 | # Follow symlink for asset mount (see https://github.com/Chainlit/chainlit/issues/317) 19 | # follow_symlink = false 20 | 21 | [features] 22 | # Show the prompt playground 23 | prompt_playground = true 24 | 25 | # Process and display HTML in messages. This can be a security risk (see https://stackoverflow.com/questions/19603097/why-is-it-dangerous-to-render-user-generated-html-or-javascript) 26 | unsafe_allow_html = false 27 | 28 | # Process and display mathematical expressions. This can clash with "$" characters in messages. 29 | latex = false 30 | 31 | # Authorize users to upload files with messages 32 | multi_modal = true 33 | 34 | # Allows user to use speech to text 35 | [features.speech_to_text] 36 | enabled = false 37 | # See all languages here https://github.com/JamesBrill/react-speech-recognition/blob/HEAD/docs/API.md#language-string 38 | # language = "en-US" 39 | 40 | [UI] 41 | # Name of the app and chatbot. 42 | name = "Chatbot" 43 | 44 | # Show the readme while the thread is empty. 45 | show_readme_as_default = true 46 | 47 | # Description of the app and chatbot. This is used for HTML tags. 48 | # description = "" 49 | 50 | # Large size content are by default collapsed for a cleaner ui 51 | default_collapse_content = true 52 | 53 | # The default value for the expand messages settings. 54 | default_expand_messages = false 55 | 56 | # Hide the chain of thought details from the user in the UI. 57 | hide_cot = false 58 | 59 | # Link to your github repo. This will add a github button in the UI's header. 60 | # github = "" 61 | 62 | # Specify a CSS file that can be used to customize the user interface. 63 | # The CSS file can be served from the public directory or via an external link. 64 | # custom_css = "/public/test.css" 65 | 66 | # Specify a custom font url. 67 | # custom_font = "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap" 68 | 69 | # Override default MUI light theme. (Check theme.ts) 70 | [UI.theme] 71 | #font_family = "Inter, sans-serif" 72 | [UI.theme.light] 73 | #background = "#FAFAFA" 74 | #paper = "#FFFFFF" 75 | 76 | [UI.theme.light.primary] 77 | #main = "#F80061" 78 | #dark = "#980039" 79 | #light = "#FFE7EB" 80 | 81 | # Override default MUI dark theme. (Check theme.ts) 82 | [UI.theme.dark] 83 | #background = "#FAFAFA" 84 | #paper = "#FFFFFF" 85 | 86 | [UI.theme.dark.primary] 87 | #main = "#F80061" 88 | #dark = "#980039" 89 | #light = "#FFE7EB" 90 | 91 | 92 | [meta] 93 | generated_by = "1.0.200" 94 | -------------------------------------------------------------------------------- /.chainlit/translations/en-US.json: -------------------------------------------------------------------------------- 1 | { 2 | "components": { 3 | "atoms": { 4 | "buttons": { 5 | "userButton": { 6 | "menu": { 7 | "settings": "Settings", 8 | "settingsKey": "S", 9 | "APIKeys": "API Keys", 10 | "logout": "Logout" 11 | } 12 | } 13 | } 14 | }, 15 | "molecules": { 16 | "newChatButton": { 17 | "newChat": "New Chat" 18 | }, 19 | "tasklist": { 20 | "TaskList": { 21 | "title": "\ud83d\uddd2\ufe0f Task List", 22 | "loading": "Loading...", 23 | "error": "An error occured" 24 | } 25 | }, 26 | "attachments": { 27 | "cancelUpload": "Cancel upload", 28 | "removeAttachment": "Remove attachment" 29 | }, 30 | "newChatDialog": { 31 | "createNewChat": "Create new chat?", 32 | "clearChat": "This will clear the current messages and start a new chat.", 33 | "cancel": "Cancel", 34 | "confirm": "Confirm" 35 | }, 36 | "settingsModal": { 37 | "expandMessages": "Expand Messages", 38 | "hideChainOfThought": "Hide Chain of Thought", 39 | "darkMode": "Dark Mode" 40 | } 41 | }, 42 | "organisms": { 43 | "chat": { 44 | "history": { 45 | "index": { 46 | "lastInputs": "Last Inputs", 47 | "noInputs": "Such empty...", 48 | "loading": "Loading..." 49 | } 50 | }, 51 | "inputBox": { 52 | "input": { 53 | "placeholder": "Type your message here..." 54 | }, 55 | "speechButton": { 56 | "start": "Start recording", 57 | "stop": "Stop recording" 58 | }, 59 | "SubmitButton": { 60 | "sendMessage": "Send message", 61 | "stopTask": "Stop Task" 62 | }, 63 | "UploadButton": { 64 | "attachFiles": "Attach files" 65 | }, 66 | "waterMark": { 67 | "text": "Built with" 68 | } 69 | }, 70 | "Messages": { 71 | "index": { 72 | "running": "Running", 73 | "executedSuccessfully": "executed successfully", 74 | "failed": "failed", 75 | "feedbackUpdated": "Feedback updated", 76 | "updating": "Updating" 77 | } 78 | }, 79 | "dropScreen": { 80 | "dropYourFilesHere": "Drop your files here" 81 | }, 82 | "index": { 83 | "failedToUpload": "Failed to upload", 84 | "cancelledUploadOf": "Cancelled upload of", 85 | "couldNotReachServer": "Could not reach the server", 86 | "continuingChat": "Continuing previous chat" 87 | }, 88 | "settings": { 89 | "settingsPanel": "Settings panel", 90 | "reset": "Reset", 91 | "cancel": "Cancel", 92 | "confirm": "Confirm" 93 | } 94 | }, 95 | "threadHistory": { 96 | "sidebar": { 97 | "filters": { 98 | "FeedbackSelect": { 99 | "feedbackAll": "Feedback: All", 100 | "feedbackPositive": "Feedback: Positive", 101 | "feedbackNegative": "Feedback: Negative" 102 | }, 103 | "SearchBar": { 104 | "search": "Search" 105 | } 106 | }, 107 | "DeleteThreadButton": { 108 | "confirmMessage": "This will delete the thread as well as it's messages and elements.", 109 | "cancel": "Cancel", 110 | "confirm": "Confirm", 111 | "deletingChat": "Deleting chat", 112 | "chatDeleted": "Chat deleted" 113 | }, 114 | "index": { 115 | "pastChats": "Past Chats" 116 | }, 117 | "ThreadList": { 118 | "empty": "Empty..." 119 | }, 120 | "TriggerButton": { 121 | "closeSidebar": "Close sidebar", 122 | "openSidebar": "Open sidebar" 123 | } 124 | }, 125 | "Thread": { 126 | "backToChat": "Go back to chat", 127 | "chatCreatedOn": "This chat was created on" 128 | } 129 | }, 130 | "header": { 131 | "chat": "Chat", 132 | "readme": "Readme" 133 | } 134 | } 135 | }, 136 | "hooks": { 137 | "useLLMProviders": { 138 | "failedToFetchProviders": "Failed to fetch providers:" 139 | } 140 | }, 141 | "pages": { 142 | "Design": {}, 143 | "Env": { 144 | "savedSuccessfully": "Saved successfully", 145 | "requiredApiKeys": "Required API Keys", 146 | "requiredApiKeysInfo": "To use this app, the following API keys are required. The keys are stored on your device's local storage." 147 | }, 148 | "Page": { 149 | "notPartOfProject": "You are not part of this project." 150 | }, 151 | "ResumeButton": { 152 | "resumeChat": "Resume Chat" 153 | } 154 | } 155 | } -------------------------------------------------------------------------------- /.chainlit/translations/pt-BR.json: -------------------------------------------------------------------------------- 1 | { 2 | "components": { 3 | "atoms": { 4 | "buttons": { 5 | "userButton": { 6 | "menu": { 7 | "settings": "Configura\u00e7\u00f5es", 8 | "settingsKey": "S", 9 | "APIKeys": "Chaves de API", 10 | "logout": "Sair" 11 | } 12 | } 13 | } 14 | }, 15 | "molecules": { 16 | "newChatButton": { 17 | "newChat": "Nova Conversa" 18 | }, 19 | "tasklist": { 20 | "TaskList": { 21 | "title": "\ud83d\uddd2\ufe0f Lista de Tarefas", 22 | "loading": "Carregando...", 23 | "error": "Ocorreu um erro" 24 | } 25 | }, 26 | "attachments": { 27 | "cancelUpload": "Cancelar envio", 28 | "removeAttachment": "Remover anexo" 29 | }, 30 | "newChatDialog": { 31 | "createNewChat": "Criar novo chat?", 32 | "clearChat": "Isso limpar\u00e1 as mensagens atuais e iniciar\u00e1 uma nova conversa.", 33 | "cancel": "Cancelar", 34 | "confirm": "Confirmar" 35 | }, 36 | "settingsModal": { 37 | "expandMessages": "Expandir Mensagens", 38 | "hideChainOfThought": "Esconder Sequ\u00eancia de Pensamento", 39 | "darkMode": "Modo Escuro" 40 | } 41 | }, 42 | "organisms": { 43 | "chat": { 44 | "history": { 45 | "index": { 46 | "lastInputs": "\u00daltimas Entradas", 47 | "noInputs": "Vazio...", 48 | "loading": "Carregando..." 49 | } 50 | }, 51 | "inputBox": { 52 | "input": { 53 | "placeholder": "Digite sua mensagem aqui..." 54 | }, 55 | "speechButton": { 56 | "start": "Iniciar grava\u00e7\u00e3o", 57 | "stop": "Parar grava\u00e7\u00e3o" 58 | }, 59 | "SubmitButton": { 60 | "sendMessage": "Enviar mensagem", 61 | "stopTask": "Parar Tarefa" 62 | }, 63 | "UploadButton": { 64 | "attachFiles": "Anexar arquivos" 65 | }, 66 | "waterMark": { 67 | "text": "Constru\u00eddo com" 68 | } 69 | }, 70 | "Messages": { 71 | "index": { 72 | "running": "Executando", 73 | "executedSuccessfully": "executado com sucesso", 74 | "failed": "falhou", 75 | "feedbackUpdated": "Feedback atualizado", 76 | "updating": "Atualizando" 77 | } 78 | }, 79 | "dropScreen": { 80 | "dropYourFilesHere": "Solte seus arquivos aqui" 81 | }, 82 | "index": { 83 | "failedToUpload": "Falha ao enviar", 84 | "cancelledUploadOf": "Envio cancelado de", 85 | "couldNotReachServer": "N\u00e3o foi poss\u00edvel conectar ao servidor", 86 | "continuingChat": "Continuando o chat anterior" 87 | }, 88 | "settings": { 89 | "settingsPanel": "Painel de Configura\u00e7\u00f5es", 90 | "reset": "Redefinir", 91 | "cancel": "Cancelar", 92 | "confirm": "Confirmar" 93 | } 94 | }, 95 | "threadHistory": { 96 | "sidebar": { 97 | "filters": { 98 | "FeedbackSelect": { 99 | "feedbackAll": "Feedback: Todos", 100 | "feedbackPositive": "Feedback: Positivo", 101 | "feedbackNegative": "Feedback: Negativo" 102 | }, 103 | "SearchBar": { 104 | "search": "Buscar" 105 | } 106 | }, 107 | "DeleteThreadButton": { 108 | "confirmMessage": "Isso deletar\u00e1 a conversa, assim como suas mensagens e elementos.", 109 | "cancel": "Cancelar", 110 | "confirm": "Confirmar", 111 | "deletingChat": "Deletando conversa", 112 | "chatDeleted": "Conversa deletada" 113 | }, 114 | "index": { 115 | "pastChats": "Conversas Anteriores" 116 | }, 117 | "ThreadList": { 118 | "empty": "Vazio..." 119 | }, 120 | "TriggerButton": { 121 | "closeSidebar": "Fechar barra lateral", 122 | "openSidebar": "Abrir barra lateral" 123 | } 124 | }, 125 | "Thread": { 126 | "backToChat": "Voltar para a conversa", 127 | "chatCreatedOn": "Esta conversa foi criada em" 128 | } 129 | }, 130 | "header": { 131 | "chat": "Conversa", 132 | "readme": "Leia-me" 133 | } 134 | }, 135 | "hooks": { 136 | "useLLMProviders": { 137 | "failedToFetchProviders": "Falha ao buscar provedores:" 138 | } 139 | }, 140 | "pages": { 141 | "Design": {}, 142 | "Env": { 143 | "savedSuccessfully": "Salvo com sucesso", 144 | "requiredApiKeys": "Chaves de API necess\u00e1rias", 145 | "requiredApiKeysInfo": "Para usar este aplicativo, as seguintes chaves de API s\u00e3o necess\u00e1rias. As chaves s\u00e3o armazenadas localmente em seu dispositivo." 146 | }, 147 | "Page": { 148 | "notPartOfProject": "Voc\u00ea n\u00e3o faz parte deste projeto." 149 | }, 150 | "ResumeButton": { 151 | "resumeChat": "Continuar Conversa" 152 | } 153 | } 154 | } 155 | } --------------------------------------------------------------------------------