[
  {
    "path": ".editorconfig",
    "content": "{\n  \"arrowParens\": \"always\",\n  \"bracketSpacing\": true,\n  \"endOfLine\": \"lf\",\n  \"htmlWhitespaceSensitivity\": \"css\",\n  \"insertPragma\": false,\n  \"singleAttributePerLine\": false,\n  \"bracketSameLine\": true,\n  \"jsxBracketSameLine\": false,\n  \"jsxSingleQuote\": false,\n  \"printWidth\": 80,\n  \"proseWrap\": \"preserve\",\n  \"quoteProps\": \"as-needed\",\n  \"requirePragma\": false,\n  \"semi\": true,\n  \"singleQuote\": false,\n  \"tabWidth\": 4,\n  \"trailingComma\": \"es5\",\n  \"useTabs\": false,\n  \"vueIndentScriptAndStyle\": false,\n  \"parser\": \"html\"\n}"
  },
  {
    "path": ".gitattributes",
    "content": "*.html linguist-detectable=false\n*.css linguist-detectable=false\n"
  },
  {
    "path": ".gitignore",
    "content": ".vercel\n*.log\n*.pyc\n__pycache__\n\n# Environments \n.env \n.venv \nenv/ \nvenv/ \nENV/ \nenv.bak/ \nvenv.bak/ "
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n    \"cSpell.words\": [\n        \"dotenv\",\n        \"upstash\",\n        \"vercel\"\n    ]\n}"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\nI'm thrilled you are interested in contributing this ChatGPT Prompt Splitter. Your help may be essential to the project's success and I want you in! There are many ways to contribute to this project, and I appreciate all of them.\n\n## How to contribute\n\n### Reporting bugs\n\nThis section guides you through submitting a bug report for this project. Following these guidelines helps maintainers and the community understand your report, reproduce the behavior, and find related reports.\n\n### Suggesting enhancements\n\nThis section guides you through submitting an enhancement suggestion for this project, including completely new features and minor improvements to existing functionality. Following these guidelines helps maintainers and the community understand your suggestion and find related suggestions.\n\n### Your first code contribution\n\nUnsure where to begin contributing to this project? You can start by looking through the \"good first issue\" and \"help wanted\" issues:\n\nThank you!\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2023 Jose Diaz\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Procfile",
    "content": "web: python index.py"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n  <img src=\"static/chatgpt_prompt_splitter.png\" width=\"150\" alt=\"ChatGPT PROMPTs Splitter\" />\n  <h1 align=\"center\">ChatGPT PROMPTs Splitter</h1>\n</p>\n\n[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fjupediaz%2Fchatgpt-prompt-splitter)\n\n### ❓ Have you ever received a message from ChatGPT about sending too much data and needing to send a shorter text?\n\n#### **Here's a great alternative to bypass this limitation!** 🚀\n\n![Error Message Too Long](/static/screenshots/screenshot_error_message_too_long.png)\n## Overview\n\n**ChatGPT PROMPTs Splitter** is an open-source tool designed to help you split long text prompts into smaller chunks, making them suitable for usage with ChatGPT (or other language models with character limitations).\n\nThe tool ensures that the text is divided into safe chunks of up to 15,000 characters per request as default, although can be changed.\n\nThe project includes an easy-to-use web interface for inputting the long text, selecting the maximum length of each chunk, and copying the chunks individually to paste them to ChatGPT.\n\n## Post on Medium\n\nYou can read the full article on Medium: [ChatGPT PROMPTs Splitter: Split long text prompts into smaller chunks for ChatGPT](https://medium.com/@josediazmoreno/break-the-limits-send-large-text-blocks-to-chatgpt-with-ease-6824b86d3270)\n\n## How it works\n\nThe tool uses a simple algorithm to split the text into smaller chunks. The algorithm is based on the following rules:\n\n1. Divide the prompt into chunks based on the specified maximum length.\n\n2. Add information to the first chunk to instruct the AI on the process of receiving and acknowledging the chunks, and to wait for the completion of chunk transmission before processing subsequent requests.\n\n## Features\n\n- Python 3.9\n- Web interface for splitting text into smaller chunks\n- Customizable maximum length for each chunk\n- Copy chunks individually to send to ChatGPT\n- Instructions for ChatGPT on how to process the chunks\n- Tests included\n- Easy deployment to Vercel included\n\n## Usage example\n\nFollow these simple steps to use the ChatGPT Prompt Splitter web application, illustrated with screenshots.\n\n### Step 1: Access the application\nOpen your web browser and navigate to the application URL.\n\nhttps://chatgpt-prompt-splitter.jjdiaz.dev/\n\nYou should see the main screen, displaying the input fields for your long text prompt and maximum chunk length.\n\n![Set Max Length](/static/screenshots/screenshot_main_screen.png)\n\n### Step 2: Input the long prompt\nEnter the text you want to split into smaller chunks for use with ChatGPT.\n\nYou can also specify custom length for each chunk by entering the number of characters in the *\"Max chars length...\"* field.\n\nIn this example, we are gonna split into chunks of just 25 characters.\n\n![Input Text](/static/screenshots/screenshot_example_text.png)\n\n### Step 3: Click \"Split\"\nClick the \"Split\" button to process the text and divide it into smaller chunks.\n\n![Click Split](/static/screenshots/screenshot_example_text_splitted.png)\n\n### Step 4: Copy the chunks\nThe application will display the text divided into smaller chunks. You can copy each chunk individually by clicking the \"Copy\" button next to it.\n\n![Copy Chunks](/static/screenshots/screenshot_example_copy_chunks.png)\n\n### Step 5: Paste the chunks into ChatGPT\nNow that you have your chunks copied, you can paste them into ChatGPT or any other language model with character limitations.\n\n![Paste Chunks](/static/screenshots/screenshot_example_paste_chunks.png)\n\nThat's it! You've successfully split a **long PROMPT** into smaller, manageable chunks using the **ChatGPT Prompt Splitter**.\n\n## Getting Started\n\n### Prerequisites\n\n- Python 3.x\n- Flask\n\n### Installation\n\n1. Clone the repository:\n\n```bash\ngit clone https://github.com/jupediaz/chatgpt-prompt-splitter.git\n```\n\n2. Change to the project directory:\n\n```bash\ncd chatgpt-prompt-splitter\n```\n\n3. Install the required dependencies:\n\n```bash\npip install -r requirements.txt\n```\n\n### Usage\n\n#### Running the Flask application in development mode\n\n1. Run the Flask application:\n\n```bash\nvercel dev\n```\n\n2. Open your web browser and navigate to <http://localhost:3000>.\n\n#### Deploy the Flask application to production\n\n1. Deploy the Flask application:\n\n```bash\nvercel --prod\n```\n\n2. Open your web browser and navigate to <https://chatgpt-prompt-splitter.jjdiaz.dev/>.\n\n## Running Tests\n\nThis project includes a suite of unit tests to ensure the proper functionality of the tool. To run the tests, follow these steps:\n\n1. Make sure you have the required dependencies installed:\n\n```bash\npip install -r requirements.txt\n```\n\n2. Run the tests using the unittest module:\n\n```bash\npython3 -m unittest discover tests\n```\n\nThe test suite will run, and the results will be displayed in the terminal.\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n## Contributing\n\nContributions are welcome! Please read the [CONTRIBUTING](CONTRIBUTING.md) file for details on how to contribute to the project.\n\n## Contact\n\nIf you have any questions or suggestions, please contact me at [hello@jjdiaz.dev](mailto:hello@jjdiaz.dev).\n\n## Disclaimer\n\nThis project is not affiliated with OpenAI, Microsoft, or any other entity. The project is provided \"as is\" without warranty of any kind, express or implied. The author is not responsible for any damages or losses arising from the use of this project.\n\n## Changelog\n\n### 1.0.0\n\n- Initial release\n"
  },
  {
    "path": "api/index.py",
    "content": "from flask import Flask, render_template, request\nfrom dotenv import load_dotenv\nimport os\nimport random\nimport redis\nimport string\n\n# Load environment variables from .env file\nload_dotenv()\n\napp = Flask(__name__)\n\n# Set up Redis client\nupstash_redis_url = os.environ.get('UPSTASH_REDIS_URL')\nredis_client = redis.from_url(upstash_redis_url)\n\n@app.route(\"/\", methods=[\"GET\", \"POST\"])\ndef index():\n    prompt = \"\"\n    split_length = \"\"\n    file_data = []\n    \n    redis_client.incr(\"visit_counter\")\n    visit_count = int(redis_client.get(\"visit_counter\"))\n\n    if request.method == \"POST\":\n        prompt = request.form[\"prompt\"]\n        split_length = int(request.form[\"split_length\"])\n\n        file_data = split_prompt(prompt, split_length)\n        \n    hash_value = generate_random_hash(8)\n    \n    return render_template(\"index.html\", prompt=prompt, split_length=split_length, file_data=file_data, hash=hash_value, visit_count=visit_count)\n            \ndef split_prompt(text, split_length):\n    if split_length <= 0:\n        raise ValueError(\"Max length must be greater than 0.\")\n\n    num_parts = -(-len(text) // split_length)\n    file_data = []\n\n    for i in range(num_parts):\n        start = i * split_length\n        end = min((i + 1) * split_length, len(text))\n\n        if i == num_parts - 1:\n            content = f'[START PART {i + 1}/{num_parts}]\\n' + text[start:end] + f'\\n[END PART {i + 1}/{num_parts}]'\n            content += '\\nALL PARTS SENT. Now you can continue processing the request.'\n        else:\n            content = f'Do not answer yet. This is just another part of the text I want to send you. Just receive and acknowledge as \"Part {i + 1}/{num_parts} received\" and wait for the next part.\\n[START PART {i + 1}/{num_parts}]\\n' + text[start:end] + f'\\n[END PART {i + 1}/{num_parts}]'\n            content += f'\\nRemember not answering yet. Just acknowledge you received this part with the message \"Part {i + 1}/{num_parts} received\" and wait for the next part.'\n\n        file_data.append({\n            'name': f'split_{str(i + 1).zfill(3)}_of_{str(num_parts).zfill(3)}.txt',\n            'content': content\n        })\n\n    return file_data\n\ndef generate_random_hash(length):\n    return ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(length))\n\nif __name__ == '__main__':\n    app.run(host='0.0.0.0', port=int(os.environ.get('PORT', 3000)))"
  },
  {
    "path": "api/templates/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <!-- Google tag (gtag.js) -->\n    <script async src=\"https://www.googletagmanager.com/gtag/js?id=G-LEGMCTW1RN\"></script>\n    <script>\n      window.dataLayer = window.dataLayer || [];\n      function gtag(){dataLayer.push(arguments);}\n      gtag('js', new Date());\n\n      gtag('config', 'G-LEGMCTW1RN');\n    </script>\n    <meta charset=\"UTF-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Long PROMPTs Splitter</title>\n    <link rel=\"stylesheet\" href=\"/static/styles.css\">\n    <link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"/static/apple-touch-icon.png\">\n    <link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/static/favicon-32x32.png\">\n    <link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/static/favicon-16x16.png\">\n    <link rel=\"manifest\" href=\"/static/site.webmanifest\">\n    <link rel=\"mask-icon\" href=\"/static/safari-pinned-tab.svg\" color=\"#5bbad5\">\n    <meta name=\"msapplication-TileColor\" content=\"#da532c\">\n    <meta name=\"theme-color\" content=\"#ffffff\">\n    <meta og:title=\"Long PROMPTs Splitter\">\n    <meta og:description=\"Tool for safely process chunks of up to 15,000 characters per request\">\n    <meta og:image=\"https://opengraph.githubassets.com/{{ hash }}/jupediaz/chatgpt-prompt-splitter\">\n  </head>\n  <body>\n    <div class=\"container\">\n      <div class=\"header\">\n        <div class=\"header-logo\">\n          <a href=\"/\"><img src=\"/static/chatgpt_prompt_splitter.png\" alt=\"Logo\" class=\"logo\"/></a>\n        </div>\n        <div class=\"header-text\">\n          <h1>ChatGPT PROMPTs Splitter</h1>\n          <h2>Open-source tool for safely process chunks of up to 15,000 characters per request</h2>\n          <div class=\"how-this-works\"><a href=\"https://medium.com/@josediazmoreno/break-the-limits-send-large-text-blocks-to-chatgpt-with-ease-6824b86d3270\" target=\"_blank\">How this works</a></div>\n        </div>\n      </div>\n      <div class=\"visits\">Visit count: {{ visit_count }}</div>\n      <form action=\"/\" method=\"post\">\n        <div class=\"form-group\">\n          <label for=\"prompt\">Enter the long prompt to be splitted</label>\n          <textarea class=\"form-control\" id=\"prompt\" name=\"prompt\" rows=\"10\">{{ prompt }}</textarea>\n          <help>Enter the PROMPT that you want to use for the ChatGPT request.</help>\n          <div class=\"char-count-container\">\n            <span id=\"prompt-char-count\">{{ prompt|length }}</span> characters\n          </div>\n        </div>\n        <label for=\"split_length\">Max chars length for each splitted part</label>\n        <select name=\"preset\" id=\"preset\" onchange=\"toggleCustomLength(this)\" required>\n            <option value=\"15000\" {% if split_length == 15000 or split_length is none or split_length == '' %}selected{% endif %}>General max safe chunk for ChatGPT (15,000)</option>\n            <option value=\"custom\" {% if split_length != 8000 and split_length != 15000 and split_length != '' and split_length is not none %}selected{% endif %}>Custom</option>\n        </select>\n        <input type=\"number\" name=\"split_length\" class=\"custom-length\" id=\"split_length\" min=\"1\" required value=\"{{ split_length if split_length is not none and split_length != '' else 15000 }}\" {% if split_length == 8000 or split_length == 15000 or split_length is none or split_length == '' %}style=\"display: none;\"{% endif %}>\n        <help>Choose the max length for each split part.</help>\n        <button type=\"submit\" id=\"split-btn\" class=\"{% if btn_disabled %}disabled{% endif %}\"{% if btn_disabled %} disabled{% endif%}>Enter a prompt</button>\n      </form>\n      {% if file_data %}\n      <div class=\"instructions\">\n        <h3>Instructions</h3>\n        <pre id=\"instructions\">The total length of the content that I want to send you is too large to send in only one piece.\n        \nFor sending you that content, I will follow this rule:\n        \n[START PART 1/10]\nthis is the content of the part 1 out of 10 in total\n[END PART 1/10]\n        \nThen you just answer: \"Received part 1/10\"\n        \nAnd when I tell you \"ALL PARTS SENT\", then you can continue processing the data and answering my requests.</pre>\n        <button type=\"button\" class=\"copy-btn\" id=\"copy-instructions-btn\" onclick=\"copyInstructions()\">Copy Instructions (first message to send)</button>\n        <help>This way we explain ChatGPT how to process the messages we are gonna send.</help>\n      </div>\n      <div class=\"buttons-container\">\n        {% for file in file_data %}\n        {% set partNumber = file.name[6:9]|int %}\n        {% set totalParts = file.name[13:16]|int %}\n        <button class=\"copy-btn\" data-content=\"{{ file.content }}\" onclick=\"copyToClipboard(this)\">Copy part {{ partNumber }}/{{ totalParts }} to clipboard</button>\n        {% endfor %}\n      </div>\n      {% endif %}\n      <footer>\n        <p>\n          <div class=\"left\">Created with ❤️ by <a href=\"https://jjdiaz.dev\" target=\"_blank\">jjdiaz.dev</a></div>\n          <div class=\"right\"><a href=\"https://github.com/jupediaz/chatgpt-prompt-splitter\" target=\"_blank\">GitHub repository</a></div>\n        </p>\n      </footer>\n      <div class=\"powered-by-vercel\"><a href=\"https://vercel.app/\" target=\"_blank\"><img src=\"/static/powered-by-vercel.svg\" alt=\"Powered by Vercel\" /></a></div>\n    </div>\n    <script src=\"/static/scripts.js\"></script>\n  </body>\n</html>"
  },
  {
    "path": "requirements.txt",
    "content": "Flask==2.2.2\nredis==4.5.1\npython-dotenv==1.0.0"
  },
  {
    "path": "static/browserconfig.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<browserconfig>\n    <msapplication>\n        <tile>\n            <square150x150logo src=\"/mstile-150x150.png\"/>\n            <TileColor>#da532c</TileColor>\n        </tile>\n    </msapplication>\n</browserconfig>\n"
  },
  {
    "path": "static/scripts.js",
    "content": "document.getElementById(\"prompt\").addEventListener(\"input\", function () {\n    updateSplitButtonStatus();\n    updatePromptCharCount();\n  });\n\n  document.getElementById('split_length').addEventListener('input', updateSplitButtonStatus);\n\n  function copyToClipboard(element) {\n    const textArea = document.createElement(\"textarea\");\n    textArea.value = element.getAttribute(\"data-content\");\n    document.body.appendChild(textArea);\n    textArea.select();\n    document.execCommand(\"copy\");\n    document.body.removeChild(textArea);\n    element.classList.add(\"clicked\");\n  }\n\n  function copyInstructions() {\n    const instructionsButton = document.getElementById(\"copy-instructions-btn\");\n    const instructions = document.getElementById(\"instructions\").textContent;\n    const textArea = document.createElement(\"textarea\");\n    textArea.value = instructions;\n    document.body.appendChild(textArea);\n    textArea.select();\n    document.execCommand(\"copy\");\n    document.body.removeChild(textArea);\n    instructionsButton.classList.add(\"clicked\");\n  }\n\n  function toggleCustomLength(select) {\n    const customLengthInput = document.getElementById(\"split_length\");\n    if (select.value === \"custom\") {\n      customLengthInput.style.display = \"inline\";\n    } else {\n      customLengthInput.value = select.value;\n      customLengthInput.style.display = \"none\";\n    }\n  }\n\n  function updateSplitButtonStatus() {\n    const promptField = document.getElementById('prompt');\n    const splitLength = document.getElementById('split_length');\n    const splitBtn = document.getElementById('split-btn');\n    const promptLength = promptField.value.trim().length;\n    const splitLengthValue = parseInt(splitLength.value);\n\n    if (promptLength === 0) {\n        splitBtn.setAttribute('disabled', 'disabled');\n        splitBtn.classList.add('disabled');\n        splitBtn.textContent = 'Enter a prompt';\n    } else if (isNaN(splitLengthValue) || splitLengthValue === 0) {\n        splitBtn.setAttribute('disabled', 'disabled');\n        splitBtn.classList.add('disabled');\n        splitBtn.textContent = 'Enter the length for calculating';\n    } else if (promptLength < splitLengthValue) {\n        splitBtn.setAttribute('disabled', 'disabled');\n        splitBtn.classList.add('disabled');\n        splitBtn.textContent = 'Prompt is shorter than split length';\n    } else {\n        splitBtn.removeAttribute('disabled');\n        splitBtn.classList.remove('disabled');\n        splitBtn.textContent = `Split into ${Math.ceil(promptLength / splitLengthValue)} parts`;\n    }\n  }\n\n  function updatePromptCharCount() {\n    const promptField = document.getElementById(\"prompt\");\n    const charCount = document.getElementById(\"prompt-char-count\");\n    const promptLength = promptField.value.trim().length;\n    charCount.textContent = promptLength;\n  }\n\n  updateSplitButtonStatus();"
  },
  {
    "path": "static/site.webmanifest",
    "content": "{\n    \"name\": \"\",\n    \"short_name\": \"\",\n    \"icons\": [\n        {\n            \"src\": \"/android-chrome-192x192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"/android-chrome-384x384.png\",\n            \"sizes\": \"384x384\",\n            \"type\": \"image/png\"\n        }\n    ],\n    \"theme_color\": \"#ffffff\",\n    \"background_color\": \"#ffffff\",\n    \"display\": \"standalone\"\n}\n"
  },
  {
    "path": "static/styles.css",
    "content": "body,\nhtml {\n    margin: 0;\n    padding: 0;\n}\n\nbody {\n    font-family: Arial, sans-serif;\n    background-color: #f0f0f0;\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    overflow: auto;\n    padding: 20px;\n}\n\nhtml {\n    height: 100%;\n}\n\n*,\ntextarea,\ninput {\n    box-sizing: border-box;\n    font-family: inherit;\n}\n\n.container {\n    max-width: 1200px;\n    margin: 0 auto;\n    padding: 20px;\n    background-color: #fff;\n    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n}\n\nh1,\nh2 {\n    text-align: center;\n    width: 100%;\n    margin: 0;\n}\n\nh1 {\n    font-size: 1rem;\n}\n\nh2 {\n    font-size: 11px;\n    font-weight: normal;\n}\n\nlabel {\n    display: block;\n    font-weight: bold;\n    margin-top: 10px;\n}\n\ntextarea,\ninput {\n    width: -webkit-fill-available;\n}\n\ntextarea {\n    width: 100%;\n    height: 200px;\n    margin-bottom: 20px;\n}\n\nbutton {\n    display: block;\n    width: 100%;\n    padding: 10px;\n    background-color: #007bff;\n    color: #fff;\n    font-size: 16px;\n    border: none;\n    cursor: pointer;\n    margin-top: 20px;\n}\n\nbutton:hover {\n    background-color: #0056b3;\n}\n\n.buttons-container {\n    display: flex;\n    flex-wrap: wrap;\n    justify-content: space-between;\n    margin-top: 20px;\n}\n\n.copy-btn,\n.copy-instructions-btn {\n    padding: 10px;\n    border: none;\n    cursor: pointer;\n    color: white;\n    text-align: center;\n    font-size: 13px;\n}\n\n.copy-btn {\n    background-color: #4caf50;\n    flex-basis: min-content;\n    margin-bottom: 10px;\n}\n\n.copy-btn:hover {\n    background-color: #3e8e41;\n}\n\n.clicked {\n    background-color: #888;\n    color: #ccc;\n}\n\n.clicked:hover {\n    background-color: #666;\n}\n\n.copy-instructions-btn {\n    background-color: #FF8000;\n    margin-top: 10px;\n}\n\n.copy-instructions-btn:hover {\n    background-color: #CC6600;\n}\n\n.custom-length {\n    font-size: 14px;\n    width: 100px;\n    height: 24px;\n}\n\nselect {\n    height: 24px;\n}\n\n.instructions {\n    margin-top: 20px;\n    font-size: 12px;\n    background-color: lightgray;\n    padding: 1px 10px 10px 10px;\n}\n\nhelp {\n    font-size: 12px;\n    color: #888;\n}\n\nbutton[disabled] {\n    background-color: #cccccc;\n    color: #666666;\n    cursor: not-allowed;\n}\n\n.char-count-container {\n    text-align: right;\n}\n\n.header {\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n    width: 100%;\n}\n\n.logo {\n    max-width: 90%;\n}\n\n.header-text {\n    display: flex;\n    flex-direction: column;\n    align-items: flex-start;\n    justify-content: center;\n    width: 80%;\n}\n\n.header-logo {\n    width: 20%;\n    display: flex;\n    justify-content: flex-end;\n    padding-right: 10px;\n}\n\npre {\n    overflow-x: auto;\n    white-space: pre-wrap;\n    word-wrap: break-word;\n    max-width: 100%;\n    padding: 1em;\n    background-color: #f8f8f8;\n    border-radius: 5px;\n}\n\nfooter {\n    margin-top: 20px;\n    padding-top: 10px;\n    border-top: 1px solid #ccc;\n    display: flex;\n    width: 100%;\n    bottom: 0;\n    font-size: 12px;\n}\n\n.left,\n.right {\n    width: 50%;\n}\n\n.left {\n    text-align: left;\n}\n\n.right {\n    text-align: right;\n}\n\n.powered-by-vercel {\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    margin-top: 20px;\n}\n\n.powered-by-vercel img {\n    width: 180px;\n    height: auto;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n}\n\n.visits {\n    text-align:right;\n    font-size: 14px;\n}\n\n.how-this-works {\n    font-size: 14px;\n    text-align: center;\n    width: 100%;\n}\n\n@media (min-width: 768px) {\n    textarea {\n        height: 300px;\n    }\n\n    h1 {\n        font-size: 2rem;\n        margin-bottom: 20px;\n    }\n\n    h2 {\n        font-size: 16px;\n        margin-bottom: 30px;\n    }\n}"
  },
  {
    "path": "tests/test_chatgpt_prompt_splitter.py",
    "content": "import unittest\nimport sys\nimport os\n\nsys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))\nfrom api.index import split_prompt\n\nclass TestChatGPTPromptSplitter(unittest.TestCase):\n\n    def test_split_prompt_single_chunk(self):\n        input_text = \"This is a short text.\"\n        max_length = 50\n        chunks = split_prompt(input_text, max_length)\n        self.assertEqual(len(chunks), 1)\n        self.assertIn(input_text, chunks[0]['content'])\n\n    def test_split_prompt_multiple_chunks(self):\n        input_text = \"This is a long text that should be split into multiple chunks.\"\n        max_length = 20\n        chunks = split_prompt(input_text, max_length)\n        self.assertEqual(len(chunks), 4)\n\n    def test_split_prompt_chunk_length(self):\n        input_text = \"This is a long text that should be split into multiple chunks with a specified maximum length.\"\n        max_length = 30\n        chunks = split_prompt(input_text, max_length)\n        for chunk in chunks:\n            self.assertLessEqual(len(chunk), max_length)\n\n    def test_split_prompt_empty_input(self):\n        input_text = \"\"\n        max_length = 50\n        chunks = split_prompt(input_text, max_length)\n        self.assertEqual(len(chunks), 0)\n\n    def test_split_prompt_negative_max_length(self):\n        input_text = \"This is a short text.\"\n        max_length = -10\n        with self.assertRaises(ValueError):\n            split_prompt(input_text, max_length)\n"
  },
  {
    "path": "vercel.json",
    "content": "{\n  \"rewrites\": [\n    { \"source\": \"/(.*)\", \"destination\": \"/api/index\" }\n  ]\n}\n"
  }
]