rifatramadhani commited on
Commit
d067c3d
·
0 Parent(s):
Files changed (9) hide show
  1. .gitattributes +35 -0
  2. .gitignore +1 -0
  3. Dockerfile +38 -0
  4. README.md +10 -0
  5. app.py +255 -0
  6. index.html +101 -0
  7. models_config.py +60 -0
  8. requirements.txt +6 -0
  9. static/index.html +142 -0
.gitattributes ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+ __pycache__
Dockerfile ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.13
2
+
3
+ # Install basic tools: wget, curl, unzip
4
+ RUN apt-get update && \
5
+ apt-get install -y wget curl unzip && \
6
+ rm -rf /var/lib/apt/lists/*
7
+
8
+ RUN useradd -m -u 1000 user
9
+ USER user
10
+ ENV PATH="/home/user/.local/bin:$PATH"
11
+
12
+ WORKDIR /app
13
+
14
+ COPY --chown=user ./requirements.txt requirements.txt
15
+
16
+ RUN mkdir -p /home/user/.cache/pip
17
+
18
+ RUN chown user:user /home/user/.cache/pip
19
+ RUN --mount=type=cache,target=/home/user/.cache/pip pip install --upgrade -r requirements.txt
20
+
21
+ COPY --chown=user . /app
22
+
23
+
24
+ ENV APP_PORT=7860
25
+ ENV APP_HOST="0.0.0.0"
26
+ ENV ENVIRONMENT="production"
27
+ ENV TOKENIZERS_PARALLELISM=false
28
+
29
+ ENV DEFAULT_MODEL="text-embedding-3-large"
30
+ ENV WARMUP_ENABLED=true
31
+ ENV CUDA_CACHE_CLEAR_ENABLED=true
32
+ ENV EMBEDDING_BATCH_SIZE=8
33
+ ENV EMBEDDINGS_CACHE_ENABLED=true
34
+ ENV EMBEDDINGS_CACHE_MAXSIZE=2048
35
+ ENV REPORT_CACHED_TOKENS=false
36
+
37
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
38
+ # CMD ["python", "app.py"]
README.md ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Wip Test
3
+ emoji: 🐨
4
+ colorFrom: pink
5
+ colorTo: gray
6
+ sdk: docker
7
+ pinned: false
8
+ ---
9
+
10
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py ADDED
@@ -0,0 +1,255 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ from fastapi import FastAPI, Request, Response, WebSocket, WebSocketDisconnect
3
+ from fastapi.responses import HTMLResponse
4
+ from fastapi.concurrency import run_in_threadpool
5
+ from gradio_client import Client
6
+ import gradio as gr
7
+ import uvicorn
8
+ import httpx
9
+ import websockets
10
+ import asyncio
11
+ from urllib.parse import urljoin, urlparse, unquote
12
+ import logging
13
+
14
+ # Set up logging
15
+ logging.basicConfig(level=logging.INFO)
16
+ logger = logging.getLogger(__name__)
17
+
18
+ app = FastAPI()
19
+
20
+ class HttpClient:
21
+ def __init__(self):
22
+ # Configure the HTTP client with appropriate timeouts
23
+ self.client = httpx.AsyncClient(
24
+ timeout=httpx.Timeout(30.0),
25
+ follow_redirects=False
26
+ )
27
+
28
+ async def forward_request(self, request: Request, target_url: str):
29
+ """
30
+ Forward an incoming request to a target URL
31
+ """
32
+ try:
33
+ # Extract method, headers, and body from the incoming request
34
+ method = request.method
35
+ headers = dict(request.headers)
36
+
37
+ # Remove headers that shouldn't be forwarded
38
+ headers.pop("host", None)
39
+ headers.pop("connection", None)
40
+
41
+ # Get the request body
42
+ body = await request.body()
43
+
44
+ logger.info(f"Forwarding {method} request to {target_url}")
45
+
46
+ # Forward the request to the target URL
47
+ response = await self.client.request(
48
+ method=method,
49
+ url=target_url,
50
+ headers=headers,
51
+ content=body
52
+ )
53
+
54
+ # Handle the response from the target server
55
+ response_headers = dict(response.headers)
56
+ # Remove headers that shouldn't be forwarded from the response
57
+ response_headers.pop("connection", None)
58
+ response_headers.pop("transfer-encoding", None)
59
+
60
+ return Response(
61
+ content=response.content,
62
+ status_code=response.status_code,
63
+ headers=response_headers
64
+ )
65
+
66
+ except httpx.TimeoutException:
67
+ logger.error(f"Timeout error while forwarding request to {target_url}")
68
+ return Response(
69
+ content="Request timeout error",
70
+ status_code=504
71
+ )
72
+ except httpx.NetworkError as e:
73
+ logger.error(f"Network error while forwarding request: {str(e)}")
74
+ return Response(
75
+ content=f"Network error: {str(e)}",
76
+ status_code=502
77
+ )
78
+ except Exception as e:
79
+ logger.error(f"Error forwarding request: {str(e)}")
80
+ return Response(
81
+ content=f"Request error: {str(e)}",
82
+ status_code=500
83
+ )
84
+
85
+ async def close(self):
86
+ await self.client.aclose()
87
+
88
+ # Initialize the HTTP client
89
+ http_client = HttpClient()
90
+
91
+ @app.get("/", response_class=HTMLResponse)
92
+ async def read_root():
93
+ with open("index.html") as f:
94
+ return f.read()
95
+
96
+ @app.post("/gp/{repo_id:path}/{api_name:path}")
97
+ async def gradio_client(repo_id: str, api_name: str, request: Request):
98
+ client = Client(repo_id)
99
+ data = await request.json()
100
+ result = await run_in_threadpool(client.predict, *data["args"], api_name=f"/{api_name}")
101
+ return result
102
+
103
+ @app.api_route("/wp/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"])
104
+ async def web_client(request: Request, path: str):
105
+ """
106
+ Main web client endpoint that forwards all requests to the target URL
107
+ specified in the path or the 'X-Target-Url' header
108
+ """
109
+ # Prioritize URL in path if it starts with http:// or https://
110
+ if path.lower().startswith("http://") or path.lower().startswith("https://"):
111
+ target_url = path
112
+ else:
113
+ # Get the target URL from the header
114
+ target_url = request.headers.get("X-Target-Url")
115
+
116
+ # If we have a target URL from header and a path, combine them
117
+ if target_url and path:
118
+ # Validate the target URL from header
119
+ try:
120
+ parsed_url = urlparse(target_url)
121
+ if not parsed_url.scheme or not parsed_url.netloc:
122
+ return Response(
123
+ content="Invalid X-Target-Url header",
124
+ status_code=400
125
+ )
126
+ except Exception:
127
+ return Response(
128
+ content="Invalid X-Target-Url header",
129
+ status_code=400
130
+ )
131
+ # Join the target URL with the path properly
132
+ target_url = urljoin(target_url.rstrip('/') + '/', path.lstrip('/'))
133
+
134
+ if not target_url:
135
+ return Response(
136
+ content="Missing X-Target-Url header or URL in path",
137
+ status_code=400
138
+ )
139
+
140
+ # Validate the target URL
141
+ try:
142
+ parsed_url = urlparse(target_url)
143
+ if not parsed_url.scheme or not parsed_url.netloc:
144
+ return Response(
145
+ content="Invalid X-Target-Url header or URL in path",
146
+ status_code=400
147
+ )
148
+ except Exception:
149
+ return Response(
150
+ content="Invalid X-Target-Url header or URL in path",
151
+ status_code=400
152
+ )
153
+
154
+ # Forward the request
155
+ return await http_client.forward_request(request, target_url)
156
+
157
+ @app.websocket("/wp/{path:path}")
158
+ async def websocket_client(websocket: WebSocket, path: str):
159
+ """
160
+ WebSocket endpoint that forwards WebSocket connections to the target URL
161
+ specified in the 'X-Target-Url' header or in the path
162
+ """
163
+ # Get the target URL from the header or path
164
+ target_url = websocket.headers.get("X-Target-Url")
165
+
166
+ # If no header, use path as target URL if it's a valid WebSocket URL
167
+ if not target_url:
168
+ # Handle URL-encoded paths
169
+ decoded_path = path
170
+ if path and '%' in path:
171
+ # URL decode the path
172
+ decoded_path = unquote(path)
173
+
174
+ if decoded_path and (decoded_path.lower().startswith("ws://") or decoded_path.lower().startswith("wss://")):
175
+ target_url = decoded_path
176
+ else:
177
+ await websocket.close(code=1008, reason="Missing X-Target-Url header or invalid URL in path")
178
+ return
179
+
180
+ # Validate the target URL
181
+ try:
182
+ parsed_url = urlparse(target_url)
183
+ if not parsed_url.scheme or not parsed_url.netloc:
184
+ await websocket.close(code=1008, reason="Invalid target URL")
185
+ return
186
+ except Exception:
187
+ await websocket.close(code=1008, reason="Invalid target URL")
188
+ return
189
+
190
+ # Accept the WebSocket connection
191
+ await websocket.accept()
192
+
193
+ # Convert HTTP/HTTPS URL to WebSocket URL
194
+ if target_url.lower().startswith("https://"):
195
+ ws_target_url = "wss://" + target_url[8:]
196
+ elif target_url.lower().startswith("http://"):
197
+ ws_target_url = "ws://" + target_url[7:]
198
+ else:
199
+ ws_target_url = target_url
200
+
201
+ # Add path if provided (but only if it's not already a complete URL)
202
+ if path and not (path.lower().startswith("ws://") or path.lower().startswith("wss://")):
203
+ # Join the target URL with the path properly
204
+ ws_target_url = urljoin(ws_target_url.rstrip('/') + '/', path.lstrip('/'))
205
+
206
+ try:
207
+ # Connect to the target WebSocket server
208
+ async with websockets.connect(ws_target_url) as target_ws:
209
+ # Forward messages between client and target server
210
+ async def forward_client_to_target():
211
+ try:
212
+ while True:
213
+ data = await websocket.receive_text()
214
+ await target_ws.send(data)
215
+ except WebSocketDisconnect:
216
+ pass
217
+
218
+ async def forward_target_to_client():
219
+ try:
220
+ while True:
221
+ data = await target_ws.recv()
222
+ await websocket.send_text(data)
223
+ except websockets.ConnectionClosed:
224
+ pass
225
+
226
+ # Run both forwarding tasks concurrently
227
+ await asyncio.gather(
228
+ forward_client_to_target(),
229
+ forward_target_to_client(),
230
+ return_exceptions=True
231
+ )
232
+ except websockets.InvalidURI:
233
+ await websocket.close(code=1008, reason="Invalid WebSocket URL")
234
+ except websockets.InvalidHandshake:
235
+ await websocket.close(code=1008, reason="WebSocket handshake failed")
236
+ except Exception as e:
237
+ logger.error(f"Error in WebSocket connection: {str(e)}")
238
+ await websocket.close(code=1011, reason="Internal server error")
239
+ finally:
240
+ try:
241
+ await websocket.close()
242
+ except:
243
+ pass
244
+
245
+ @app.get("/health")
246
+ async def health_check():
247
+ """Health check endpoint"""
248
+ return {"status": "ok"}
249
+
250
+ @app.on_event("shutdown")
251
+ async def shutdown_event():
252
+ await http_client.close()
253
+
254
+ if __name__ == "__main__":
255
+ uvicorn.run(app, host="0.0.0.0", port=7860)
index.html ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Gradio Playgrounf</title>
5
+ <script src="https://cdn.tailwindcss.com"></script>
6
+ <script src="https://cdn.jsdelivr.net/npm/json-schema-faker@0.5.9/dist/json-schema-faker.min.js"></script>
7
+ </head>
8
+ <body class="bg-gray-100">
9
+ <div class="container mx-auto px-4 py-8">
10
+ <h1 class="text-3xl font-bold mb-4">Gradio Playground</h1>
11
+ <div class="bg-white p-6 rounded-lg shadow-lg">
12
+ <div class="mb-4">
13
+ <label class="block text-gray-700 font-bold mb-2" for="repo_id">
14
+ Repo ID
15
+ </label>
16
+ <div class="flex">
17
+ <input class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" id="repo_id" type="text" value="Agents-MCP-Hackathon/calculator-mcp-marcodsn">
18
+ <button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded ml-2" id="search">
19
+ Search
20
+ </button>
21
+ </div>
22
+ </div>
23
+ <div class="mb-4">
24
+ <label class="block text-gray-700 font-bold mb-2" for="api_name">
25
+ API Name
26
+ </label>
27
+ <input class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" id="api_name" type="text" value="predict">
28
+ </div>
29
+ <div class="mb-4">
30
+ <label class="block text-gray-700 font-bold mb-2" for="args">
31
+ Arguments (JSON)
32
+ </label>
33
+ <textarea class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" id="args" rows="3">["1 + 2"]</textarea>
34
+ </div>
35
+ <button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline disabled:opacity-50 disabled:cursor-not-allowed" id="submit">
36
+ Submit
37
+ </button>
38
+ <div class="mt-4">
39
+ <h2 class="text-xl font-bold mb-2">Result</h2>
40
+ <pre class="bg-gray-200 p-4 rounded" id="result"></pre>
41
+ <div class="hidden" id="loading">Loading...</div>
42
+ </div>
43
+ </div>
44
+ </div>
45
+ <script type="module">
46
+ import { client } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js";
47
+
48
+ const searchButton = document.getElementById("search");
49
+ const submitButton = document.getElementById("submit");
50
+ const repoIdInput = document.getElementById("repo_id");
51
+ const apiNameInput = document.getElementById("api_name");
52
+ const argsInput = document.getElementById("args");
53
+ const resultDiv = document.getElementById("result");
54
+ const loadingDiv = document.getElementById("loading");
55
+
56
+ searchButton.addEventListener("click", async () => {
57
+ const repoId = repoIdInput.value;
58
+ const app = await client(repoId);
59
+ const apiInfo = await app.view_api();
60
+
61
+ if (apiInfo.named_endpoints.length > 0) {
62
+ const firstApiName = apiInfo.named_endpoints[0].name;
63
+ apiNameInput.value = firstApiName;
64
+
65
+ const exampleArgs = apiInfo.named_endpoints[0].parameters.map(p => JSONSchemaFaker.generate(p.component_schema));
66
+ argsInput.value = JSON.stringify(exampleArgs);
67
+ } else if (apiInfo.unnamed_endpoints.length > 0) {
68
+ apiNameInput.value = apiInfo.unnamed_endpoints[0].name;
69
+
70
+ const exampleArgs = apiInfo.unnamed_endpoints[0].parameters.map(p => JSONSchemaFaker.generate(p.component_schema));
71
+ argsInput.value = JSON.stringify(exampleArgs);
72
+ }
73
+ });
74
+
75
+ submitButton.addEventListener("click", async () => {
76
+ const repoId = repoIdInput.value;
77
+ const apiName = apiNameInput.value;
78
+ const args = JSON.parse(argsInput.value);
79
+
80
+ submitButton.disabled = true;
81
+ loadingDiv.classList.remove("hidden");
82
+ resultDiv.classList.add("hidden");
83
+
84
+ const response = await fetch(`/gp/${repoId}/${apiName}`, {
85
+ method: "POST",
86
+ headers: {
87
+ "Content-Type": "application/json"
88
+ },
89
+ body: JSON.stringify({ args })
90
+ });
91
+
92
+ const result = await response.json();
93
+ resultDiv.textContent = JSON.stringify(result, null, 2);
94
+
95
+ submitButton.disabled = false;
96
+ loadingDiv.classList.add("hidden");
97
+ resultDiv.classList.remove("hidden");
98
+ });
99
+ </script>
100
+ </body>
101
+ </html>
models_config.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # models_config.py
2
+
3
+ CANONICAL_MODELS = {
4
+ "all-MiniLM-L6-v2": {
5
+ "name": "sentence-transformers/all-MiniLM-L6-v2",
6
+ "dimension": 384,
7
+ "requires_remote_code": False,
8
+ "max_tokens": 512,
9
+ },
10
+ "gte-multilingual-base": {
11
+ "name": "Alibaba-NLP/gte-multilingual-base",
12
+ "dimension": 768,
13
+ "requires_remote_code": True,
14
+ "max_tokens": 8192,
15
+ },
16
+ "nomic-embed-text-v1.5": {
17
+ "name": "nomic-ai/nomic-embed-text-v1.5",
18
+ "dimension": 768,
19
+ "requires_remote_code": True,
20
+ "max_tokens": 8192,
21
+ "instruction_prefix_required": True,
22
+ "default_instruction_prefix": "search_document:",
23
+ "known_instruction_prefixes": [
24
+ "search_document:",
25
+ "search_query:",
26
+ "clustering:",
27
+ "classification:",
28
+ ],
29
+ },
30
+ "all-mpnet-base-v2": {
31
+ "name": "sentence-transformers/all-mpnet-base-v2",
32
+ "dimension": 768,
33
+ "requires_remote_code": False,
34
+ "max_tokens": 384,
35
+ },
36
+ }
37
+
38
+ # Mapping of aliases to their canonical model names
39
+ MODEL_ALIASES = {
40
+ "all-minilm": "all-MiniLM-L6-v2",
41
+ "text-embedding-3-small": "all-MiniLM-L6-v2",
42
+ "text-embedding-3-large": "gte-multilingual-base",
43
+ "nomic-embed-text": "nomic-embed-text-v1.5",
44
+ }
45
+
46
+ # This global MODELS dictionary will be used for listing available models and validation.
47
+ # It combines canonical names and aliases for easy lookup.
48
+ MODELS = {**CANONICAL_MODELS, **{alias: CANONICAL_MODELS[canonical] for alias, canonical in MODEL_ALIASES.items()}}
49
+
50
+ def get_model_config(requested_model_name: str) -> dict:
51
+ """
52
+ Resolves a requested model name (which might be an alias) to its canonical
53
+ configuration. Raises ValueError if the model is not found.
54
+ """
55
+ canonical_name = MODEL_ALIASES.get(requested_model_name, requested_model_name)
56
+
57
+ if canonical_name not in CANONICAL_MODELS:
58
+ raise ValueError(f"Model '{requested_model_name}' (canonical: '{canonical_name}') is not a recognized model.")
59
+
60
+ return CANONICAL_MODELS[canonical_name]
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ fastapi>=0.116.1
2
+ uvicorn>=0.35.0
3
+ gradio>=5.43.1
4
+ gradio-client>=1.12.1
5
+ httpx>=0.28.1
6
+ websockets>=15.0
static/index.html ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Embedding Demo</title>
7
+ <!-- Include Tailwind CSS via CDN -->
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ <!-- Include Heroicons for the copy icon -->
10
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
11
+ </head>
12
+ <body class="bg-gray-100 flex items-center justify-center min-h-screen">
13
+ <div class="bg-white p-8 rounded-lg shadow-md w-full max-w-md">
14
+ <h1 class="text-2xl font-bold mb-6 text-center">Embedding Demo</h1>
15
+ <div class="mb-4">
16
+ <label for="inputText" class="block text-gray-700 text-sm font-bold mb-2">Enter Text:</label>
17
+ <textarea id="inputText" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" rows="4" placeholder="Enter text to embed..."></textarea>
18
+ </div>
19
+ <div class="mb-6">
20
+ <label for="modelSelect" class="block text-gray-700 text-sm font-bold mb-2">Select Model:</label>
21
+ <select id="modelSelect" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline">
22
+ <!-- Options will be populated by JavaScript -->
23
+ </select>
24
+ </div>
25
+ <div class="flex items-center justify-between">
26
+ <button id="embedButton" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline disabled:opacity-50 disabled:cursor-not-allowed">
27
+ Get Embedding
28
+ </button>
29
+ </div>
30
+ <div class="mt-6">
31
+ <label class="block text-gray-700 text-sm font-bold mb-2">Embedding Result:</label>
32
+ <div class="flex items-center bg-gray-200 rounded">
33
+ <div id="result" class="p-4 text-gray-800 text-sm overflow-auto max-h-60 flex-grow">
34
+ <p>Embedding result will appear here...</p>
35
+ </div>
36
+ <button id="copyButton" class="p-4 self-start text-gray-600 hover:text-gray-800 focus:outline-none">
37
+ <i class="fas fa-copy"></i>
38
+ </button>
39
+ </div>
40
+ </div>
41
+ </div>
42
+
43
+ <script>
44
+ document.addEventListener('DOMContentLoaded', async () => {
45
+ const modelSelect = document.getElementById('modelSelect');
46
+
47
+ try {
48
+ const response = await fetch('/v1/models');
49
+ if (!response.ok) {
50
+ throw new Error(`HTTP error! status: ${response.status}`);
51
+ }
52
+ const data = await response.json();
53
+
54
+ // Clear existing options
55
+ modelSelect.innerHTML = '';
56
+
57
+ // Populate dropdown with models
58
+ data.data.forEach(model => {
59
+ const option = document.createElement('option');
60
+ option.value = model.id;
61
+ option.textContent = model.id;
62
+ modelSelect.appendChild(option);
63
+ });
64
+
65
+ } catch (error) {
66
+ console.error('Error fetching models:', error);
67
+ // Optionally, add an error message to the dropdown or a separate element
68
+ const option = document.createElement('option');
69
+ option.value = '';
70
+ option.textContent = 'Error loading models';
71
+ modelSelect.appendChild(option);
72
+ }
73
+ });
74
+
75
+
76
+ document.getElementById('embedButton').addEventListener('click', async () => {
77
+ const inputText = document.getElementById('inputText').value;
78
+ const model = document.getElementById('modelSelect').value;
79
+ const resultDiv = document.getElementById('result');
80
+ const copyButton = document.getElementById('copyButton');
81
+ const embedButton = document.getElementById('embedButton'); // Get button reference
82
+
83
+ if (!inputText) {
84
+ resultDiv.textContent = 'Please enter some text.';
85
+ return;
86
+ }
87
+
88
+ resultDiv.textContent = 'Fetching embedding...';
89
+ copyButton.style.display = 'none'; // Hide copy button while fetching
90
+ embedButton.disabled = true; // Disable button
91
+
92
+ try {
93
+ const response = await fetch('/v1/embeddings', {
94
+ method: 'POST',
95
+ headers: {
96
+ 'Content-Type': 'application/json',
97
+ },
98
+ body: JSON.stringify({
99
+ input: inputText,
100
+ model: model,
101
+ encoding_format: 'float' // Assuming 'float' is the only supported format
102
+ }),
103
+ });
104
+
105
+ if (!response.ok) {
106
+ const error = await response.json();
107
+ throw new Error(`HTTP error! status: ${response.status}, detail: ${error.detail}`);
108
+ }
109
+
110
+ const data = await response.json();
111
+ resultDiv.textContent = JSON.stringify(data, null, 2); // Display pretty-printed JSON
112
+ copyButton.style.display = 'block'; // Show copy button after result is displayed
113
+
114
+ } catch (error) {
115
+ resultDiv.textContent = `Error: ${error.message}`;
116
+ console.error('Error fetching embedding:', error);
117
+ copyButton.style.display = 'none'; // Hide copy button on error
118
+ } finally {
119
+ embedButton.disabled = false; // Re-enable button
120
+ }
121
+ });
122
+
123
+ document.getElementById('copyButton').addEventListener('click', async () => {
124
+ const resultDiv = document.getElementById('result');
125
+ const textToCopy = resultDiv.textContent;
126
+
127
+ try {
128
+ await navigator.clipboard.writeText(textToCopy);
129
+ // Optional: Provide visual feedback to the user
130
+ alert('Copied to clipboard!');
131
+ } catch (err) {
132
+ console.error('Failed to copy text: ', err);
133
+ // Optional: Provide visual feedback to the user
134
+ alert('Failed to copy text.');
135
+ }
136
+ });
137
+
138
+ // Initially hide the copy button
139
+ document.getElementById('copyButton').style.display = 'none';
140
+ </script>
141
+ </body>
142
+ </html>