xinference 1.11.0__py3-none-any.whl → 1.12.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of xinference might be problematic. Click here for more details.
- xinference/__init__.py +8 -0
- xinference/_version.py +3 -3
- xinference/api/oauth2/utils.py +26 -5
- xinference/core/model.py +1 -10
- xinference/device_utils.py +11 -1
- xinference/model/embedding/model_spec.json +70 -0
- xinference/model/image/core.py +20 -10
- xinference/model/image/model_spec.json +55 -3
- xinference/model/image/ocr/__init__.py +5 -0
- xinference/model/image/ocr/deepseek_ocr.py +958 -0
- xinference/model/llm/core.py +2 -0
- xinference/model/llm/llama_cpp/core.py +2 -0
- xinference/model/llm/llm_family.json +319 -6
- xinference/model/llm/lmdeploy/core.py +2 -0
- xinference/model/llm/sglang/core.py +2 -0
- xinference/model/llm/transformers/core.py +22 -36
- xinference/model/llm/transformers/multimodal/qwen-omni.py +60 -11
- xinference/model/llm/transformers/multimodal/qwen2_vl.py +2 -2
- xinference/model/llm/transformers/utils.py +0 -20
- xinference/model/llm/vllm/core.py +2 -0
- xinference/model/rerank/model_spec.json +368 -252
- xinference/model/rerank/sentence_transformers/core.py +10 -2
- xinference/thirdparty/indextts/gpt/transformers_generation_utils.py +71 -5
- xinference/thirdparty/indextts/gpt/transformers_gpt2.py +51 -1
- xinference/ui/gradio/media_interface.py +469 -4
- xinference/ui/gradio/utils/__init__.py +19 -0
- xinference/ui/gradio/utils/latex.py +342 -0
- xinference/ui/web/ui/build/asset-manifest.json +3 -3
- xinference/ui/web/ui/build/index.html +1 -1
- xinference/ui/web/ui/build/static/js/{main.45e78536.js → main.87d6859b.js} +3 -3
- xinference/ui/web/ui/build/static/js/main.87d6859b.js.map +1 -0
- xinference/ui/web/ui/node_modules/.cache/babel-loader/412a6b414a8267c7a349d9beda4593cdf218abf32edaaf339e6a230df40397b8.json +1 -0
- xinference/ui/web/ui/node_modules/.cache/babel-loader/e6770a05771952175c9fbf48fce283c9bb1bc8b5763e39edc36d099d1fe16b4a.json +1 -0
- {xinference-1.11.0.dist-info → xinference-1.12.0.dist-info}/METADATA +11 -11
- {xinference-1.11.0.dist-info → xinference-1.12.0.dist-info}/RECORD +40 -37
- xinference/ui/web/ui/build/static/js/main.45e78536.js.map +0 -1
- xinference/ui/web/ui/node_modules/.cache/babel-loader/7275b67c78ec76ce38a686bb8a576d8c9cecf54e1573614c84859d538efb9be5.json +0 -1
- xinference/ui/web/ui/node_modules/.cache/babel-loader/bb4e8722d2d41d87f1fce3661bc8937bffe9448e231fc5f0462630849e851592.json +0 -1
- /xinference/ui/web/ui/build/static/js/{main.45e78536.js.LICENSE.txt → main.87d6859b.js.LICENSE.txt} +0 -0
- {xinference-1.11.0.dist-info → xinference-1.12.0.dist-info}/WHEEL +0 -0
- {xinference-1.11.0.dist-info → xinference-1.12.0.dist-info}/entry_points.txt +0 -0
- {xinference-1.11.0.dist-info → xinference-1.12.0.dist-info}/licenses/LICENSE +0 -0
- {xinference-1.11.0.dist-info → xinference-1.12.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
# Copyright 2022-2025 XProbe Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
LaTeX processing utilities for OCR text formatting.
|
|
17
|
+
|
|
18
|
+
This module provides functions to process LaTeX formulas in OCR text,
|
|
19
|
+
making them compatible with different output formats like Markdown,
|
|
20
|
+
HTML, and pure LaTeX.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import re
|
|
24
|
+
from typing import Any, Dict, Union
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def process_latex_formulas(text: str, output_format: str = "markdown") -> str:
|
|
28
|
+
"""
|
|
29
|
+
Process LaTeX formulas in OCR text to make them compatible with different output formats.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
text: The OCR text containing LaTeX formulas
|
|
33
|
+
output_format: Target format ("markdown", "html", "latex", "gradio")
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Processed text with formulas converted to the target format
|
|
37
|
+
"""
|
|
38
|
+
if not text:
|
|
39
|
+
return text
|
|
40
|
+
|
|
41
|
+
processed_text = text
|
|
42
|
+
|
|
43
|
+
if output_format == "markdown":
|
|
44
|
+
# Convert \[ ... \] to $$ ... $$ for block math in Markdown
|
|
45
|
+
processed_text = re.sub(
|
|
46
|
+
r"\\\[\s*(.*?)\s*\\\]",
|
|
47
|
+
lambda m: f"\n$$\n{m.group(1).strip()}\n$$\n",
|
|
48
|
+
processed_text,
|
|
49
|
+
flags=re.DOTALL,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Convert \( ... \) to $ ... $ for inline math in Markdown
|
|
53
|
+
processed_text = re.sub(
|
|
54
|
+
r"\\\((.*?)\\\)",
|
|
55
|
+
lambda m: f"${m.group(1).strip()}$",
|
|
56
|
+
processed_text,
|
|
57
|
+
flags=re.DOTALL,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# Handle common LaTeX math environments
|
|
61
|
+
# Convert \begin{equation} ... \end{equation} to $$ ... $$
|
|
62
|
+
processed_text = re.sub(
|
|
63
|
+
r"\\begin\{equation\}(.*?)\\end\{equation\}",
|
|
64
|
+
lambda m: f"\n$$\n{m.group(1).strip()}\n$$\n",
|
|
65
|
+
processed_text,
|
|
66
|
+
flags=re.DOTALL,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Convert \begin{align} ... \end{align} to $$ ... $$
|
|
70
|
+
processed_text = re.sub(
|
|
71
|
+
r"\\begin\{align\*?\}(.*?)\\end\{align\*?\}",
|
|
72
|
+
lambda m: f"\n$$\n{m.group(1).strip()}\n$$\n",
|
|
73
|
+
processed_text,
|
|
74
|
+
flags=re.DOTALL,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# Convert \begin{gather} ... \end{gather} to $$ ... $$
|
|
78
|
+
processed_text = re.sub(
|
|
79
|
+
r"\\begin\{gather\}(.*?)\\end\{gather\}",
|
|
80
|
+
lambda m: f"\n$$\n{m.group(1).strip()}\n$$\n",
|
|
81
|
+
processed_text,
|
|
82
|
+
flags=re.DOTALL,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
elif output_format == "html":
|
|
86
|
+
# Convert \[ ... \] to <div>...</div> with proper formatting
|
|
87
|
+
processed_text = re.sub(
|
|
88
|
+
r"\\\[(.*?)\\\]",
|
|
89
|
+
lambda m: f'<div class="math-display">\\[{m.group(1).strip()}\\]</div>',
|
|
90
|
+
processed_text,
|
|
91
|
+
flags=re.DOTALL,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# Convert \( ... \) to <span>...</span> for inline math
|
|
95
|
+
processed_text = re.sub(
|
|
96
|
+
r"\\\((.*?)\\\)",
|
|
97
|
+
lambda m: f'<span class="math-inline">\\({m.group(1).strip()}\\)</span>',
|
|
98
|
+
processed_text,
|
|
99
|
+
flags=re.DOTALL,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
elif output_format == "latex":
|
|
103
|
+
# Keep original LaTeX format, just clean up spacing
|
|
104
|
+
processed_text = re.sub(
|
|
105
|
+
r"\\\[(.*?)\\\]",
|
|
106
|
+
lambda m: f"\\[\n{m.group(1).strip()}\n\\]",
|
|
107
|
+
processed_text,
|
|
108
|
+
flags=re.DOTALL,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
processed_text = re.sub(
|
|
112
|
+
r"\\\((.*?)\\\)",
|
|
113
|
+
lambda m: f"\\({m.group(1).strip()}\\)",
|
|
114
|
+
processed_text,
|
|
115
|
+
flags=re.DOTALL,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
return processed_text
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def clean_latex_syntax(text: str) -> str:
|
|
122
|
+
"""
|
|
123
|
+
Clean up common LaTeX syntax issues in OCR text.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
text: The OCR text containing LaTeX
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
Cleaned LaTeX text
|
|
130
|
+
"""
|
|
131
|
+
if not text:
|
|
132
|
+
return text
|
|
133
|
+
|
|
134
|
+
# Fix common OCR errors in LaTeX
|
|
135
|
+
cleaned = text
|
|
136
|
+
|
|
137
|
+
# Fix spacing around operators
|
|
138
|
+
cleaned = re.sub(r"(\w)([+\-=*/])(\w)", r"\1 \2 \3", cleaned)
|
|
139
|
+
|
|
140
|
+
# Fix double backslashes that might be mangled, but preserve LaTeX delimiters
|
|
141
|
+
# Only fix excessive backslashes (3+), not double backslashes which are valid LaTeX
|
|
142
|
+
cleaned = re.sub(r"\\\\{3,}", r"\\\\", cleaned)
|
|
143
|
+
|
|
144
|
+
# Fix fractions that might be incorrectly spaced
|
|
145
|
+
cleaned = re.sub(
|
|
146
|
+
r"\\frac\s*\{\s*(\w+)\s*\}\s*\{\s*(\w+)\s*\}", r"\\frac{\1}{\2}", cleaned
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# Fix superscripts and subscripts
|
|
150
|
+
cleaned = re.sub(r"\^\s*\{\s*(\w+)\s*\}", r"^{\1}", cleaned)
|
|
151
|
+
cleaned = re.sub(r"_\s*\{\s*(\w+)\s*\}", r"_{\1}", cleaned)
|
|
152
|
+
|
|
153
|
+
# Fix common misrecognized Greek letters
|
|
154
|
+
greek_corrections = {
|
|
155
|
+
r"\\alpha\s": r"\\alpha ",
|
|
156
|
+
r"\\beta\s": r"\\beta ",
|
|
157
|
+
r"\\gamma\s": r"\\gamma ",
|
|
158
|
+
r"\\delta\s": r"\\delta ",
|
|
159
|
+
r"\\epsilon\s": r"\\epsilon ",
|
|
160
|
+
r"\\theta\s": r"\\theta ",
|
|
161
|
+
r"\\lambda\s": r"\\lambda ",
|
|
162
|
+
r"\\mu\s": r"\\mu ",
|
|
163
|
+
r"\\pi\s": r"\\pi ",
|
|
164
|
+
r"\\sigma\s": r"\\sigma ",
|
|
165
|
+
r"\\phi\s": r"\\phi ",
|
|
166
|
+
r"\\omega\s": r"\\omega ",
|
|
167
|
+
r"\\Delta\s": r"\\Delta ",
|
|
168
|
+
r"\\Sigma\s": r"\\Sigma ",
|
|
169
|
+
r"\\Pi\s": r"\\Pi ",
|
|
170
|
+
r"\\infty\s": r"\\infty ",
|
|
171
|
+
r"\\pm\s": r"\\pm ",
|
|
172
|
+
r"\\times\s": r"\\times ",
|
|
173
|
+
r"\\div\s": r"\\div ",
|
|
174
|
+
r"\\neq\s": r"\\neq ",
|
|
175
|
+
r"\\leq\s": r"\\leq ",
|
|
176
|
+
r"\\geq\s": r"\\geq ",
|
|
177
|
+
r"\\approx\s": r"\\approx ",
|
|
178
|
+
r"\\in\s": r"\\in ",
|
|
179
|
+
r"\\subset\s": r"\\subset ",
|
|
180
|
+
r"\\supset\s": r"\\supset ",
|
|
181
|
+
r"\\int\s": r"\\int ",
|
|
182
|
+
r"\\sum\s": r"\\sum ",
|
|
183
|
+
r"\\prod\s": r"\\prod ",
|
|
184
|
+
r"\\partial\s": r"\\partial ",
|
|
185
|
+
r"\\nabla\s": r"\\nabla ",
|
|
186
|
+
r"\\sqrt\s": r"\\sqrt ",
|
|
187
|
+
r"^\{2\}": r"²",
|
|
188
|
+
r"^\{3\}": r"³",
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
for latex, unicode in greek_corrections.items():
|
|
192
|
+
cleaned = re.sub(latex, unicode, cleaned)
|
|
193
|
+
|
|
194
|
+
return cleaned
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def process_ocr_latex(text: str, output_format: str = "markdown") -> str:
|
|
198
|
+
"""
|
|
199
|
+
Convenience function to process OCR text containing LaTeX formulas.
|
|
200
|
+
|
|
201
|
+
This function combines LaTeX syntax cleaning and formula formatting
|
|
202
|
+
in a single call, which is useful for OCR post-processing.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
text: The OCR text containing LaTeX formulas
|
|
206
|
+
output_format: Target format ("markdown", "html", "latex", "gradio")
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
Processed text with cleaned LaTeX syntax and properly formatted formulas
|
|
210
|
+
"""
|
|
211
|
+
if not text:
|
|
212
|
+
return text
|
|
213
|
+
|
|
214
|
+
# First clean up LaTeX syntax issues
|
|
215
|
+
cleaned_text = clean_latex_syntax(text)
|
|
216
|
+
|
|
217
|
+
# Then convert formulas to the desired format
|
|
218
|
+
processed_text = process_latex_formulas(cleaned_text, output_format)
|
|
219
|
+
|
|
220
|
+
return processed_text
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def contains_latex(text: str) -> bool:
|
|
224
|
+
"""
|
|
225
|
+
Detect if text contains LaTeX formulas.
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
text: The text to check for LaTeX formulas
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
True if the text contains LaTeX formulas, False otherwise
|
|
232
|
+
"""
|
|
233
|
+
if not text:
|
|
234
|
+
return False
|
|
235
|
+
|
|
236
|
+
# Check for common LaTeX indicators
|
|
237
|
+
latex_indicators = [
|
|
238
|
+
r"\\\[.*?\\\]", # Block formulas \[...\]
|
|
239
|
+
r"\\\(.*?\\\)", # Inline formulas \(...\)
|
|
240
|
+
r"\$.*?\$", # Inline formulas $...$
|
|
241
|
+
r"\$\$.*?\$\$", # Block formulas $$...$$
|
|
242
|
+
r"\\begin\{.*?\}", # LaTeX environments
|
|
243
|
+
r"\\[a-zA-Z]+\{", # LaTeX commands with arguments
|
|
244
|
+
r"\\[a-zA-Z]+", # Simple LaTeX commands
|
|
245
|
+
]
|
|
246
|
+
|
|
247
|
+
for pattern in latex_indicators:
|
|
248
|
+
if re.search(pattern, text):
|
|
249
|
+
return True
|
|
250
|
+
|
|
251
|
+
return False
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def process_ocr_result_with_latex(
|
|
255
|
+
result: Union[str, Dict[str, Any]],
|
|
256
|
+
output_format: str = "markdown",
|
|
257
|
+
debug_info: bool = False,
|
|
258
|
+
) -> Union[str, Dict[str, Any]]:
|
|
259
|
+
"""
|
|
260
|
+
Process OCR results, applying LaTeX formatting if needed.
|
|
261
|
+
|
|
262
|
+
This is a unified function to handle both LaTeX detection and processing
|
|
263
|
+
for OCR results, supporting both string and dict formats.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
result: OCR result, either as string or dict with 'text' key
|
|
267
|
+
output_format: Target format ("markdown", "html", "latex", "gradio")
|
|
268
|
+
debug_info: Whether to print debug information about LaTeX processing
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
Processed result with LaTeX formulas formatted appropriately
|
|
272
|
+
"""
|
|
273
|
+
# Handle dict format (from visualize_ocr and other methods)
|
|
274
|
+
if isinstance(result, dict):
|
|
275
|
+
if "text" not in result:
|
|
276
|
+
return result
|
|
277
|
+
|
|
278
|
+
text = result["text"]
|
|
279
|
+
if not contains_latex(text):
|
|
280
|
+
return result
|
|
281
|
+
|
|
282
|
+
# Process LaTeX
|
|
283
|
+
processed_text = process_ocr_latex(text, output_format)
|
|
284
|
+
|
|
285
|
+
# Create new result dict with processed text
|
|
286
|
+
processed_result = result.copy()
|
|
287
|
+
processed_result["text"] = processed_text
|
|
288
|
+
processed_result["latex_processing"] = {
|
|
289
|
+
"latex_detected": True,
|
|
290
|
+
"output_format": output_format,
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if debug_info:
|
|
294
|
+
print("🧮 LaTeX Formula Processing:")
|
|
295
|
+
original_block_formulas = len(re.findall(r"\\\[(.*?)\\\]", text, re.DOTALL))
|
|
296
|
+
original_inline_dollar = len(re.findall(r"\$(.*?)\$", text, re.DOTALL))
|
|
297
|
+
original_inline_paren = len(re.findall(r"\\\((.*?)\\\)", text, re.DOTALL))
|
|
298
|
+
converted_inline = len(re.findall(r"\$(.*?)\$", processed_text, re.DOTALL))
|
|
299
|
+
|
|
300
|
+
print(f" - Original block formulas (\\[...\\]): {original_block_formulas}")
|
|
301
|
+
print(f" - Original inline formulas ($...$): {original_inline_dollar}")
|
|
302
|
+
print(f" - Original inline formulas (\\(...\\)): {original_inline_paren}")
|
|
303
|
+
print(f" - Final inline formulas ($...$): {converted_inline}")
|
|
304
|
+
print(
|
|
305
|
+
f" - Format: Markdown-compatible ($...$ for inline, $$...$$ for block)"
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
return processed_result
|
|
309
|
+
|
|
310
|
+
# Handle string format
|
|
311
|
+
else:
|
|
312
|
+
# Ensure result is treated as string
|
|
313
|
+
text_result = str(result)
|
|
314
|
+
|
|
315
|
+
if not contains_latex(text_result):
|
|
316
|
+
return result
|
|
317
|
+
|
|
318
|
+
if debug_info:
|
|
319
|
+
print("🧮 LaTeX Formula Processing:")
|
|
320
|
+
original_block_formulas = len(
|
|
321
|
+
re.findall(r"\\\[(.*?)\\\]", text_result, re.DOTALL)
|
|
322
|
+
)
|
|
323
|
+
original_inline_dollar = len(
|
|
324
|
+
re.findall(r"\$(.*?)\$", text_result, re.DOTALL)
|
|
325
|
+
)
|
|
326
|
+
original_inline_paren = len(
|
|
327
|
+
re.findall(r"\\\((.*?)\\\)", text_result, re.DOTALL)
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
processed_text = process_ocr_latex(text_result, output_format)
|
|
331
|
+
|
|
332
|
+
if debug_info:
|
|
333
|
+
converted_inline = len(re.findall(r"\$(.*?)\$", processed_text, re.DOTALL))
|
|
334
|
+
print(f" - Original block formulas (\\[...\\]): {original_block_formulas}")
|
|
335
|
+
print(f" - Original inline formulas ($...$): {original_inline_dollar}")
|
|
336
|
+
print(f" - Original inline formulas (\\(...\\)): {original_inline_paren}")
|
|
337
|
+
print(f" - Final inline formulas ($...$): {converted_inline}")
|
|
338
|
+
print(
|
|
339
|
+
f" - Format: Markdown-compatible ($...$ for inline, $$...$$ for block)"
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
return processed_text
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"files": {
|
|
3
3
|
"main.css": "./static/css/main.5ea97072.css",
|
|
4
|
-
"main.js": "./static/js/main.
|
|
4
|
+
"main.js": "./static/js/main.87d6859b.js",
|
|
5
5
|
"static/media/icon.webp": "./static/media/icon.4603d52c63041e5dfbfd.webp",
|
|
6
6
|
"index.html": "./index.html",
|
|
7
7
|
"main.5ea97072.css.map": "./static/css/main.5ea97072.css.map",
|
|
8
|
-
"main.
|
|
8
|
+
"main.87d6859b.js.map": "./static/js/main.87d6859b.js.map"
|
|
9
9
|
},
|
|
10
10
|
"entrypoints": [
|
|
11
11
|
"static/css/main.5ea97072.css",
|
|
12
|
-
"static/js/main.
|
|
12
|
+
"static/js/main.87d6859b.js"
|
|
13
13
|
]
|
|
14
14
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.svg"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="./logo192.png"/><link rel="manifest" href="./manifest.json"/><title>Xinference</title><script defer="defer" src="./static/js/main.
|
|
1
|
+
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.svg"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="./logo192.png"/><link rel="manifest" href="./manifest.json"/><title>Xinference</title><script defer="defer" src="./static/js/main.87d6859b.js"></script><link href="./static/css/main.5ea97072.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|