返回 CodeWhale
page.tsx
根目录 / web / app / [locale] / computer-use / page.tsx
1 import { Icon } from "@/components/icon";
2 import { PageHeader, Section } from "@/components/page-header";
3 import { Status } from "@/components/status-badge";
4 import { getComputerUse } from "@/lib/i18n/dictionaries";
5 import { buildPageMetadata } from "@/lib/page-meta";
6 import { COMPUTER_USE_REPO, getComputerUseRelease } from "@/lib/computer-use-release";
7 import { getEnv } from "@/lib/kv";
8
9 // Rendered per request rather than through ISR: the download state must be
10 // right the moment a release is published, the page must never serve a
11 // build-time snapshot, and OpenNext's in-isolate revalidation queue was seen
12 // leaving stale copies in place for many minutes. The GitHub reads inside
13 // getComputerUseRelease stay cached for five minutes via the fetch data cache.
14 export const dynamic = "force-dynamic";
15
16 export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
17 const { locale } = await params;
18 const copy = getComputerUse(locale);
19 return buildPageMetadata({ path: "/computer-use", locale, title: copy.metaTitle, description: copy.metaDescription });
20 }
21
22 /**
23 * /computer-use — the Mac helper. The download button exists only while the
24 * release check reports a ready, verified release; otherwise the page says
25 * why there is no download and links the published releases.
26 */
27 export default async function ComputerUsePage({ params }: { params: Promise<{ locale: string }> }) {
28 const { locale } = await params;
29 const t = getComputerUse(locale);
30 const release = await getComputerUseRelease((await getEnv()).GITHUB_TOKEN);
31 // The disk image is the human download; the archive stays for the updater.
32 const primary = release.status === "ready" ? (release.dmg ?? { downloadUrl: release.downloadUrl, size: release.size }) : null;
33
34 return (
35 <>
36 <PageHeader
37 kicker={t.publisher}
38 title={t.title}
39 lede={t.lead}
40 pose="computer"
41 meta={
42 <>
43 {t.requirements}
44 <br />
45 {t.included}
46 </>
47 }
48 >
49 {release.status === "ready" && primary ? (
50 <div className="cu-download">
51 <div className="actions">
52 <a className="btn btn-primary btn-lg" href={primary.downloadUrl}>
53 {t.download}
54 </a>
55 <span className="page-meta tabular">
56 v{release.version} · {Math.ceil(primary.size / 1024 / 1024)} MB
57 </span>
58 </div>
59 <p className="status-line">
60 <a href={release.receiptUrl} className="link">{t.receipt}</a>
61 {release.dmg ? <a href={release.downloadUrl} className="link">{t.downloadZip}</a> : null}
62 </p>
63 </div>
64 ) : (
65 <div className="callout" role="status">
66 <p className="callout-title">
67 <Status tone={release.status === "pending" ? "attention" : "idle"}>
68 {release.status === "pending" ? t.pendingTitle : t.unavailableTitle}
69 </Status>
70 </p>
71 <p>{release.status === "pending" ? t.pendingBody : t.unavailableBody}</p>
72 <p>
73 <a href={`${COMPUTER_USE_REPO}/releases`} className="link">{t.releases}</a>
74 </p>
75 </div>
76 )}
77 </PageHeader>
78
79 <div className="page-body">
80 <Section id="cu-setup" title={t.setup}>
81 <ol className="steps">
82 {t.steps.map((step, index) => (
83 <li key={index}>
84 <span className="gs-step-index" aria-hidden="true">{index + 1}</span>
85 <h3>{step.title}</h3>
86 <p>{step.body}</p>
87 </li>
88 ))}
89 </ol>
90 </Section>
91
92 <section className="page-section" aria-label={t.controlsTitle}>
93 <div className="grid-2">
94 <div className="tile">
95 <span className="tile-icon" aria-hidden="true"><Icon name="shield" /></span>
96 <h2 className="section-title">{t.controlsTitle}</h2>
97 <p>{t.controlsBody}</p>
98 <a href={`${COMPUTER_USE_REPO}/blob/main/docs/DEMO.md`} className="section-link">
99 {t.demo}
100 <Icon name="arrow-right" className="icon icon-flip" />
101 </a>
102 </div>
103 <div className="tile">
104 <span className="tile-icon" aria-hidden="true"><Icon name="repeat" /></span>
105 <h2 className="section-title">{t.updateTitle}</h2>
106 <p>{t.updateBody}</p>
107 <a href={`${COMPUTER_USE_REPO}/blob/main/CHANGELOG.md`} className="section-link">
108 {t.notes}
109 <Icon name="arrow-right" className="icon icon-flip" />
110 </a>
111 </div>
112 </div>
113 </section>
114
115 <section className="page-section">
116 <div className="callout">
117 <p className="callout-title">
118 <Icon name="info" className="icon" />
119 {t.platforms}
120 </p>
121 <div className="actions">
122 <a href={`${COMPUTER_USE_REPO}/blob/main/docs/TROUBLESHOOTING.md`} className="btn btn-secondary">{t.help}</a>
123 <a href={COMPUTER_USE_REPO} className="btn btn-ghost">
124 {t.source}
125 <Icon name="external" className="icon" />
126 </a>
127 </div>
128 </div>
129 </section>
130 </div>
131 </>
132 );
133 }
134
134 lines Plain Text