mirror of
https://github.com/Xevion/vastly.git
synced 2025-12-05 23:16:48 -06:00
111 lines
3.8 KiB
TypeScript
111 lines
3.8 KiB
TypeScript
import { api } from "@wails/go/models";
|
|
import { useState } from "react";
|
|
import { cn } from "@src/utils";
|
|
import { Tooltip } from "react-tooltip";
|
|
|
|
export default function Offer({
|
|
offer: { Offer: offer, Score: score, Reasons: reasons, Latency: latency },
|
|
}: {
|
|
offer: api.ScoredOffer;
|
|
}) {
|
|
const copy = (text: string) => navigator.clipboard.writeText(text);
|
|
const [showDetails, setShowDetails] = useState(false);
|
|
const mb_to_gb = (mb: number) => Math.round(mb / 1024);
|
|
|
|
return (
|
|
<div
|
|
className={cn({
|
|
"flex [&>*]:px-2 flex-col relative bg-zinc-700/90 rounded max-w-md overflow-hidden":
|
|
true,
|
|
"h-24": !showDetails,
|
|
"min-h-24 max-h-48": showDetails,
|
|
})}
|
|
>
|
|
<div className="flex">
|
|
<span className="text-4xl font-bold pr-2">
|
|
{score >= 10 ? Math.round(score) : score.toFixed(1)}
|
|
</span>
|
|
<span className="relative text-xl top-2.5 pr-2">
|
|
{offer.num_gpus}x {offer.gpu_name}{" "}
|
|
<span className="text-sm">{mb_to_gb(offer.gpu_ram)} GB</span>
|
|
</span>
|
|
<span className="relative top-3 text-base">
|
|
<span>{latency ?? "?"}</span>
|
|
<span className="text-xs select-none">ms</span>
|
|
</span>
|
|
</div>
|
|
<div>
|
|
<span>${offer.search.totalHour.toFixed(2)}/hr</span>
|
|
<span className="pl-3 text-sm">
|
|
<span className="text-xs select-none">mem</span>{" "}
|
|
{mb_to_gb(offer.cpu_ram)}/{mb_to_gb(offer.cpu_ram / offer.gpu_frac)}GB
|
|
</span>
|
|
<span className="pl-3 text-sm">
|
|
<span className="text-xs select-none">dlperf</span>{" "}
|
|
{offer.dlperf.toFixed(0)}
|
|
</span>
|
|
</div>
|
|
<div className="select-none [>button]:select-auto [>button]:text-blue-500 w-full left-1 text-xs space-x-1">
|
|
<button onClick={() => copy(offer.machine_id.toString())}>
|
|
m{offer.machine_id}
|
|
</button>
|
|
<button onClick={() => copy(offer.host_id.toString())}>
|
|
h{offer.host_id}
|
|
</button>
|
|
<span>{Math.round(offer.duration / 60 / 60 / 24)} days</span>
|
|
<span
|
|
className={
|
|
offer.verification != "verified" ? "text-orange-400/90" : ""
|
|
}
|
|
>
|
|
{offer.verification}
|
|
</span>
|
|
</div>
|
|
<div
|
|
onClick={() => setShowDetails(!showDetails)}
|
|
className={cn({
|
|
"px-0 w-full bg-zinc-900/70 border-t cursor-pointer border-zinc-600/80 text-center":
|
|
true,
|
|
"select-none h-3 leading-[0.2rem] text-zinc-100 absolute bottom-0":
|
|
!showDetails,
|
|
"h-40 overflow-y-auto text-sm": showDetails,
|
|
})}
|
|
>
|
|
{showDetails ? (
|
|
<>
|
|
<Tooltip id="reason" />
|
|
{reasons
|
|
.sort((a, _) => (a.IsMultiplier ? 1 : -1))
|
|
.map((reason, i) => (
|
|
<div
|
|
data-tooltip-id="reason"
|
|
data-tooltip-content={reason.Value}
|
|
key={i}
|
|
className={cn(
|
|
"space-x-2",
|
|
(reason.IsMultiplier && reason.Offset < 1) ||
|
|
(!reason.IsMultiplier && reason.Offset < 0)
|
|
? ""
|
|
: ""
|
|
)}
|
|
>
|
|
{reason.IsMultiplier ? (
|
|
<span>x{reason.Offset.toFixed(2)}</span>
|
|
) : (
|
|
<span>
|
|
{reason.Offset > 0 ? "+" : null}
|
|
{reason.Offset}
|
|
</span>
|
|
)}
|
|
<span>{reason.Reason}</span>
|
|
</div>
|
|
))}
|
|
</>
|
|
) : (
|
|
"..."
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|