Spaces:
Running on Zero
Running on Zero
update app
Browse files
app.py
CHANGED
|
@@ -78,6 +78,7 @@ MODEL_MAP = {
|
|
| 78 |
"Qwen2.5-VL-7B-Instruct": (processor_m7b, model_m7b),
|
| 79 |
"Qwen2.5-VL-3B-Instruct": (processor_x3b, model_x3b),
|
| 80 |
}
|
|
|
|
| 81 |
MODEL_CHOICES = list(MODEL_MAP.keys())
|
| 82 |
|
| 83 |
image_examples = [
|
|
@@ -211,22 +212,19 @@ EXAMPLE_CARDS_HTML = build_example_cards_html()
|
|
| 211 |
|
| 212 |
|
| 213 |
def load_example_data(idx_str):
|
| 214 |
-
|
| 215 |
-
|
|
|
|
|
|
|
| 216 |
return json.dumps({"status": "error", "message": "Invalid example index"})
|
| 217 |
-
|
| 218 |
try:
|
| 219 |
-
|
| 220 |
-
idx = int(float(idx_raw))
|
| 221 |
-
else:
|
| 222 |
-
idx = int(idx_raw)
|
| 223 |
except Exception:
|
| 224 |
return json.dumps({"status": "error", "message": "Invalid example index"})
|
| 225 |
-
|
| 226 |
if idx < 0 or idx >= len(all_examples):
|
| 227 |
return json.dumps({"status": "error", "message": "Example index out of range"})
|
| 228 |
-
|
| 229 |
ex = all_examples[idx]
|
|
|
|
| 230 |
payload = {
|
| 231 |
"status": "ok",
|
| 232 |
"query": ex["query"],
|
|
@@ -334,16 +332,17 @@ def generate_image(model_name: str, text: str, image: Image.Image,
|
|
| 334 |
|
| 335 |
|
| 336 |
@spaces.GPU(duration=calc_timeout_generic)
|
| 337 |
-
def generate_pdf(model_name: str, text: str,
|
| 338 |
max_new_tokens: int = 2048, temperature: float = 0.6,
|
| 339 |
top_p: float = 0.9, top_k: int = 50,
|
| 340 |
repetition_penalty: float = 1.2, gpu_timeout: int = 120):
|
| 341 |
-
if not pages:
|
| 342 |
raise gr.Error("Please upload a PDF file first.")
|
| 343 |
processor, model = select_model(model_name)
|
|
|
|
| 344 |
full_response = ""
|
| 345 |
-
for i, image in enumerate(
|
| 346 |
-
page_header = f"--- Page {i+1}/{len(
|
| 347 |
yield full_response + page_header
|
| 348 |
messages = [{"role": "user", "content": [{"type": "image"}, {"type": "text", "text": text}]}]
|
| 349 |
prompt_full = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
|
|
@@ -449,7 +448,7 @@ def generate_gif(model_name: str, text: str, gif_path: str,
|
|
| 449 |
torch.cuda.empty_cache()
|
| 450 |
|
| 451 |
|
| 452 |
-
def run_router(tab_kind, model_name, text, image_b64, pdf_b64, gif_b64,
|
| 453 |
max_new_tokens_v, temperature_v, top_p_v, top_k_v, repetition_penalty_v, gpu_timeout_v):
|
| 454 |
if tab_kind == "pdf":
|
| 455 |
temp_pdf_path = b64_to_temp_file(pdf_b64)
|
|
@@ -457,10 +456,11 @@ def run_router(tab_kind, model_name, text, image_b64, pdf_b64, gif_b64,
|
|
| 457 |
raise gr.Error("Could not decode uploaded PDF.")
|
| 458 |
try:
|
| 459 |
pages = convert_pdf_to_images(temp_pdf_path)
|
|
|
|
| 460 |
yield from generate_pdf(
|
| 461 |
model_name=model_name,
|
| 462 |
text=text,
|
| 463 |
-
|
| 464 |
max_new_tokens=max_new_tokens_v,
|
| 465 |
temperature=temperature_v,
|
| 466 |
top_p=top_p_v,
|
|
@@ -827,6 +827,7 @@ function init() {
|
|
| 827 |
const runBtnEl = document.getElementById('custom-run-btn');
|
| 828 |
const outputArea = document.getElementById('custom-output-textarea');
|
| 829 |
const mediaStatus = document.getElementById('sb-media-status');
|
|
|
|
| 830 |
const pdfPrevBtn = document.getElementById('pdf-prev-btn');
|
| 831 |
const pdfNextBtn = document.getElementById('pdf-next-btn');
|
| 832 |
const pdfPageInfo = document.getElementById('pdf-page-info');
|
|
@@ -842,7 +843,6 @@ function init() {
|
|
| 842 |
let toastTimer = null;
|
| 843 |
let examplePoller = null;
|
| 844 |
let lastSeenExamplePayload = null;
|
| 845 |
-
let suppressNextExampleEmpty = false;
|
| 846 |
|
| 847 |
function showToast(message, type) {
|
| 848 |
let toast = document.getElementById('app-toast');
|
|
@@ -901,17 +901,28 @@ function init() {
|
|
| 901 |
|
| 902 |
function setGradioValue(containerId, value) {
|
| 903 |
const container = document.getElementById(containerId);
|
| 904 |
-
if (!container) return;
|
| 905 |
-
container.
|
| 906 |
-
|
| 907 |
-
|
| 908 |
-
|
| 909 |
-
|
| 910 |
-
|
| 911 |
-
|
| 912 |
-
|
| 913 |
-
|
| 914 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 915 |
}
|
| 916 |
|
| 917 |
function syncMediaToGradio() {
|
|
@@ -995,6 +1006,7 @@ function init() {
|
|
| 995 |
mediaState = null;
|
| 996 |
renderPreview();
|
| 997 |
}
|
|
|
|
| 998 |
|
| 999 |
function processFile(file) {
|
| 1000 |
if (!file) return;
|
|
@@ -1106,6 +1118,9 @@ function init() {
|
|
| 1106 |
if (mediaState && mediaState.mode !== mode) clearPreview();
|
| 1107 |
}
|
| 1108 |
|
|
|
|
|
|
|
|
|
|
| 1109 |
if (uploadClick) uploadClick.addEventListener('click', () => fileInput.click());
|
| 1110 |
if (btnUpload) btnUpload.addEventListener('click', () => fileInput.click());
|
| 1111 |
if (btnClear) btnClear.addEventListener('click', clearPreview);
|
|
@@ -1330,6 +1345,32 @@ function init() {
|
|
| 1330 |
}, 150);
|
| 1331 |
}
|
| 1332 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1333 |
document.querySelectorAll('.example-card[data-idx]').forEach(card => {
|
| 1334 |
card.addEventListener('click', () => {
|
| 1335 |
const idx = card.getAttribute('data-idx');
|
|
@@ -1337,23 +1378,10 @@ function init() {
|
|
| 1337 |
showToast('Invalid example index', 'error');
|
| 1338 |
return;
|
| 1339 |
}
|
| 1340 |
-
|
| 1341 |
document.querySelectorAll('.example-card.loading').forEach(c => c.classList.remove('loading'));
|
| 1342 |
card.classList.add('loading');
|
| 1343 |
showToast('Loading example...', 'info');
|
| 1344 |
-
|
| 1345 |
-
suppressNextExampleEmpty = true;
|
| 1346 |
-
setGradioValue('example-result-data', '');
|
| 1347 |
-
setGradioValue('example-idx-input', String(idx).trim());
|
| 1348 |
-
|
| 1349 |
-
setTimeout(() => {
|
| 1350 |
-
const btn = document.getElementById('example-load-btn');
|
| 1351 |
-
if (btn) {
|
| 1352 |
-
const b = btn.querySelector('button');
|
| 1353 |
-
if (b) b.click(); else btn.click();
|
| 1354 |
-
}
|
| 1355 |
-
startExamplePolling();
|
| 1356 |
-
}, 220);
|
| 1357 |
});
|
| 1358 |
});
|
| 1359 |
|
|
@@ -1361,14 +1389,6 @@ function init() {
|
|
| 1361 |
if (observerTarget) {
|
| 1362 |
const obs = new MutationObserver(() => {
|
| 1363 |
const current = getValueFromContainer('example-result-data');
|
| 1364 |
-
|
| 1365 |
-
if (suppressNextExampleEmpty && !current) {
|
| 1366 |
-
suppressNextExampleEmpty = false;
|
| 1367 |
-
return;
|
| 1368 |
-
}
|
| 1369 |
-
|
| 1370 |
-
if (!current) return;
|
| 1371 |
-
|
| 1372 |
if (current && current !== lastSeenExamplePayload) {
|
| 1373 |
lastSeenExamplePayload = current;
|
| 1374 |
if (examplePoller) {
|
|
@@ -1454,6 +1474,7 @@ with gr.Blocks() as demo:
|
|
| 1454 |
hidden_image_b64 = gr.Textbox(value="", elem_id="hidden-image-b64", elem_classes="hidden-input", container=False)
|
| 1455 |
hidden_pdf_b64 = gr.Textbox(value="", elem_id="hidden-pdf-b64", elem_classes="hidden-input", container=False)
|
| 1456 |
hidden_gif_b64 = gr.Textbox(value="", elem_id="hidden-gif-b64", elem_classes="hidden-input", container=False)
|
|
|
|
| 1457 |
prompt = gr.Textbox(value="", elem_id="prompt-gradio-input", elem_classes="hidden-input", container=False)
|
| 1458 |
hidden_model_name = gr.Textbox(value="Qwen3-VL-4B-Instruct", elem_id="hidden-model-name", elem_classes="hidden-input", container=False)
|
| 1459 |
|
|
@@ -1466,7 +1487,7 @@ with gr.Blocks() as demo:
|
|
| 1466 |
|
| 1467 |
result = gr.Textbox(value="", elem_id="gradio-result", elem_classes="hidden-input", container=False)
|
| 1468 |
|
| 1469 |
-
example_idx = gr.Textbox(value="", elem_id="example-idx-input", elem_classes="hidden-input", container=False)
|
| 1470 |
example_result = gr.Textbox(value="", elem_id="example-result-data", elem_classes="hidden-input", container=False)
|
| 1471 |
example_load_btn = gr.Button("Load Example", elem_id="example-load-btn")
|
| 1472 |
|
|
@@ -1631,6 +1652,7 @@ with gr.Blocks() as demo:
|
|
| 1631 |
hidden_image_b64,
|
| 1632 |
hidden_pdf_b64,
|
| 1633 |
hidden_gif_b64,
|
|
|
|
| 1634 |
max_new_tokens,
|
| 1635 |
temperature,
|
| 1636 |
top_p,
|
|
@@ -1639,7 +1661,7 @@ with gr.Blocks() as demo:
|
|
| 1639 |
gpu_duration_state,
|
| 1640 |
],
|
| 1641 |
outputs=[result],
|
| 1642 |
-
js=r"""(mode, model, p, img, pdf, gif, mnt, t, tp, tk, rp, gd) => {
|
| 1643 |
const modelEl = document.querySelector('.model-tab.active');
|
| 1644 |
const modeEl = document.querySelector('.mode-tab.active');
|
| 1645 |
const modelVal = modelEl ? modelEl.getAttribute('data-model') : model;
|
|
@@ -1665,7 +1687,7 @@ with gr.Blocks() as demo:
|
|
| 1665 |
if (inner) gifVal = inner.value;
|
| 1666 |
}
|
| 1667 |
|
| 1668 |
-
return [modeVal, modelVal, promptVal, imgVal, pdfVal, gifVal, mnt, t, tp, tk, rp, gd];
|
| 1669 |
}""",
|
| 1670 |
)
|
| 1671 |
|
|
|
|
| 78 |
"Qwen2.5-VL-7B-Instruct": (processor_m7b, model_m7b),
|
| 79 |
"Qwen2.5-VL-3B-Instruct": (processor_x3b, model_x3b),
|
| 80 |
}
|
| 81 |
+
|
| 82 |
MODEL_CHOICES = list(MODEL_MAP.keys())
|
| 83 |
|
| 84 |
image_examples = [
|
|
|
|
| 212 |
|
| 213 |
|
| 214 |
def load_example_data(idx_str):
|
| 215 |
+
if idx_str is None:
|
| 216 |
+
return json.dumps({"status": "error", "message": "Invalid example index"})
|
| 217 |
+
idx_str = str(idx_str).strip()
|
| 218 |
+
if idx_str == "":
|
| 219 |
return json.dumps({"status": "error", "message": "Invalid example index"})
|
|
|
|
| 220 |
try:
|
| 221 |
+
idx = int(float(idx_str))
|
|
|
|
|
|
|
|
|
|
| 222 |
except Exception:
|
| 223 |
return json.dumps({"status": "error", "message": "Invalid example index"})
|
|
|
|
| 224 |
if idx < 0 or idx >= len(all_examples):
|
| 225 |
return json.dumps({"status": "error", "message": "Example index out of range"})
|
|
|
|
| 226 |
ex = all_examples[idx]
|
| 227 |
+
|
| 228 |
payload = {
|
| 229 |
"status": "ok",
|
| 230 |
"query": ex["query"],
|
|
|
|
| 332 |
|
| 333 |
|
| 334 |
@spaces.GPU(duration=calc_timeout_generic)
|
| 335 |
+
def generate_pdf(model_name: str, text: str, state,
|
| 336 |
max_new_tokens: int = 2048, temperature: float = 0.6,
|
| 337 |
top_p: float = 0.9, top_k: int = 50,
|
| 338 |
repetition_penalty: float = 1.2, gpu_timeout: int = 120):
|
| 339 |
+
if not state or not state["pages"]:
|
| 340 |
raise gr.Error("Please upload a PDF file first.")
|
| 341 |
processor, model = select_model(model_name)
|
| 342 |
+
page_images = state["pages"]
|
| 343 |
full_response = ""
|
| 344 |
+
for i, image in enumerate(page_images):
|
| 345 |
+
page_header = f"--- Page {i+1}/{len(page_images)} ---\n"
|
| 346 |
yield full_response + page_header
|
| 347 |
messages = [{"role": "user", "content": [{"type": "image"}, {"type": "text", "text": text}]}]
|
| 348 |
prompt_full = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
|
|
|
|
| 448 |
torch.cuda.empty_cache()
|
| 449 |
|
| 450 |
|
| 451 |
+
def run_router(tab_kind, model_name, text, image_b64, pdf_b64, gif_b64, pdf_state_json,
|
| 452 |
max_new_tokens_v, temperature_v, top_p_v, top_k_v, repetition_penalty_v, gpu_timeout_v):
|
| 453 |
if tab_kind == "pdf":
|
| 454 |
temp_pdf_path = b64_to_temp_file(pdf_b64)
|
|
|
|
| 456 |
raise gr.Error("Could not decode uploaded PDF.")
|
| 457 |
try:
|
| 458 |
pages = convert_pdf_to_images(temp_pdf_path)
|
| 459 |
+
state = {"pages": pages, "total_pages": len(pages), "current_page_index": 0}
|
| 460 |
yield from generate_pdf(
|
| 461 |
model_name=model_name,
|
| 462 |
text=text,
|
| 463 |
+
state=state,
|
| 464 |
max_new_tokens=max_new_tokens_v,
|
| 465 |
temperature=temperature_v,
|
| 466 |
top_p=top_p_v,
|
|
|
|
| 827 |
const runBtnEl = document.getElementById('custom-run-btn');
|
| 828 |
const outputArea = document.getElementById('custom-output-textarea');
|
| 829 |
const mediaStatus = document.getElementById('sb-media-status');
|
| 830 |
+
const exampleResultContainer = document.getElementById('example-result-data');
|
| 831 |
const pdfPrevBtn = document.getElementById('pdf-prev-btn');
|
| 832 |
const pdfNextBtn = document.getElementById('pdf-next-btn');
|
| 833 |
const pdfPageInfo = document.getElementById('pdf-page-info');
|
|
|
|
| 843 |
let toastTimer = null;
|
| 844 |
let examplePoller = null;
|
| 845 |
let lastSeenExamplePayload = null;
|
|
|
|
| 846 |
|
| 847 |
function showToast(message, type) {
|
| 848 |
let toast = document.getElementById('app-toast');
|
|
|
|
| 901 |
|
| 902 |
function setGradioValue(containerId, value) {
|
| 903 |
const container = document.getElementById(containerId);
|
| 904 |
+
if (!container) return false;
|
| 905 |
+
const el = container.querySelector('textarea, input');
|
| 906 |
+
if (!el) return false;
|
| 907 |
+
const proto = el.tagName === 'TEXTAREA' ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
|
| 908 |
+
const ns = Object.getOwnPropertyDescriptor(proto, 'value');
|
| 909 |
+
if (ns && ns.set) {
|
| 910 |
+
ns.set.call(el, value);
|
| 911 |
+
el.dispatchEvent(new Event('input', {bubbles:true, composed:true}));
|
| 912 |
+
el.dispatchEvent(new Event('change', {bubbles:true, composed:true}));
|
| 913 |
+
return true;
|
| 914 |
+
}
|
| 915 |
+
return false;
|
| 916 |
+
}
|
| 917 |
+
|
| 918 |
+
function setGradioValueWithRetry(containerId, value, tries = 25, delay = 120) {
|
| 919 |
+
let attempt = 0;
|
| 920 |
+
function tick() {
|
| 921 |
+
attempt += 1;
|
| 922 |
+
const ok = setGradioValue(containerId, value);
|
| 923 |
+
if (!ok && attempt < tries) setTimeout(tick, delay);
|
| 924 |
+
}
|
| 925 |
+
tick();
|
| 926 |
}
|
| 927 |
|
| 928 |
function syncMediaToGradio() {
|
|
|
|
| 1006 |
mediaState = null;
|
| 1007 |
renderPreview();
|
| 1008 |
}
|
| 1009 |
+
window.__clearPreview = clearPreview;
|
| 1010 |
|
| 1011 |
function processFile(file) {
|
| 1012 |
if (!file) return;
|
|
|
|
| 1118 |
if (mediaState && mediaState.mode !== mode) clearPreview();
|
| 1119 |
}
|
| 1120 |
|
| 1121 |
+
window.__activateModeTab = activateModeTab;
|
| 1122 |
+
window.__activateModelTab = activateModelTab;
|
| 1123 |
+
|
| 1124 |
if (uploadClick) uploadClick.addEventListener('click', () => fileInput.click());
|
| 1125 |
if (btnUpload) btnUpload.addEventListener('click', () => fileInput.click());
|
| 1126 |
if (btnClear) btnClear.addEventListener('click', clearPreview);
|
|
|
|
| 1345 |
}, 150);
|
| 1346 |
}
|
| 1347 |
|
| 1348 |
+
function triggerExampleLoad(idx) {
|
| 1349 |
+
setGradioValueWithRetry('example-result-data', '');
|
| 1350 |
+
setGradioValueWithRetry('example-idx-input', String(idx));
|
| 1351 |
+
|
| 1352 |
+
let triggerAttempts = 0;
|
| 1353 |
+
function tryClick() {
|
| 1354 |
+
triggerAttempts += 1;
|
| 1355 |
+
const idxNow = getValueFromContainer('example-idx-input');
|
| 1356 |
+
const btnWrap = document.getElementById('example-load-btn');
|
| 1357 |
+
const btn = btnWrap ? (btnWrap.querySelector('button') || btnWrap) : null;
|
| 1358 |
+
|
| 1359 |
+
if (idxNow === String(idx) && btn) {
|
| 1360 |
+
btn.click();
|
| 1361 |
+
startExamplePolling();
|
| 1362 |
+
return;
|
| 1363 |
+
}
|
| 1364 |
+
if (triggerAttempts < 25) {
|
| 1365 |
+
setTimeout(tryClick, 120);
|
| 1366 |
+
} else {
|
| 1367 |
+
document.querySelectorAll('.example-card.loading').forEach(c => c.classList.remove('loading'));
|
| 1368 |
+
showToast('Failed to initialize example loader', 'error');
|
| 1369 |
+
}
|
| 1370 |
+
}
|
| 1371 |
+
setTimeout(tryClick, 180);
|
| 1372 |
+
}
|
| 1373 |
+
|
| 1374 |
document.querySelectorAll('.example-card[data-idx]').forEach(card => {
|
| 1375 |
card.addEventListener('click', () => {
|
| 1376 |
const idx = card.getAttribute('data-idx');
|
|
|
|
| 1378 |
showToast('Invalid example index', 'error');
|
| 1379 |
return;
|
| 1380 |
}
|
|
|
|
| 1381 |
document.querySelectorAll('.example-card.loading').forEach(c => c.classList.remove('loading'));
|
| 1382 |
card.classList.add('loading');
|
| 1383 |
showToast('Loading example...', 'info');
|
| 1384 |
+
triggerExampleLoad(idx);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1385 |
});
|
| 1386 |
});
|
| 1387 |
|
|
|
|
| 1389 |
if (observerTarget) {
|
| 1390 |
const obs = new MutationObserver(() => {
|
| 1391 |
const current = getValueFromContainer('example-result-data');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1392 |
if (current && current !== lastSeenExamplePayload) {
|
| 1393 |
lastSeenExamplePayload = current;
|
| 1394 |
if (examplePoller) {
|
|
|
|
| 1474 |
hidden_image_b64 = gr.Textbox(value="", elem_id="hidden-image-b64", elem_classes="hidden-input", container=False)
|
| 1475 |
hidden_pdf_b64 = gr.Textbox(value="", elem_id="hidden-pdf-b64", elem_classes="hidden-input", container=False)
|
| 1476 |
hidden_gif_b64 = gr.Textbox(value="", elem_id="hidden-gif-b64", elem_classes="hidden-input", container=False)
|
| 1477 |
+
hidden_pdf_state = gr.Textbox(value="", elem_id="hidden-pdf-state", elem_classes="hidden-input", container=False)
|
| 1478 |
prompt = gr.Textbox(value="", elem_id="prompt-gradio-input", elem_classes="hidden-input", container=False)
|
| 1479 |
hidden_model_name = gr.Textbox(value="Qwen3-VL-4B-Instruct", elem_id="hidden-model-name", elem_classes="hidden-input", container=False)
|
| 1480 |
|
|
|
|
| 1487 |
|
| 1488 |
result = gr.Textbox(value="", elem_id="gradio-result", elem_classes="hidden-input", container=False)
|
| 1489 |
|
| 1490 |
+
example_idx = gr.Textbox(value="-1", elem_id="example-idx-input", elem_classes="hidden-input", container=False)
|
| 1491 |
example_result = gr.Textbox(value="", elem_id="example-result-data", elem_classes="hidden-input", container=False)
|
| 1492 |
example_load_btn = gr.Button("Load Example", elem_id="example-load-btn")
|
| 1493 |
|
|
|
|
| 1652 |
hidden_image_b64,
|
| 1653 |
hidden_pdf_b64,
|
| 1654 |
hidden_gif_b64,
|
| 1655 |
+
hidden_pdf_state,
|
| 1656 |
max_new_tokens,
|
| 1657 |
temperature,
|
| 1658 |
top_p,
|
|
|
|
| 1661 |
gpu_duration_state,
|
| 1662 |
],
|
| 1663 |
outputs=[result],
|
| 1664 |
+
js=r"""(mode, model, p, img, pdf, gif, pdfs, mnt, t, tp, tk, rp, gd) => {
|
| 1665 |
const modelEl = document.querySelector('.model-tab.active');
|
| 1666 |
const modeEl = document.querySelector('.mode-tab.active');
|
| 1667 |
const modelVal = modelEl ? modelEl.getAttribute('data-model') : model;
|
|
|
|
| 1687 |
if (inner) gifVal = inner.value;
|
| 1688 |
}
|
| 1689 |
|
| 1690 |
+
return [modeVal, modelVal, promptVal, imgVal, pdfVal, gifVal, pdfs, mnt, t, tp, tk, rp, gd];
|
| 1691 |
}""",
|
| 1692 |
)
|
| 1693 |
|