├── 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 |
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 |
329 |
AI is thinking...
330 |
331 |
332 |
333 | )}
334 |
335 |
336 | )}
337 |
338 |
339 | {/* Image Upload Area */}
340 | {imagePreview && (
341 |
342 |
343 |

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 |
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 |
--------------------------------------------------------------------------------