Spaces:
Running
Running
feat: add WebGPULogo component and integrate it into LoadingScreen
Browse files- src/components/LoadingScreen.tsx +117 -73
- src/components/icons/WebGPULogo.tsx +48 -0
src/components/LoadingScreen.tsx
CHANGED
|
@@ -2,6 +2,7 @@ import { ChevronDown } from "lucide-react";
|
|
| 2 |
import { MODEL_OPTIONS } from "../constants/models";
|
| 3 |
import HfLogo from "./icons/HfLogo";
|
| 4 |
import MCPLogo from "./icons/MCPLogo";
|
|
|
|
| 5 |
import { useEffect, useMemo, useRef, useState } from "react";
|
| 6 |
import ReactDOM from "react-dom";
|
| 7 |
|
|
@@ -188,7 +189,6 @@ export const LoadingScreen = ({
|
|
| 188 |
handleModelSelect,
|
| 189 |
]);
|
| 190 |
|
| 191 |
-
|
| 192 |
return (
|
| 193 |
<div className="relative flex flex-col items-center justify-center min-h-screen bg-gradient-to-br from-gray-900 via-slate-900 to-gray-900 text-white p-6 overflow-hidden">
|
| 194 |
{/* Background Canvas */}
|
|
@@ -207,6 +207,16 @@ export const LoadingScreen = ({
|
|
| 207 |
<div className="relative z-20 max-w-4xl w-full flex flex-col items-center">
|
| 208 |
{/* Logos */}
|
| 209 |
<div className="flex items-center justify-center mb-8 gap-5">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 210 |
<a
|
| 211 |
href="https://huggingface.co/docs/transformers.js"
|
| 212 |
target="_blank"
|
|
@@ -234,7 +244,8 @@ export const LoadingScreen = ({
|
|
| 234 |
WebGPU MCP
|
| 235 |
</h1>
|
| 236 |
<p className="text-lg sm:text-xl md:text-2xl text-gray-300 font-light leading-relaxed">
|
| 237 |
-
Run WebGPU-based models with tool calling in your browser, powered
|
|
|
|
| 238 |
<a
|
| 239 |
href="https://modelcontextprotocol.io/"
|
| 240 |
target="_blank"
|
|
@@ -377,78 +388,112 @@ export const LoadingScreen = ({
|
|
| 377 |
</div>
|
| 378 |
|
| 379 |
{/* Dropdown - render in portal to avoid z-index issues */}
|
| 380 |
-
{isModelDropdownOpen &&
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
|
| 384 |
-
|
| 385 |
-
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
|
| 417 |
-
|
| 418 |
-
|
| 419 |
-
|
| 420 |
-
|
| 421 |
-
|
| 422 |
-
|
| 423 |
-
|
| 424 |
-
|
| 425 |
-
|
| 426 |
-
|
| 427 |
-
|
| 428 |
-
|
| 429 |
-
|
| 430 |
-
|
| 431 |
-
|
| 432 |
-
|
| 433 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 434 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 435 |
</div>
|
| 436 |
-
|
| 437 |
-
|
| 438 |
-
|
| 439 |
-
|
| 440 |
-
|
| 441 |
-
|
| 442 |
-
|
| 443 |
-
)}
|
| 444 |
-
</div>
|
| 445 |
-
</button>
|
| 446 |
-
);
|
| 447 |
-
})}
|
| 448 |
-
</div>
|
| 449 |
-
</>,
|
| 450 |
-
document.body
|
| 451 |
-
)}
|
| 452 |
</div>
|
| 453 |
</div>
|
| 454 |
|
|
@@ -468,7 +513,6 @@ export const LoadingScreen = ({
|
|
| 468 |
</div>
|
| 469 |
)}
|
| 470 |
</div>
|
| 471 |
-
|
| 472 |
</div>
|
| 473 |
);
|
| 474 |
};
|
|
|
|
| 2 |
import { MODEL_OPTIONS } from "../constants/models";
|
| 3 |
import HfLogo from "./icons/HfLogo";
|
| 4 |
import MCPLogo from "./icons/MCPLogo";
|
| 5 |
+
import WebGPULogo from "./icons/WebGPULogo";
|
| 6 |
import { useEffect, useMemo, useRef, useState } from "react";
|
| 7 |
import ReactDOM from "react-dom";
|
| 8 |
|
|
|
|
| 189 |
handleModelSelect,
|
| 190 |
]);
|
| 191 |
|
|
|
|
| 192 |
return (
|
| 193 |
<div className="relative flex flex-col items-center justify-center min-h-screen bg-gradient-to-br from-gray-900 via-slate-900 to-gray-900 text-white p-6 overflow-hidden">
|
| 194 |
{/* Background Canvas */}
|
|
|
|
| 207 |
<div className="relative z-20 max-w-4xl w-full flex flex-col items-center">
|
| 208 |
{/* Logos */}
|
| 209 |
<div className="flex items-center justify-center mb-8 gap-5">
|
| 210 |
+
<a
|
| 211 |
+
href="https://gpuweb.github.io/gpuweb/"
|
| 212 |
+
target="_blank"
|
| 213 |
+
rel="noopener noreferrer"
|
| 214 |
+
title="WebGPU"
|
| 215 |
+
className="transform transition-all duration-300 hover:scale-105 hover:-translate-y-1"
|
| 216 |
+
>
|
| 217 |
+
<WebGPULogo className="h-16 w-16 md:h-20 md:w-20 text-gray-300 hover:text-white drop-shadow-lg" />
|
| 218 |
+
</a>
|
| 219 |
+
<span className="text-gray-500 text-3xl font-extralight">×</span>
|
| 220 |
<a
|
| 221 |
href="https://huggingface.co/docs/transformers.js"
|
| 222 |
target="_blank"
|
|
|
|
| 244 |
WebGPU MCP
|
| 245 |
</h1>
|
| 246 |
<p className="text-lg sm:text-xl md:text-2xl text-gray-300 font-light leading-relaxed">
|
| 247 |
+
Run WebGPU-based models with tool calling in your browser, powered
|
| 248 |
+
by the{" "}
|
| 249 |
<a
|
| 250 |
href="https://modelcontextprotocol.io/"
|
| 251 |
target="_blank"
|
|
|
|
| 388 |
</div>
|
| 389 |
|
| 390 |
{/* Dropdown - render in portal to avoid z-index issues */}
|
| 391 |
+
{isModelDropdownOpen &&
|
| 392 |
+
typeof document !== "undefined" &&
|
| 393 |
+
ReactDOM.createPortal(
|
| 394 |
+
<>
|
| 395 |
+
{/* Backdrop */}
|
| 396 |
+
<div
|
| 397 |
+
className="fixed inset-0 z-[999]"
|
| 398 |
+
onClick={() => setIsModelDropdownOpen(false)}
|
| 399 |
+
/>
|
| 400 |
+
{/* Dropdown */}
|
| 401 |
+
<div
|
| 402 |
+
ref={dropdownRef}
|
| 403 |
+
role="menu"
|
| 404 |
+
aria-label="Model options"
|
| 405 |
+
style={{
|
| 406 |
+
position: "fixed",
|
| 407 |
+
bottom: dropdownBtnRef.current
|
| 408 |
+
? `${
|
| 409 |
+
window.innerHeight -
|
| 410 |
+
dropdownBtnRef.current.getBoundingClientRect().top
|
| 411 |
+
}px`
|
| 412 |
+
: "auto",
|
| 413 |
+
left: dropdownBtnRef.current
|
| 414 |
+
? `${
|
| 415 |
+
dropdownBtnRef.current.getBoundingClientRect().left
|
| 416 |
+
}px`
|
| 417 |
+
: "auto",
|
| 418 |
+
width: dropdownBtnRef.current
|
| 419 |
+
? `${
|
| 420 |
+
dropdownBtnRef.current.getBoundingClientRect()
|
| 421 |
+
.width + 200
|
| 422 |
+
}px`
|
| 423 |
+
: "320px",
|
| 424 |
+
zIndex: 1000,
|
| 425 |
+
}}
|
| 426 |
+
className="bg-gray-900 border-2 border-gray-600 rounded-2xl shadow-2xl overflow-y-auto max-h-[300px] min-w-[320px]"
|
| 427 |
+
onClick={(e) => e.stopPropagation()}
|
| 428 |
+
>
|
| 429 |
+
{MODEL_OPTIONS.map((option, index) => {
|
| 430 |
+
const selected = selectedModelId === option.id;
|
| 431 |
+
const isActive = activeIndex === index;
|
| 432 |
+
return (
|
| 433 |
+
<button
|
| 434 |
+
key={option.id}
|
| 435 |
+
role="menuitem"
|
| 436 |
+
aria-checked={selected}
|
| 437 |
+
onMouseEnter={() => setActiveIndex(index)}
|
| 438 |
+
onClick={(e) => {
|
| 439 |
+
e.stopPropagation();
|
| 440 |
+
handleModelSelect(option.id);
|
| 441 |
+
setIsModelDropdownOpen(false);
|
| 442 |
+
dropdownBtnRef.current?.focus();
|
| 443 |
+
}}
|
| 444 |
+
className={`w-full px-6 py-4 text-left transition-all duration-150 relative outline-none border-b border-gray-700/50 last:border-b-0 cursor-pointer ${
|
| 445 |
+
selected
|
| 446 |
+
? "bg-indigo-600 text-white hover:bg-indigo-500"
|
| 447 |
+
: "bg-gray-800 text-gray-300 hover:bg-gray-700 hover:text-white active:bg-gray-600"
|
| 448 |
+
} ${index === 0 ? "rounded-t-2xl" : ""} ${
|
| 449 |
+
index === MODEL_OPTIONS.length - 1
|
| 450 |
+
? "rounded-b-2xl"
|
| 451 |
+
: ""
|
| 452 |
+
} ${isActive && !selected ? "bg-gray-700" : ""}`}
|
| 453 |
+
>
|
| 454 |
+
<div className="flex items-center justify-between">
|
| 455 |
+
<div className="flex-1">
|
| 456 |
+
<div
|
| 457 |
+
className={`font-semibold text-base mb-1 ${
|
| 458 |
+
selected ? "text-white" : "text-gray-100"
|
| 459 |
+
}`}
|
| 460 |
+
>
|
| 461 |
+
{option.label}
|
| 462 |
+
</div>
|
| 463 |
+
<div
|
| 464 |
+
className={`text-sm ${
|
| 465 |
+
selected ? "text-indigo-200" : "text-gray-500"
|
| 466 |
+
}`}
|
| 467 |
+
>
|
| 468 |
+
{option.size}
|
| 469 |
+
</div>
|
| 470 |
</div>
|
| 471 |
+
{selected && (
|
| 472 |
+
<div className="flex items-center gap-2 ml-4">
|
| 473 |
+
<span className="text-xs font-medium text-indigo-200">
|
| 474 |
+
Selected
|
| 475 |
+
</span>
|
| 476 |
+
<svg
|
| 477 |
+
className="w-5 h-5 text-white"
|
| 478 |
+
fill="currentColor"
|
| 479 |
+
viewBox="0 0 20 20"
|
| 480 |
+
>
|
| 481 |
+
<path
|
| 482 |
+
fillRule="evenodd"
|
| 483 |
+
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
| 484 |
+
clipRule="evenodd"
|
| 485 |
+
/>
|
| 486 |
+
</svg>
|
| 487 |
+
</div>
|
| 488 |
+
)}
|
| 489 |
</div>
|
| 490 |
+
</button>
|
| 491 |
+
);
|
| 492 |
+
})}
|
| 493 |
+
</div>
|
| 494 |
+
</>,
|
| 495 |
+
document.body
|
| 496 |
+
)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 497 |
</div>
|
| 498 |
</div>
|
| 499 |
|
|
|
|
| 513 |
</div>
|
| 514 |
)}
|
| 515 |
</div>
|
|
|
|
| 516 |
</div>
|
| 517 |
);
|
| 518 |
};
|
src/components/icons/WebGPULogo.tsx
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from "react";
|
| 2 |
+
|
| 3 |
+
const WebGPULogo = (props: React.SVGProps<SVGSVGElement>) => (
|
| 4 |
+
<svg
|
| 5 |
+
{...props}
|
| 6 |
+
xmlns="http://www.w3.org/2000/svg"
|
| 7 |
+
width="768"
|
| 8 |
+
height="600"
|
| 9 |
+
viewBox="0 0 768 600"
|
| 10 |
+
fill="currentColor"
|
| 11 |
+
>
|
| 12 |
+
<defs>
|
| 13 |
+
<style>
|
| 14 |
+
{`.cls-1, .cls-2, .cls-3, .cls-4, .cls-5 {
|
| 15 |
+
fill-rule: evenodd;
|
| 16 |
+
stroke-linejoin: round;
|
| 17 |
+
}
|
| 18 |
+
.cls-1 {
|
| 19 |
+
fill: #005a9c;
|
| 20 |
+
stroke: #005a9c;
|
| 21 |
+
}
|
| 22 |
+
.cls-2 {
|
| 23 |
+
fill: #0066b0;
|
| 24 |
+
stroke: #0066b0;
|
| 25 |
+
}
|
| 26 |
+
.cls-3 {
|
| 27 |
+
fill: #0076cc;
|
| 28 |
+
stroke: #0076cc;
|
| 29 |
+
}
|
| 30 |
+
.cls-4 {
|
| 31 |
+
fill: #0086e8;
|
| 32 |
+
stroke: #0086e8;
|
| 33 |
+
}
|
| 34 |
+
.cls-5 {
|
| 35 |
+
fill: #0093ff;
|
| 36 |
+
stroke: #0093ff;
|
| 37 |
+
}`}
|
| 38 |
+
</style>
|
| 39 |
+
</defs>
|
| 40 |
+
<path className="cls-4" d="m626.63 295.5-60.189-104.25h120.55z" />
|
| 41 |
+
<path className="cls-5" d="m626.63 87.001-60.189 104.25h120.55z" />
|
| 42 |
+
<path className="cls-3" d="m506.26 504-120.38-208.5 240.76-2e-3z" />
|
| 43 |
+
<path className="cls-2" d="m506.26 87-120.38 208.5 240.76-2e-3z" />
|
| 44 |
+
<path className="cls-1" d="m265.5 504-240.76-417h481.51z" />
|
| 45 |
+
</svg>
|
| 46 |
+
);
|
| 47 |
+
|
| 48 |
+
export default WebGPULogo;
|