Spaces:
Running
Running
Updated Space
Browse files- app.py +115 -41
- index.html +47 -22
- requirements.txt +0 -1
app.py
CHANGED
@@ -1,6 +1,8 @@
|
|
1 |
-
from sse_starlette.sse import EventSourceResponse
|
2 |
from starlette.responses import JSONResponse, FileResponse
|
|
|
|
|
3 |
from fastapi import FastAPI, Request
|
|
|
4 |
import gradio as gr
|
5 |
import requests
|
6 |
import argparse
|
@@ -16,9 +18,10 @@ import os
|
|
16 |
# --- === CONFIG === ---
|
17 |
|
18 |
IMAGE_HANDLE = "url"# or "base64"
|
19 |
-
API_BASE = "
|
20 |
api_key = os.environ['OPENAI_API_KEY']
|
21 |
base_url = os.environ.get('OPENAI_BASE_URL', "https://api.openai.com/v1")
|
|
|
22 |
|
23 |
# --- === CONFIG === ---
|
24 |
|
@@ -29,11 +32,57 @@ if API_BASE == "env":
|
|
29 |
models = response.json()
|
30 |
if not ('data' in models):
|
31 |
base_url = "https://api.openai.com/v1"
|
|
|
32 |
except Exception as e:
|
33 |
print(f"Error testing API endpoint: {e}")
|
34 |
else:
|
35 |
base_url = "https://api.openai.com/v1"
|
36 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
37 |
async def streamChat(params):
|
38 |
async with aiohttp.ClientSession() as session:
|
39 |
async with session.post(f"{base_url}/chat/completions", headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}, json=params) as r:
|
@@ -55,20 +104,27 @@ def rnd(length=8):
|
|
55 |
letters = string.ascii_letters + string.digits
|
56 |
return ''.join(random.choice(letters) for i in range(length))
|
57 |
|
58 |
-
def getModels():
|
59 |
-
response = requests.get(f"{base_url}/models", headers={"Authorization": f"Bearer {api_key}",})
|
60 |
-
response.raise_for_status()
|
61 |
-
models = response.json()
|
62 |
-
return sorted([
|
63 |
-
model['id'] for model in models['data']
|
64 |
-
if 'gpt' in model['id'] and model['id'] not in {"gpt-3.5-turbo-instruct", "gpt-3.5-turbo-instruct-0914"}
|
65 |
-
])
|
66 |
-
|
67 |
def handleMultimodalData(model, role, data):
|
68 |
-
if
|
69 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
70 |
elif isinstance(data, str):
|
71 |
-
return {"role": role, "content": data
|
72 |
elif hasattr(data, 'files') and data.files and len(data.files) > 0 and model in {"gpt-4-1106-vision-preview", "gpt-4-vision-preview", "gpt-4-turbo", "gpt-4o", "gpt-4o-2024-05-13", "gpt-4o-mini", "gpt-4o-mini-2024-07-18"}:
|
73 |
result, handler, hasFoundFile = [], ["[System: This message contains files; the system will be splitting it.]"], False
|
74 |
for file in data.files:
|
@@ -81,8 +137,11 @@ def handleMultimodalData(model, role, data):
|
|
81 |
result.append({"type": "image_url", "image_url": {"url": file.url}})
|
82 |
if file.mime_type.startswith("text/") or file.mime_type.startswith("application/"):
|
83 |
hasFoundFile = True
|
84 |
-
|
85 |
-
|
|
|
|
|
|
|
86 |
if hasFoundFile:
|
87 |
handler.append(data.text)
|
88 |
return {"role": role, "content": [{"type": "text", "text": "\n\n".join(handler)}] + result}
|
@@ -93,21 +152,30 @@ def handleMultimodalData(model, role, data):
|
|
93 |
for file in data.files:
|
94 |
if file.mime_type.startswith("text/") or file.mime_type.startswith("application/"):
|
95 |
hasFoundFile = True
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
else:
|
102 |
-
return {"role": role, "content": data.text}
|
103 |
else:
|
104 |
if isinstance(data, tuple):
|
105 |
return {"role": role, "content": str(data)}
|
106 |
return {"role": role, "content": getattr(data, 'text', str(data))}
|
107 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
108 |
async def respond(
|
109 |
message,
|
110 |
-
history:
|
|
|
|
|
|
|
111 |
system_message,
|
112 |
model_name,
|
113 |
max_tokens,
|
@@ -116,16 +184,27 @@ async def respond(
|
|
116 |
seed,
|
117 |
random_seed
|
118 |
):
|
119 |
-
messages = [{"role": "system", "content":
|
120 |
|
121 |
for val in history:
|
122 |
-
if val[0]:
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
|
|
|
|
128 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
129 |
response = ""
|
130 |
|
131 |
completion = streamChat({
|
@@ -146,11 +225,11 @@ async def respond(
|
|
146 |
demo = gr.ChatInterface(
|
147 |
respond,
|
148 |
title="GPT-4O-mini",
|
149 |
-
description="A simple proxy to OpenAI!<br/>You can use this space as a proxy! click [here](/api/v1/docs) to view the documents
|
150 |
multimodal=True,
|
151 |
additional_inputs=[
|
152 |
gr.Textbox(value="You are a helpful assistant.", label="System message"),
|
153 |
-
gr.Dropdown(choices=
|
154 |
gr.Slider(minimum=1, maximum=4096, value=4096, step=1, label="Max new tokens"),
|
155 |
gr.Slider(minimum=0.1, maximum=2.0, value=0.7, step=0.05, label="Temperature"),
|
156 |
gr.Slider(
|
@@ -173,14 +252,7 @@ def html():
|
|
173 |
|
174 |
@app.get("/api/v1/models")
|
175 |
async def test_endpoint():
|
176 |
-
|
177 |
-
response.raise_for_status()
|
178 |
-
models = response.json()
|
179 |
-
models['data'] = sorted(
|
180 |
-
[model for model in models['data'] if 'gpt' in model['id'] and model['id'] not in {"gpt-3.5-turbo-instruct", "gpt-3.5-turbo-instruct-0914"}],
|
181 |
-
key=lambda x: x['id']
|
182 |
-
)
|
183 |
-
return JSONResponse(content=models)
|
184 |
|
185 |
@app.post("/api/v1/chat/completions")
|
186 |
async def chat_completion(request: Request):
|
@@ -189,6 +261,8 @@ async def chat_completion(request: Request):
|
|
189 |
|
190 |
if not body.get("messages") or not body.get("model"):
|
191 |
return JSONResponse(content={"error": { "code": "MISSING_VALUE", "message": "Both 'messages' and 'model' are required fields."}}, status_code=400)
|
|
|
|
|
192 |
|
193 |
params = {
|
194 |
key: value for key, value in {
|
|
|
|
|
1 |
from starlette.responses import JSONResponse, FileResponse
|
2 |
+
from gradio.data_classes import FileData, GradioModel
|
3 |
+
from sse_starlette.sse import EventSourceResponse
|
4 |
from fastapi import FastAPI, Request
|
5 |
+
from typing import (List, Tuple, Optional)
|
6 |
import gradio as gr
|
7 |
import requests
|
8 |
import argparse
|
|
|
18 |
# --- === CONFIG === ---
|
19 |
|
20 |
IMAGE_HANDLE = "url"# or "base64"
|
21 |
+
API_BASE = "env"# or "env"
|
22 |
api_key = os.environ['OPENAI_API_KEY']
|
23 |
base_url = os.environ.get('OPENAI_BASE_URL', "https://api.openai.com/v1")
|
24 |
+
def_models = '["gpt-3.5-turbo", "gpt-3.5-turbo-0125", "gpt-3.5-turbo-1106", "gpt-3.5-turbo-16k", "gpt-4", "gpt-4-0125-preview", "gpt-4-0314", "gpt-4-0613", "gpt-4-1106-preview", "gpt-4-1106-vision-preview", "gpt-4-32k-0314", "gpt-4-turbo", "gpt-4-turbo-2024-04-09", "gpt-4-turbo-preview", "gpt-4-vision-preview", "gpt-4o", "gpt-4o-2024-05-13", "gpt-4o-mini", "gpt-4o-mini-2024-07-18"]'
|
25 |
|
26 |
# --- === CONFIG === ---
|
27 |
|
|
|
32 |
models = response.json()
|
33 |
if not ('data' in models):
|
34 |
base_url = "https://api.openai.com/v1"
|
35 |
+
print("no models?")
|
36 |
except Exception as e:
|
37 |
print(f"Error testing API endpoint: {e}")
|
38 |
else:
|
39 |
base_url = "https://api.openai.com/v1"
|
40 |
|
41 |
+
try:
|
42 |
+
models = json.loads(os.environ.get('OPENAI_API_MODELS', def_models))
|
43 |
+
except json.JSONDecodeError:
|
44 |
+
models = json.loads(def_models)
|
45 |
+
|
46 |
+
models = sorted(models)
|
47 |
+
|
48 |
+
modelList = {
|
49 |
+
"object": "list",
|
50 |
+
"data": []
|
51 |
+
}
|
52 |
+
|
53 |
+
for i, v in enumerate(models):
|
54 |
+
modelList["data"].append({"id": v, "object": "model", "created": 0, "owned_by": "system"})
|
55 |
+
|
56 |
+
def encodeChat(messages):
|
57 |
+
output = []
|
58 |
+
for message in messages:
|
59 |
+
role = message['role']
|
60 |
+
name = f" [{message['name']}]" if 'name' in message else ''
|
61 |
+
content = message['content']
|
62 |
+
formatted_message = f"<|im_start|>{role}{name}\n{content}<|end_of_text|>"
|
63 |
+
output.append(formatted_message)
|
64 |
+
return "\n".join(output)
|
65 |
+
|
66 |
+
def moderate(messages):
|
67 |
+
try:
|
68 |
+
response = requests.post(
|
69 |
+
f"{base_url}/moderations",
|
70 |
+
headers={
|
71 |
+
"Content-Type": "application/json",
|
72 |
+
"Authorization": f"Bearer {api_key}"
|
73 |
+
},
|
74 |
+
json={"input": encodeChat(messages)}
|
75 |
+
)
|
76 |
+
response.raise_for_status()
|
77 |
+
moderation_result = response.json()
|
78 |
+
try:
|
79 |
+
return any(result["flagged"] for result in moderation_result["results"])
|
80 |
+
except KeyError:
|
81 |
+
return moderation_result["flagged"]
|
82 |
+
except requests.exceptions.RequestException as e:
|
83 |
+
print(f"Error during moderation request: {e}")
|
84 |
+
return False
|
85 |
+
|
86 |
async def streamChat(params):
|
87 |
async with aiohttp.ClientSession() as session:
|
88 |
async with session.post(f"{base_url}/chat/completions", headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}, json=params) as r:
|
|
|
104 |
letters = string.ascii_letters + string.digits
|
105 |
return ''.join(random.choice(letters) for i in range(length))
|
106 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
107 |
def handleMultimodalData(model, role, data):
|
108 |
+
if isinstance(data, tuple):
|
109 |
+
data = data[0]
|
110 |
+
|
111 |
+
if isinstance(data, FileData):
|
112 |
+
if data.mime_type.startswith("image/"):
|
113 |
+
if IMAGE_HANDLE == "base64":
|
114 |
+
with open(data.path, "rb") as image_file:
|
115 |
+
b64image = base64.b64encode(image_file.read()).decode('utf-8')
|
116 |
+
image_file.close()
|
117 |
+
return {"role": role, "content": [{"type": "image_url", "image_url": {"url": "data:" + data.mime_type + ";base64," + b64image}}]}
|
118 |
+
else:
|
119 |
+
return {"role": role, "content": [{"type": "image_url", "image_url": {"url": data.url}}]}
|
120 |
+
elif data.mime_type.startswith("text/") or data.mime_type.startswith("application/"):
|
121 |
+
try:
|
122 |
+
with open(data.path, "rb") as data_file:
|
123 |
+
return {"role": role, "content": "[System: This message contains file.]\n\n<|file_start|>" + data.orig_name + "\n" + data_file.read().decode('utf-8') + "<|file_end|>"}
|
124 |
+
except UnicodeDecodeError:
|
125 |
+
pass
|
126 |
elif isinstance(data, str):
|
127 |
+
return {"role": role, "content": data}
|
128 |
elif hasattr(data, 'files') and data.files and len(data.files) > 0 and model in {"gpt-4-1106-vision-preview", "gpt-4-vision-preview", "gpt-4-turbo", "gpt-4o", "gpt-4o-2024-05-13", "gpt-4o-mini", "gpt-4o-mini-2024-07-18"}:
|
129 |
result, handler, hasFoundFile = [], ["[System: This message contains files; the system will be splitting it.]"], False
|
130 |
for file in data.files:
|
|
|
137 |
result.append({"type": "image_url", "image_url": {"url": file.url}})
|
138 |
if file.mime_type.startswith("text/") or file.mime_type.startswith("application/"):
|
139 |
hasFoundFile = True
|
140 |
+
try:
|
141 |
+
with open(file.path, "rb") as data_file:
|
142 |
+
handler.append("<|file_start|>" + file.orig_name + "\n" + data_file.read().decode('utf-8') + "<|file_end|>")
|
143 |
+
except UnicodeDecodeError:
|
144 |
+
continue
|
145 |
if hasFoundFile:
|
146 |
handler.append(data.text)
|
147 |
return {"role": role, "content": [{"type": "text", "text": "\n\n".join(handler)}] + result}
|
|
|
152 |
for file in data.files:
|
153 |
if file.mime_type.startswith("text/") or file.mime_type.startswith("application/"):
|
154 |
hasFoundFile = True
|
155 |
+
try:
|
156 |
+
with open(file.path, "rb") as data_file:
|
157 |
+
return {"role": role, "content": "<|file_start|>" + file.orig_name + "\n" + data_file.read().decode('utf-8') + "<|file_end|>"}
|
158 |
+
except UnicodeDecodeError:
|
159 |
+
continue
|
|
|
|
|
160 |
else:
|
161 |
if isinstance(data, tuple):
|
162 |
return {"role": role, "content": str(data)}
|
163 |
return {"role": role, "content": getattr(data, 'text', str(data))}
|
164 |
|
165 |
+
class FileMessage(GradioModel):
|
166 |
+
file: FileData
|
167 |
+
alt_text: Optional[str] = None
|
168 |
+
|
169 |
+
class MultimodalMessage(GradioModel):
|
170 |
+
text: Optional[str] = None
|
171 |
+
files: Optional[List[FileMessage]]
|
172 |
+
|
173 |
async def respond(
|
174 |
message,
|
175 |
+
history: List[Tuple[
|
176 |
+
Optional[MultimodalMessage],
|
177 |
+
Optional[MultimodalMessage],
|
178 |
+
]],
|
179 |
system_message,
|
180 |
model_name,
|
181 |
max_tokens,
|
|
|
184 |
seed,
|
185 |
random_seed
|
186 |
):
|
187 |
+
messages = [{"role": "system", "content": system_message}]
|
188 |
|
189 |
for val in history:
|
190 |
+
if val[0] is not None:
|
191 |
+
user_message = handleMultimodalData(model_name, "user", val[0])
|
192 |
+
if user_message:
|
193 |
+
messages.append(user_message)
|
194 |
+
if val[1] is not None:
|
195 |
+
assistant_message = handleMultimodalData(model_name, "assistant", val[1])
|
196 |
+
if assistant_message:
|
197 |
+
messages.append(assistant_message)
|
198 |
|
199 |
+
user_message = handleMultimodalData(model_name, "user", message)
|
200 |
+
if user_message:
|
201 |
+
messages.append(user_message)
|
202 |
+
|
203 |
+
if moderate(messages):
|
204 |
+
response = "[MODERATION] I'm sorry, but I can't assist with that."
|
205 |
+
yield response
|
206 |
+
return
|
207 |
+
|
208 |
response = ""
|
209 |
|
210 |
completion = streamChat({
|
|
|
225 |
demo = gr.ChatInterface(
|
226 |
respond,
|
227 |
title="GPT-4O-mini",
|
228 |
+
description="A simple proxy to OpenAI!<br/>You can use this space as a proxy! click [here](/api/v1/docs) to view the documents. <strong>[last update: Fixed file problems.]</strong><br/>Also you can only submit images to vision/4o models but can submit txt/code/etc. files to all models.",
|
229 |
multimodal=True,
|
230 |
additional_inputs=[
|
231 |
gr.Textbox(value="You are a helpful assistant.", label="System message"),
|
232 |
+
gr.Dropdown(choices=models, value="gpt-4o-mini-2024-07-18", label="Model"),
|
233 |
gr.Slider(minimum=1, maximum=4096, value=4096, step=1, label="Max new tokens"),
|
234 |
gr.Slider(minimum=0.1, maximum=2.0, value=0.7, step=0.05, label="Temperature"),
|
235 |
gr.Slider(
|
|
|
252 |
|
253 |
@app.get("/api/v1/models")
|
254 |
async def test_endpoint():
|
255 |
+
return JSONResponse(content=modelList)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
256 |
|
257 |
@app.post("/api/v1/chat/completions")
|
258 |
async def chat_completion(request: Request):
|
|
|
261 |
|
262 |
if not body.get("messages") or not body.get("model"):
|
263 |
return JSONResponse(content={"error": { "code": "MISSING_VALUE", "message": "Both 'messages' and 'model' are required fields."}}, status_code=400)
|
264 |
+
if not body.get("model") in models:
|
265 |
+
return JSONResponse(content={"error": { "code": "INVALID_MODEL", "message": "The model name provided in the request does not exists in predefined list of models."}}, status_code=400)
|
266 |
|
267 |
params = {
|
268 |
key: value for key, value in {
|
index.html
CHANGED
@@ -36,9 +36,27 @@
|
|
36 |
<h1>Documentation of the API/updates</h1>
|
37 |
<hr/>
|
38 |
<h2>Updates</h2>
|
39 |
-
<
|
40 |
-
<
|
41 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
42 |
<hr/>
|
43 |
<h2>API Endpoints</h2>
|
44 |
<p>Here are some example codes to interact with the API:</p>
|
@@ -46,21 +64,21 @@
|
|
46 |
from openai import OpenAI
|
47 |
|
48 |
client = OpenAI(
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
)
|
55 |
|
56 |
completion = client.chat.completions.create( # or openai.ChatCompletion.create (idk)
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
)
|
65 |
|
66 |
print(completion.choices[0].message.content)</textarea>
|
@@ -72,13 +90,13 @@ openai.api_key = 'none'
|
|
72 |
openai.base_url = "https://quardo-gpt-4o-mini.hf.space/api/v1"
|
73 |
|
74 |
completion = openai.chat.completions.create( # or openai.ChatCompletion.create (idk)
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
)
|
83 |
|
84 |
print(completion.choices[0].message.content)</textarea>
|
@@ -87,6 +105,13 @@ print(completion.choices[0].message.content)</textarea>
|
|
87 |
# I am too lazy to look into openai's docs
|
88 |
# so please kindly take a look at github there is probably examples there.</textarea>
|
89 |
<hr/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
90 |
</div>
|
91 |
<script>
|
92 |
CodeMirror.fromTextArea(document.getElementById("example1"), { lineNumbers: true, mode: "python", theme: "monokai", readOnly: true });
|
|
|
36 |
<h1>Documentation of the API/updates</h1>
|
37 |
<hr/>
|
38 |
<h2>Updates</h2>
|
39 |
+
<div>
|
40 |
+
<div>
|
41 |
+
<strong> - 3. Update.</strong>
|
42 |
+
<p> * Managed to fix the file being non-visible to the AI model after one round.</p>
|
43 |
+
<p> * And fix the div error on this page.</p>
|
44 |
+
</div><hr/>
|
45 |
+
<div>
|
46 |
+
<strong> - 2. Update.</strong>
|
47 |
+
<p> * Added light moderation. [Might add more strict detection later]</p>
|
48 |
+
</div><hr/>
|
49 |
+
<div>
|
50 |
+
<strong> - 1. Update.</strong>
|
51 |
+
<p> * Fixed a bug where file encoding could trash the entire chat.</p>
|
52 |
+
<p> * Made models allowed set by the maintainer. [OPENAI_API_MODELS variable]</p>
|
53 |
+
</div><hr/>
|
54 |
+
<div>
|
55 |
+
<p><strong id="w" alt="yes i love fear"> * </strong>yippeeee</p>
|
56 |
+
<script>let q=25;setInterval(_=>{q+=.001;w.style.fontSize=`${q}px`},100);</script>
|
57 |
+
<label style="font-size: 2px;">The message above is edited by a script every 100ms. you can check it via view-source.</label>
|
58 |
+
</div>
|
59 |
+
</div>
|
60 |
<hr/>
|
61 |
<h2>API Endpoints</h2>
|
62 |
<p>Here are some example codes to interact with the API:</p>
|
|
|
64 |
from openai import OpenAI
|
65 |
|
66 |
client = OpenAI(
|
67 |
+
# Or use the `OPENAI_BASE_URL` env var
|
68 |
+
base_url="https://quardo-gpt-4o-mini.hf.space/api/v1",
|
69 |
+
|
70 |
+
# No key is needed cause this is a proxy
|
71 |
+
api_key="none"
|
72 |
)
|
73 |
|
74 |
completion = client.chat.completions.create( # or openai.ChatCompletion.create (idk)
|
75 |
+
model="gpt-4o-mini",
|
76 |
+
messages=[
|
77 |
+
{
|
78 |
+
"role": "user",
|
79 |
+
"content": "Say this is a test",
|
80 |
+
}
|
81 |
+
],
|
82 |
)
|
83 |
|
84 |
print(completion.choices[0].message.content)</textarea>
|
|
|
90 |
openai.base_url = "https://quardo-gpt-4o-mini.hf.space/api/v1"
|
91 |
|
92 |
completion = openai.chat.completions.create( # or openai.ChatCompletion.create (idk)
|
93 |
+
model="gpt-4o-mini",
|
94 |
+
messages=[
|
95 |
+
{
|
96 |
+
"role": "user",
|
97 |
+
"content": "Say this is a test",
|
98 |
+
},
|
99 |
+
],
|
100 |
)
|
101 |
|
102 |
print(completion.choices[0].message.content)</textarea>
|
|
|
105 |
# I am too lazy to look into openai's docs
|
106 |
# so please kindly take a look at github there is probably examples there.</textarea>
|
107 |
<hr/>
|
108 |
+
<div>
|
109 |
+
<h3>Actual endpoints:</h3>
|
110 |
+
<p><strong>1. [GET]</strong> <a href="https://quardo-gpt-4o-mini.hf.space/api/v1/models">/api/v1/models</a></p>
|
111 |
+
<label>Simply shows you the available models</label>
|
112 |
+
<p><strong>3. [POST]</strong> <a href="https://quardo-gpt-4o-mini.hf.space/api/v1/chat/completions">/api/v1/chat/completions</a></p>
|
113 |
+
<label>Generates a chat completion based on the provided messages and model</label>
|
114 |
+
</div>
|
115 |
</div>
|
116 |
<script>
|
117 |
CodeMirror.fromTextArea(document.getElementById("example1"), { lineNumbers: true, mode: "python", theme: "monokai", readOnly: true });
|
requirements.txt
CHANGED
@@ -1,6 +1,5 @@
|
|
1 |
uvicorn==0.27.1
|
2 |
starlette==0.37.2
|
3 |
sse-starlette==2.1.2
|
4 |
-
gradio==3.1.4
|
5 |
requests==2.31.0
|
6 |
aiohttp==3.8.5
|
|
|
1 |
uvicorn==0.27.1
|
2 |
starlette==0.37.2
|
3 |
sse-starlette==2.1.2
|
|
|
4 |
requests==2.31.0
|
5 |
aiohttp==3.8.5
|