Spaces:
Sleeping
Sleeping
Update main.py
Browse files
main.py
CHANGED
|
@@ -364,649 +364,6 @@ class SpeechRequest(BaseModel):
|
|
| 364 |
# ---------------------------------------------------------------------------
|
| 365 |
# Routes
|
| 366 |
# ---------------------------------------------------------------------------
|
| 367 |
-
html = """
|
| 368 |
-
<!DOCTYPE html>
|
| 369 |
-
<html lang="en">
|
| 370 |
-
<head>
|
| 371 |
-
<meta charset="UTF-8" />
|
| 372 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 373 |
-
<title>Devil Studio — TTS Demo</title>
|
| 374 |
-
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
| 375 |
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
| 376 |
-
<link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=DM+Mono:ital,wght@0,300;0,400;0,500;1,300&family=DM+Sans:ital,wght@0,300;0,400;0,500;1,300&display=swap" rel="stylesheet" />
|
| 377 |
-
|
| 378 |
-
<style>
|
| 379 |
-
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
| 380 |
-
|
| 381 |
-
:root {
|
| 382 |
-
--bg: #0a0a0a;
|
| 383 |
-
--surface: #111111;
|
| 384 |
-
--surface2: #181818;
|
| 385 |
-
--border: #242424;
|
| 386 |
-
--accent: #ff3c00;
|
| 387 |
-
--accent2: #ff6b35;
|
| 388 |
-
--muted: #444;
|
| 389 |
-
--text: #e8e8e8;
|
| 390 |
-
--dim: #666;
|
| 391 |
-
--mono: 'DM Mono', monospace;
|
| 392 |
-
--sans: 'DM Sans', sans-serif;
|
| 393 |
-
--disp: 'Bebas Neue', sans-serif;
|
| 394 |
-
--r: 4px;
|
| 395 |
-
}
|
| 396 |
-
|
| 397 |
-
html { font-size: 16px; scroll-behavior: smooth; }
|
| 398 |
-
|
| 399 |
-
body {
|
| 400 |
-
background: var(--bg);
|
| 401 |
-
color: var(--text);
|
| 402 |
-
font-family: var(--sans);
|
| 403 |
-
font-weight: 300;
|
| 404 |
-
min-height: 100vh;
|
| 405 |
-
overflow-x: hidden;
|
| 406 |
-
}
|
| 407 |
-
|
| 408 |
-
/* noise */
|
| 409 |
-
body::before {
|
| 410 |
-
content: '';
|
| 411 |
-
position: fixed; inset: 0;
|
| 412 |
-
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.04'/%3E%3C/svg%3E");
|
| 413 |
-
pointer-events: none; z-index: 999; opacity: 0.35;
|
| 414 |
-
}
|
| 415 |
-
|
| 416 |
-
/* grid */
|
| 417 |
-
body::after {
|
| 418 |
-
content: '';
|
| 419 |
-
position: fixed; inset: 0;
|
| 420 |
-
background-image: linear-gradient(rgba(255,60,0,0.025) 1px,transparent 1px),linear-gradient(90deg,rgba(255,60,0,0.025) 1px,transparent 1px);
|
| 421 |
-
background-size: 40px 40px;
|
| 422 |
-
pointer-events: none; z-index: 0;
|
| 423 |
-
}
|
| 424 |
-
|
| 425 |
-
/* ── Header ──────────────────────────────────────────────────── */
|
| 426 |
-
header {
|
| 427 |
-
position: relative; z-index: 10;
|
| 428 |
-
padding: 36px 48px 0;
|
| 429 |
-
display: flex; align-items: flex-start; justify-content: space-between;
|
| 430 |
-
animation: fadeDown .6s ease both;
|
| 431 |
-
}
|
| 432 |
-
|
| 433 |
-
.logo-name {
|
| 434 |
-
font-family: var(--disp);
|
| 435 |
-
font-size: clamp(40px,6vw,76px);
|
| 436 |
-
letter-spacing: .04em; line-height: .9;
|
| 437 |
-
background: linear-gradient(135deg,#fff 0%,#ff3c00 55%,#ff6b35 100%);
|
| 438 |
-
-webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;
|
| 439 |
-
}
|
| 440 |
-
|
| 441 |
-
.logo-sub {
|
| 442 |
-
font-family: var(--mono); font-size: 10px;
|
| 443 |
-
letter-spacing: .28em; text-transform: uppercase;
|
| 444 |
-
color: var(--dim); padding-left: 2px; margin-top: 4px; display: block;
|
| 445 |
-
}
|
| 446 |
-
|
| 447 |
-
.status-badge {
|
| 448 |
-
display: flex; align-items: center; gap: 8px;
|
| 449 |
-
font-family: var(--mono); font-size: 11px;
|
| 450 |
-
letter-spacing: .1em; color: var(--dim);
|
| 451 |
-
text-transform: uppercase; padding-top: 10px;
|
| 452 |
-
}
|
| 453 |
-
|
| 454 |
-
.dot {
|
| 455 |
-
width: 7px; height: 7px; border-radius: 50%;
|
| 456 |
-
background: #2a2a2a; transition: background .3s, box-shadow .3s;
|
| 457 |
-
}
|
| 458 |
-
.dot.online { background: #00e676; box-shadow: 0 0 8px rgba(0,230,118,.6); animation: pulse 2s infinite; }
|
| 459 |
-
.dot.error { background: var(--accent); }
|
| 460 |
-
|
| 461 |
-
@keyframes pulse { 0%,100%{opacity:1} 50%{opacity:.45} }
|
| 462 |
-
|
| 463 |
-
/* ── Divider ──────────────────────────────────────────────────── */
|
| 464 |
-
.hero {
|
| 465 |
-
position: relative; z-index: 10;
|
| 466 |
-
padding: 40px 48px 0;
|
| 467 |
-
animation: fadeDown .7s .1s ease both;
|
| 468 |
-
}
|
| 469 |
-
.hero-line {
|
| 470 |
-
display: block;
|
| 471 |
-
font-family: var(--disp);
|
| 472 |
-
font-size: clamp(16px,3vw,34px);
|
| 473 |
-
letter-spacing: .1em; color: var(--muted); line-height: 1;
|
| 474 |
-
}
|
| 475 |
-
.divider {
|
| 476 |
-
width: 80px; height: 2px; background: var(--accent);
|
| 477 |
-
margin: 20px 0; position: relative;
|
| 478 |
-
}
|
| 479 |
-
.divider::after {
|
| 480 |
-
content: '';
|
| 481 |
-
position: absolute; left: 80px; top: 0;
|
| 482 |
-
width: 300px; height: 2px;
|
| 483 |
-
background: linear-gradient(90deg,var(--accent),transparent); opacity: .18;
|
| 484 |
-
}
|
| 485 |
-
|
| 486 |
-
/* ── Layout ───────────────────────────────────────────────────── */
|
| 487 |
-
main {
|
| 488 |
-
position: relative; z-index: 10;
|
| 489 |
-
display: grid; grid-template-columns: 1fr 320px;
|
| 490 |
-
gap: 2px; padding: 28px 48px 48px; max-width: 1300px;
|
| 491 |
-
}
|
| 492 |
-
|
| 493 |
-
@media(max-width:860px){
|
| 494 |
-
main { grid-template-columns:1fr; padding:20px; }
|
| 495 |
-
header,.hero { padding-left:20px; padding-right:20px; }
|
| 496 |
-
}
|
| 497 |
-
|
| 498 |
-
/* ── Panel ────────────────────────────────────────────────────── */
|
| 499 |
-
.panel {
|
| 500 |
-
background: var(--surface); border: 1px solid var(--border);
|
| 501 |
-
padding: 26px; position: relative;
|
| 502 |
-
animation: fadeUp .6s .2s ease both;
|
| 503 |
-
}
|
| 504 |
-
.panel-r { border-left: none; animation-delay: .32s; display:flex; flex-direction:column; gap:22px; }
|
| 505 |
-
|
| 506 |
-
@media(max-width:860px){
|
| 507 |
-
.panel-r { border-left:1px solid var(--border); border-top:none; }
|
| 508 |
-
}
|
| 509 |
-
|
| 510 |
-
.sec-label {
|
| 511 |
-
font-family: var(--mono); font-size: 10px;
|
| 512 |
-
letter-spacing: .28em; text-transform: uppercase; color: var(--dim);
|
| 513 |
-
margin-bottom: 14px;
|
| 514 |
-
display: flex; align-items: center; gap: 10px;
|
| 515 |
-
}
|
| 516 |
-
.sec-label::after { content:''; flex:1; height:1px; background:var(--border); }
|
| 517 |
-
|
| 518 |
-
/* ── Textarea ─────────────────────────────────────────────────── */
|
| 519 |
-
.ta-wrap { position: relative; }
|
| 520 |
-
|
| 521 |
-
textarea {
|
| 522 |
-
width: 100%; min-height: 170px;
|
| 523 |
-
background: var(--surface2); border: 1px solid var(--border);
|
| 524 |
-
color: var(--text); font-family: var(--sans); font-size: 15px;
|
| 525 |
-
font-weight: 300; line-height: 1.75; padding: 14px 16px;
|
| 526 |
-
resize: vertical; outline: none; border-radius: var(--r);
|
| 527 |
-
transition: border-color .2s; caret-color: var(--accent);
|
| 528 |
-
}
|
| 529 |
-
textarea:focus { border-color: var(--accent); }
|
| 530 |
-
textarea::placeholder { color: var(--dim); }
|
| 531 |
-
|
| 532 |
-
.cc {
|
| 533 |
-
position: absolute; bottom: 10px; right: 12px;
|
| 534 |
-
font-family: var(--mono); font-size: 10px; color: var(--dim); pointer-events: none;
|
| 535 |
-
}
|
| 536 |
-
|
| 537 |
-
/* ── Speed ────────────────────────────────────────────────────── */
|
| 538 |
-
.speed-row {
|
| 539 |
-
display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;
|
| 540 |
-
}
|
| 541 |
-
.speed-label { font-family:var(--mono); font-size:10px; letter-spacing:.22em; text-transform:uppercase; color:var(--dim); }
|
| 542 |
-
.speed-num { font-family:var(--mono); font-size:18px; font-weight:500; color:var(--accent); }
|
| 543 |
-
|
| 544 |
-
input[type=range] {
|
| 545 |
-
-webkit-appearance:none; appearance:none;
|
| 546 |
-
width:100%; height:2px; background:var(--border); outline:none; cursor:pointer; border-radius:1px; display:block;
|
| 547 |
-
}
|
| 548 |
-
input[type=range]::-webkit-slider-thumb {
|
| 549 |
-
-webkit-appearance:none; width:15px; height:15px; border-radius:50%;
|
| 550 |
-
background:var(--accent); cursor:pointer;
|
| 551 |
-
box-shadow:0 0 8px rgba(255,60,0,.5); transition:box-shadow .2s,transform .1s;
|
| 552 |
-
}
|
| 553 |
-
input[type=range]::-webkit-slider-thumb:hover { box-shadow:0 0 16px rgba(255,60,0,.8); transform:scale(1.2); }
|
| 554 |
-
|
| 555 |
-
.speed-marks {
|
| 556 |
-
display:flex; justify-content:space-between;
|
| 557 |
-
font-family:var(--mono); font-size:9px; color:var(--dim); margin-top:5px;
|
| 558 |
-
}
|
| 559 |
-
|
| 560 |
-
/* ── Generate btn ─────────────────────────────────────────────── */
|
| 561 |
-
.btn-gen {
|
| 562 |
-
width:100%; margin-top:18px; padding:15px;
|
| 563 |
-
background:var(--accent); border:none; color:#fff;
|
| 564 |
-
font-family:var(--disp); font-size:20px; letter-spacing:.12em;
|
| 565 |
-
cursor:pointer; border-radius:var(--r); position:relative; overflow:hidden;
|
| 566 |
-
transition:background .2s,transform .1s;
|
| 567 |
-
}
|
| 568 |
-
.btn-gen::before {
|
| 569 |
-
content:''; position:absolute; inset:0;
|
| 570 |
-
background:linear-gradient(135deg,rgba(255,255,255,.1),transparent 55%); pointer-events:none;
|
| 571 |
-
}
|
| 572 |
-
.btn-gen:hover { background:var(--accent2); }
|
| 573 |
-
.btn-gen:active { transform:scale(.99); }
|
| 574 |
-
.btn-gen:disabled { background:var(--muted); cursor:not-allowed; transform:none; }
|
| 575 |
-
|
| 576 |
-
.btn-inner { display:inline-flex; align-items:center; gap:10px; }
|
| 577 |
-
.spinner {
|
| 578 |
-
display:none; width:15px; height:15px;
|
| 579 |
-
border:2px solid rgba(255,255,255,.3); border-top-color:#fff;
|
| 580 |
-
border-radius:50%; animation:spin .7s linear infinite;
|
| 581 |
-
}
|
| 582 |
-
.btn-gen.loading .spinner { display:block; }
|
| 583 |
-
.btn-gen.loading .blabel { opacity:.7; }
|
| 584 |
-
@keyframes spin { to{transform:rotate(360deg)} }
|
| 585 |
-
|
| 586 |
-
/* ── Audio output ─────────────────────────────────────────────── */
|
| 587 |
-
.out-section { margin-top:18px; }
|
| 588 |
-
|
| 589 |
-
.wvz {
|
| 590 |
-
background:var(--surface2); border:1px solid var(--border);
|
| 591 |
-
border-radius:var(--r); height:72px;
|
| 592 |
-
display:flex; align-items:center; justify-content:center;
|
| 593 |
-
overflow:hidden; position:relative; margin-bottom:10px;
|
| 594 |
-
}
|
| 595 |
-
|
| 596 |
-
.wvz-ph { font-family:var(--mono); font-size:11px; color:var(--dim); letter-spacing:.2em; text-transform:uppercase; }
|
| 597 |
-
|
| 598 |
-
.wvz-bars { display:none; align-items:center; gap:2px; height:100%; padding:10px 16px; }
|
| 599 |
-
.wvz-bars.on { display:flex; }
|
| 600 |
-
|
| 601 |
-
.bar {
|
| 602 |
-
width:3px; border-radius:2px; background:var(--accent); opacity:.7;
|
| 603 |
-
animation:wa 1.2s ease-in-out infinite;
|
| 604 |
-
}
|
| 605 |
-
@keyframes wa { 0%,100%{transform:scaleY(.25)} 50%{transform:scaleY(1)} }
|
| 606 |
-
|
| 607 |
-
audio {
|
| 608 |
-
width:100%; height:38px; outline:none; border-radius:var(--r);
|
| 609 |
-
filter:invert(1) hue-rotate(180deg); opacity:.8;
|
| 610 |
-
}
|
| 611 |
-
|
| 612 |
-
.act-row { display:flex; gap:7px; margin-top:9px; }
|
| 613 |
-
|
| 614 |
-
.act-btn {
|
| 615 |
-
flex:1; padding:9px; background:var(--surface2); border:1px solid var(--border);
|
| 616 |
-
color:var(--dim); font-family:var(--mono); font-size:11px;
|
| 617 |
-
letter-spacing:.1em; text-transform:uppercase; cursor:pointer;
|
| 618 |
-
border-radius:var(--r); transition:all .15s;
|
| 619 |
-
display:flex; align-items:center; justify-content:center; gap:6px;
|
| 620 |
-
text-decoration:none;
|
| 621 |
-
}
|
| 622 |
-
.act-btn:hover { border-color:var(--accent); color:var(--accent); }
|
| 623 |
-
|
| 624 |
-
/* ── Right panel pieces ───────────────────────────────────────── */
|
| 625 |
-
.model-cards { display:flex; flex-direction:column; gap:5px; }
|
| 626 |
-
|
| 627 |
-
.mc {
|
| 628 |
-
background:var(--surface2); border:1px solid var(--border);
|
| 629 |
-
border-radius:var(--r); padding:10px 13px;
|
| 630 |
-
cursor:pointer; transition:border-color .2s,background .2s;
|
| 631 |
-
display:flex; align-items:center; justify-content:space-between;
|
| 632 |
-
}
|
| 633 |
-
.mc:hover { border-color:var(--muted); }
|
| 634 |
-
.mc.sel { border-color:var(--accent); background:rgba(255,60,0,.06); }
|
| 635 |
-
.mc-name { font-family:var(--mono); font-size:12px; font-weight:500; color:var(--text); letter-spacing:.04em; }
|
| 636 |
-
.mc-desc { font-size:11px; color:var(--dim); margin-top:2px; }
|
| 637 |
-
.mc-badge {
|
| 638 |
-
font-family:var(--mono); font-size:9px; letter-spacing:.1em; text-transform:uppercase;
|
| 639 |
-
padding:3px 7px; border-radius:2px; border:1px solid var(--border); color:var(--dim);
|
| 640 |
-
transition:border-color .2s,color .2s;
|
| 641 |
-
}
|
| 642 |
-
.mc.sel .mc-badge { border-color:var(--accent); color:var(--accent); }
|
| 643 |
-
|
| 644 |
-
.voice-grid { display:grid; grid-template-columns:repeat(4,1fr); gap:5px; }
|
| 645 |
-
|
| 646 |
-
.vbtn {
|
| 647 |
-
background:var(--surface2); border:1px solid var(--border);
|
| 648 |
-
color:var(--dim); font-family:var(--mono); font-size:11px;
|
| 649 |
-
padding:9px 4px; text-align:center; cursor:pointer;
|
| 650 |
-
border-radius:var(--r); transition:all .15s; letter-spacing:.04em;
|
| 651 |
-
}
|
| 652 |
-
.vbtn:hover { border-color:var(--muted); color:var(--text); }
|
| 653 |
-
.vbtn.sel { border-color:var(--accent); color:var(--accent); background:rgba(255,60,0,.06); }
|
| 654 |
-
|
| 655 |
-
.fmt-chips { display:flex; gap:5px; flex-wrap:wrap; }
|
| 656 |
-
|
| 657 |
-
.chip {
|
| 658 |
-
font-family:var(--mono); font-size:11px; letter-spacing:.1em;
|
| 659 |
-
text-transform:uppercase; padding:6px 13px;
|
| 660 |
-
border:1px solid var(--border); border-radius:var(--r);
|
| 661 |
-
cursor:pointer; color:var(--dim); background:var(--surface2); transition:all .15s;
|
| 662 |
-
}
|
| 663 |
-
.chip:hover { border-color:var(--muted); color:var(--text); }
|
| 664 |
-
.chip.sel { border-color:var(--accent); color:var(--accent); background:rgba(255,60,0,.06); }
|
| 665 |
-
|
| 666 |
-
.srv-info { font-family:var(--mono); font-size:11px; color:var(--dim); line-height:2; }
|
| 667 |
-
|
| 668 |
-
/* ── Status bar ───────────────────────────────────────────────── */
|
| 669 |
-
.sbar {
|
| 670 |
-
grid-column:1/-1; border:1px solid var(--border); border-top:none;
|
| 671 |
-
background:var(--surface); padding:9px 26px;
|
| 672 |
-
display:flex; align-items:center; gap:22px; flex-wrap:wrap;
|
| 673 |
-
font-family:var(--mono); font-size:10px; color:var(--dim); letter-spacing:.07em;
|
| 674 |
-
animation:fadeUp .6s .5s ease both;
|
| 675 |
-
}
|
| 676 |
-
.si { display:flex; align-items:center; gap:5px; }
|
| 677 |
-
.si .k { color:var(--muted); }
|
| 678 |
-
.si .v { color:var(--text); }
|
| 679 |
-
.si .v.ac { color:var(--accent); }
|
| 680 |
-
|
| 681 |
-
.mbars { display:flex; align-items:flex-end; gap:2px; margin-left:auto; }
|
| 682 |
-
.mb { width:3px; height:10px; background:var(--border); border-radius:1px; transition:background .3s; }
|
| 683 |
-
.mb.lit { background:#00e676; }
|
| 684 |
-
|
| 685 |
-
/* ── Toast ────────────────────────────────────────────────────── */
|
| 686 |
-
.toast {
|
| 687 |
-
position:fixed; bottom:22px; right:22px;
|
| 688 |
-
background:#1a0a0a; border:1px solid var(--accent);
|
| 689 |
-
color:var(--text); font-family:var(--mono); font-size:12px;
|
| 690 |
-
padding:13px 17px; border-radius:var(--r); z-index:1000;
|
| 691 |
-
max-width:300px; transform:translateY(70px); opacity:0;
|
| 692 |
-
transition:all .3s ease; box-shadow:0 0 20px rgba(255,60,0,.15);
|
| 693 |
-
}
|
| 694 |
-
.toast.show { transform:translateY(0); opacity:1; }
|
| 695 |
-
|
| 696 |
-
@keyframes fadeDown { from{opacity:0;transform:translateY(-14px)} to{opacity:1;transform:translateY(0)} }
|
| 697 |
-
@keyframes fadeUp { from{opacity:0;transform:translateY(14px)} to{opacity:1;transform:translateY(0)} }
|
| 698 |
-
</style>
|
| 699 |
-
</head>
|
| 700 |
-
<body>
|
| 701 |
-
|
| 702 |
-
<header>
|
| 703 |
-
<div>
|
| 704 |
-
<div class="logo-name">DEVIL STUDIO</div>
|
| 705 |
-
<span class="logo-sub">Text · to · Speech · API / v1.0.0</span>
|
| 706 |
-
</div>
|
| 707 |
-
<div class="status-badge">
|
| 708 |
-
<div class="dot" id="dot"></div>
|
| 709 |
-
<span id="statusTxt">Connecting…</span>
|
| 710 |
-
</div>
|
| 711 |
-
</header>
|
| 712 |
-
|
| 713 |
-
<div class="hero">
|
| 714 |
-
<span class="hero-line">SYNTHESISE SPEECH.</span>
|
| 715 |
-
<span class="hero-line">INSTANTLY.</span>
|
| 716 |
-
<div class="divider"></div>
|
| 717 |
-
</div>
|
| 718 |
-
|
| 719 |
-
<main>
|
| 720 |
-
|
| 721 |
-
<!-- ── Left panel ──────────────────────────────────────────── -->
|
| 722 |
-
<div class="panel">
|
| 723 |
-
<div class="sec-label">Input</div>
|
| 724 |
-
|
| 725 |
-
<div class="ta-wrap">
|
| 726 |
-
<textarea id="tin" placeholder="Type or paste your text here…" maxlength="5000" spellcheck="true">Devil Studio delivers low-latency, high-quality speech synthesis powered by KittenTTS — three models, eight voices, permanently loaded in memory and ready to respond.</textarea>
|
| 727 |
-
<span class="cc"><span id="cc">0</span> / 5000</span>
|
| 728 |
-
</div>
|
| 729 |
-
|
| 730 |
-
<div style="margin-top:18px;">
|
| 731 |
-
<div class="speed-row">
|
| 732 |
-
<span class="speed-label">Speed</span>
|
| 733 |
-
<span class="speed-num" id="sv">1.00×</span>
|
| 734 |
-
</div>
|
| 735 |
-
<input type="range" id="spd" min="0.25" max="4" step="0.05" value="1.0" />
|
| 736 |
-
<div class="speed-marks">
|
| 737 |
-
<span>0.25×</span><span>1×</span><span>2×</span><span>3×</span><span>4×</span>
|
| 738 |
-
</div>
|
| 739 |
-
</div>
|
| 740 |
-
|
| 741 |
-
<button class="btn-gen" id="genBtn" onclick="generate()">
|
| 742 |
-
<span class="btn-inner">
|
| 743 |
-
<div class="spinner"></div>
|
| 744 |
-
<span class="blabel">GENERATE SPEECH</span>
|
| 745 |
-
</span>
|
| 746 |
-
</button>
|
| 747 |
-
|
| 748 |
-
<!-- Output -->
|
| 749 |
-
<div class="out-section" id="outSection" style="display:none;">
|
| 750 |
-
<div style="height:14px;"></div>
|
| 751 |
-
<div class="sec-label">Output</div>
|
| 752 |
-
<div class="wvz">
|
| 753 |
-
<span class="wvz-ph" id="wvph">AWAITING SIGNAL</span>
|
| 754 |
-
<div class="wvz-bars" id="wvbars"></div>
|
| 755 |
-
</div>
|
| 756 |
-
<audio id="ap" controls></audio>
|
| 757 |
-
<div class="act-row">
|
| 758 |
-
<a class="act-btn" id="dlBtn" href="#" download="speech.wav">↓ Download</a>
|
| 759 |
-
<button class="act-btn" onclick="copyCurl()">⌘ Copy cURL</button>
|
| 760 |
-
</div>
|
| 761 |
-
</div>
|
| 762 |
-
</div>
|
| 763 |
-
|
| 764 |
-
<!-- ── Right panel ─────────────────────────────────────────── -->
|
| 765 |
-
<div class="panel panel-r">
|
| 766 |
-
|
| 767 |
-
<div>
|
| 768 |
-
<div class="sec-label">Model</div>
|
| 769 |
-
<div class="model-cards">
|
| 770 |
-
<div class="mc sel" data-m="tts-1" onclick="selModel(this)">
|
| 771 |
-
<div><div class="mc-name">tts-1</div><div class="mc-desc">Nano · 15M · Fastest</div></div>
|
| 772 |
-
<span class="mc-badge">Speed</span>
|
| 773 |
-
</div>
|
| 774 |
-
<div class="mc" data-m="tts-1-hd" onclick="selModel(this)">
|
| 775 |
-
<div><div class="mc-name">tts-1-hd</div><div class="mc-desc">Micro · 40M · Balanced</div></div>
|
| 776 |
-
<span class="mc-badge">Balance</span>
|
| 777 |
-
</div>
|
| 778 |
-
<div class="mc" data-m="tts-1-hd-mini" onclick="selModel(this)">
|
| 779 |
-
<div><div class="mc-name">tts-1-hd-mini</div><div class="mc-desc">Mini · 80M · Best Quality</div></div>
|
| 780 |
-
<span class="mc-badge">Quality</span>
|
| 781 |
-
</div>
|
| 782 |
-
</div>
|
| 783 |
-
</div>
|
| 784 |
-
|
| 785 |
-
<div>
|
| 786 |
-
<div class="sec-label">Voice</div>
|
| 787 |
-
<div class="voice-grid">
|
| 788 |
-
<button class="vbtn sel" data-v="Jasper" onclick="selVoice(this)">Jasper</button>
|
| 789 |
-
<button class="vbtn" data-v="Bella" onclick="selVoice(this)">Bella</button>
|
| 790 |
-
<button class="vbtn" data-v="Luna" onclick="selVoice(this)">Luna</button>
|
| 791 |
-
<button class="vbtn" data-v="Bruno" onclick="selVoice(this)">Bruno</button>
|
| 792 |
-
<button class="vbtn" data-v="Rosie" onclick="selVoice(this)">Rosie</button>
|
| 793 |
-
<button class="vbtn" data-v="Hugo" onclick="selVoice(this)">Hugo</button>
|
| 794 |
-
<button class="vbtn" data-v="Kiki" onclick="selVoice(this)">Kiki</button>
|
| 795 |
-
<button class="vbtn" data-v="Leo" onclick="selVoice(this)">Leo</button>
|
| 796 |
-
</div>
|
| 797 |
-
</div>
|
| 798 |
-
|
| 799 |
-
<div>
|
| 800 |
-
<div class="sec-label">Format</div>
|
| 801 |
-
<div class="fmt-chips">
|
| 802 |
-
<div class="chip sel" data-f="wav" onclick="selFmt(this)">WAV</div>
|
| 803 |
-
<div class="chip" data-f="flac" onclick="selFmt(this)">FLAC</div>
|
| 804 |
-
<div class="chip" data-f="pcm" onclick="selFmt(this)">PCM</div>
|
| 805 |
-
<div class="chip" data-f="mp3" onclick="selFmt(this)">MP3*</div>
|
| 806 |
-
</div>
|
| 807 |
-
<p style="font-size:10px;color:var(--dim);margin-top:7px;font-family:var(--mono);">
|
| 808 |
-
* MP3 / OPUS / AAC served as WAV (ffmpeg not bundled)
|
| 809 |
-
</p>
|
| 810 |
-
</div>
|
| 811 |
-
|
| 812 |
-
<div>
|
| 813 |
-
<div class="sec-label">Server</div>
|
| 814 |
-
<div class="srv-info" id="srvInfo">Fetching status…</div>
|
| 815 |
-
</div>
|
| 816 |
-
|
| 817 |
-
</div>
|
| 818 |
-
|
| 819 |
-
<!-- ── Status bar ──────────────────────────────────────────── -->
|
| 820 |
-
<div class="sbar">
|
| 821 |
-
<div class="si"><span class="k">ENDPOINT</span> <span class="v">pyxilabs-srv-tts-01.hf.space</span></div>
|
| 822 |
-
<div class="si"><span class="k">LATENCY</span> <span class="v ac" id="latD">—</span></div>
|
| 823 |
-
<div class="si"><span class="k">LAST GEN</span> <span class="v" id="lgD">—</span></div>
|
| 824 |
-
<div class="mbars" id="mbars">
|
| 825 |
-
<div class="mb"></div><div class="mb"></div><div class="mb"></div>
|
| 826 |
-
<div class="mb"></div><div class="mb"></div>
|
| 827 |
-
</div>
|
| 828 |
-
</div>
|
| 829 |
-
|
| 830 |
-
</main>
|
| 831 |
-
|
| 832 |
-
<div class="toast" id="toast"></div>
|
| 833 |
-
|
| 834 |
-
<script>
|
| 835 |
-
const API = 'https://pyxilabs-srv-tts-01.hf.space';
|
| 836 |
-
|
| 837 |
-
let model = 'tts-1';
|
| 838 |
-
let voice = 'Jasper';
|
| 839 |
-
let fmt = 'wav';
|
| 840 |
-
let blobUrl = null;
|
| 841 |
-
|
| 842 |
-
// refs
|
| 843 |
-
const tin = document.getElementById('tin');
|
| 844 |
-
const ccEl = document.getElementById('cc');
|
| 845 |
-
const spd = document.getElementById('spd');
|
| 846 |
-
const svEl = document.getElementById('sv');
|
| 847 |
-
const genBtn = document.getElementById('genBtn');
|
| 848 |
-
const outSec = document.getElementById('outSection');
|
| 849 |
-
const ap = document.getElementById('ap');
|
| 850 |
-
const dlBtn = document.getElementById('dlBtn');
|
| 851 |
-
const wvbars = document.getElementById('wvbars');
|
| 852 |
-
const wvph = document.getElementById('wvph');
|
| 853 |
-
const latD = document.getElementById('latD');
|
| 854 |
-
const lgD = document.getElementById('lgD');
|
| 855 |
-
const dotEl = document.getElementById('dot');
|
| 856 |
-
const stxtEl = document.getElementById('statusTxt');
|
| 857 |
-
const toastEl = document.getElementById('toast');
|
| 858 |
-
const srvInfo = document.getElementById('srvInfo');
|
| 859 |
-
const mbarsEl = document.getElementById('mbars');
|
| 860 |
-
|
| 861 |
-
// char count
|
| 862 |
-
tin.addEventListener('input', () => ccEl.textContent = tin.value.length);
|
| 863 |
-
ccEl.textContent = tin.value.length;
|
| 864 |
-
|
| 865 |
-
// speed
|
| 866 |
-
spd.addEventListener('input', () => svEl.textContent = (+spd.value).toFixed(2) + '×');
|
| 867 |
-
|
| 868 |
-
// selections
|
| 869 |
-
function selModel(el) {
|
| 870 |
-
document.querySelectorAll('.mc').forEach(e => e.classList.remove('sel'));
|
| 871 |
-
el.classList.add('sel'); model = el.dataset.m;
|
| 872 |
-
}
|
| 873 |
-
function selVoice(el) {
|
| 874 |
-
document.querySelectorAll('.vbtn').forEach(e => e.classList.remove('sel'));
|
| 875 |
-
el.classList.add('sel'); voice = el.dataset.v;
|
| 876 |
-
}
|
| 877 |
-
function selFmt(el) {
|
| 878 |
-
document.querySelectorAll('.chip').forEach(e => e.classList.remove('sel'));
|
| 879 |
-
el.classList.add('sel'); fmt = el.dataset.f;
|
| 880 |
-
}
|
| 881 |
-
|
| 882 |
-
// waveform
|
| 883 |
-
function buildWave() {
|
| 884 |
-
wvbars.innerHTML = '';
|
| 885 |
-
for (let i = 0; i < 58; i++) {
|
| 886 |
-
const b = document.createElement('div');
|
| 887 |
-
b.className = 'bar';
|
| 888 |
-
b.style.height = (20 + Math.random() * 60) + '%';
|
| 889 |
-
b.style.animationDelay = (i / 58 * 1.2) + 's';
|
| 890 |
-
wvbars.appendChild(b);
|
| 891 |
-
}
|
| 892 |
-
}
|
| 893 |
-
|
| 894 |
-
// latency meter
|
| 895 |
-
function setMeter(ms) {
|
| 896 |
-
const bars = mbarsEl.querySelectorAll('.mb');
|
| 897 |
-
const lit = ms < 500 ? 5 : ms < 1000 ? 4 : ms < 2000 ? 3 : ms < 3500 ? 2 : 1;
|
| 898 |
-
bars.forEach((b, i) => b.classList.toggle('lit', (5 - i) <= lit));
|
| 899 |
-
}
|
| 900 |
-
|
| 901 |
-
// toast
|
| 902 |
-
let tt;
|
| 903 |
-
function toast(msg, dur = 3500) {
|
| 904 |
-
toastEl.textContent = msg;
|
| 905 |
-
toastEl.classList.add('show');
|
| 906 |
-
clearTimeout(tt);
|
| 907 |
-
tt = setTimeout(() => toastEl.classList.remove('show'), dur);
|
| 908 |
-
}
|
| 909 |
-
|
| 910 |
-
// copy curl
|
| 911 |
-
function copyCurl() {
|
| 912 |
-
const txt = (tin.value.trim() || 'Hello!').replace(/'/g, "\\'");
|
| 913 |
-
const speed = (+spd.value).toFixed(2);
|
| 914 |
-
const cmd = `curl -X POST ${API}/v1/audio/speech \\\n -H "Content-Type: application/json" \\\n -d '{"model":"${model}","input":"${txt}","voice":"${voice}","response_format":"${fmt}","speed":${speed}}' \\\n --output speech.${fmt}`;
|
| 915 |
-
navigator.clipboard.writeText(cmd)
|
| 916 |
-
.then(() => toast('⌘ cURL command copied!'))
|
| 917 |
-
.catch(() => toast('Copy failed.'));
|
| 918 |
-
}
|
| 919 |
-
|
| 920 |
-
// status
|
| 921 |
-
async function checkStatus() {
|
| 922 |
-
try {
|
| 923 |
-
const r = await fetch(`${API}/v1/status`);
|
| 924 |
-
if (!r.ok) throw new Error();
|
| 925 |
-
const d = await r.json();
|
| 926 |
-
dotEl.className = 'dot online';
|
| 927 |
-
stxtEl.textContent = 'Online';
|
| 928 |
-
|
| 929 |
-
const models = (d.models || []).map(m => {
|
| 930 |
-
const col = m.status === 'idle' ? '#00e676' : m.status === 'running' ? 'var(--accent)' : 'var(--dim)';
|
| 931 |
-
return `<span style="color:${col}">■</span> ${m.name} <span style="color:var(--text)">${m.status.toUpperCase()}</span>`;
|
| 932 |
-
}).join('<br>');
|
| 933 |
-
|
| 934 |
-
const sys = d.system || {};
|
| 935 |
-
const mem = sys.memory || {};
|
| 936 |
-
srvInfo.innerHTML = `${models}<br>CPU <span style="color:var(--text)">${sys.cpu_usage_percent ?? '—'}%</span> · MEM <span style="color:var(--text)">${mem.used_mb ?? '—'} / ${mem.total_mb ?? '—'} MB</span> · UP <span style="color:var(--text)">${d.uptime ?? '—'}</span>`;
|
| 937 |
-
} catch {
|
| 938 |
-
dotEl.className = 'dot error';
|
| 939 |
-
stxtEl.textContent = 'Offline';
|
| 940 |
-
srvInfo.innerHTML = '<span style="color:var(--accent)">Cannot reach server</span>';
|
| 941 |
-
}
|
| 942 |
-
}
|
| 943 |
-
|
| 944 |
-
// generate
|
| 945 |
-
async function generate() {
|
| 946 |
-
const text = tin.value.trim();
|
| 947 |
-
if (!text) { toast('⚠ Please enter some text.'); return; }
|
| 948 |
-
|
| 949 |
-
genBtn.disabled = true;
|
| 950 |
-
genBtn.classList.add('loading');
|
| 951 |
-
genBtn.querySelector('.blabel').textContent = 'SYNTHESISING…';
|
| 952 |
-
|
| 953 |
-
const t0 = performance.now();
|
| 954 |
-
try {
|
| 955 |
-
const res = await fetch(`${API}/v1/audio/speech`, {
|
| 956 |
-
method: 'POST',
|
| 957 |
-
headers: { 'Content-Type': 'application/json' },
|
| 958 |
-
body: JSON.stringify({ model, input: text, voice, response_format: fmt, speed: +spd.value }),
|
| 959 |
-
});
|
| 960 |
-
|
| 961 |
-
if (!res.ok) {
|
| 962 |
-
const e = await res.json().catch(() => ({ detail: res.statusText }));
|
| 963 |
-
throw new Error(e.detail || `HTTP ${res.status}`);
|
| 964 |
-
}
|
| 965 |
-
|
| 966 |
-
const blob = await res.blob();
|
| 967 |
-
const ms = Math.round(performance.now() - t0);
|
| 968 |
-
|
| 969 |
-
if (blobUrl) URL.revokeObjectURL(blobUrl);
|
| 970 |
-
blobUrl = URL.createObjectURL(blob);
|
| 971 |
-
|
| 972 |
-
ap.src = blobUrl; ap.load(); ap.play().catch(() => {});
|
| 973 |
-
|
| 974 |
-
const ext = fmt === 'mp3' ? 'wav' : fmt;
|
| 975 |
-
dlBtn.href = blobUrl;
|
| 976 |
-
dlBtn.download = `devil-studio-${voice.toLowerCase()}.${ext}`;
|
| 977 |
-
|
| 978 |
-
outSec.style.display = 'block';
|
| 979 |
-
buildWave();
|
| 980 |
-
wvph.style.display = 'none';
|
| 981 |
-
wvbars.classList.add('on');
|
| 982 |
-
|
| 983 |
-
const latStr = ms >= 1000 ? (ms / 1000).toFixed(2) + 's' : ms + 'ms';
|
| 984 |
-
latD.textContent = latStr;
|
| 985 |
-
lgD.textContent = new Date().toLocaleTimeString();
|
| 986 |
-
setMeter(ms);
|
| 987 |
-
|
| 988 |
-
checkStatus();
|
| 989 |
-
} catch (err) {
|
| 990 |
-
toast('✗ ' + err.message, 5000);
|
| 991 |
-
} finally {
|
| 992 |
-
genBtn.disabled = false;
|
| 993 |
-
genBtn.classList.remove('loading');
|
| 994 |
-
genBtn.querySelector('.blabel').textContent = 'GENERATE SPEECH';
|
| 995 |
-
}
|
| 996 |
-
}
|
| 997 |
-
|
| 998 |
-
document.addEventListener('keydown', e => { if ((e.ctrlKey||e.metaKey) && e.key==='Enter') generate(); });
|
| 999 |
-
|
| 1000 |
-
checkStatus();
|
| 1001 |
-
setInterval(checkStatus, 30000);
|
| 1002 |
-
</script>
|
| 1003 |
-
</body>
|
| 1004 |
-
</html>
|
| 1005 |
-
"""
|
| 1006 |
-
@app.get("/", include_in_schema=False)
|
| 1007 |
-
async def index():
|
| 1008 |
-
return HTMLResponse(content=html)
|
| 1009 |
-
|
| 1010 |
@app.get("/health", tags=["Utility"], summary="Liveness probe")
|
| 1011 |
async def health():
|
| 1012 |
return {"status": "ok", "server": "Devil Studio"}
|
|
@@ -1127,6 +484,6 @@ if __name__ == "__main__":
|
|
| 1127 |
"main:app",
|
| 1128 |
host="0.0.0.0",
|
| 1129 |
port=int(os.getenv("PORT", "7860")),
|
| 1130 |
-
workers=
|
| 1131 |
log_level="info",
|
| 1132 |
)
|
|
|
|
| 364 |
# ---------------------------------------------------------------------------
|
| 365 |
# Routes
|
| 366 |
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 367 |
@app.get("/health", tags=["Utility"], summary="Liveness probe")
|
| 368 |
async def health():
|
| 369 |
return {"status": "ok", "server": "Devil Studio"}
|
|
|
|
| 484 |
"main:app",
|
| 485 |
host="0.0.0.0",
|
| 486 |
port=int(os.getenv("PORT", "7860")),
|
| 487 |
+
workers=2,
|
| 488 |
log_level="info",
|
| 489 |
)
|