├── frontend ├── src │ ├── react-app-env.d.ts │ ├── setupTests.ts │ ├── App.test.tsx │ ├── index.css │ ├── reportWebVitals.ts │ ├── index.tsx │ ├── logo.svg │ ├── markdown.css │ ├── App.css │ └── App.tsx ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── index.html ├── tsconfig.json ├── package.json └── README.md ├── pyproject.toml ├── LICENSE ├── test_api.py ├── .gitignore ├── README.md ├── main.py └── uv.lock /frontend/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIAnytime/GPT-5-Multimodal-Chat-App/main/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIAnytime/GPT-5-Multimodal-Chat-App/main/frontend/public/logo192.png -------------------------------------------------------------------------------- /frontend/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIAnytime/GPT-5-Multimodal-Chat-App/main/frontend/public/logo512.png -------------------------------------------------------------------------------- /frontend/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /frontend/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, screen } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | render(); 7 | const linkElement = screen.getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "gpt-5-demo" 3 | version = "0.1.0" 4 | description = "Add your description here" 5 | readme = "README.md" 6 | requires-python = ">=3.10" 7 | dependencies = [ 8 | "fastapi>=0.116.1", 9 | "openai>=1.99.2", 10 | "pydantic>=2.11.7", 11 | "python-dotenv>=1.1.1", 12 | "requests>=2.32.4", 13 | "uvicorn>=0.35.0", 14 | ] 15 | -------------------------------------------------------------------------------- /frontend/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /frontend/src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from 'web-vitals'; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /frontend/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import './index.css'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | 7 | const root = ReactDOM.createRoot( 8 | document.getElementById('root') as HTMLElement 9 | ); 10 | root.render( 11 | 12 | 13 | 14 | ); 15 | 16 | // If you want to start measuring performance in your app, pass a function 17 | // to log results (for example: reportWebVitals(console.log)) 18 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 19 | reportWebVitals(); 20 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 AI Anytime 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/dom": "^10.4.1", 7 | "@testing-library/jest-dom": "^6.6.4", 8 | "@testing-library/react": "^16.3.0", 9 | "@testing-library/user-event": "^13.5.0", 10 | "@types/jest": "^27.5.2", 11 | "@types/node": "^16.18.126", 12 | "@types/react": "^19.1.9", 13 | "@types/react-dom": "^19.1.7", 14 | "axios": "^1.11.0", 15 | "lucide-react": "^0.537.0", 16 | "react": "^19.1.1", 17 | "react-dom": "^19.1.1", 18 | "react-dropzone": "^14.3.8", 19 | "react-markdown": "^10.1.0", 20 | "react-scripts": "5.0.1", 21 | "rehype-highlight": "^7.0.2", 22 | "typescript": "^4.9.5", 23 | "web-vitals": "^2.1.4" 24 | }, 25 | "scripts": { 26 | "start": "react-scripts start", 27 | "build": "react-scripts build", 28 | "test": "react-scripts test", 29 | "eject": "react-scripts eject" 30 | }, 31 | "eslintConfig": { 32 | "extends": [ 33 | "react-app", 34 | "react-app/jest" 35 | ] 36 | }, 37 | "browserslist": { 38 | "production": [ 39 | ">0.2%", 40 | "not dead", 41 | "not op_mini all" 42 | ], 43 | "development": [ 44 | "last 1 chrome version", 45 | "last 1 firefox version", 46 | "last 1 safari version" 47 | ] 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 35 | 36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 39 | 40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | -------------------------------------------------------------------------------- /frontend/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/markdown.css: -------------------------------------------------------------------------------- 1 | /* Markdown rendering styles */ 2 | .markdown-content { 3 | line-height: 1.6; 4 | } 5 | 6 | .markdown-content p { 7 | margin-bottom: 1rem; 8 | } 9 | 10 | .markdown-content h1, 11 | .markdown-content h2, 12 | .markdown-content h3, 13 | .markdown-content h4, 14 | .markdown-content h5, 15 | .markdown-content h6 { 16 | margin-top: 1.5rem; 17 | margin-bottom: 1rem; 18 | font-weight: 600; 19 | line-height: 1.25; 20 | } 21 | 22 | .markdown-content h1 { font-size: 1.8rem; } 23 | .markdown-content h2 { font-size: 1.5rem; } 24 | .markdown-content h3 { font-size: 1.3rem; } 25 | 26 | .markdown-content ul, 27 | .markdown-content ol { 28 | margin-bottom: 1rem; 29 | padding-left: 1.5rem; 30 | } 31 | 32 | .markdown-content li { 33 | margin-bottom: 0.25rem; 34 | } 35 | 36 | .markdown-content code { 37 | font-family: 'Menlo', 'Monaco', 'Courier New', monospace; 38 | font-size: 0.9rem; 39 | background-color: #f3f4f6; 40 | padding: 0.2rem 0.4rem; 41 | border-radius: 0.25rem; 42 | color: #374151; 43 | } 44 | 45 | .markdown-content pre { 46 | margin-bottom: 1rem; 47 | padding: 1rem; 48 | background-color: #282c34; 49 | border-radius: 0.5rem; 50 | overflow-x: auto; 51 | } 52 | 53 | .markdown-content pre code { 54 | font-family: 'Menlo', 'Monaco', 'Courier New', monospace; 55 | font-size: 0.9rem; 56 | color: #e5e7eb; 57 | background-color: transparent; 58 | padding: 0; 59 | } 60 | 61 | .markdown-content blockquote { 62 | border-left: 4px solid #e5e7eb; 63 | padding-left: 1rem; 64 | margin-left: 0; 65 | margin-bottom: 1rem; 66 | color: #6b7280; 67 | } 68 | 69 | .markdown-content a { 70 | color: #3b82f6; 71 | text-decoration: underline; 72 | } 73 | 74 | .markdown-content table { 75 | width: 100%; 76 | border-collapse: collapse; 77 | margin-bottom: 1rem; 78 | } 79 | 80 | .markdown-content th, 81 | .markdown-content td { 82 | border: 1px solid #e5e7eb; 83 | padding: 0.5rem; 84 | text-align: left; 85 | } 86 | 87 | .markdown-content th { 88 | background-color: #f3f4f6; 89 | } 90 | 91 | /* Syntax highlighting */ 92 | .hljs { 93 | display: block; 94 | overflow-x: auto; 95 | padding: 0.5em; 96 | color: #abb2bf; 97 | background: #282c34; 98 | } 99 | 100 | .hljs-comment, 101 | .hljs-quote { 102 | color: #5c6370; 103 | font-style: italic; 104 | } 105 | 106 | .hljs-doctag, 107 | .hljs-keyword, 108 | .hljs-formula { 109 | color: #c678dd; 110 | } 111 | 112 | .hljs-section, 113 | .hljs-name, 114 | .hljs-selector-tag, 115 | .hljs-deletion, 116 | .hljs-subst { 117 | color: #e06c75; 118 | } 119 | 120 | .hljs-literal { 121 | color: #56b6c2; 122 | } 123 | 124 | .hljs-string, 125 | .hljs-regexp, 126 | .hljs-addition, 127 | .hljs-attribute, 128 | .hljs-meta-string { 129 | color: #98c379; 130 | } 131 | 132 | .hljs-built_in, 133 | .hljs-class .hljs-title { 134 | color: #e6c07b; 135 | } 136 | 137 | .hljs-attr, 138 | .hljs-variable, 139 | .hljs-template-variable, 140 | .hljs-type, 141 | .hljs-selector-class, 142 | .hljs-selector-attr, 143 | .hljs-selector-pseudo, 144 | .hljs-number { 145 | color: #d19a66; 146 | } 147 | 148 | .hljs-symbol, 149 | .hljs-bullet, 150 | .hljs-link, 151 | .hljs-meta, 152 | .hljs-selector-id, 153 | .hljs-title { 154 | color: #61aeee; 155 | } 156 | 157 | .hljs-emphasis { 158 | font-style: italic; 159 | } 160 | 161 | .hljs-strong { 162 | font-weight: bold; 163 | } 164 | 165 | .hljs-link { 166 | text-decoration: underline; 167 | } 168 | -------------------------------------------------------------------------------- /test_api.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Test script for GPT-5 Multimodal API 4 | This script demonstrates how to use all the API endpoints 5 | """ 6 | 7 | import requests 8 | import json 9 | import base64 10 | from pathlib import Path 11 | 12 | # API base URL 13 | BASE_URL = "http://localhost:8000" 14 | 15 | def test_root_endpoint(): 16 | """Test the root endpoint""" 17 | print("🔍 Testing root endpoint...") 18 | response = requests.get(f"{BASE_URL}/") 19 | print(f"Status: {response.status_code}") 20 | print(f"Response: {json.dumps(response.json(), indent=2)}") 21 | print("-" * 50) 22 | 23 | def test_presets_endpoint(): 24 | """Test the presets endpoint""" 25 | print("🔍 Testing presets endpoint...") 26 | response = requests.get(f"{BASE_URL}/presets") 27 | print(f"Status: {response.status_code}") 28 | print(f"Response: {json.dumps(response.json(), indent=2)}") 29 | print("-" * 50) 30 | 31 | def test_text_chat(): 32 | """Test text-only chat""" 33 | print("🔍 Testing text chat endpoint...") 34 | 35 | data = { 36 | "message": "Hello! Can you tell me a short joke?", 37 | "conversation_history": [] 38 | } 39 | 40 | response = requests.post(f"{BASE_URL}/chat/text", json=data) 41 | print(f"Status: {response.status_code}") 42 | 43 | if response.status_code == 200: 44 | result = response.json() 45 | print(f"Response: {result['response']}") 46 | print(f"Conversation length: {len(result['conversation_history'])}") 47 | else: 48 | print(f"Error: {response.text}") 49 | print("-" * 50) 50 | 51 | def test_image_base64_chat(): 52 | """Test image analysis with base64 encoded image""" 53 | print("🔍 Testing image base64 chat endpoint...") 54 | 55 | # Create a simple test image (1x1 pixel PNG) 56 | # This is just for testing - replace with actual image data 57 | test_image_b64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==" 58 | 59 | data = { 60 | "image_base64": test_image_b64, 61 | "prompt": "What do you see in this image?", 62 | "preset_action": None 63 | } 64 | 65 | response = requests.post(f"{BASE_URL}/chat/image-base64", json=data) 66 | print(f"Status: {response.status_code}") 67 | 68 | if response.status_code == 200: 69 | result = response.json() 70 | print(f"Response: {result['response']}") 71 | print(f"Analysis type: {result['analysis_type']}") 72 | else: 73 | print(f"Error: {response.text}") 74 | print("-" * 50) 75 | 76 | def test_preset_actions(): 77 | """Test preset actions with image""" 78 | print("🔍 Testing preset actions...") 79 | 80 | # Simple test image 81 | test_image_b64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==" 82 | 83 | presets = ["analyze", "summarize", "describe"] 84 | 85 | for preset in presets: 86 | print(f"Testing preset: {preset}") 87 | data = { 88 | "image_base64": test_image_b64, 89 | "preset_action": preset 90 | } 91 | 92 | response = requests.post(f"{BASE_URL}/chat/image-base64", json=data) 93 | print(f"Status: {response.status_code}") 94 | 95 | if response.status_code == 200: 96 | result = response.json() 97 | print(f"Response: {result['response'][:100]}...") 98 | else: 99 | print(f"Error: {response.text}") 100 | print() 101 | 102 | def main(): 103 | """Run all tests""" 104 | print("🚀 Starting GPT-5 Multimodal API Tests") 105 | print("=" * 60) 106 | 107 | try: 108 | test_root_endpoint() 109 | test_presets_endpoint() 110 | test_text_chat() 111 | test_image_base64_chat() 112 | test_preset_actions() 113 | 114 | print("✅ All tests completed!") 115 | 116 | except requests.exceptions.ConnectionError: 117 | print("❌ Error: Could not connect to the API server.") 118 | print("Make sure the server is running on http://localhost:8000") 119 | print("Run: python3 main.py") 120 | except Exception as e: 121 | print(f"❌ Error: {e}") 122 | 123 | if __name__ == "__main__": 124 | main() 125 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[codz] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py.cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # UV 98 | # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | #uv.lock 102 | 103 | # poetry 104 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 105 | # This is especially recommended for binary packages to ensure reproducibility, and is more 106 | # commonly ignored for libraries. 107 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 108 | #poetry.lock 109 | #poetry.toml 110 | 111 | # pdm 112 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 113 | # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. 114 | # https://pdm-project.org/en/latest/usage/project/#working-with-version-control 115 | #pdm.lock 116 | #pdm.toml 117 | .pdm-python 118 | .pdm-build/ 119 | 120 | # pixi 121 | # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. 122 | #pixi.lock 123 | # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one 124 | # in the .venv directory. It is recommended not to include this directory in version control. 125 | .pixi 126 | 127 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 128 | __pypackages__/ 129 | 130 | # Celery stuff 131 | celerybeat-schedule 132 | celerybeat.pid 133 | 134 | # SageMath parsed files 135 | *.sage.py 136 | 137 | # Environments 138 | .env 139 | .envrc 140 | .venv 141 | env/ 142 | venv/ 143 | ENV/ 144 | env.bak/ 145 | venv.bak/ 146 | 147 | # Spyder project settings 148 | .spyderproject 149 | .spyproject 150 | 151 | # Rope project settings 152 | .ropeproject 153 | 154 | # mkdocs documentation 155 | /site 156 | 157 | # mypy 158 | .mypy_cache/ 159 | .dmypy.json 160 | dmypy.json 161 | 162 | # Pyre type checker 163 | .pyre/ 164 | 165 | # pytype static type analyzer 166 | .pytype/ 167 | 168 | # Cython debug symbols 169 | cython_debug/ 170 | 171 | # PyCharm 172 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 173 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 174 | # and can be added to the global gitignore or merged into this file. For a more nuclear 175 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 176 | #.idea/ 177 | 178 | # Abstra 179 | # Abstra is an AI-powered process automation framework. 180 | # Ignore directories containing user credentials, local state, and settings. 181 | # Learn more at https://abstra.io/docs 182 | .abstra/ 183 | 184 | # Visual Studio Code 185 | # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore 186 | # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore 187 | # and can be added to the global gitignore or merged into this file. However, if you prefer, 188 | # you could uncomment the following to ignore the entire vscode folder 189 | # .vscode/ 190 | 191 | # Ruff stuff: 192 | .ruff_cache/ 193 | 194 | # PyPI configuration file 195 | .pypirc 196 | 197 | # Cursor 198 | # Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to 199 | # exclude from AI features like autocomplete and code analysis. Recommended for sensitive data 200 | # refer to https://docs.cursor.com/context/ignore-files 201 | .cursorignore 202 | .cursorindexingignore 203 | 204 | # Marimo 205 | marimo/_static/ 206 | marimo/_lsp/ 207 | __marimo__/ 208 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GPT-5 Multimodal Chat App 2 | 3 | A chat app built using GPT-5 model. 4 | 5 | - Chat with GPT-5 using text-only messages 6 | - Upload images and ask questions about them 7 | - Use preset actions for common image analysis tasks 8 | - Maintain conversation history 9 | 10 | ## Features 11 | 12 | ✨ **Text Chat**: Simple text-based conversations with GPT-5 13 | 14 | 🖼️ **Image Analysis**: Upload images and get AI-powered analysis 15 | 16 | 🎯 **Preset Actions**: Quick actions like "Analyze", "Summarize", "Extract Text" 17 | 18 | 💬 **Conversation History**: Maintain context across multiple messages 19 | 20 | 🔄 **Multiple Input Methods**: Support for file uploads and base64 encoded images 21 | 22 | ## Quick Start 23 | 24 | ### 1. Environment Setup 25 | 26 | Copy the example environment file and add your OpenAI API key: 27 | 28 | ```bash 29 | cp .env.example .env 30 | ``` 31 | 32 | Edit `.env` and add your OpenAI API key: 33 | 34 | ``` 35 | OPENAI_API_KEY=your_openai_api_key_here 36 | ``` 37 | 38 | ### 2. Install Dependencies 39 | 40 | This project uses `uv` for dependency management. If you haven't already: 41 | 42 | ```bash 43 | uv sync 44 | ``` 45 | 46 | ### 3. Run the Server 47 | 48 | ```bash 49 | python3 main.py 50 | ``` 51 | 52 | The API will be available at `http://localhost:8000` 53 | 54 | ### 4. View API Documentation 55 | 56 | Once the server is running, visit: 57 | - **Interactive API Docs**: http://localhost:8000/docs 58 | - **Alternative Docs**: http://localhost:8000/redoc 59 | 60 | ## Frontend Setup (React) 61 | 62 | The frontend is a React application with TypeScript that provides a modern chat interface for the GPT-5 multimodal API. 63 | 64 | ### Prerequisites 65 | 66 | - Node.js 16+ and npm (or yarn) 67 | - Backend server running on `http://localhost:8000` 68 | 69 | ### Installation 70 | 71 | 1. Navigate to the frontend directory: 72 | ```bash 73 | cd frontend 74 | ``` 75 | 76 | 2. Install dependencies: 77 | ```bash 78 | npm install 79 | ``` 80 | 81 | ### Running the Frontend 82 | 83 | 1. Start the development server: 84 | ```bash 85 | npm start 86 | ``` 87 | 88 | 2. The React app will start on `http://localhost:3000` 89 | 90 | 3. Open your browser and navigate to `http://localhost:3000` 91 | 92 | ### Frontend Features 93 | 94 | - **Multimodal Chat Interface**: Text and image input support 95 | - **Drag & Drop Image Upload**: Easy image uploading with preview 96 | - **Preset Actions**: Quick analysis buttons after image upload (Analyze, Summarize, Describe, Extract Text) 97 | - **Default Questions**: 4 sample questions to get started quickly 98 | - **Markdown Rendering**: AI responses rendered with syntax highlighting 99 | - **Copy to Clipboard**: Copy AI responses with one click 100 | - **Responsive Design**: Works on desktop and mobile devices 101 | - **Loading Animation**: Bouncing dots while waiting for AI responses 102 | - **Message Avatars**: Visual distinction between user and AI messages 103 | 104 | ### Frontend Dependencies 105 | 106 | - **React 18**: Modern React with TypeScript 107 | - **Axios**: HTTP client for API requests 108 | - **React Dropzone**: Drag and drop file uploads 109 | - **React Markdown**: Markdown rendering with syntax highlighting 110 | - **Lucide React**: Beautiful icons 111 | - **Highlight.js**: Code syntax highlighting 112 | 113 | ### Usage 114 | 115 | 1. **Text Chat**: Type a message and press Enter or click Send 116 | 2. **Image Upload**: Drag and drop an image or click the upload area 117 | 3. **Preset Actions**: After uploading an image, use quick action buttons 118 | 4. **Custom Image Questions**: Upload an image and type a custom question 119 | 5. **Copy Responses**: Click the copy button on any AI responses 120 | 121 | ## API Endpoints 122 | 123 | ### 🏠 Root Endpoint 124 | 125 | ```http 126 | GET / 127 | ``` 128 | 129 | Returns API information and available endpoints. 130 | 131 | ### 💬 Text Chat 132 | 133 | ```http 134 | POST /chat/text 135 | ``` 136 | 137 | Chat with GPT-5 using text only. 138 | 139 | **Request Body:** 140 | ```json 141 | { 142 | "message": "Hello! How are you?", 143 | "conversation_history": [] 144 | } 145 | ``` 146 | 147 | **Response:** 148 | ```json 149 | { 150 | "response": "Hello! I'm doing well, thank you for asking...", 151 | "conversation_history": [ 152 | {"role": "user", "content": "Hello! How are you?"}, 153 | {"role": "assistant", "content": "Hello! I'm doing well..."} 154 | ] 155 | } 156 | ``` 157 | 158 | ### 🖼️ Image Upload Chat 159 | 160 | ```http 161 | POST /chat/image-upload 162 | ``` 163 | 164 | Upload an image file and chat about it. 165 | 166 | **Form Data:** 167 | - `image`: Image file (required) 168 | - `prompt`: Custom prompt (optional) 169 | - `preset_action`: Preset action key (optional) 170 | 171 | **Response:** 172 | ```json 173 | { 174 | "response": "I can see this is an image of...", 175 | "analysis_type": "custom" 176 | } 177 | ``` 178 | 179 | ### 🔗 Base64 Image Chat 180 | 181 | ```http 182 | POST /chat/image-base64 183 | ``` 184 | 185 | Send a base64 encoded image for analysis. 186 | 187 | **Request Body:** 188 | ```json 189 | { 190 | "image_base64": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB...", 191 | "prompt": "What do you see in this image?", 192 | "preset_action": "analyze" 193 | } 194 | ``` 195 | 196 | ### 🎯 Preset Actions 197 | 198 | ```http 199 | GET /presets 200 | ``` 201 | 202 | Get available preset actions for image analysis. 203 | 204 | **Available Presets:** 205 | - `analyze`: Detailed analysis of the image 206 | - `summarize`: Quick summary of image content 207 | - `describe`: Detailed description for accessibility 208 | - `extract_text`: Extract any text from the image 209 | - `identify_objects`: List objects and items in the image 210 | - `explain_context`: Explain the setting and context 211 | 212 | ### 🔄 Multimodal Chat 213 | 214 | ```http 215 | POST /chat/multimodal 216 | ``` 217 | 218 | Combined endpoint for both text and image input with conversation history. 219 | 220 | **Form Data:** 221 | - `message`: Text message (required) 222 | - `image`: Image file (optional) 223 | - `conversation_history`: JSON string of previous messages (optional) 224 | 225 | ## Usage Examples 226 | 227 | ### Python Example 228 | 229 | ```python 230 | import requests 231 | import base64 232 | 233 | # Text chat 234 | response = requests.post('http://localhost:8000/chat/text', json={ 235 | 'message': 'Tell me a joke', 236 | 'conversation_history': [] 237 | }) 238 | print(response.json()['response']) 239 | 240 | # Image analysis 241 | with open('image.jpg', 'rb') as f: 242 | files = {'image': f} 243 | data = {'prompt': 'What is in this image?'} 244 | response = requests.post('http://localhost:8000/chat/image-upload', 245 | files=files, data=data) 246 | print(response.json()['response']) 247 | ``` 248 | 249 | ### JavaScript/React Example 250 | 251 | ```javascript 252 | // Text chat 253 | const textChat = async (message) => { 254 | const response = await fetch('http://localhost:8000/chat/text', { 255 | method: 'POST', 256 | headers: { 'Content-Type': 'application/json' }, 257 | body: JSON.stringify({ 258 | message: message, 259 | conversation_history: [] 260 | }) 261 | }); 262 | return await response.json(); 263 | }; 264 | 265 | // Image upload 266 | const imageChat = async (imageFile, prompt) => { 267 | const formData = new FormData(); 268 | formData.append('image', imageFile); 269 | formData.append('prompt', prompt); 270 | 271 | const response = await fetch('http://localhost:8000/chat/image-upload', { 272 | method: 'POST', 273 | body: formData 274 | }); 275 | return await response.json(); 276 | }; 277 | ``` 278 | 279 | ### cURL Examples 280 | 281 | ```bash 282 | # Text chat 283 | curl -X POST "http://localhost:8000/chat/text" \ 284 | -H "Content-Type: application/json" \ 285 | -d '{ 286 | "message": "Hello, how are you?", 287 | "conversation_history": [] 288 | }' 289 | 290 | # Image upload 291 | curl -X POST "http://localhost:8000/chat/image-upload" \ 292 | -F "image=@/path/to/your/image.jpg" \ 293 | -F "prompt=What do you see in this image?" 294 | 295 | # Get presets 296 | curl "http://localhost:8000/presets" 297 | ``` 298 | 299 | ## Testing 300 | 301 | Run the test script to verify all endpoints: 302 | 303 | ```bash 304 | python3 test_api.py 305 | ``` 306 | 307 | This will test: 308 | - Root endpoint 309 | - Presets endpoint 310 | - Text chat 311 | - Image analysis 312 | - Preset actions 313 | 314 | ## Project Structure 315 | 316 | ``` 317 | gpt-5-demo/ 318 | ├── main.py # FastAPI backend application 319 | ├── test_api.py # Backend API test script 320 | ├── .env # Environment variables (create from .env.example) 321 | ├── .env.example # Environment variables template 322 | ├── pyproject.toml # Backend dependencies 323 | ├── README.md # This file 324 | ├── uv.lock # Backend dependency lock file 325 | └── frontend/ # React frontend application 326 | ├── public/ # Static assets 327 | ├── src/ # React source code 328 | │ ├── App.tsx # Main React component 329 | │ ├── App.css # Main styles 330 | │ ├── markdown.css # Markdown rendering styles 331 | │ └── index.tsx # React entry point 332 | ├── package.json # Frontend dependencies 333 | └── package-lock.json # Frontend dependency lock 334 | ``` 335 | 336 | ## Dependencies 337 | 338 | - **FastAPI**: Modern web framework for building APIs 339 | - **OpenAI**: Official OpenAI Python client 340 | - **Uvicorn**: ASGI server for running FastAPI 341 | - **Pydantic**: Data validation using Python type hints 342 | - **python-dotenv**: Load environment variables from .env file 343 | - **Requests**: HTTP library for testing 344 | 345 | ## Configuration 346 | 347 | ### Environment Variables 348 | 349 | - `OPENAI_API_KEY`: Your OpenAI API key (required) 350 | - `OPENAI_MODEL`: Model to use (optional, defaults to gpt-4o) 351 | - `HOST`: Server host (optional, defaults to 0.0.0.0) 352 | - `PORT`: Server port (optional, defaults to 8000) 353 | 354 | ### Model Configuration 355 | 356 | The API currently uses `gpt-4o` for both text and vision capabilities. When GPT-5 becomes available, you can update the model name in the code. 357 | 358 | ## Error Handling 359 | 360 | The API includes comprehensive error handling: 361 | 362 | - **400 Bad Request**: Invalid input (e.g., non-image file) 363 | - **500 Internal Server Error**: API errors or processing failures 364 | 365 | All errors return JSON with a `detail` field explaining the issue. 366 | 367 | ## CORS Support 368 | 369 | The API includes CORS middleware configured to allow all origins during development. For production, update the `allow_origins` list in `main.py` to include only your frontend domains. 370 | 371 | ## Next Steps 372 | 373 | 1. **Frontend Development**: Build a React frontend to interact with these APIs 374 | 2. **Authentication**: Add user authentication and API key management 375 | 3. **Rate Limiting**: Implement rate limiting for production use 376 | 4. **Caching**: Add response caching for frequently requested analyses 377 | 5. **File Storage**: Implement proper file storage for uploaded images 378 | 6. **Monitoring**: Add logging and monitoring for production deployment 379 | 380 | ## Contributing 381 | 382 | Feel free to submit issues and enhancement requests! 383 | 384 | ## License 385 | 386 | This project is open source and available under the [MIT License](LICENSE). 387 | -------------------------------------------------------------------------------- /frontend/src/App.css: -------------------------------------------------------------------------------- 1 | /* Reset and base styles */ 2 | * { 3 | margin: 0; 4 | padding: 0; 5 | box-sizing: border-box; 6 | } 7 | 8 | body { 9 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 10 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 11 | sans-serif; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 15 | min-height: 100vh; 16 | } 17 | 18 | /* App container */ 19 | .app { 20 | display: flex; 21 | flex-direction: column; 22 | min-height: 100vh; 23 | max-width: 1200px; 24 | margin: 0 auto; 25 | background: rgba(255, 255, 255, 0.95); 26 | backdrop-filter: blur(10px); 27 | box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); 28 | } 29 | 30 | /* Header */ 31 | .app-header { 32 | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 33 | color: white; 34 | padding: 2rem; 35 | text-align: center; 36 | box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); 37 | } 38 | 39 | .header-content { 40 | max-width: 800px; 41 | margin: 0 auto; 42 | } 43 | 44 | .logo { 45 | display: flex; 46 | align-items: center; 47 | justify-content: center; 48 | gap: 0.75rem; 49 | margin-bottom: 0.5rem; 50 | } 51 | 52 | .logo-icon { 53 | width: 2rem; 54 | height: 2rem; 55 | color: #fbbf24; 56 | } 57 | 58 | .logo h1 { 59 | font-size: 2rem; 60 | font-weight: 700; 61 | margin: 0; 62 | } 63 | 64 | .subtitle { 65 | font-size: 1.1rem; 66 | opacity: 0.9; 67 | margin: 0; 68 | } 69 | 70 | /* Main content */ 71 | .app-main { 72 | flex: 1; 73 | display: flex; 74 | flex-direction: column; 75 | padding: 1.5rem; 76 | gap: 1.5rem; 77 | } 78 | 79 | /* Chat container */ 80 | .chat-container { 81 | flex: 1; 82 | display: flex; 83 | flex-direction: column; 84 | min-height: 400px; 85 | } 86 | 87 | /* Welcome screen */ 88 | .welcome-screen { 89 | flex: 1; 90 | display: flex; 91 | align-items: center; 92 | justify-content: center; 93 | text-align: center; 94 | padding: 2rem; 95 | } 96 | 97 | .welcome-content { 98 | max-width: 600px; 99 | } 100 | 101 | .welcome-icon { 102 | width: 4rem; 103 | height: 4rem; 104 | color: #667eea; 105 | margin: 0 auto 1.5rem; 106 | } 107 | 108 | .welcome-content h2 { 109 | font-size: 1.8rem; 110 | color: #1f2937; 111 | margin-bottom: 1rem; 112 | } 113 | 114 | .welcome-content p { 115 | font-size: 1.1rem; 116 | color: #6b7280; 117 | margin-bottom: 2rem; 118 | } 119 | 120 | /* Default questions */ 121 | .default-questions { 122 | display: grid; 123 | gap: 1rem; 124 | margin-top: 1.5rem; 125 | } 126 | 127 | .question-button { 128 | display: flex; 129 | align-items: center; 130 | gap: 0.75rem; 131 | padding: 1rem 1.5rem; 132 | background: white; 133 | border: 2px solid #e5e7eb; 134 | border-radius: 12px; 135 | font-size: 1rem; 136 | color: #374151; 137 | cursor: pointer; 138 | transition: all 0.2s ease; 139 | text-align: left; 140 | } 141 | 142 | .question-button:hover { 143 | border-color: #667eea; 144 | background: #f8faff; 145 | transform: translateY(-2px); 146 | box-shadow: 0 4px 12px rgba(102, 126, 234, 0.15); 147 | } 148 | 149 | .question-button:disabled { 150 | opacity: 0.6; 151 | cursor: not-allowed; 152 | transform: none; 153 | } 154 | 155 | .question-icon { 156 | width: 1.25rem; 157 | height: 1.25rem; 158 | color: #667eea; 159 | flex-shrink: 0; 160 | } 161 | 162 | /* Message avatars */ 163 | .message-avatar { 164 | flex-shrink: 0; 165 | width: 2.5rem; 166 | height: 2.5rem; 167 | border-radius: 50%; 168 | display: flex; 169 | align-items: center; 170 | justify-content: center; 171 | margin-top: 0.25rem; 172 | } 173 | 174 | .message.user .message-avatar { 175 | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 176 | color: white; 177 | } 178 | 179 | .message.assistant .message-avatar { 180 | background: #f3f4f6; 181 | color: #667eea; 182 | border: 2px solid #e5e7eb; 183 | } 184 | 185 | .avatar-icon { 186 | width: 1.25rem; 187 | height: 1.25rem; 188 | } 189 | 190 | /* Messages */ 191 | .messages { 192 | flex: 1; 193 | display: flex; 194 | flex-direction: column; 195 | gap: 1rem; 196 | padding: 1rem; 197 | overflow-y: auto; 198 | max-height: 500px; 199 | } 200 | 201 | .message { 202 | display: flex; 203 | max-width: 80%; 204 | } 205 | 206 | .message.user { 207 | align-self: flex-end; 208 | } 209 | 210 | .message.assistant { 211 | align-self: flex-start; 212 | } 213 | 214 | .message-content { 215 | position: relative; 216 | padding: 1rem 1.25rem; 217 | border-radius: 18px; 218 | font-size: 1rem; 219 | line-height: 1.5; 220 | } 221 | 222 | .message.user .message-content { 223 | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 224 | color: white; 225 | border-bottom-right-radius: 6px; 226 | } 227 | 228 | .message.assistant .message-content { 229 | background: #f3f4f6; 230 | color: #1f2937; 231 | border-bottom-left-radius: 6px; 232 | } 233 | 234 | .message-text { 235 | white-space: pre-wrap; 236 | word-wrap: break-word; 237 | } 238 | 239 | /* Copy button */ 240 | .copy-button { 241 | position: absolute; 242 | top: 0.5rem; 243 | right: 0.5rem; 244 | background: rgba(255, 255, 255, 0.9); 245 | border: none; 246 | border-radius: 6px; 247 | padding: 0.5rem; 248 | cursor: pointer; 249 | opacity: 0; 250 | transition: opacity 0.2s ease; 251 | } 252 | 253 | .message-content:hover .copy-button { 254 | opacity: 1; 255 | } 256 | 257 | .copy-icon { 258 | width: 1rem; 259 | height: 1rem; 260 | color: #6b7280; 261 | } 262 | 263 | /* Loading */ 264 | .loading { 265 | display: flex; 266 | align-items: center; 267 | gap: 0.75rem; 268 | color: #6b7280; 269 | font-style: italic; 270 | padding: 0.5rem 0; 271 | } 272 | 273 | .loading-dots { 274 | display: flex; 275 | gap: 0.25rem; 276 | align-items: center; 277 | height: 24px; 278 | } 279 | 280 | .dot { 281 | width: 8px; 282 | height: 8px; 283 | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 284 | border-radius: 50%; 285 | animation: bounce 1.4s infinite ease-in-out both; 286 | box-shadow: 0 2px 4px rgba(102, 126, 234, 0.3); 287 | } 288 | 289 | .dot:nth-child(1) { 290 | animation-delay: -0.32s; 291 | } 292 | 293 | .dot:nth-child(2) { 294 | animation-delay: -0.16s; 295 | } 296 | 297 | @keyframes bounce { 298 | 0%, 80%, 100% { 299 | transform: translateY(0) scale(0.6); 300 | opacity: 0.6; 301 | } 302 | 40% { 303 | transform: translateY(-10px) scale(1); 304 | opacity: 1; 305 | } 306 | } 307 | 308 | /* Image preview */ 309 | .image-preview-container { 310 | background: white; 311 | border-radius: 12px; 312 | padding: 1.5rem; 313 | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); 314 | border: 2px solid #e5e7eb; 315 | } 316 | 317 | .image-preview { 318 | position: relative; 319 | display: inline-block; 320 | margin-bottom: 1rem; 321 | } 322 | 323 | .preview-image { 324 | max-width: 300px; 325 | max-height: 200px; 326 | border-radius: 8px; 327 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); 328 | } 329 | 330 | .remove-image { 331 | position: absolute; 332 | top: -8px; 333 | right: -8px; 334 | background: #ef4444; 335 | color: white; 336 | border: none; 337 | border-radius: 50%; 338 | width: 2rem; 339 | height: 2rem; 340 | display: flex; 341 | align-items: center; 342 | justify-content: center; 343 | cursor: pointer; 344 | transition: background 0.2s ease; 345 | } 346 | 347 | .remove-image:hover { 348 | background: #dc2626; 349 | } 350 | 351 | .remove-icon { 352 | width: 1rem; 353 | height: 1rem; 354 | } 355 | 356 | /* Preset actions */ 357 | .preset-actions h3 { 358 | color: #1f2937; 359 | margin-bottom: 1rem; 360 | font-size: 1.1rem; 361 | } 362 | 363 | .preset-buttons { 364 | display: grid; 365 | grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); 366 | gap: 0.75rem; 367 | margin-bottom: 1rem; 368 | } 369 | 370 | .preset-button { 371 | display: flex; 372 | flex-direction: column; 373 | align-items: center; 374 | gap: 0.5rem; 375 | padding: 1rem; 376 | background: white; 377 | border: 2px solid #e5e7eb; 378 | border-radius: 8px; 379 | cursor: pointer; 380 | transition: all 0.2s ease; 381 | font-size: 0.9rem; 382 | font-weight: 500; 383 | } 384 | 385 | .preset-button:hover { 386 | border-color: #667eea; 387 | background: #f8faff; 388 | transform: translateY(-2px); 389 | box-shadow: 0 4px 8px rgba(102, 126, 234, 0.15); 390 | } 391 | 392 | .preset-button:disabled { 393 | opacity: 0.6; 394 | cursor: not-allowed; 395 | transform: none; 396 | } 397 | 398 | .preset-icon { 399 | width: 1.5rem; 400 | height: 1.5rem; 401 | color: #667eea; 402 | } 403 | 404 | .preset-hint { 405 | color: #6b7280; 406 | font-size: 0.9rem; 407 | text-align: center; 408 | margin: 0; 409 | } 410 | 411 | /* Input container */ 412 | .input-container { 413 | display: flex; 414 | gap: 1rem; 415 | align-items: flex-end; 416 | } 417 | 418 | /* Dropzone */ 419 | .dropzone { 420 | display: flex; 421 | flex-direction: column; 422 | align-items: center; 423 | justify-content: center; 424 | padding: 1rem; 425 | border: 2px dashed #d1d5db; 426 | border-radius: 8px; 427 | background: #f9fafb; 428 | cursor: pointer; 429 | transition: all 0.2s ease; 430 | min-width: 140px; 431 | text-align: center; 432 | } 433 | 434 | .dropzone:hover, 435 | .dropzone.active { 436 | border-color: #667eea; 437 | background: #f8faff; 438 | } 439 | 440 | .upload-icon { 441 | width: 1.5rem; 442 | height: 1.5rem; 443 | color: #6b7280; 444 | margin-bottom: 0.5rem; 445 | } 446 | 447 | .dropzone span { 448 | font-size: 0.9rem; 449 | color: #6b7280; 450 | } 451 | 452 | /* Input form */ 453 | .input-form { 454 | flex: 1; 455 | } 456 | 457 | .input-wrapper { 458 | display: flex; 459 | gap: 0.75rem; 460 | align-items: center; 461 | background: white; 462 | border: 2px solid #e5e7eb; 463 | border-radius: 24px; 464 | padding: 0.5rem; 465 | transition: border-color 0.2s ease; 466 | } 467 | 468 | .input-wrapper:focus-within { 469 | border-color: #667eea; 470 | box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); 471 | } 472 | 473 | .message-input { 474 | flex: 1; 475 | border: none; 476 | outline: none; 477 | padding: 0.75rem 1rem; 478 | font-size: 1rem; 479 | background: transparent; 480 | } 481 | 482 | .message-input::placeholder { 483 | color: #9ca3af; 484 | } 485 | 486 | .send-button { 487 | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 488 | color: white; 489 | border: none; 490 | border-radius: 50%; 491 | width: 2.5rem; 492 | height: 2.5rem; 493 | display: flex; 494 | align-items: center; 495 | justify-content: center; 496 | cursor: pointer; 497 | transition: all 0.2s ease; 498 | } 499 | 500 | .send-button:hover:not(:disabled) { 501 | transform: scale(1.05); 502 | box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3); 503 | } 504 | 505 | .send-button:disabled { 506 | opacity: 0.6; 507 | cursor: not-allowed; 508 | transform: none; 509 | } 510 | 511 | .send-icon { 512 | width: 1.25rem; 513 | height: 1.25rem; 514 | } 515 | 516 | /* Responsive design */ 517 | @media (max-width: 768px) { 518 | .app { 519 | margin: 0; 520 | border-radius: 0; 521 | } 522 | 523 | .app-header { 524 | padding: 1.5rem 1rem; 525 | } 526 | 527 | .logo h1 { 528 | font-size: 1.5rem; 529 | } 530 | 531 | .app-main { 532 | padding: 1rem; 533 | } 534 | 535 | .input-container { 536 | flex-direction: column; 537 | gap: 0.75rem; 538 | } 539 | 540 | .dropzone { 541 | min-width: auto; 542 | width: 100%; 543 | } 544 | 545 | .message { 546 | max-width: 90%; 547 | } 548 | 549 | .default-questions { 550 | grid-template-columns: 1fr; 551 | } 552 | 553 | .preset-buttons { 554 | grid-template-columns: repeat(2, 1fr); 555 | } 556 | } 557 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import base64 3 | import io 4 | from typing import Optional, List 5 | from fastapi import FastAPI, File, UploadFile, HTTPException, Form 6 | from fastapi.middleware.cors import CORSMiddleware 7 | from fastapi.responses import JSONResponse 8 | from pydantic import BaseModel 9 | from openai import OpenAI 10 | from dotenv import load_dotenv 11 | import uvicorn 12 | 13 | # Load environment variables 14 | load_dotenv() 15 | 16 | # Initialize OpenAI client 17 | client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) 18 | 19 | # Initialize FastAPI app 20 | app = FastAPI( 21 | title="GPT-5 Multimodal Chat API", 22 | description="A FastAPI backend for GPT-5 multimodal chat with image and text support", 23 | version="1.0.0" 24 | ) 25 | 26 | # Add CORS middleware for React frontend 27 | app.add_middleware( 28 | CORSMiddleware, 29 | allow_origins=["*"], # In production, replace with specific origins 30 | allow_credentials=True, 31 | allow_methods=["*"], 32 | allow_headers=["*"], 33 | ) 34 | 35 | # Pydantic models 36 | class TextChatRequest(BaseModel): 37 | message: str 38 | conversation_history: Optional[List[dict]] = [] 39 | 40 | class TextChatResponse(BaseModel): 41 | response: str 42 | conversation_history: List[dict] 43 | 44 | class ImageAnalysisRequest(BaseModel): 45 | image_base64: str 46 | prompt: Optional[str] = "Analyze this image" 47 | preset_action: Optional[str] = None # "analyze", "summarize", "describe", etc. 48 | 49 | class ImageAnalysisResponse(BaseModel): 50 | response: str 51 | analysis_type: str 52 | 53 | # Helper functions 54 | def encode_image_to_base64(image_file: UploadFile) -> str: 55 | """Convert uploaded image to base64 string""" 56 | try: 57 | image_data = image_file.file.read() 58 | base64_string = base64.b64encode(image_data).decode('utf-8') 59 | return base64_string 60 | except Exception as e: 61 | raise HTTPException(status_code=400, detail=f"Error processing image: {str(e)}") 62 | 63 | def get_preset_prompt(action: str) -> str: 64 | """Get predefined prompts for preset actions""" 65 | presets = { 66 | "analyze": "Analyze this image in detail. Describe what you see, identify key elements, colors, composition, and any notable features.", 67 | "summarize": "Provide a concise summary of what's shown in this image in 2-3 sentences.", 68 | "describe": "Describe this image as if you're explaining it to someone who cannot see it. Be detailed and specific.", 69 | "extract_text": "Extract and transcribe any text visible in this image. If no text is present, say 'No text detected'.", 70 | "identify_objects": "Identify and list all the objects, people, or items you can see in this image.", 71 | "explain_context": "Explain the context and setting of this image. What's happening? Where might this be taken?" 72 | } 73 | return presets.get(action, "Analyze this image") 74 | 75 | # API Endpoints 76 | 77 | @app.get("/") 78 | async def root(): 79 | """Root endpoint with API information""" 80 | return { 81 | "message": "GPT-5 Multimodal Chat API", 82 | "version": "1.0.0", 83 | "endpoints": { 84 | "/chat/text": "Text-only chat with GPT-5", 85 | "/chat/image-upload": "Upload image and chat with GPT-5", 86 | "/chat/image-base64": "Send base64 image and chat with GPT-5", 87 | "/presets": "Get available preset actions for images" 88 | } 89 | } 90 | 91 | @app.get("/presets") 92 | async def get_presets(): 93 | """Get available preset actions for image analysis""" 94 | return { 95 | "presets": [ 96 | {"key": "analyze", "label": "Analyze Image", "description": "Detailed analysis of the image"}, 97 | {"key": "summarize", "label": "Summarize", "description": "Quick summary of image content"}, 98 | {"key": "describe", "label": "Describe", "description": "Detailed description for accessibility"}, 99 | {"key": "extract_text", "label": "Extract Text", "description": "Extract any text from the image"}, 100 | {"key": "identify_objects", "label": "Identify Objects", "description": "List objects and items in the image"}, 101 | {"key": "explain_context", "label": "Explain Context", "description": "Explain the setting and context"} 102 | ] 103 | } 104 | 105 | @app.post("/chat/text", response_model=TextChatResponse) 106 | async def text_chat(request: TextChatRequest): 107 | """Text-only chat with GPT-5""" 108 | try: 109 | # Prepare conversation history 110 | messages = request.conversation_history.copy() if request.conversation_history else [] 111 | messages.append({"role": "user", "content": request.message}) 112 | 113 | # Call GPT-5 API 114 | response = client.chat.completions.create( 115 | model="gpt-4o", # Using GPT-4o as GPT-5 might not be available yet 116 | messages=messages, 117 | max_tokens=2048, 118 | temperature=0.7 119 | ) 120 | 121 | assistant_response = response.choices[0].message.content 122 | 123 | # Update conversation history 124 | messages.append({"role": "assistant", "content": assistant_response}) 125 | 126 | return TextChatResponse( 127 | response=assistant_response, 128 | conversation_history=messages 129 | ) 130 | 131 | except Exception as e: 132 | raise HTTPException(status_code=500, detail=f"Error processing chat: {str(e)}") 133 | 134 | @app.post("/chat/image-upload") 135 | async def image_upload_chat( 136 | image: UploadFile = File(...), 137 | prompt: Optional[str] = Form(None), 138 | preset_action: Optional[str] = Form(None) 139 | ): 140 | """Upload an image file and chat with GPT-5""" 141 | try: 142 | # Validate image file 143 | if not image.content_type.startswith('image/'): 144 | raise HTTPException(status_code=400, detail="File must be an image") 145 | 146 | # Convert image to base64 147 | base64_image = encode_image_to_base64(image) 148 | 149 | # Determine the prompt to use 150 | if preset_action: 151 | final_prompt = get_preset_prompt(preset_action) 152 | analysis_type = preset_action 153 | elif prompt: 154 | final_prompt = prompt 155 | analysis_type = "custom" 156 | else: 157 | final_prompt = "Analyze this image" 158 | analysis_type = "default" 159 | 160 | # Prepare the message for GPT-5 161 | messages = [ 162 | { 163 | "role": "user", 164 | "content": [ 165 | {"type": "text", "text": final_prompt}, 166 | { 167 | "type": "image_url", 168 | "image_url": { 169 | "url": f"data:image/jpeg;base64,{base64_image}" 170 | } 171 | } 172 | ] 173 | } 174 | ] 175 | 176 | # Call GPT-5 API 177 | response = client.chat.completions.create( 178 | model="gpt-4o", # Using GPT-4o for vision capabilities 179 | messages=messages, 180 | max_tokens=2048, 181 | temperature=0.7 182 | ) 183 | 184 | assistant_response = response.choices[0].message.content 185 | 186 | return ImageAnalysisResponse( 187 | response=assistant_response, 188 | analysis_type=analysis_type 189 | ) 190 | 191 | except Exception as e: 192 | raise HTTPException(status_code=500, detail=f"Error processing image: {str(e)}") 193 | 194 | @app.post("/chat/image-base64", response_model=ImageAnalysisResponse) 195 | async def image_base64_chat(request: ImageAnalysisRequest): 196 | """Send base64 encoded image and chat with GPT-5""" 197 | try: 198 | # Determine the prompt to use 199 | if request.preset_action: 200 | final_prompt = get_preset_prompt(request.preset_action) 201 | analysis_type = request.preset_action 202 | else: 203 | final_prompt = request.prompt or "Analyze this image" 204 | analysis_type = "custom" if request.prompt else "default" 205 | 206 | # Prepare the message for GPT-5 207 | messages = [ 208 | { 209 | "role": "user", 210 | "content": [ 211 | {"type": "text", "text": final_prompt}, 212 | { 213 | "type": "image_url", 214 | "image_url": { 215 | "url": f"data:image/jpeg;base64,{request.image_base64}" 216 | } 217 | } 218 | ] 219 | } 220 | ] 221 | 222 | # Call GPT-5 API 223 | response = client.chat.completions.create( 224 | model="gpt-4o", # Using GPT-4o for vision capabilities 225 | messages=messages, 226 | max_tokens=2048, 227 | temperature=0.7 228 | ) 229 | 230 | assistant_response = response.choices[0].message.content 231 | 232 | return ImageAnalysisResponse( 233 | response=assistant_response, 234 | analysis_type=analysis_type 235 | ) 236 | 237 | except Exception as e: 238 | raise HTTPException(status_code=500, detail=f"Error processing image: {str(e)}") 239 | 240 | @app.post("/chat/multimodal") 241 | async def multimodal_chat( 242 | message: str = Form(...), 243 | image: Optional[UploadFile] = File(None), 244 | conversation_history: Optional[str] = Form(None) # JSON string 245 | ): 246 | """Combined endpoint for both text and image input""" 247 | try: 248 | import json 249 | 250 | # Parse conversation history if provided 251 | messages = [] 252 | if conversation_history: 253 | try: 254 | messages = json.loads(conversation_history) 255 | except json.JSONDecodeError: 256 | messages = [] 257 | 258 | # Prepare the current message 259 | current_message = { 260 | "role": "user", 261 | "content": [] 262 | } 263 | 264 | # Add text content 265 | current_message["content"].append({"type": "text", "text": message}) 266 | 267 | # Add image if provided 268 | if image and image.content_type.startswith('image/'): 269 | base64_image = encode_image_to_base64(image) 270 | current_message["content"].append({ 271 | "type": "image_url", 272 | "image_url": { 273 | "url": f"data:image/jpeg;base64,{base64_image}" 274 | } 275 | }) 276 | 277 | messages.append(current_message) 278 | 279 | # Call GPT-5 API 280 | response = client.chat.completions.create( 281 | model="gpt-4o", 282 | messages=messages, 283 | max_tokens=2048, 284 | temperature=0.7 285 | ) 286 | 287 | assistant_response = response.choices[0].message.content 288 | 289 | # Update conversation history 290 | messages.append({"role": "assistant", "content": assistant_response}) 291 | 292 | return { 293 | "response": assistant_response, 294 | "conversation_history": messages, 295 | "has_image": image is not None 296 | } 297 | 298 | except Exception as e: 299 | raise HTTPException(status_code=500, detail=f"Error processing multimodal chat: {str(e)}") 300 | 301 | if __name__ == "__main__": 302 | uvicorn.run( 303 | "main:app", 304 | host="0.0.0.0", 305 | port=8000, 306 | reload=True 307 | ) 308 | -------------------------------------------------------------------------------- /frontend/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef } from 'react'; 2 | import axios from 'axios'; 3 | import { useDropzone } from 'react-dropzone'; 4 | import ReactMarkdown from 'react-markdown'; 5 | import rehypeHighlight from 'rehype-highlight'; 6 | import { 7 | Send, 8 | Copy, 9 | Upload, 10 | MessageSquare, 11 | Sparkles, 12 | FileText, 13 | Eye, 14 | Search, 15 | RotateCcw, 16 | Bot, 17 | User 18 | } from 'lucide-react'; 19 | import './App.css'; 20 | import './markdown.css'; 21 | import 'highlight.js/styles/github-dark.css'; 22 | 23 | interface Message { 24 | role: 'user' | 'assistant'; 25 | content: string; 26 | } 27 | 28 | interface ChatResponse { 29 | response: string; 30 | conversation_history: Message[]; 31 | analysis_type?: string; 32 | } 33 | 34 | const API_BASE_URL = 'http://localhost:8000'; 35 | 36 | // Default questions to show users 37 | const DEFAULT_QUESTIONS = [ 38 | "What is artificial intelligence?", 39 | "Explain machine learning in simple terms", 40 | "How does neural networks work?", 41 | "What are the applications of AI?" 42 | ]; 43 | 44 | // Preset actions for image analysis 45 | const PRESET_ACTIONS = [ 46 | { key: 'analyze', label: 'Analyze', icon: Search, description: 'Detailed analysis' }, 47 | { key: 'summarize', label: 'Summarize', icon: FileText, description: 'Quick summary' }, 48 | { key: 'describe', label: 'Describe', icon: Eye, description: 'Detailed description' }, 49 | { key: 'extract_text', label: 'Extract Text', icon: FileText, description: 'OCR text extraction' } 50 | ]; 51 | 52 | function App() { 53 | const [messages, setMessages] = useState([]); 54 | const [inputMessage, setInputMessage] = useState(''); 55 | const [isLoading, setIsLoading] = useState(false); 56 | const [uploadedImage, setUploadedImage] = useState(null); 57 | const [imagePreview, setImagePreview] = useState(null); 58 | const [showPresets, setShowPresets] = useState(false); 59 | const messagesEndRef = useRef(null); 60 | 61 | const scrollToBottom = () => { 62 | messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); 63 | }; 64 | 65 | React.useEffect(() => { 66 | scrollToBottom(); 67 | }, [messages]); 68 | 69 | // Handle file drop 70 | const onDrop = (acceptedFiles: File[]) => { 71 | const file = acceptedFiles[0]; 72 | if (file && file.type.startsWith('image/')) { 73 | setUploadedImage(file); 74 | const reader = new FileReader(); 75 | reader.onload = () => { 76 | setImagePreview(reader.result as string); 77 | setShowPresets(true); 78 | }; 79 | reader.readAsDataURL(file); 80 | } 81 | }; 82 | 83 | const { getRootProps, getInputProps, isDragActive } = useDropzone({ 84 | onDrop, 85 | accept: { 86 | 'image/*': ['.png', '.jpg', '.jpeg', '.gif', '.webp'] 87 | }, 88 | multiple: false 89 | }); 90 | 91 | // Send text message 92 | const sendTextMessage = async (message: string) => { 93 | if (!message.trim()) return; 94 | 95 | const newUserMessage: Message = { role: 'user', content: message }; 96 | const updatedMessages = [...messages, newUserMessage]; 97 | setMessages(updatedMessages); 98 | setInputMessage(''); 99 | setIsLoading(true); 100 | 101 | try { 102 | const response = await axios.post(`${API_BASE_URL}/chat/text`, { 103 | message: message, 104 | conversation_history: messages 105 | }); 106 | 107 | setMessages(response.data.conversation_history); 108 | } catch (error) { 109 | console.error('Error sending message:', error); 110 | setMessages([...updatedMessages, { 111 | role: 'assistant', 112 | content: 'Sorry, there was an error processing your request. Please try again.' 113 | }]); 114 | } finally { 115 | setIsLoading(false); 116 | } 117 | }; 118 | 119 | // Send image with preset action 120 | const sendImageWithPreset = async (presetAction: string) => { 121 | if (!uploadedImage) return; 122 | 123 | setIsLoading(true); 124 | setShowPresets(false); 125 | 126 | const formData = new FormData(); 127 | formData.append('image', uploadedImage); 128 | formData.append('preset_action', presetAction); 129 | 130 | try { 131 | const response = await axios.post(`${API_BASE_URL}/chat/image-upload`, formData, { 132 | headers: { 133 | 'Content-Type': 'multipart/form-data' 134 | } 135 | }); 136 | 137 | const userMessage: Message = { 138 | role: 'user', 139 | content: `[Image uploaded] - ${PRESET_ACTIONS.find(p => p.key === presetAction)?.label}` 140 | }; 141 | 142 | const assistantMessage: Message = { 143 | role: 'assistant', 144 | content: response.data.response 145 | }; 146 | 147 | setMessages([...messages, userMessage, assistantMessage]); 148 | } catch (error) { 149 | console.error('Error analyzing image:', error); 150 | setMessages([...messages, { 151 | role: 'assistant', 152 | content: 'Sorry, there was an error analyzing the image. Please try again.' 153 | }]); 154 | } finally { 155 | setIsLoading(false); 156 | } 157 | }; 158 | 159 | // Send image with custom prompt 160 | const sendImageWithPrompt = async (prompt: string) => { 161 | if (!uploadedImage || !prompt.trim()) return; 162 | 163 | setIsLoading(true); 164 | setShowPresets(false); 165 | 166 | const formData = new FormData(); 167 | formData.append('image', uploadedImage); 168 | formData.append('prompt', prompt); 169 | 170 | try { 171 | const response = await axios.post(`${API_BASE_URL}/chat/image-upload`, formData, { 172 | headers: { 173 | 'Content-Type': 'multipart/form-data' 174 | } 175 | }); 176 | 177 | const userMessage: Message = { 178 | role: 'user', 179 | content: `[Image uploaded] ${prompt}` 180 | }; 181 | 182 | const assistantMessage: Message = { 183 | role: 'assistant', 184 | content: response.data.response 185 | }; 186 | 187 | setMessages([...messages, userMessage, assistantMessage]); 188 | setInputMessage(''); 189 | } catch (error) { 190 | console.error('Error analyzing image:', error); 191 | setMessages([...messages, { 192 | role: 'assistant', 193 | content: 'Sorry, there was an error analyzing the image. Please try again.' 194 | }]); 195 | } finally { 196 | setIsLoading(false); 197 | } 198 | }; 199 | 200 | // Copy to clipboard 201 | const copyToClipboard = async (text: string) => { 202 | try { 203 | await navigator.clipboard.writeText(text); 204 | // You could add a toast notification here 205 | } catch (error) { 206 | console.error('Failed to copy to clipboard:', error); 207 | } 208 | }; 209 | 210 | // Clear uploaded image 211 | const clearImage = () => { 212 | setUploadedImage(null); 213 | setImagePreview(null); 214 | setShowPresets(false); 215 | }; 216 | 217 | // Handle form submission 218 | const handleSubmit = (e: React.FormEvent) => { 219 | e.preventDefault(); 220 | if (uploadedImage && inputMessage.trim()) { 221 | sendImageWithPrompt(inputMessage); 222 | } else if (inputMessage.trim()) { 223 | sendTextMessage(inputMessage); 224 | } 225 | }; 226 | 227 | return ( 228 |
229 |
230 |
231 |
232 | 233 |

GPT-5 Multimodal Chat

234 |
235 |

Chat with AI using text and images

236 |
237 |
238 | 239 |
240 | {/* Chat Messages */} 241 |
242 | {messages.length === 0 ? ( 243 |
244 |
245 | 246 |

Welcome to GPT-5 Multimodal Chat

247 |

Ask questions, upload images, or try one of these examples:

248 | 249 |
250 | {DEFAULT_QUESTIONS.map((question, index) => ( 251 | 260 | ))} 261 |
262 |
263 |
264 | ) : ( 265 |
266 | {messages.map((message, index) => ( 267 |
268 |
269 | {message.role === 'user' ? ( 270 | 271 | ) : ( 272 | 273 | )} 274 |
275 |
276 |
277 | {message.role === 'assistant' ? ( 278 |
279 | { 283 | const match = /language-(\w+)/.exec(className || ''); 284 | return match ? ( 285 |
286 |                                   
287 |                                     {children}
288 |                                   
289 |                                 
290 | ) : ( 291 | 292 | {children} 293 | 294 | ); 295 | }, 296 | }} 297 | > 298 | {message.content} 299 |
300 |
301 | ) : ( 302 | message.content 303 | )} 304 |
305 | {message.role === 'assistant' && ( 306 | 313 | )} 314 |
315 |
316 | ))} 317 | {isLoading && ( 318 |
319 |
320 | 321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 | AI is thinking... 330 |
331 |
332 |
333 | )} 334 |
335 |
336 | )} 337 |
338 | 339 | {/* Image Upload Area */} 340 | {imagePreview && ( 341 |
342 |
343 | Uploaded 344 | 347 |
348 | 349 | {showPresets && ( 350 |
351 |

Quick Actions:

352 |
353 | {PRESET_ACTIONS.map((preset) => { 354 | const IconComponent = preset.icon; 355 | return ( 356 | 365 | ); 366 | })} 367 |
368 |

Or type a custom question below

369 |
370 | )} 371 |
372 | )} 373 | 374 | {/* Input Area */} 375 |
376 | {/* File Upload */} 377 |
378 | 379 | 380 | {isDragActive ? 'Drop image here' : 'Drag & drop image'} 381 |
382 | 383 | {/* Text Input */} 384 |
385 |
386 | setInputMessage(e.target.value)} 390 | placeholder={uploadedImage ? "Ask something about the image..." : "Type your message..."} 391 | className="message-input" 392 | disabled={isLoading} 393 | /> 394 | 401 |
402 |
403 |
404 |
405 |
406 | ); 407 | } 408 | 409 | export default App; 410 | -------------------------------------------------------------------------------- /uv.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | revision = 1 3 | requires-python = ">=3.10" 4 | 5 | [[package]] 6 | name = "annotated-types" 7 | version = "0.7.0" 8 | source = { registry = "https://pypi.org/simple" } 9 | sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } 10 | wheels = [ 11 | { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, 12 | ] 13 | 14 | [[package]] 15 | name = "anyio" 16 | version = "4.10.0" 17 | source = { registry = "https://pypi.org/simple" } 18 | dependencies = [ 19 | { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, 20 | { name = "idna" }, 21 | { name = "sniffio" }, 22 | { name = "typing-extensions", marker = "python_full_version < '3.13'" }, 23 | ] 24 | sdist = { url = "https://files.pythonhosted.org/packages/f1/b4/636b3b65173d3ce9a38ef5f0522789614e590dab6a8d505340a4efe4c567/anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6", size = 213252 } 25 | wheels = [ 26 | { url = "https://files.pythonhosted.org/packages/6f/12/e5e0282d673bb9746bacfb6e2dba8719989d3660cdb2ea79aee9a9651afb/anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1", size = 107213 }, 27 | ] 28 | 29 | [[package]] 30 | name = "certifi" 31 | version = "2025.8.3" 32 | source = { registry = "https://pypi.org/simple" } 33 | sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386 } 34 | wheels = [ 35 | { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216 }, 36 | ] 37 | 38 | [[package]] 39 | name = "charset-normalizer" 40 | version = "3.4.2" 41 | source = { registry = "https://pypi.org/simple" } 42 | sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367 } 43 | wheels = [ 44 | { url = "https://files.pythonhosted.org/packages/95/28/9901804da60055b406e1a1c5ba7aac1276fb77f1dde635aabfc7fd84b8ab/charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941", size = 201818 }, 45 | { url = "https://files.pythonhosted.org/packages/d9/9b/892a8c8af9110935e5adcbb06d9c6fe741b6bb02608c6513983048ba1a18/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd", size = 144649 }, 46 | { url = "https://files.pythonhosted.org/packages/7b/a5/4179abd063ff6414223575e008593861d62abfc22455b5d1a44995b7c101/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6", size = 155045 }, 47 | { url = "https://files.pythonhosted.org/packages/3b/95/bc08c7dfeddd26b4be8c8287b9bb055716f31077c8b0ea1cd09553794665/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d", size = 147356 }, 48 | { url = "https://files.pythonhosted.org/packages/a8/2d/7a5b635aa65284bf3eab7653e8b4151ab420ecbae918d3e359d1947b4d61/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86", size = 149471 }, 49 | { url = "https://files.pythonhosted.org/packages/ae/38/51fc6ac74251fd331a8cfdb7ec57beba8c23fd5493f1050f71c87ef77ed0/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c", size = 151317 }, 50 | { url = "https://files.pythonhosted.org/packages/b7/17/edee1e32215ee6e9e46c3e482645b46575a44a2d72c7dfd49e49f60ce6bf/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0", size = 146368 }, 51 | { url = "https://files.pythonhosted.org/packages/26/2c/ea3e66f2b5f21fd00b2825c94cafb8c326ea6240cd80a91eb09e4a285830/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef", size = 154491 }, 52 | { url = "https://files.pythonhosted.org/packages/52/47/7be7fa972422ad062e909fd62460d45c3ef4c141805b7078dbab15904ff7/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6", size = 157695 }, 53 | { url = "https://files.pythonhosted.org/packages/2f/42/9f02c194da282b2b340f28e5fb60762de1151387a36842a92b533685c61e/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366", size = 154849 }, 54 | { url = "https://files.pythonhosted.org/packages/67/44/89cacd6628f31fb0b63201a618049be4be2a7435a31b55b5eb1c3674547a/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db", size = 150091 }, 55 | { url = "https://files.pythonhosted.org/packages/1f/79/4b8da9f712bc079c0f16b6d67b099b0b8d808c2292c937f267d816ec5ecc/charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a", size = 98445 }, 56 | { url = "https://files.pythonhosted.org/packages/7d/d7/96970afb4fb66497a40761cdf7bd4f6fca0fc7bafde3a84f836c1f57a926/charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509", size = 105782 }, 57 | { url = "https://files.pythonhosted.org/packages/05/85/4c40d00dcc6284a1c1ad5de5e0996b06f39d8232f1031cd23c2f5c07ee86/charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", size = 198794 }, 58 | { url = "https://files.pythonhosted.org/packages/41/d9/7a6c0b9db952598e97e93cbdfcb91bacd89b9b88c7c983250a77c008703c/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", size = 142846 }, 59 | { url = "https://files.pythonhosted.org/packages/66/82/a37989cda2ace7e37f36c1a8ed16c58cf48965a79c2142713244bf945c89/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", size = 153350 }, 60 | { url = "https://files.pythonhosted.org/packages/df/68/a576b31b694d07b53807269d05ec3f6f1093e9545e8607121995ba7a8313/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", size = 145657 }, 61 | { url = "https://files.pythonhosted.org/packages/92/9b/ad67f03d74554bed3aefd56fe836e1623a50780f7c998d00ca128924a499/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f", size = 147260 }, 62 | { url = "https://files.pythonhosted.org/packages/a6/e6/8aebae25e328160b20e31a7e9929b1578bbdc7f42e66f46595a432f8539e/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", size = 149164 }, 63 | { url = "https://files.pythonhosted.org/packages/8b/f2/b3c2f07dbcc248805f10e67a0262c93308cfa149a4cd3d1fe01f593e5fd2/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", size = 144571 }, 64 | { url = "https://files.pythonhosted.org/packages/60/5b/c3f3a94bc345bc211622ea59b4bed9ae63c00920e2e8f11824aa5708e8b7/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", size = 151952 }, 65 | { url = "https://files.pythonhosted.org/packages/e2/4d/ff460c8b474122334c2fa394a3f99a04cf11c646da895f81402ae54f5c42/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", size = 155959 }, 66 | { url = "https://files.pythonhosted.org/packages/a2/2b/b964c6a2fda88611a1fe3d4c400d39c66a42d6c169c924818c848f922415/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", size = 153030 }, 67 | { url = "https://files.pythonhosted.org/packages/59/2e/d3b9811db26a5ebf444bc0fa4f4be5aa6d76fc6e1c0fd537b16c14e849b6/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", size = 148015 }, 68 | { url = "https://files.pythonhosted.org/packages/90/07/c5fd7c11eafd561bb51220d600a788f1c8d77c5eef37ee49454cc5c35575/charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", size = 98106 }, 69 | { url = "https://files.pythonhosted.org/packages/a8/05/5e33dbef7e2f773d672b6d79f10ec633d4a71cd96db6673625838a4fd532/charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", size = 105402 }, 70 | { url = "https://files.pythonhosted.org/packages/d7/a4/37f4d6035c89cac7930395a35cc0f1b872e652eaafb76a6075943754f095/charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", size = 199936 }, 71 | { url = "https://files.pythonhosted.org/packages/ee/8a/1a5e33b73e0d9287274f899d967907cd0bf9c343e651755d9307e0dbf2b3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", size = 143790 }, 72 | { url = "https://files.pythonhosted.org/packages/66/52/59521f1d8e6ab1482164fa21409c5ef44da3e9f653c13ba71becdd98dec3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", size = 153924 }, 73 | { url = "https://files.pythonhosted.org/packages/86/2d/fb55fdf41964ec782febbf33cb64be480a6b8f16ded2dbe8db27a405c09f/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", size = 146626 }, 74 | { url = "https://files.pythonhosted.org/packages/8c/73/6ede2ec59bce19b3edf4209d70004253ec5f4e319f9a2e3f2f15601ed5f7/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", size = 148567 }, 75 | { url = "https://files.pythonhosted.org/packages/09/14/957d03c6dc343c04904530b6bef4e5efae5ec7d7990a7cbb868e4595ee30/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", size = 150957 }, 76 | { url = "https://files.pythonhosted.org/packages/0d/c8/8174d0e5c10ccebdcb1b53cc959591c4c722a3ad92461a273e86b9f5a302/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", size = 145408 }, 77 | { url = "https://files.pythonhosted.org/packages/58/aa/8904b84bc8084ac19dc52feb4f5952c6df03ffb460a887b42615ee1382e8/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", size = 153399 }, 78 | { url = "https://files.pythonhosted.org/packages/c2/26/89ee1f0e264d201cb65cf054aca6038c03b1a0c6b4ae998070392a3ce605/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", size = 156815 }, 79 | { url = "https://files.pythonhosted.org/packages/fd/07/68e95b4b345bad3dbbd3a8681737b4338ff2c9df29856a6d6d23ac4c73cb/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", size = 154537 }, 80 | { url = "https://files.pythonhosted.org/packages/77/1a/5eefc0ce04affb98af07bc05f3bac9094513c0e23b0562d64af46a06aae4/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", size = 149565 }, 81 | { url = "https://files.pythonhosted.org/packages/37/a0/2410e5e6032a174c95e0806b1a6585eb21e12f445ebe239fac441995226a/charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", size = 98357 }, 82 | { url = "https://files.pythonhosted.org/packages/6c/4f/c02d5c493967af3eda9c771ad4d2bbc8df6f99ddbeb37ceea6e8716a32bc/charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", size = 105776 }, 83 | { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622 }, 84 | { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435 }, 85 | { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653 }, 86 | { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231 }, 87 | { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243 }, 88 | { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442 }, 89 | { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147 }, 90 | { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057 }, 91 | { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454 }, 92 | { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174 }, 93 | { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166 }, 94 | { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064 }, 95 | { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641 }, 96 | { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626 }, 97 | ] 98 | 99 | [[package]] 100 | name = "click" 101 | version = "8.2.1" 102 | source = { registry = "https://pypi.org/simple" } 103 | dependencies = [ 104 | { name = "colorama", marker = "sys_platform == 'win32'" }, 105 | ] 106 | sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342 } 107 | wheels = [ 108 | { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215 }, 109 | ] 110 | 111 | [[package]] 112 | name = "colorama" 113 | version = "0.4.6" 114 | source = { registry = "https://pypi.org/simple" } 115 | sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } 116 | wheels = [ 117 | { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, 118 | ] 119 | 120 | [[package]] 121 | name = "distro" 122 | version = "1.9.0" 123 | source = { registry = "https://pypi.org/simple" } 124 | sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722 } 125 | wheels = [ 126 | { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277 }, 127 | ] 128 | 129 | [[package]] 130 | name = "exceptiongroup" 131 | version = "1.3.0" 132 | source = { registry = "https://pypi.org/simple" } 133 | dependencies = [ 134 | { name = "typing-extensions", marker = "python_full_version < '3.13'" }, 135 | ] 136 | sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749 } 137 | wheels = [ 138 | { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674 }, 139 | ] 140 | 141 | [[package]] 142 | name = "fastapi" 143 | version = "0.116.1" 144 | source = { registry = "https://pypi.org/simple" } 145 | dependencies = [ 146 | { name = "pydantic" }, 147 | { name = "starlette" }, 148 | { name = "typing-extensions" }, 149 | ] 150 | sdist = { url = "https://files.pythonhosted.org/packages/78/d7/6c8b3bfe33eeffa208183ec037fee0cce9f7f024089ab1c5d12ef04bd27c/fastapi-0.116.1.tar.gz", hash = "sha256:ed52cbf946abfd70c5a0dccb24673f0670deeb517a88b3544d03c2a6bf283143", size = 296485 } 151 | wheels = [ 152 | { url = "https://files.pythonhosted.org/packages/e5/47/d63c60f59a59467fda0f93f46335c9d18526d7071f025cb5b89d5353ea42/fastapi-0.116.1-py3-none-any.whl", hash = "sha256:c46ac7c312df840f0c9e220f7964bada936781bc4e2e6eb71f1c4d7553786565", size = 95631 }, 153 | ] 154 | 155 | [[package]] 156 | name = "gpt-5-demo" 157 | version = "0.1.0" 158 | source = { virtual = "." } 159 | dependencies = [ 160 | { name = "fastapi" }, 161 | { name = "openai" }, 162 | { name = "pydantic" }, 163 | { name = "python-dotenv" }, 164 | { name = "requests" }, 165 | { name = "uvicorn" }, 166 | ] 167 | 168 | [package.metadata] 169 | requires-dist = [ 170 | { name = "fastapi", specifier = ">=0.116.1" }, 171 | { name = "openai", specifier = ">=1.99.2" }, 172 | { name = "pydantic", specifier = ">=2.11.7" }, 173 | { name = "python-dotenv", specifier = ">=1.1.1" }, 174 | { name = "requests", specifier = ">=2.32.4" }, 175 | { name = "uvicorn", specifier = ">=0.35.0" }, 176 | ] 177 | 178 | [[package]] 179 | name = "h11" 180 | version = "0.16.0" 181 | source = { registry = "https://pypi.org/simple" } 182 | sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250 } 183 | wheels = [ 184 | { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515 }, 185 | ] 186 | 187 | [[package]] 188 | name = "httpcore" 189 | version = "1.0.9" 190 | source = { registry = "https://pypi.org/simple" } 191 | dependencies = [ 192 | { name = "certifi" }, 193 | { name = "h11" }, 194 | ] 195 | sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484 } 196 | wheels = [ 197 | { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784 }, 198 | ] 199 | 200 | [[package]] 201 | name = "httpx" 202 | version = "0.28.1" 203 | source = { registry = "https://pypi.org/simple" } 204 | dependencies = [ 205 | { name = "anyio" }, 206 | { name = "certifi" }, 207 | { name = "httpcore" }, 208 | { name = "idna" }, 209 | ] 210 | sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 } 211 | wheels = [ 212 | { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 }, 213 | ] 214 | 215 | [[package]] 216 | name = "idna" 217 | version = "3.10" 218 | source = { registry = "https://pypi.org/simple" } 219 | sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } 220 | wheels = [ 221 | { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, 222 | ] 223 | 224 | [[package]] 225 | name = "jiter" 226 | version = "0.10.0" 227 | source = { registry = "https://pypi.org/simple" } 228 | sdist = { url = "https://files.pythonhosted.org/packages/ee/9d/ae7ddb4b8ab3fb1b51faf4deb36cb48a4fbbd7cb36bad6a5fca4741306f7/jiter-0.10.0.tar.gz", hash = "sha256:07a7142c38aacc85194391108dc91b5b57093c978a9932bd86a36862759d9500", size = 162759 } 229 | wheels = [ 230 | { url = "https://files.pythonhosted.org/packages/be/7e/4011b5c77bec97cb2b572f566220364e3e21b51c48c5bd9c4a9c26b41b67/jiter-0.10.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:cd2fb72b02478f06a900a5782de2ef47e0396b3e1f7d5aba30daeb1fce66f303", size = 317215 }, 231 | { url = "https://files.pythonhosted.org/packages/8a/4f/144c1b57c39692efc7ea7d8e247acf28e47d0912800b34d0ad815f6b2824/jiter-0.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:32bb468e3af278f095d3fa5b90314728a6916d89ba3d0ffb726dd9bf7367285e", size = 322814 }, 232 | { url = "https://files.pythonhosted.org/packages/63/1f/db977336d332a9406c0b1f0b82be6f71f72526a806cbb2281baf201d38e3/jiter-0.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa8b3e0068c26ddedc7abc6fac37da2d0af16b921e288a5a613f4b86f050354f", size = 345237 }, 233 | { url = "https://files.pythonhosted.org/packages/d7/1c/aa30a4a775e8a672ad7f21532bdbfb269f0706b39c6ff14e1f86bdd9e5ff/jiter-0.10.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:286299b74cc49e25cd42eea19b72aa82c515d2f2ee12d11392c56d8701f52224", size = 370999 }, 234 | { url = "https://files.pythonhosted.org/packages/35/df/f8257abc4207830cb18880781b5f5b716bad5b2a22fb4330cfd357407c5b/jiter-0.10.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6ed5649ceeaeffc28d87fb012d25a4cd356dcd53eff5acff1f0466b831dda2a7", size = 491109 }, 235 | { url = "https://files.pythonhosted.org/packages/06/76/9e1516fd7b4278aa13a2cc7f159e56befbea9aa65c71586305e7afa8b0b3/jiter-0.10.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2ab0051160cb758a70716448908ef14ad476c3774bd03ddce075f3c1f90a3d6", size = 388608 }, 236 | { url = "https://files.pythonhosted.org/packages/6d/64/67750672b4354ca20ca18d3d1ccf2c62a072e8a2d452ac3cf8ced73571ef/jiter-0.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03997d2f37f6b67d2f5c475da4412be584e1cec273c1cfc03d642c46db43f8cf", size = 352454 }, 237 | { url = "https://files.pythonhosted.org/packages/96/4d/5c4e36d48f169a54b53a305114be3efa2bbffd33b648cd1478a688f639c1/jiter-0.10.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c404a99352d839fed80d6afd6c1d66071f3bacaaa5c4268983fc10f769112e90", size = 391833 }, 238 | { url = "https://files.pythonhosted.org/packages/0b/de/ce4a6166a78810bd83763d2fa13f85f73cbd3743a325469a4a9289af6dae/jiter-0.10.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:66e989410b6666d3ddb27a74c7e50d0829704ede652fd4c858e91f8d64b403d0", size = 523646 }, 239 | { url = "https://files.pythonhosted.org/packages/a2/a6/3bc9acce53466972964cf4ad85efecb94f9244539ab6da1107f7aed82934/jiter-0.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b532d3af9ef4f6374609a3bcb5e05a1951d3bf6190dc6b176fdb277c9bbf15ee", size = 514735 }, 240 | { url = "https://files.pythonhosted.org/packages/b4/d8/243c2ab8426a2a4dea85ba2a2ba43df379ccece2145320dfd4799b9633c5/jiter-0.10.0-cp310-cp310-win32.whl", hash = "sha256:da9be20b333970e28b72edc4dff63d4fec3398e05770fb3205f7fb460eb48dd4", size = 210747 }, 241 | { url = "https://files.pythonhosted.org/packages/37/7a/8021bd615ef7788b98fc76ff533eaac846322c170e93cbffa01979197a45/jiter-0.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:f59e533afed0c5b0ac3eba20d2548c4a550336d8282ee69eb07b37ea526ee4e5", size = 207484 }, 242 | { url = "https://files.pythonhosted.org/packages/1b/dd/6cefc6bd68b1c3c979cecfa7029ab582b57690a31cd2f346c4d0ce7951b6/jiter-0.10.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:3bebe0c558e19902c96e99217e0b8e8b17d570906e72ed8a87170bc290b1e978", size = 317473 }, 243 | { url = "https://files.pythonhosted.org/packages/be/cf/fc33f5159ce132be1d8dd57251a1ec7a631c7df4bd11e1cd198308c6ae32/jiter-0.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:558cc7e44fd8e507a236bee6a02fa17199ba752874400a0ca6cd6e2196cdb7dc", size = 321971 }, 244 | { url = "https://files.pythonhosted.org/packages/68/a4/da3f150cf1d51f6c472616fb7650429c7ce053e0c962b41b68557fdf6379/jiter-0.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d613e4b379a07d7c8453c5712ce7014e86c6ac93d990a0b8e7377e18505e98d", size = 345574 }, 245 | { url = "https://files.pythonhosted.org/packages/84/34/6e8d412e60ff06b186040e77da5f83bc158e9735759fcae65b37d681f28b/jiter-0.10.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f62cf8ba0618eda841b9bf61797f21c5ebd15a7a1e19daab76e4e4b498d515b2", size = 371028 }, 246 | { url = "https://files.pythonhosted.org/packages/fb/d9/9ee86173aae4576c35a2f50ae930d2ccb4c4c236f6cb9353267aa1d626b7/jiter-0.10.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:919d139cdfa8ae8945112398511cb7fca58a77382617d279556b344867a37e61", size = 491083 }, 247 | { url = "https://files.pythonhosted.org/packages/d9/2c/f955de55e74771493ac9e188b0f731524c6a995dffdcb8c255b89c6fb74b/jiter-0.10.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13ddbc6ae311175a3b03bd8994881bc4635c923754932918e18da841632349db", size = 388821 }, 248 | { url = "https://files.pythonhosted.org/packages/81/5a/0e73541b6edd3f4aada586c24e50626c7815c561a7ba337d6a7eb0a915b4/jiter-0.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c440ea003ad10927a30521a9062ce10b5479592e8a70da27f21eeb457b4a9c5", size = 352174 }, 249 | { url = "https://files.pythonhosted.org/packages/1c/c0/61eeec33b8c75b31cae42be14d44f9e6fe3ac15a4e58010256ac3abf3638/jiter-0.10.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dc347c87944983481e138dea467c0551080c86b9d21de6ea9306efb12ca8f606", size = 391869 }, 250 | { url = "https://files.pythonhosted.org/packages/41/22/5beb5ee4ad4ef7d86f5ea5b4509f680a20706c4a7659e74344777efb7739/jiter-0.10.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:13252b58c1f4d8c5b63ab103c03d909e8e1e7842d302473f482915d95fefd605", size = 523741 }, 251 | { url = "https://files.pythonhosted.org/packages/ea/10/768e8818538e5817c637b0df52e54366ec4cebc3346108a4457ea7a98f32/jiter-0.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7d1bbf3c465de4a24ab12fb7766a0003f6f9bce48b8b6a886158c4d569452dc5", size = 514527 }, 252 | { url = "https://files.pythonhosted.org/packages/73/6d/29b7c2dc76ce93cbedabfd842fc9096d01a0550c52692dfc33d3cc889815/jiter-0.10.0-cp311-cp311-win32.whl", hash = "sha256:db16e4848b7e826edca4ccdd5b145939758dadf0dc06e7007ad0e9cfb5928ae7", size = 210765 }, 253 | { url = "https://files.pythonhosted.org/packages/c2/c9/d394706deb4c660137caf13e33d05a031d734eb99c051142e039d8ceb794/jiter-0.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c9c1d5f10e18909e993f9641f12fe1c77b3e9b533ee94ffa970acc14ded3812", size = 209234 }, 254 | { url = "https://files.pythonhosted.org/packages/6d/b5/348b3313c58f5fbfb2194eb4d07e46a35748ba6e5b3b3046143f3040bafa/jiter-0.10.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1e274728e4a5345a6dde2d343c8da018b9d4bd4350f5a472fa91f66fda44911b", size = 312262 }, 255 | { url = "https://files.pythonhosted.org/packages/9c/4a/6a2397096162b21645162825f058d1709a02965606e537e3304b02742e9b/jiter-0.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7202ae396446c988cb2a5feb33a543ab2165b786ac97f53b59aafb803fef0744", size = 320124 }, 256 | { url = "https://files.pythonhosted.org/packages/2a/85/1ce02cade7516b726dd88f59a4ee46914bf79d1676d1228ef2002ed2f1c9/jiter-0.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23ba7722d6748b6920ed02a8f1726fb4b33e0fd2f3f621816a8b486c66410ab2", size = 345330 }, 257 | { url = "https://files.pythonhosted.org/packages/75/d0/bb6b4f209a77190ce10ea8d7e50bf3725fc16d3372d0a9f11985a2b23eff/jiter-0.10.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:371eab43c0a288537d30e1f0b193bc4eca90439fc08a022dd83e5e07500ed026", size = 369670 }, 258 | { url = "https://files.pythonhosted.org/packages/a0/f5/a61787da9b8847a601e6827fbc42ecb12be2c925ced3252c8ffcb56afcaf/jiter-0.10.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c675736059020365cebc845a820214765162728b51ab1e03a1b7b3abb70f74c", size = 489057 }, 259 | { url = "https://files.pythonhosted.org/packages/12/e4/6f906272810a7b21406c760a53aadbe52e99ee070fc5c0cb191e316de30b/jiter-0.10.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c5867d40ab716e4684858e4887489685968a47e3ba222e44cde6e4a2154f959", size = 389372 }, 260 | { url = "https://files.pythonhosted.org/packages/e2/ba/77013b0b8ba904bf3762f11e0129b8928bff7f978a81838dfcc958ad5728/jiter-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395bb9a26111b60141757d874d27fdea01b17e8fac958b91c20128ba8f4acc8a", size = 352038 }, 261 | { url = "https://files.pythonhosted.org/packages/67/27/c62568e3ccb03368dbcc44a1ef3a423cb86778a4389e995125d3d1aaa0a4/jiter-0.10.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6842184aed5cdb07e0c7e20e5bdcfafe33515ee1741a6835353bb45fe5d1bd95", size = 391538 }, 262 | { url = "https://files.pythonhosted.org/packages/c0/72/0d6b7e31fc17a8fdce76164884edef0698ba556b8eb0af9546ae1a06b91d/jiter-0.10.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:62755d1bcea9876770d4df713d82606c8c1a3dca88ff39046b85a048566d56ea", size = 523557 }, 263 | { url = "https://files.pythonhosted.org/packages/2f/09/bc1661fbbcbeb6244bd2904ff3a06f340aa77a2b94e5a7373fd165960ea3/jiter-0.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:533efbce2cacec78d5ba73a41756beff8431dfa1694b6346ce7af3a12c42202b", size = 514202 }, 264 | { url = "https://files.pythonhosted.org/packages/1b/84/5a5d5400e9d4d54b8004c9673bbe4403928a00d28529ff35b19e9d176b19/jiter-0.10.0-cp312-cp312-win32.whl", hash = "sha256:8be921f0cadd245e981b964dfbcd6fd4bc4e254cdc069490416dd7a2632ecc01", size = 211781 }, 265 | { url = "https://files.pythonhosted.org/packages/9b/52/7ec47455e26f2d6e5f2ea4951a0652c06e5b995c291f723973ae9e724a65/jiter-0.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:a7c7d785ae9dda68c2678532a5a1581347e9c15362ae9f6e68f3fdbfb64f2e49", size = 206176 }, 266 | { url = "https://files.pythonhosted.org/packages/2e/b0/279597e7a270e8d22623fea6c5d4eeac328e7d95c236ed51a2b884c54f70/jiter-0.10.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e0588107ec8e11b6f5ef0e0d656fb2803ac6cf94a96b2b9fc675c0e3ab5e8644", size = 311617 }, 267 | { url = "https://files.pythonhosted.org/packages/91/e3/0916334936f356d605f54cc164af4060e3e7094364add445a3bc79335d46/jiter-0.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cafc4628b616dc32530c20ee53d71589816cf385dd9449633e910d596b1f5c8a", size = 318947 }, 268 | { url = "https://files.pythonhosted.org/packages/6a/8e/fd94e8c02d0e94539b7d669a7ebbd2776e51f329bb2c84d4385e8063a2ad/jiter-0.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:520ef6d981172693786a49ff5b09eda72a42e539f14788124a07530f785c3ad6", size = 344618 }, 269 | { url = "https://files.pythonhosted.org/packages/6f/b0/f9f0a2ec42c6e9c2e61c327824687f1e2415b767e1089c1d9135f43816bd/jiter-0.10.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:554dedfd05937f8fc45d17ebdf298fe7e0c77458232bcb73d9fbbf4c6455f5b3", size = 368829 }, 270 | { url = "https://files.pythonhosted.org/packages/e8/57/5bbcd5331910595ad53b9fd0c610392ac68692176f05ae48d6ce5c852967/jiter-0.10.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5bc299da7789deacf95f64052d97f75c16d4fc8c4c214a22bf8d859a4288a1c2", size = 491034 }, 271 | { url = "https://files.pythonhosted.org/packages/9b/be/c393df00e6e6e9e623a73551774449f2f23b6ec6a502a3297aeeece2c65a/jiter-0.10.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5161e201172de298a8a1baad95eb85db4fb90e902353b1f6a41d64ea64644e25", size = 388529 }, 272 | { url = "https://files.pythonhosted.org/packages/42/3e/df2235c54d365434c7f150b986a6e35f41ebdc2f95acea3036d99613025d/jiter-0.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e2227db6ba93cb3e2bf67c87e594adde0609f146344e8207e8730364db27041", size = 350671 }, 273 | { url = "https://files.pythonhosted.org/packages/c6/77/71b0b24cbcc28f55ab4dbfe029f9a5b73aeadaba677843fc6dc9ed2b1d0a/jiter-0.10.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:15acb267ea5e2c64515574b06a8bf393fbfee6a50eb1673614aa45f4613c0cca", size = 390864 }, 274 | { url = "https://files.pythonhosted.org/packages/6a/d3/ef774b6969b9b6178e1d1e7a89a3bd37d241f3d3ec5f8deb37bbd203714a/jiter-0.10.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:901b92f2e2947dc6dfcb52fd624453862e16665ea909a08398dde19c0731b7f4", size = 522989 }, 275 | { url = "https://files.pythonhosted.org/packages/0c/41/9becdb1d8dd5d854142f45a9d71949ed7e87a8e312b0bede2de849388cb9/jiter-0.10.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d0cb9a125d5a3ec971a094a845eadde2db0de85b33c9f13eb94a0c63d463879e", size = 513495 }, 276 | { url = "https://files.pythonhosted.org/packages/9c/36/3468e5a18238bdedae7c4d19461265b5e9b8e288d3f86cd89d00cbb48686/jiter-0.10.0-cp313-cp313-win32.whl", hash = "sha256:48a403277ad1ee208fb930bdf91745e4d2d6e47253eedc96e2559d1e6527006d", size = 211289 }, 277 | { url = "https://files.pythonhosted.org/packages/7e/07/1c96b623128bcb913706e294adb5f768fb7baf8db5e1338ce7b4ee8c78ef/jiter-0.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:75f9eb72ecb640619c29bf714e78c9c46c9c4eaafd644bf78577ede459f330d4", size = 205074 }, 278 | { url = "https://files.pythonhosted.org/packages/54/46/caa2c1342655f57d8f0f2519774c6d67132205909c65e9aa8255e1d7b4f4/jiter-0.10.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:28ed2a4c05a1f32ef0e1d24c2611330219fed727dae01789f4a335617634b1ca", size = 318225 }, 279 | { url = "https://files.pythonhosted.org/packages/43/84/c7d44c75767e18946219ba2d703a5a32ab37b0bc21886a97bc6062e4da42/jiter-0.10.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a4c418b1ec86a195f1ca69da8b23e8926c752b685af665ce30777233dfe070", size = 350235 }, 280 | { url = "https://files.pythonhosted.org/packages/01/16/f5a0135ccd968b480daad0e6ab34b0c7c5ba3bc447e5088152696140dcb3/jiter-0.10.0-cp313-cp313t-win_amd64.whl", hash = "sha256:d7bfed2fe1fe0e4dda6ef682cee888ba444b21e7a6553e03252e4feb6cf0adca", size = 207278 }, 281 | { url = "https://files.pythonhosted.org/packages/1c/9b/1d646da42c3de6c2188fdaa15bce8ecb22b635904fc68be025e21249ba44/jiter-0.10.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:5e9251a5e83fab8d87799d3e1a46cb4b7f2919b895c6f4483629ed2446f66522", size = 310866 }, 282 | { url = "https://files.pythonhosted.org/packages/ad/0e/26538b158e8a7c7987e94e7aeb2999e2e82b1f9d2e1f6e9874ddf71ebda0/jiter-0.10.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:023aa0204126fe5b87ccbcd75c8a0d0261b9abdbbf46d55e7ae9f8e22424eeb8", size = 318772 }, 283 | { url = "https://files.pythonhosted.org/packages/7b/fb/d302893151caa1c2636d6574d213e4b34e31fd077af6050a9c5cbb42f6fb/jiter-0.10.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c189c4f1779c05f75fc17c0c1267594ed918996a231593a21a5ca5438445216", size = 344534 }, 284 | { url = "https://files.pythonhosted.org/packages/01/d8/5780b64a149d74e347c5128d82176eb1e3241b1391ac07935693466d6219/jiter-0.10.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:15720084d90d1098ca0229352607cd68256c76991f6b374af96f36920eae13c4", size = 369087 }, 285 | { url = "https://files.pythonhosted.org/packages/e8/5b/f235a1437445160e777544f3ade57544daf96ba7e96c1a5b24a6f7ac7004/jiter-0.10.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4f2fb68e5f1cfee30e2b2a09549a00683e0fde4c6a2ab88c94072fc33cb7426", size = 490694 }, 286 | { url = "https://files.pythonhosted.org/packages/85/a9/9c3d4617caa2ff89cf61b41e83820c27ebb3f7b5fae8a72901e8cd6ff9be/jiter-0.10.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce541693355fc6da424c08b7edf39a2895f58d6ea17d92cc2b168d20907dee12", size = 388992 }, 287 | { url = "https://files.pythonhosted.org/packages/68/b1/344fd14049ba5c94526540af7eb661871f9c54d5f5601ff41a959b9a0bbd/jiter-0.10.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31c50c40272e189d50006ad5c73883caabb73d4e9748a688b216e85a9a9ca3b9", size = 351723 }, 288 | { url = "https://files.pythonhosted.org/packages/41/89/4c0e345041186f82a31aee7b9d4219a910df672b9fef26f129f0cda07a29/jiter-0.10.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fa3402a2ff9815960e0372a47b75c76979d74402448509ccd49a275fa983ef8a", size = 392215 }, 289 | { url = "https://files.pythonhosted.org/packages/55/58/ee607863e18d3f895feb802154a2177d7e823a7103f000df182e0f718b38/jiter-0.10.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:1956f934dca32d7bb647ea21d06d93ca40868b505c228556d3373cbd255ce853", size = 522762 }, 290 | { url = "https://files.pythonhosted.org/packages/15/d0/9123fb41825490d16929e73c212de9a42913d68324a8ce3c8476cae7ac9d/jiter-0.10.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:fcedb049bdfc555e261d6f65a6abe1d5ad68825b7202ccb9692636c70fcced86", size = 513427 }, 291 | { url = "https://files.pythonhosted.org/packages/d8/b3/2bd02071c5a2430d0b70403a34411fc519c2f227da7b03da9ba6a956f931/jiter-0.10.0-cp314-cp314-win32.whl", hash = "sha256:ac509f7eccca54b2a29daeb516fb95b6f0bd0d0d8084efaf8ed5dfc7b9f0b357", size = 210127 }, 292 | { url = "https://files.pythonhosted.org/packages/03/0c/5fe86614ea050c3ecd728ab4035534387cd41e7c1855ef6c031f1ca93e3f/jiter-0.10.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5ed975b83a2b8639356151cef5c0d597c68376fc4922b45d0eb384ac058cfa00", size = 318527 }, 293 | { url = "https://files.pythonhosted.org/packages/b3/4a/4175a563579e884192ba6e81725fc0448b042024419be8d83aa8a80a3f44/jiter-0.10.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aa96f2abba33dc77f79b4cf791840230375f9534e5fac927ccceb58c5e604a5", size = 354213 }, 294 | ] 295 | 296 | [[package]] 297 | name = "openai" 298 | version = "1.99.2" 299 | source = { registry = "https://pypi.org/simple" } 300 | dependencies = [ 301 | { name = "anyio" }, 302 | { name = "distro" }, 303 | { name = "httpx" }, 304 | { name = "jiter" }, 305 | { name = "pydantic" }, 306 | { name = "sniffio" }, 307 | { name = "tqdm" }, 308 | { name = "typing-extensions" }, 309 | ] 310 | sdist = { url = "https://files.pythonhosted.org/packages/de/2c/8cd1684364551237a5e6db24ce25c25ff54efcf1805b39110ec713dc2972/openai-1.99.2.tar.gz", hash = "sha256:118075b48109aa237636607b1346cf03b37cb9d74b0414cb11095850a0a22c96", size = 504752 } 311 | wheels = [ 312 | { url = "https://files.pythonhosted.org/packages/b7/06/f3338c1b8685dc1634fa5174dc5ba2d3eecc7887c9fc539bb5da6f75ebb3/openai-1.99.2-py3-none-any.whl", hash = "sha256:110d85b8ed400e1d7b02f8db8e245bd757bcde347cb6923155f42cd66a10aa0b", size = 785594 }, 313 | ] 314 | 315 | [[package]] 316 | name = "pydantic" 317 | version = "2.11.7" 318 | source = { registry = "https://pypi.org/simple" } 319 | dependencies = [ 320 | { name = "annotated-types" }, 321 | { name = "pydantic-core" }, 322 | { name = "typing-extensions" }, 323 | { name = "typing-inspection" }, 324 | ] 325 | sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350 } 326 | wheels = [ 327 | { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782 }, 328 | ] 329 | 330 | [[package]] 331 | name = "pydantic-core" 332 | version = "2.33.2" 333 | source = { registry = "https://pypi.org/simple" } 334 | dependencies = [ 335 | { name = "typing-extensions" }, 336 | ] 337 | sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195 } 338 | wheels = [ 339 | { url = "https://files.pythonhosted.org/packages/e5/92/b31726561b5dae176c2d2c2dc43a9c5bfba5d32f96f8b4c0a600dd492447/pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8", size = 2028817 }, 340 | { url = "https://files.pythonhosted.org/packages/a3/44/3f0b95fafdaca04a483c4e685fe437c6891001bf3ce8b2fded82b9ea3aa1/pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d", size = 1861357 }, 341 | { url = "https://files.pythonhosted.org/packages/30/97/e8f13b55766234caae05372826e8e4b3b96e7b248be3157f53237682e43c/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d", size = 1898011 }, 342 | { url = "https://files.pythonhosted.org/packages/9b/a3/99c48cf7bafc991cc3ee66fd544c0aae8dc907b752f1dad2d79b1b5a471f/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572", size = 1982730 }, 343 | { url = "https://files.pythonhosted.org/packages/de/8e/a5b882ec4307010a840fb8b58bd9bf65d1840c92eae7534c7441709bf54b/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02", size = 2136178 }, 344 | { url = "https://files.pythonhosted.org/packages/e4/bb/71e35fc3ed05af6834e890edb75968e2802fe98778971ab5cba20a162315/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b", size = 2736462 }, 345 | { url = "https://files.pythonhosted.org/packages/31/0d/c8f7593e6bc7066289bbc366f2235701dcbebcd1ff0ef8e64f6f239fb47d/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2", size = 2005652 }, 346 | { url = "https://files.pythonhosted.org/packages/d2/7a/996d8bd75f3eda405e3dd219ff5ff0a283cd8e34add39d8ef9157e722867/pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a", size = 2113306 }, 347 | { url = "https://files.pythonhosted.org/packages/ff/84/daf2a6fb2db40ffda6578a7e8c5a6e9c8affb251a05c233ae37098118788/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac", size = 2073720 }, 348 | { url = "https://files.pythonhosted.org/packages/77/fb/2258da019f4825128445ae79456a5499c032b55849dbd5bed78c95ccf163/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a", size = 2244915 }, 349 | { url = "https://files.pythonhosted.org/packages/d8/7a/925ff73756031289468326e355b6fa8316960d0d65f8b5d6b3a3e7866de7/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b", size = 2241884 }, 350 | { url = "https://files.pythonhosted.org/packages/0b/b0/249ee6d2646f1cdadcb813805fe76265745c4010cf20a8eba7b0e639d9b2/pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22", size = 1910496 }, 351 | { url = "https://files.pythonhosted.org/packages/66/ff/172ba8f12a42d4b552917aa65d1f2328990d3ccfc01d5b7c943ec084299f/pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640", size = 1955019 }, 352 | { url = "https://files.pythonhosted.org/packages/3f/8d/71db63483d518cbbf290261a1fc2839d17ff89fce7089e08cad07ccfce67/pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7", size = 2028584 }, 353 | { url = "https://files.pythonhosted.org/packages/24/2f/3cfa7244ae292dd850989f328722d2aef313f74ffc471184dc509e1e4e5a/pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246", size = 1855071 }, 354 | { url = "https://files.pythonhosted.org/packages/b3/d3/4ae42d33f5e3f50dd467761304be2fa0a9417fbf09735bc2cce003480f2a/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f", size = 1897823 }, 355 | { url = "https://files.pythonhosted.org/packages/f4/f3/aa5976e8352b7695ff808599794b1fba2a9ae2ee954a3426855935799488/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc", size = 1983792 }, 356 | { url = "https://files.pythonhosted.org/packages/d5/7a/cda9b5a23c552037717f2b2a5257e9b2bfe45e687386df9591eff7b46d28/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de", size = 2136338 }, 357 | { url = "https://files.pythonhosted.org/packages/2b/9f/b8f9ec8dd1417eb9da784e91e1667d58a2a4a7b7b34cf4af765ef663a7e5/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a", size = 2730998 }, 358 | { url = "https://files.pythonhosted.org/packages/47/bc/cd720e078576bdb8255d5032c5d63ee5c0bf4b7173dd955185a1d658c456/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef", size = 2003200 }, 359 | { url = "https://files.pythonhosted.org/packages/ca/22/3602b895ee2cd29d11a2b349372446ae9727c32e78a94b3d588a40fdf187/pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e", size = 2113890 }, 360 | { url = "https://files.pythonhosted.org/packages/ff/e6/e3c5908c03cf00d629eb38393a98fccc38ee0ce8ecce32f69fc7d7b558a7/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d", size = 2073359 }, 361 | { url = "https://files.pythonhosted.org/packages/12/e7/6a36a07c59ebefc8777d1ffdaf5ae71b06b21952582e4b07eba88a421c79/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30", size = 2245883 }, 362 | { url = "https://files.pythonhosted.org/packages/16/3f/59b3187aaa6cc0c1e6616e8045b284de2b6a87b027cce2ffcea073adf1d2/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf", size = 2241074 }, 363 | { url = "https://files.pythonhosted.org/packages/e0/ed/55532bb88f674d5d8f67ab121a2a13c385df382de2a1677f30ad385f7438/pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51", size = 1910538 }, 364 | { url = "https://files.pythonhosted.org/packages/fe/1b/25b7cccd4519c0b23c2dd636ad39d381abf113085ce4f7bec2b0dc755eb1/pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab", size = 1952909 }, 365 | { url = "https://files.pythonhosted.org/packages/49/a9/d809358e49126438055884c4366a1f6227f0f84f635a9014e2deb9b9de54/pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65", size = 1897786 }, 366 | { url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000 }, 367 | { url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996 }, 368 | { url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957 }, 369 | { url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199 }, 370 | { url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296 }, 371 | { url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109 }, 372 | { url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028 }, 373 | { url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044 }, 374 | { url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881 }, 375 | { url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034 }, 376 | { url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187 }, 377 | { url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628 }, 378 | { url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866 }, 379 | { url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894 }, 380 | { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688 }, 381 | { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808 }, 382 | { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580 }, 383 | { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859 }, 384 | { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810 }, 385 | { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498 }, 386 | { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611 }, 387 | { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924 }, 388 | { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196 }, 389 | { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389 }, 390 | { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223 }, 391 | { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473 }, 392 | { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269 }, 393 | { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921 }, 394 | { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162 }, 395 | { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560 }, 396 | { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777 }, 397 | { url = "https://files.pythonhosted.org/packages/30/68/373d55e58b7e83ce371691f6eaa7175e3a24b956c44628eb25d7da007917/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa", size = 2023982 }, 398 | { url = "https://files.pythonhosted.org/packages/a4/16/145f54ac08c96a63d8ed6442f9dec17b2773d19920b627b18d4f10a061ea/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29", size = 1858412 }, 399 | { url = "https://files.pythonhosted.org/packages/41/b1/c6dc6c3e2de4516c0bb2c46f6a373b91b5660312342a0cf5826e38ad82fa/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d", size = 1892749 }, 400 | { url = "https://files.pythonhosted.org/packages/12/73/8cd57e20afba760b21b742106f9dbdfa6697f1570b189c7457a1af4cd8a0/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e", size = 2067527 }, 401 | { url = "https://files.pythonhosted.org/packages/e3/d5/0bb5d988cc019b3cba4a78f2d4b3854427fc47ee8ec8e9eaabf787da239c/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c", size = 2108225 }, 402 | { url = "https://files.pythonhosted.org/packages/f1/c5/00c02d1571913d496aabf146106ad8239dc132485ee22efe08085084ff7c/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec", size = 2069490 }, 403 | { url = "https://files.pythonhosted.org/packages/22/a8/dccc38768274d3ed3a59b5d06f59ccb845778687652daa71df0cab4040d7/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052", size = 2237525 }, 404 | { url = "https://files.pythonhosted.org/packages/d4/e7/4f98c0b125dda7cf7ccd14ba936218397b44f50a56dd8c16a3091df116c3/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c", size = 2238446 }, 405 | { url = "https://files.pythonhosted.org/packages/ce/91/2ec36480fdb0b783cd9ef6795753c1dea13882f2e68e73bce76ae8c21e6a/pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808", size = 2066678 }, 406 | { url = "https://files.pythonhosted.org/packages/7b/27/d4ae6487d73948d6f20dddcd94be4ea43e74349b56eba82e9bdee2d7494c/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8", size = 2025200 }, 407 | { url = "https://files.pythonhosted.org/packages/f1/b8/b3cb95375f05d33801024079b9392a5ab45267a63400bf1866e7ce0f0de4/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593", size = 1859123 }, 408 | { url = "https://files.pythonhosted.org/packages/05/bc/0d0b5adeda59a261cd30a1235a445bf55c7e46ae44aea28f7bd6ed46e091/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612", size = 1892852 }, 409 | { url = "https://files.pythonhosted.org/packages/3e/11/d37bdebbda2e449cb3f519f6ce950927b56d62f0b84fd9cb9e372a26a3d5/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7", size = 2067484 }, 410 | { url = "https://files.pythonhosted.org/packages/8c/55/1f95f0a05ce72ecb02a8a8a1c3be0579bbc29b1d5ab68f1378b7bebc5057/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e", size = 2108896 }, 411 | { url = "https://files.pythonhosted.org/packages/53/89/2b2de6c81fa131f423246a9109d7b2a375e83968ad0800d6e57d0574629b/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8", size = 2069475 }, 412 | { url = "https://files.pythonhosted.org/packages/b8/e9/1f7efbe20d0b2b10f6718944b5d8ece9152390904f29a78e68d4e7961159/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf", size = 2239013 }, 413 | { url = "https://files.pythonhosted.org/packages/3c/b2/5309c905a93811524a49b4e031e9851a6b00ff0fb668794472ea7746b448/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb", size = 2238715 }, 414 | { url = "https://files.pythonhosted.org/packages/32/56/8a7ca5d2cd2cda1d245d34b1c9a942920a718082ae8e54e5f3e5a58b7add/pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1", size = 2066757 }, 415 | ] 416 | 417 | [[package]] 418 | name = "python-dotenv" 419 | version = "1.1.1" 420 | source = { registry = "https://pypi.org/simple" } 421 | sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978 } 422 | wheels = [ 423 | { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556 }, 424 | ] 425 | 426 | [[package]] 427 | name = "requests" 428 | version = "2.32.4" 429 | source = { registry = "https://pypi.org/simple" } 430 | dependencies = [ 431 | { name = "certifi" }, 432 | { name = "charset-normalizer" }, 433 | { name = "idna" }, 434 | { name = "urllib3" }, 435 | ] 436 | sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258 } 437 | wheels = [ 438 | { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847 }, 439 | ] 440 | 441 | [[package]] 442 | name = "sniffio" 443 | version = "1.3.1" 444 | source = { registry = "https://pypi.org/simple" } 445 | sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } 446 | wheels = [ 447 | { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, 448 | ] 449 | 450 | [[package]] 451 | name = "starlette" 452 | version = "0.47.2" 453 | source = { registry = "https://pypi.org/simple" } 454 | dependencies = [ 455 | { name = "anyio" }, 456 | { name = "typing-extensions", marker = "python_full_version < '3.13'" }, 457 | ] 458 | sdist = { url = "https://files.pythonhosted.org/packages/04/57/d062573f391d062710d4088fa1369428c38d51460ab6fedff920efef932e/starlette-0.47.2.tar.gz", hash = "sha256:6ae9aa5db235e4846decc1e7b79c4f346adf41e9777aebeb49dfd09bbd7023d8", size = 2583948 } 459 | wheels = [ 460 | { url = "https://files.pythonhosted.org/packages/f7/1f/b876b1f83aef204198a42dc101613fefccb32258e5428b5f9259677864b4/starlette-0.47.2-py3-none-any.whl", hash = "sha256:c5847e96134e5c5371ee9fac6fdf1a67336d5815e09eb2a01fdb57a351ef915b", size = 72984 }, 461 | ] 462 | 463 | [[package]] 464 | name = "tqdm" 465 | version = "4.67.1" 466 | source = { registry = "https://pypi.org/simple" } 467 | dependencies = [ 468 | { name = "colorama", marker = "sys_platform == 'win32'" }, 469 | ] 470 | sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737 } 471 | wheels = [ 472 | { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540 }, 473 | ] 474 | 475 | [[package]] 476 | name = "typing-extensions" 477 | version = "4.14.1" 478 | source = { registry = "https://pypi.org/simple" } 479 | sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673 } 480 | wheels = [ 481 | { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906 }, 482 | ] 483 | 484 | [[package]] 485 | name = "typing-inspection" 486 | version = "0.4.1" 487 | source = { registry = "https://pypi.org/simple" } 488 | dependencies = [ 489 | { name = "typing-extensions" }, 490 | ] 491 | sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726 } 492 | wheels = [ 493 | { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552 }, 494 | ] 495 | 496 | [[package]] 497 | name = "urllib3" 498 | version = "2.5.0" 499 | source = { registry = "https://pypi.org/simple" } 500 | sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185 } 501 | wheels = [ 502 | { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795 }, 503 | ] 504 | 505 | [[package]] 506 | name = "uvicorn" 507 | version = "0.35.0" 508 | source = { registry = "https://pypi.org/simple" } 509 | dependencies = [ 510 | { name = "click" }, 511 | { name = "h11" }, 512 | { name = "typing-extensions", marker = "python_full_version < '3.11'" }, 513 | ] 514 | sdist = { url = "https://files.pythonhosted.org/packages/5e/42/e0e305207bb88c6b8d3061399c6a961ffe5fbb7e2aa63c9234df7259e9cd/uvicorn-0.35.0.tar.gz", hash = "sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01", size = 78473 } 515 | wheels = [ 516 | { url = "https://files.pythonhosted.org/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406 }, 517 | ] 518 | --------------------------------------------------------------------------------