Use direct Gradio Space app
Browse files- app.py +21 -17
- src/time_machine/adapters/fixtures/fixture_immersion.py +4 -9
- src/time_machine/ui/assets/cockpit.css +43 -25
- src/time_machine/ui/assets/cockpit.js +5 -5
- src/time_machine/ui/gradio_app.py +40 -23
- static/img/travel/reference_arrival.png +2 -2
- static/img/travel/reference_departure.png +2 -2
- static/img/travel/reference_warp_blend.png +2 -2
app.py
CHANGED
|
@@ -28,20 +28,20 @@ def _load_local_env(path: Path, *, override: bool = False) -> None:
|
|
| 28 |
_load_local_env(ROOT_ENV, override=False)
|
| 29 |
_load_local_env(LOCAL_ENV, override=False)
|
| 30 |
|
| 31 |
-
from time_machine.application.container import create_container
|
| 32 |
-
from time_machine.ui.gradio_app import create_app
|
| 33 |
-
from time_machine.ui.realtime import realtime_voice_socket
|
| 34 |
-
|
| 35 |
-
import gradio as gr
|
| 36 |
from fastapi import FastAPI, WebSocket
|
| 37 |
from fastapi.responses import JSONResponse, RedirectResponse, Response
|
| 38 |
from fastapi.staticfiles import StaticFiles
|
| 39 |
|
| 40 |
|
| 41 |
-
container = create_container()
|
| 42 |
-
demo = create_app(container)
|
| 43 |
-
fastapi_app = FastAPI(title="AI Time Machine")
|
| 44 |
-
fastapi_app.mount("/static", StaticFiles(directory=ROOT / "static"), name="static")
|
| 45 |
|
| 46 |
|
| 47 |
@fastapi_app.websocket("/realtime/voice")
|
|
@@ -77,12 +77,16 @@ async def blank_redirect():
|
|
| 77 |
|
| 78 |
|
| 79 |
head = getattr(demo, "time_machine_head", None)
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
|
|
|
| 86 |
host = os.getenv("GRADIO_SERVER_NAME", "0.0.0.0")
|
| 87 |
-
port = int(os.getenv("GRADIO_SERVER_PORT", "7860"))
|
| 88 |
-
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
_load_local_env(ROOT_ENV, override=False)
|
| 29 |
_load_local_env(LOCAL_ENV, override=False)
|
| 30 |
|
| 31 |
+
from time_machine.application.container import create_container
|
| 32 |
+
from time_machine.ui.gradio_app import create_app
|
| 33 |
+
from time_machine.ui.realtime import realtime_voice_socket
|
| 34 |
+
|
| 35 |
+
import gradio as gr
|
| 36 |
from fastapi import FastAPI, WebSocket
|
| 37 |
from fastapi.responses import JSONResponse, RedirectResponse, Response
|
| 38 |
from fastapi.staticfiles import StaticFiles
|
| 39 |
|
| 40 |
|
| 41 |
+
container = create_container()
|
| 42 |
+
demo = create_app(container)
|
| 43 |
+
fastapi_app = FastAPI(title="AI Time Machine")
|
| 44 |
+
fastapi_app.mount("/static", StaticFiles(directory=ROOT / "static"), name="static")
|
| 45 |
|
| 46 |
|
| 47 |
@fastapi_app.websocket("/realtime/voice")
|
|
|
|
| 77 |
|
| 78 |
|
| 79 |
head = getattr(demo, "time_machine_head", None)
|
| 80 |
+
fastapi_app_with_gradio = gr.mount_gradio_app(fastapi_app, demo, path="/app", head=head)
|
| 81 |
+
app = fastapi_app_with_gradio if os.getenv("TIME_MACHINE_FASTAPI_WRAPPER") else demo
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
if __name__ == "__main__":
|
| 85 |
+
import uvicorn
|
| 86 |
+
|
| 87 |
host = os.getenv("GRADIO_SERVER_NAME", "0.0.0.0")
|
| 88 |
+
port = int(os.getenv("GRADIO_SERVER_PORT", "7860"))
|
| 89 |
+
if os.getenv("TIME_MACHINE_FASTAPI_WRAPPER"):
|
| 90 |
+
uvicorn.run(fastapi_app_with_gradio, host=host, port=port)
|
| 91 |
+
else:
|
| 92 |
+
demo.launch(server_name=host, server_port=port)
|
src/time_machine/adapters/fixtures/fixture_immersion.py
CHANGED
|
@@ -133,14 +133,11 @@ def _palette(destination: Destination) -> tuple[str, str, str]:
|
|
| 133 |
|
| 134 |
def _scene_svg(destination: Destination) -> str:
|
| 135 |
primary, secondary, accent = _palette(destination)
|
| 136 |
-
|
| 137 |
-
atmosphere = html.escape(destination.atmosphere[:120])
|
| 138 |
-
return f"""<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="768" viewBox="0 0 1280 768">
|
| 139 |
<defs>
|
| 140 |
<radialGradient id="portal" cx="50%" cy="38%" r="58%"><stop offset="0" stop-color="{accent}" stop-opacity=".9"/><stop offset=".42" stop-color="{primary}" stop-opacity=".46"/><stop offset="1" stop-color="#080503"/></radialGradient>
|
| 141 |
<linearGradient id="ground" x1="0" x2="1"><stop offset="0" stop-color="{secondary}"/><stop offset=".5" stop-color="#080503"/><stop offset="1" stop-color="{primary}"/></linearGradient>
|
| 142 |
-
<
|
| 143 |
-
</defs>
|
| 144 |
<rect width="1280" height="768" fill="#080503"/>
|
| 145 |
<rect width="1280" height="768" fill="url(#portal)"/>
|
| 146 |
<circle cx="640" cy="320" r="210" fill="none" stroke="{accent}" stroke-width="3" opacity=".65"/>
|
|
@@ -152,10 +149,8 @@ def _scene_svg(destination: Destination) -> str:
|
|
| 152 |
<path d="M930 590 L980 285 L1050 590 Z" fill="#080503"/>
|
| 153 |
<path d="M1110 585 L1160 350 L1210 585 Z" fill="#080503"/>
|
| 154 |
</g>
|
| 155 |
-
<g
|
| 156 |
-
<
|
| 157 |
-
<text x="64" y="734" fill="#dcb37b" font-family="Georgia, serif" font-size="20">{atmosphere}</text>
|
| 158 |
-
</svg>"""
|
| 159 |
|
| 160 |
|
| 161 |
def _portrait_svg(destination: Destination, persona: Persona) -> str:
|
|
|
|
| 133 |
|
| 134 |
def _scene_svg(destination: Destination) -> str:
|
| 135 |
primary, secondary, accent = _palette(destination)
|
| 136 |
+
return f"""<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="768" viewBox="0 0 1280 768">
|
|
|
|
|
|
|
| 137 |
<defs>
|
| 138 |
<radialGradient id="portal" cx="50%" cy="38%" r="58%"><stop offset="0" stop-color="{accent}" stop-opacity=".9"/><stop offset=".42" stop-color="{primary}" stop-opacity=".46"/><stop offset="1" stop-color="#080503"/></radialGradient>
|
| 139 |
<linearGradient id="ground" x1="0" x2="1"><stop offset="0" stop-color="{secondary}"/><stop offset=".5" stop-color="#080503"/><stop offset="1" stop-color="{primary}"/></linearGradient>
|
| 140 |
+
</defs>
|
|
|
|
| 141 |
<rect width="1280" height="768" fill="#080503"/>
|
| 142 |
<rect width="1280" height="768" fill="url(#portal)"/>
|
| 143 |
<circle cx="640" cy="320" r="210" fill="none" stroke="{accent}" stroke-width="3" opacity=".65"/>
|
|
|
|
| 149 |
<path d="M930 590 L980 285 L1050 590 Z" fill="#080503"/>
|
| 150 |
<path d="M1110 585 L1160 350 L1210 585 Z" fill="#080503"/>
|
| 151 |
</g>
|
| 152 |
+
<g opacity=".28"><circle cx="290" cy="165" r="80" fill="{accent}"/><circle cx="955" cy="195" r="110" fill="{primary}"/></g>
|
| 153 |
+
</svg>"""
|
|
|
|
|
|
|
| 154 |
|
| 155 |
|
| 156 |
def _portrait_svg(destination: Destination, persona: Persona) -> str:
|
src/time_machine/ui/assets/cockpit.css
CHANGED
|
@@ -156,7 +156,7 @@ footer,
|
|
| 156 |
.tte-reference-stage__image--warp {
|
| 157 |
opacity: var(--tte-warp-opacity, 0);
|
| 158 |
background-image: url("/static/img/travel/reference_warp_blend.png");
|
| 159 |
-
filter: saturate(1.
|
| 160 |
transform: scale(var(--tte-warp-scale, 1.04));
|
| 161 |
}
|
| 162 |
|
|
@@ -180,7 +180,7 @@ footer,
|
|
| 180 |
.tm-state-launching .tte-reference-stage__image--departure,
|
| 181 |
.tm-state-traveling .tte-reference-stage__image--departure,
|
| 182 |
.tm-state-braking .tte-reference-stage__image--departure {
|
| 183 |
-
filter: saturate(1.04) contrast(1.08)
|
| 184 |
transform: scale(var(--tte-departure-scale, 1.02));
|
| 185 |
}
|
| 186 |
|
|
@@ -450,33 +450,50 @@ footer,
|
|
| 450 |
position: absolute;
|
| 451 |
top: var(--sign-top, 42%);
|
| 452 |
left: 104%;
|
| 453 |
-
min-width:
|
| 454 |
-
padding:
|
| 455 |
-
border:
|
| 456 |
-
border-radius:
|
| 457 |
background:
|
| 458 |
-
linear-gradient(
|
|
|
|
| 459 |
color: #fff0cb;
|
| 460 |
-
font: 900 14px/1.
|
| 461 |
-
letter-spacing: 0.
|
| 462 |
text-align: center;
|
| 463 |
text-transform: uppercase;
|
| 464 |
box-shadow:
|
| 465 |
-
inset 0 0 0 2px rgba(246, 216, 145, 0.
|
| 466 |
-
0
|
|
|
|
|
|
|
| 467 |
animation: tte-sign-future var(--sign-speed, 1500ms) linear forwards;
|
| 468 |
}
|
| 469 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 470 |
.tte-era-sign::after {
|
| 471 |
content: "";
|
| 472 |
position: absolute;
|
| 473 |
left: 50%;
|
| 474 |
top: 100%;
|
| 475 |
-
width:
|
| 476 |
-
height:
|
| 477 |
transform: translateX(-50%);
|
| 478 |
-
border
|
| 479 |
-
|
|
|
|
|
|
|
|
|
|
| 480 |
}
|
| 481 |
|
| 482 |
.tte-era-sign.past {
|
|
@@ -510,11 +527,11 @@ footer,
|
|
| 510 |
|
| 511 |
.tte-generated-world {
|
| 512 |
position: absolute;
|
| 513 |
-
inset: -
|
| 514 |
z-index: 22;
|
| 515 |
opacity: 0;
|
| 516 |
pointer-events: none;
|
| 517 |
-
background-position: center;
|
| 518 |
background-size: cover;
|
| 519 |
filter: saturate(0.98) contrast(1.05);
|
| 520 |
transform: scale(1.04);
|
|
@@ -539,18 +556,18 @@ footer,
|
|
| 539 |
|
| 540 |
.tte-generated-person {
|
| 541 |
position: absolute;
|
| 542 |
-
|
| 543 |
-
bottom:
|
| 544 |
z-index: 29;
|
| 545 |
display: block;
|
| 546 |
-
width: clamp(
|
| 547 |
-
max-height:
|
| 548 |
object-fit: contain;
|
| 549 |
object-position: center bottom;
|
| 550 |
opacity: 0;
|
| 551 |
pointer-events: none;
|
| 552 |
border-radius: 26px;
|
| 553 |
-
transform: translateY(18px) scale(0.96);
|
| 554 |
transform-origin: 50% 100%;
|
| 555 |
filter: drop-shadow(0 24px 20px rgba(58, 31, 17, 0.28));
|
| 556 |
transition: opacity 900ms ease, transform 1200ms cubic-bezier(0.2, 0.9, 0.2, 1);
|
|
@@ -559,7 +576,7 @@ footer,
|
|
| 559 |
.tm-has-portrait.tm-state-arrived .tte-generated-person,
|
| 560 |
.tm-has-portrait.tm-state-conversing .tte-generated-person {
|
| 561 |
opacity: 1;
|
| 562 |
-
transform: translateY(0) scale(1);
|
| 563 |
}
|
| 564 |
|
| 565 |
.tte-steam-curtain {
|
|
@@ -1213,6 +1230,7 @@ footer,
|
|
| 1213 |
border: 3px solid rgba(54, 31, 18, 0.86);
|
| 1214 |
border-radius: 8px;
|
| 1215 |
background: rgba(255, 240, 203, 0.92);
|
|
|
|
| 1216 |
color: var(--tte-readable-ink);
|
| 1217 |
text-align: center;
|
| 1218 |
box-shadow: 0 14px 22px rgba(34, 17, 9, 0.2);
|
|
@@ -1777,9 +1795,9 @@ footer,
|
|
| 1777 |
}
|
| 1778 |
|
| 1779 |
.tte-generated-person {
|
| 1780 |
-
|
| 1781 |
bottom: 10%;
|
| 1782 |
-
width: min(
|
| 1783 |
}
|
| 1784 |
}
|
| 1785 |
|
|
|
|
| 156 |
.tte-reference-stage__image--warp {
|
| 157 |
opacity: var(--tte-warp-opacity, 0);
|
| 158 |
background-image: url("/static/img/travel/reference_warp_blend.png");
|
| 159 |
+
filter: saturate(1.06) contrast(1.06) brightness(1.03);
|
| 160 |
transform: scale(var(--tte-warp-scale, 1.04));
|
| 161 |
}
|
| 162 |
|
|
|
|
| 180 |
.tm-state-launching .tte-reference-stage__image--departure,
|
| 181 |
.tm-state-traveling .tte-reference-stage__image--departure,
|
| 182 |
.tm-state-braking .tte-reference-stage__image--departure {
|
| 183 |
+
filter: saturate(1.04) contrast(1.08);
|
| 184 |
transform: scale(var(--tte-departure-scale, 1.02));
|
| 185 |
}
|
| 186 |
|
|
|
|
| 450 |
position: absolute;
|
| 451 |
top: var(--sign-top, 42%);
|
| 452 |
left: 104%;
|
| 453 |
+
min-width: 204px;
|
| 454 |
+
padding: 9px 18px 10px;
|
| 455 |
+
border: 4px solid rgba(49, 30, 17, 0.9);
|
| 456 |
+
border-radius: 5px;
|
| 457 |
background:
|
| 458 |
+
linear-gradient(90deg, rgba(255, 235, 166, 0.16), transparent 18% 82%, rgba(67, 37, 18, 0.18)),
|
| 459 |
+
linear-gradient(180deg, #6a7a55 0%, #3b5039 42%, #243325 100%);
|
| 460 |
color: #fff0cb;
|
| 461 |
+
font: 900 14px/1.08 var(--tte-heading);
|
| 462 |
+
letter-spacing: 0.05em;
|
| 463 |
text-align: center;
|
| 464 |
text-transform: uppercase;
|
| 465 |
box-shadow:
|
| 466 |
+
inset 0 0 0 2px rgba(246, 216, 145, 0.34),
|
| 467 |
+
inset 0 12px 12px rgba(255, 239, 184, 0.12),
|
| 468 |
+
inset 0 -13px 12px rgba(17, 9, 5, 0.28),
|
| 469 |
+
0 17px 18px rgba(57, 33, 20, 0.34);
|
| 470 |
animation: tte-sign-future var(--sign-speed, 1500ms) linear forwards;
|
| 471 |
}
|
| 472 |
|
| 473 |
+
.tte-era-sign::before {
|
| 474 |
+
content: "";
|
| 475 |
+
position: absolute;
|
| 476 |
+
left: 10px;
|
| 477 |
+
right: 10px;
|
| 478 |
+
top: 7px;
|
| 479 |
+
height: 2px;
|
| 480 |
+
border-radius: 999px;
|
| 481 |
+
background: rgba(255, 241, 194, 0.42);
|
| 482 |
+
}
|
| 483 |
+
|
| 484 |
.tte-era-sign::after {
|
| 485 |
content: "";
|
| 486 |
position: absolute;
|
| 487 |
left: 50%;
|
| 488 |
top: 100%;
|
| 489 |
+
width: 9px;
|
| 490 |
+
height: 76px;
|
| 491 |
transform: translateX(-50%);
|
| 492 |
+
border: 2px solid rgba(45, 25, 14, 0.74);
|
| 493 |
+
border-top: 0;
|
| 494 |
+
background:
|
| 495 |
+
linear-gradient(90deg, #5a3019, #d6a14a 45%, #6e3d20);
|
| 496 |
+
box-shadow: 0 10px 10px rgba(46, 28, 18, 0.26);
|
| 497 |
}
|
| 498 |
|
| 499 |
.tte-era-sign.past {
|
|
|
|
| 527 |
|
| 528 |
.tte-generated-world {
|
| 529 |
position: absolute;
|
| 530 |
+
inset: -4%;
|
| 531 |
z-index: 22;
|
| 532 |
opacity: 0;
|
| 533 |
pointer-events: none;
|
| 534 |
+
background-position: center 42%;
|
| 535 |
background-size: cover;
|
| 536 |
filter: saturate(0.98) contrast(1.05);
|
| 537 |
transform: scale(1.04);
|
|
|
|
| 556 |
|
| 557 |
.tte-generated-person {
|
| 558 |
position: absolute;
|
| 559 |
+
left: 50%;
|
| 560 |
+
bottom: 7%;
|
| 561 |
z-index: 29;
|
| 562 |
display: block;
|
| 563 |
+
width: clamp(190px, 20vw, 330px);
|
| 564 |
+
max-height: 80%;
|
| 565 |
object-fit: contain;
|
| 566 |
object-position: center bottom;
|
| 567 |
opacity: 0;
|
| 568 |
pointer-events: none;
|
| 569 |
border-radius: 26px;
|
| 570 |
+
transform: translateX(-50%) translateY(18px) scale(0.96);
|
| 571 |
transform-origin: 50% 100%;
|
| 572 |
filter: drop-shadow(0 24px 20px rgba(58, 31, 17, 0.28));
|
| 573 |
transition: opacity 900ms ease, transform 1200ms cubic-bezier(0.2, 0.9, 0.2, 1);
|
|
|
|
| 576 |
.tm-has-portrait.tm-state-arrived .tte-generated-person,
|
| 577 |
.tm-has-portrait.tm-state-conversing .tte-generated-person {
|
| 578 |
opacity: 1;
|
| 579 |
+
transform: translateX(-50%) translateY(0) scale(1);
|
| 580 |
}
|
| 581 |
|
| 582 |
.tte-steam-curtain {
|
|
|
|
| 1230 |
border: 3px solid rgba(54, 31, 18, 0.86);
|
| 1231 |
border-radius: 8px;
|
| 1232 |
background: rgba(255, 240, 203, 0.92);
|
| 1233 |
+
color: #21130d;
|
| 1234 |
color: var(--tte-readable-ink);
|
| 1235 |
text-align: center;
|
| 1236 |
box-shadow: 0 14px 22px rgba(34, 17, 9, 0.2);
|
|
|
|
| 1795 |
}
|
| 1796 |
|
| 1797 |
.tte-generated-person {
|
| 1798 |
+
left: 50%;
|
| 1799 |
bottom: 10%;
|
| 1800 |
+
width: min(210px, 42vw);
|
| 1801 |
}
|
| 1802 |
}
|
| 1803 |
|
src/time_machine/ui/assets/cockpit.js
CHANGED
|
@@ -694,9 +694,9 @@ function updateRideProgress() {
|
|
| 694 |
const warpMix = Math.max(0, 1 - Math.abs(progress - 0.48) / 0.28);
|
| 695 |
cockpit.style.setProperty("--tte-progress", progress.toFixed(3));
|
| 696 |
cockpit.style.setProperty("--tte-arrival-opacity", arrivalMix.toFixed(3));
|
| 697 |
-
cockpit.style.setProperty("--tte-warp-opacity", warpMix.toFixed(3));
|
| 698 |
cockpit.style.setProperty("--tte-vortex-opacity", (warpMix * 0.82).toFixed(3));
|
| 699 |
-
cockpit.style.setProperty("--tte-warp-blur",
|
| 700 |
cockpit.style.setProperty("--tte-warp-scale", (1.04 + warpMix * 0.05).toFixed(3));
|
| 701 |
cockpit.style.setProperty("--tte-departure-scale", (1.02 + warpMix * 0.035).toFixed(3));
|
| 702 |
if (runtime.phase === "launching" && elapsed > 1050) {
|
|
@@ -793,9 +793,9 @@ function arriveAtDestination(destination, fallbackOnly) {
|
|
| 793 |
setVisualPhase("braking");
|
| 794 |
if (cockpit) {
|
| 795 |
cockpit.style.setProperty("--tte-arrival-opacity", "1");
|
| 796 |
-
cockpit.style.setProperty("--tte-warp-opacity", "0.
|
| 797 |
cockpit.style.setProperty("--tte-vortex-opacity", "0.148");
|
| 798 |
-
cockpit.style.setProperty("--tte-warp-blur", "
|
| 799 |
cockpit.style.setProperty("--tte-warp-scale", "1.049");
|
| 800 |
cockpit.style.setProperty("--tte-departure-scale", "1.026");
|
| 801 |
}
|
|
@@ -831,7 +831,7 @@ function imageSourceFromPayload(image) {
|
|
| 831 |
if (!image || !image.image_b64 || !image.mime_type) {
|
| 832 |
return "";
|
| 833 |
}
|
| 834 |
-
if (!/^image\/(png|jpe?g|webp)$/i.test(image.mime_type)) {
|
| 835 |
return "";
|
| 836 |
}
|
| 837 |
return `data:${image.mime_type};base64,${image.image_b64}`;
|
|
|
|
| 694 |
const warpMix = Math.max(0, 1 - Math.abs(progress - 0.48) / 0.28);
|
| 695 |
cockpit.style.setProperty("--tte-progress", progress.toFixed(3));
|
| 696 |
cockpit.style.setProperty("--tte-arrival-opacity", arrivalMix.toFixed(3));
|
| 697 |
+
cockpit.style.setProperty("--tte-warp-opacity", (warpMix * 0.32).toFixed(3));
|
| 698 |
cockpit.style.setProperty("--tte-vortex-opacity", (warpMix * 0.82).toFixed(3));
|
| 699 |
+
cockpit.style.setProperty("--tte-warp-blur", "0px");
|
| 700 |
cockpit.style.setProperty("--tte-warp-scale", (1.04 + warpMix * 0.05).toFixed(3));
|
| 701 |
cockpit.style.setProperty("--tte-departure-scale", (1.02 + warpMix * 0.035).toFixed(3));
|
| 702 |
if (runtime.phase === "launching" && elapsed > 1050) {
|
|
|
|
| 793 |
setVisualPhase("braking");
|
| 794 |
if (cockpit) {
|
| 795 |
cockpit.style.setProperty("--tte-arrival-opacity", "1");
|
| 796 |
+
cockpit.style.setProperty("--tte-warp-opacity", "0.06");
|
| 797 |
cockpit.style.setProperty("--tte-vortex-opacity", "0.148");
|
| 798 |
+
cockpit.style.setProperty("--tte-warp-blur", "0px");
|
| 799 |
cockpit.style.setProperty("--tte-warp-scale", "1.049");
|
| 800 |
cockpit.style.setProperty("--tte-departure-scale", "1.026");
|
| 801 |
}
|
|
|
|
| 831 |
if (!image || !image.image_b64 || !image.mime_type) {
|
| 832 |
return "";
|
| 833 |
}
|
| 834 |
+
if (!/^image\/(png|jpe?g|webp|svg\+xml)$/i.test(image.mime_type)) {
|
| 835 |
return "";
|
| 836 |
}
|
| 837 |
return `data:${image.mime_type};base64,${image.image_b64}`;
|
src/time_machine/ui/gradio_app.py
CHANGED
|
@@ -1,10 +1,12 @@
|
|
| 1 |
-
from __future__ import annotations
|
| 2 |
-
|
| 3 |
-
from pathlib import Path
|
| 4 |
-
import base64
|
| 5 |
-
import inspect
|
| 6 |
-
|
| 7 |
-
import
|
|
|
|
|
|
|
| 8 |
|
| 9 |
from time_machine.application.container import AppContainer
|
| 10 |
from time_machine.ui.handlers import GradioHandlers
|
|
@@ -15,19 +17,20 @@ ASSET_DIR = Path(__file__).resolve().parent / "assets"
|
|
| 15 |
|
| 16 |
def create_app(container: AppContainer) -> gr.Blocks:
|
| 17 |
handlers = GradioHandlers(container)
|
| 18 |
-
css = (
|
| 19 |
-
(ASSET_DIR / "intro.css").read_text(encoding="utf-8")
|
| 20 |
-
+ "\n"
|
| 21 |
-
+ (ASSET_DIR / "cockpit.css").read_text(encoding="utf-8")
|
| 22 |
-
)
|
| 23 |
-
|
| 24 |
-
bg_path = ASSET_DIR / "bg.png"
|
| 25 |
-
if bg_path.exists():
|
| 26 |
-
bg_b64 = base64.b64encode(bg_path.read_bytes()).decode("utf-8")
|
| 27 |
-
css = css.replace("url('bg.png')", f"url('data:image/png;base64,{bg_b64}')")
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
|
|
|
| 31 |
intro_js = (ASSET_DIR / "intro.js").read_text(encoding="utf-8")
|
| 32 |
cockpit_js = (ASSET_DIR / "cockpit.js").read_text(encoding="utf-8")
|
| 33 |
|
|
@@ -153,6 +156,20 @@ def create_app(container: AppContainer) -> gr.Blocks:
|
|
| 153 |
outputs=[status],
|
| 154 |
)
|
| 155 |
|
| 156 |
-
if "head" not in blocks_kwargs:
|
| 157 |
-
demo.time_machine_head = f"<script>{cockpit_js}</script><script>{intro_js}</script>"
|
| 158 |
-
return demo.queue()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
import base64
|
| 5 |
+
import inspect
|
| 6 |
+
import mimetypes
|
| 7 |
+
import re
|
| 8 |
+
|
| 9 |
+
import gradio as gr
|
| 10 |
|
| 11 |
from time_machine.application.container import AppContainer
|
| 12 |
from time_machine.ui.handlers import GradioHandlers
|
|
|
|
| 17 |
|
| 18 |
def create_app(container: AppContainer) -> gr.Blocks:
|
| 19 |
handlers = GradioHandlers(container)
|
| 20 |
+
css = (
|
| 21 |
+
(ASSET_DIR / "intro.css").read_text(encoding="utf-8")
|
| 22 |
+
+ "\n"
|
| 23 |
+
+ (ASSET_DIR / "cockpit.css").read_text(encoding="utf-8")
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
bg_path = ASSET_DIR / "bg.png"
|
| 27 |
+
if bg_path.exists():
|
| 28 |
+
bg_b64 = base64.b64encode(bg_path.read_bytes()).decode("utf-8")
|
| 29 |
+
css = css.replace("url('bg.png')", f"url('data:image/png;base64,{bg_b64}')")
|
| 30 |
+
css = _inline_static_urls(css)
|
| 31 |
+
|
| 32 |
+
cockpit_html = _inline_static_urls((ASSET_DIR / "cockpit.html").read_text(encoding="utf-8"))
|
| 33 |
+
intro_html = _inline_static_urls((ASSET_DIR / "intro.html").read_text(encoding="utf-8"))
|
| 34 |
intro_js = (ASSET_DIR / "intro.js").read_text(encoding="utf-8")
|
| 35 |
cockpit_js = (ASSET_DIR / "cockpit.js").read_text(encoding="utf-8")
|
| 36 |
|
|
|
|
| 156 |
outputs=[status],
|
| 157 |
)
|
| 158 |
|
| 159 |
+
if "head" not in blocks_kwargs:
|
| 160 |
+
demo.time_machine_head = f"<script>{cockpit_js}</script><script>{intro_js}</script>"
|
| 161 |
+
return demo.queue()
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
def _inline_static_urls(text: str) -> str:
|
| 165 |
+
return re.sub(r"(?P<quote>['\"])/static/(?P<path>[^'\")]+)(?P=quote)", _replace_static_url, text)
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
def _replace_static_url(match: re.Match[str]) -> str:
|
| 169 |
+
relative_path = match.group("path")
|
| 170 |
+
file_path = ASSET_DIR.parents[3] / "static" / relative_path
|
| 171 |
+
if not file_path.exists() or not file_path.is_file():
|
| 172 |
+
return match.group(0)
|
| 173 |
+
mime_type = mimetypes.guess_type(file_path.name)[0] or "application/octet-stream"
|
| 174 |
+
encoded = base64.b64encode(file_path.read_bytes()).decode("ascii")
|
| 175 |
+
return f"{match.group('quote')}data:{mime_type};base64,{encoded}{match.group('quote')}"
|
static/img/travel/reference_arrival.png
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
static/img/travel/reference_departure.png
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
static/img/travel/reference_warp_blend.png
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|