返回 CodeWhale
page.tsx
根目录 / web / app / [locale] / computer-use / page.tsx
1 import Image from "next/image";
2 import { getComputerUse } from "@/lib/i18n/dictionaries";
3 import { buildPageMetadata } from "@/lib/page-meta";
4 import { COMPUTER_USE_REPO, getComputerUseRelease } from "@/lib/computer-use-release";
5 import { getEnv } from "@/lib/kv";
6
7 // Rendered per request rather than through ISR: the download state must be
8 // right the moment a release is published, the page must never serve a
9 // build-time snapshot, and OpenNext's in-isolate revalidation queue was seen
10 // leaving stale copies in place for many minutes. The GitHub reads inside
11 // getComputerUseRelease stay cached for five minutes via the fetch data cache.
12 export const dynamic = "force-dynamic";
13
14 export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
15 const { locale } = await params;
16 const copy = getComputerUse(locale);
17 return buildPageMetadata({ path: "/computer-use", locale, title: copy.metaTitle, description: copy.metaDescription });
18 }
19
20 export default async function ComputerUsePage({ params }: { params: Promise<{ locale: string }> }) {
21 const { locale } = await params;
22 const t = getComputerUse(locale);
23 const release = await getComputerUseRelease((await getEnv()).GITHUB_TOKEN);
24 // The disk image is the human download; the archive stays for the updater.
25 const primary = release.status === "ready" ? (release.dmg ?? { downloadUrl: release.downloadUrl, size: release.size }) : null;
26 return (
27 <div>
28 <section className="hero">
29 <div className="portal-container community-welcome-inner">
30 <div className="flex items-center gap-5 mb-5">
31 <Image src="/brand/computer-use.png" width={80} height={80} alt="" priority className="shrink-0" />
32 <h1>{t.title}</h1>
33 </div>
34 <p className="max-w-3xl">{t.lead}</p>
35 <p className="text-sm mt-4">{t.publisher}</p>
36 <div className="mt-8 max-w-3xl">
37 {release.status === "ready" && primary ? <>
38 <a className="portal-button portal-button-primary" href={primary.downloadUrl}>{t.download}</a>
39 <p className="text-sm mt-3">v{release.version} · {Math.ceil(primary.size / 1024 / 1024)} MB</p>
40 <p className="text-sm mt-1 flex flex-wrap gap-x-4">
41 <a href={release.receiptUrl} className="body-link">{t.receipt}</a>
42 {release.dmg ? <a href={release.downloadUrl} className="body-link">{t.downloadZip}</a> : null}
43 </p>
44 </> : <>
45 <h2 className="text-xl">{release.status === "pending" ? t.pendingTitle : t.unavailableTitle}</h2>
46 <p className="mt-2">{release.status === "pending" ? t.pendingBody : t.unavailableBody}</p>
47 <a href={`${COMPUTER_USE_REPO}/releases`} className="body-link mt-3 inline-block">{t.releases}</a>
48 </>}
49 </div>
50 <p className="text-sm mt-6">{t.requirements}<br />{t.included}</p>
51 </div>
52 </section>
53
54 <section className="portal-section">
55 <div className="portal-container">
56 <h2 className="mb-8">{t.setup}</h2>
57 <ol className="grid md:grid-cols-2 gap-x-12 gap-y-8">
58 {t.steps.map((step, index) => <li key={index}>
59 <h3 className="mb-3">{index + 1}. {step.title}</h3>
60 <p className="text-ink-soft leading-relaxed max-w-2xl">{step.body}</p>
61 </li>)}
62 </ol>
63 </div>
64 </section>
65
66 <section className="portal-section portal-section-muted">
67 <div className="portal-container grid md:grid-cols-2 gap-12">
68 <div><h2 className="mb-4">{t.controlsTitle}</h2>
69 <p className="text-ink-soft leading-relaxed">{t.controlsBody}</p>
70 <a href={`${COMPUTER_USE_REPO}/blob/main/docs/DEMO.md`} className="body-link mt-4 inline-block">{t.demo}</a>
71 </div>
72 <div><h2 className="mb-4">{t.updateTitle}</h2>
73 <p className="text-ink-soft leading-relaxed">{t.updateBody}</p>
74 <a href={`${COMPUTER_USE_REPO}/blob/main/CHANGELOG.md`} className="body-link mt-4 inline-block">{t.notes}</a>
75 </div>
76 </div>
77 </section>
78
79 <section className="portal-section">
80 <div className="portal-container">
81 <p className="text-ink-soft max-w-3xl">{t.platforms}</p>
82 <div className="portal-actions">
83 <a href={`${COMPUTER_USE_REPO}/blob/main/docs/TROUBLESHOOTING.md`} className="body-link">{t.help}</a>
84 <a href={COMPUTER_USE_REPO} className="body-link">{t.source}</a>
85 </div>
86 </div>
87 </section>
88 </div>
89 );
90 }
91
91 lines Plain Text