返回 DeepSeek-Reasonix
ChangelogPage.astro
根目录 / site / src / components / ChangelogPage.astro
1 ---
2 import Base from '../layouts/Base.astro';
3 import SiteHeader from './SiteHeader.astro';
4 import type { ReleaseItem, ReleaseRecord } from '../data/releases';
5 import { releasePath } from '../data/releases';
6 import { isThematicTitle } from '../scripts/release-title.js';
7 import '../styles/changelog.css';
8
9 const { release, releases, canonical } = Astro.props as {
10 release: ReleaseRecord;
11 releases: ReleaseRecord[];
12 canonical?: string;
13 };
14 const base = import.meta.env.BASE_URL.replace(/\/$/, '');
15 const repo = 'https://github.com/esengine/DeepSeek-Reasonix';
16 const prHref = (ref: number) => `${repo}/pull/${ref}`;
17
18 /* A title that is exactly the version string ("Reasonix 1.17.14") means a
19 patch release — it gets the compact hero; anything else (including
20 "Reasonix 1.18.0 — Better agents") keeps the full hero. */
21 const isThematic = isThematicTitle(release.title.en, release.version);
22 const isPreview = release.channel === 'prerelease';
23 const latestForChannel = releases[0];
24 const downloadHref = isPreview ? 'https://reasonix.io/?download=desktop#start' : release.links.download;
25
26 const kindLabel = (kind: string) => ({
27 new: { en: 'New', zh: '新增' },
28 improved: { en: 'Improved', zh: '改进' },
29 fixed: { en: 'Fixed', zh: '修复' },
30 security: { en: 'Security', zh: '安全' },
31 }[kind] ?? { en: kind, zh: kind });
32
33 const surfaceLabel = (surface: string) => ({
34 agent: { en: 'Agent', zh: 'Agent' },
35 desktop: { en: 'Desktop', zh: '桌面端' },
36 cli: { en: 'CLI', zh: 'CLI' },
37 mcp: { en: 'MCP', zh: 'MCP' },
38 security: { en: 'Security', zh: '安全' },
39 provider: { en: 'Provider', zh: '模型服务' },
40 plugin: { en: 'Plugins', zh: '插件' },
41 }[surface] ?? { en: surface, zh: surface });
42
43 /* Sections render only when they have content; numbering is sequential. */
44 const sectionDefs = [
45 ...(release.guides.length ? [{ id: 'guides', en: 'Guides', zh: '使用攻略' }] : []),
46 { id: 'highlights', en: 'Highlights', zh: '重点内容' },
47 ...(['new', 'improved', 'fixed'] as const)
48 .filter((kind) => release.changes[kind].length)
49 .map((kind) => ({ id: kind, ...kindLabel(kind) })),
50 ...(release.upgrade.length ? [{ id: 'upgrade', en: 'Upgrade notes', zh: '升级提醒' }] : []),
51 ...(release.risks.length ? [{ id: 'risks', en: 'Risk notes', zh: '风险提示' }] : []),
52 ...(release.contributors.length ? [{ id: 'thanks', en: 'Thanks', zh: '致谢' }] : []),
53 ];
54 const sectionNum = new Map(sectionDefs.map((s, i) => [s.id, String(i + 1).padStart(2, '0')]));
55 const tocItems = [...sectionDefs, { id: 'download', en: 'Download', zh: '下载与安装' }];
56
57 /* Version rail: group by minor, show the latest few patches per group,
58 older ones behind a native <details> expander. */
59 const minorKey = (v: string) => v.split('.').slice(0, 2).join('.');
60 const railGroups: { key: string; items: ReleaseRecord[] }[] = [];
61 for (const item of releases) {
62 const key = minorKey(item.version);
63 const last = railGroups[railGroups.length - 1];
64 if (last && last.key === key) last.items.push(item);
65 else railGroups.push({ key, items: [item] });
66 }
67 const RAIL_VISIBLE = 3;
68
69 const titleEn = `Reasonix v${release.version} release notes`;
70 const titleZh = `Reasonix v${release.version} 更新日志`;
71 const description = release.summary.en;
72 ---
73
74 <Base title={titleEn} titleEn={titleEn} titleZh={titleZh} description={description} canonical={canonical} noindex={isPreview}>
75 <SiteHeader active="changelog" scrolled />
76
77 <main class="release-stage">
78 <div class="release-mobile-switcher">
79 <label for="release-version"><span class="l-en">Version</span><span class="l-zh">版本</span></label>
80 <select id="release-version" data-release-version>
81 {releases.map((item) => <option value={item.version} selected={item.version === release.version}>v{item.version}</option>)}
82 </select>
83 </div>
84
85 <div class="release-grid">
86 <aside class="release-rail" aria-label="Release versions">
87 <div class="release-rail__eyebrow"><span class="release-rail__mark"></span><span class="l-en">Release ledger</span><span class="l-zh">版本档案</span></div>
88 <nav>
89 {railGroups.map((group) => {
90 const channelItems = group.items;
91 if (!channelItems.length) return null;
92 const visible = channelItems.slice(0, RAIL_VISIBLE);
93 const overflow = channelItems.slice(RAIL_VISIBLE);
94 return (
95 <div class="release-rail__group">
96 <p class="release-rail__minor">{group.key}.x</p>
97 {visible.map((item) => (
98 <a class:list={["release-version-link", { active: item.version === release.version }]} href={`${base}${releasePath(item.version)}`}>
99 <span class="release-version-link__line"><strong>v{item.version}</strong>{item === latestForChannel && <em><span class="l-en">Latest</span><span class="l-zh">最新</span></em>}</span>
100 <small>{item.date}</small>
101 </a>
102 ))}
103 {overflow.length > 0 && (
104 <details class="release-rail__more" open={overflow.some((item) => item.version === release.version)}>
105 <summary>
106 <span class="when-closed"><span class="l-en">+{overflow.length} more</span><span class="l-zh">还有 {overflow.length} 个</span></span>
107 <span class="when-open"><span class="l-en">Show less</span><span class="l-zh">收起</span></span>
108 </summary>
109 {overflow.map((item) => (
110 <a class:list={["release-version-link", { active: item.version === release.version }]} href={`${base}${releasePath(item.version)}`}>
111 <span class="release-version-link__line"><strong>v{item.version}</strong>{item === latestForChannel && <em><span class="l-en">Latest</span><span class="l-zh">最新</span></em>}</span>
112 <small>{item.date}</small>
113 </a>
114 ))}
115 </details>
116 )}
117 </div>
118 );
119 })}
120 </nav>
121 <a class="release-rail__all" href={`${repo}/releases`}><span class="l-en">All GitHub releases ↗</span><span class="l-zh">全部 GitHub Releases ↗</span></a>
122 </aside>
123
124 <article class="release-paper">
125 <header class:list={["release-hero", { "release-hero--compact": !isThematic }]}>
126 <div class="release-hero__meta">
127 {isPreview && <span class="release-channel release-channel--preview"><span class="release-channel__dot"></span><span class="l-en">Historical archive</span><span class="l-zh">历史归档</span></span>}
128 <time datetime={release.date}>{release.date}</time>
129 </div>
130 {isThematic && <p class="release-hero__version">Reasonix / v{release.version}</p>}
131 <h1><span class="l-en">{release.title.en}</span><span class="l-zh">{release.title.zh}</span></h1>
132 <div class="release-surfaces">
133 {release.surfaces.map((surface) => {
134 const label = surfaceLabel(surface);
135 return <span><span class="l-en">{label.en}</span><span class="l-zh">{label.zh}</span></span>;
136 })}
137 </div>
138 {release.builds && (
139 <div class="release-builds" aria-label="Published builds">
140 <span><strong>CLI</strong> {release.builds.cli}</span>
141 <span><strong>Desktop</strong> {release.builds.desktop}</span>
142 <span><strong>npm</strong> {release.builds.npm}</span>
143 </div>
144 )}
145 {isThematic ? (
146 <blockquote><span class="l-en">{release.summary.en}</span><span class="l-zh">{release.summary.zh}</span></blockquote>
147 ) : (
148 <p class="release-lede"><span class="l-en">{release.summary.en}</span><span class="l-zh">{release.summary.zh}</span></p>
149 )}
150 {isThematic && (
151 <div class="release-hero__actions">
152 <a class="btn btn-dark" href={downloadHref}><span class="l-en">Download current release</span><span class="l-zh">下载当前正式版</span></a>
153 <a class="btn btn-ghost" href={release.links.github}><span class="l-en">View on GitHub ↗</span><span class="l-zh">在 GitHub 查看 ↗</span></a>
154 </div>
155 )}
156 </header>
157
158 {release.guides.length > 0 && (
159 <section id="guides" class="release-section release-guides">
160 <div class="release-section__head"><span>{sectionNum.get('guides')}</span><h2><span class="l-en">Start here</span><span class="l-zh">使用攻略</span></h2></div>
161 <div class="release-guide-grid">
162 {release.guides.map((guide) => (
163 <a class="release-guide" href={guide.href}>
164 <span class="release-guide__arrow">↗</span>
165 <h3><span class="l-en">{guide.title.en}</span><span class="l-zh">{guide.title.zh}</span></h3>
166 <p><span class="l-en">{guide.body.en}</span><span class="l-zh">{guide.body.zh}</span></p>
167 </a>
168 ))}
169 </div>
170 </section>
171 )}
172
173 <section id="highlights" class="release-section">
174 <div class="release-section__head"><span>{sectionNum.get('highlights')}</span><h2><span class="l-en">Highlights</span><span class="l-zh">重点内容</span></h2></div>
175 <div class="release-highlight-list">
176 {release.highlights.map((item, index) => {
177 const kind = kindLabel(item.kind);
178 return (
179 <article class="release-highlight" data-kind={item.kind}>
180 <div class="release-highlight__index">{String(index + 1).padStart(2, '0')}</div>
181 <div>
182 <div class="release-highlight__kind"><span class="l-en">{kind.en}</span><span class="l-zh">{kind.zh}</span></div>
183 <h3><span class="l-en">{item.title.en}</span><span class="l-zh">{item.title.zh}</span></h3>
184 <p><span class="l-en">{item.body.en}</span><span class="l-zh">{item.body.zh}</span></p>
185 {item.refs?.length && <div class="release-refs">{item.refs.map((ref) => <a href={prHref(ref)}>#{ref}</a>)}</div>}
186 </div>
187 </article>
188 );
189 })}
190 </div>
191 </section>
192
193 {(['new', 'improved', 'fixed'] as const).map((kind) => {
194 const items = release.changes[kind];
195 if (!items.length) return null;
196 const label = kindLabel(kind);
197 return (
198 <section id={kind} class="release-section release-change-section">
199 <div class="release-section__head"><span>{sectionNum.get(kind)}</span><h2><span class="l-en">{label.en}</span><span class="l-zh">{label.zh}</span></h2></div>
200 <div class="release-change-list">
201 {items.map((item: ReleaseItem) => (
202 <article>
203 <h3><span class="l-en">{item.title.en}</span><span class="l-zh">{item.title.zh}</span></h3>
204 <p><span class="l-en">{item.body.en}</span><span class="l-zh">{item.body.zh}</span></p>
205 {item.refs?.length && <div class="release-refs">{item.refs.map((ref) => <a href={prHref(ref)}>#{ref}</a>)}</div>}
206 </article>
207 ))}
208 </div>
209 </section>
210 );
211 })}
212
213 {release.upgrade.length > 0 && (
214 <section id="upgrade" class="release-section">
215 <div class="release-section__head"><span>{sectionNum.get('upgrade')}</span><h2><span class="l-en">Upgrade notes</span><span class="l-zh">升级提醒</span></h2></div>
216 <div class="release-notice-list">
217 {release.upgrade.map((item) => (
218 <article class:list={["release-notice", `release-notice--${item.level}`]}>
219 <div class="release-notice__icon">{item.level === 'warning' ? '!' : 'i'}</div>
220 <div>
221 <h3><span class="l-en">{item.title.en}</span><span class="l-zh">{item.title.zh}</span></h3>
222 <p><span class="l-en">{item.body.en}</span><span class="l-zh">{item.body.zh}</span></p>
223 {item.refs?.length && <div class="release-refs">{item.refs.map((ref) => <a href={prHref(ref)}>#{ref}</a>)}</div>}
224 </div>
225 </article>
226 ))}
227 </div>
228 </section>
229 )}
230
231 {release.risks.length > 0 && (
232 <section id="risks" class="release-section">
233 <div class="release-section__head"><span>{sectionNum.get('risks')}</span><h2><span class="l-en">Risk notes</span><span class="l-zh">风险提示</span></h2></div>
234 <div class="release-change-list">
235 {release.risks.map((item) => (
236 <article>
237 <h3><span class="l-en">{item.title.en}</span><span class="l-zh">{item.title.zh}</span></h3>
238 <p><span class="l-en">{item.body.en}</span><span class="l-zh">{item.body.zh}</span></p>
239 {item.refs?.length && <div class="release-refs">{item.refs.map((ref) => <a href={prHref(ref)}>#{ref}</a>)}</div>}
240 </article>
241 ))}
242 </div>
243 </section>
244 )}
245
246 {release.contributors.length > 0 && (
247 <section id="thanks" class="release-section release-thanks">
248 <div class="release-section__head"><span>{sectionNum.get('thanks')}</span><h2><span class="l-en">Thanks</span><span class="l-zh">致谢</span></h2></div>
249 <p><span class="l-en">This release was shaped by:</span><span class="l-zh">感谢本版本的贡献者:</span></p>
250 <div>{release.contributors.map((name) => <a href={`https://github.com/${name}`}>@{name}</a>)}</div>
251 </section>
252 )}
253
254 <section id="download" class="release-section release-download">
255 <div>
256 <p class="release-download__kicker"><span class="l-en">Ready when you are</span><span class="l-zh">准备好即可升级</span></p>
257 <h2><span class="l-en">Ship with a safer Reasonix.</span><span class="l-zh">升级到更可靠的 Reasonix。</span></h2>
258 </div>
259 <div class="release-download__actions">
260 <a class="btn btn-dark" href={downloadHref}><span class="l-en">Download</span><span class="l-zh">下载</span></a>
261 <a class="release-compare" href={release.links.compare}><span class="l-en">Full comparison ↗</span><span class="l-zh">完整差异 ↗</span></a>
262 </div>
263 </section>
264 </article>
265
266 <aside class="release-toc">
267 <p><span class="l-en">In v{release.version}</span><span class="l-zh">在 v{release.version} 中</span></p>
268 <nav>{tocItems.map((item) => <a href={`#${item.id}`}><span class="l-en">{item.en}</span><span class="l-zh">{item.zh}</span></a>)}</nav>
269 </aside>
270 </div>
271 </main>
272
273 <footer class="release-footer">
274 <a class="brand" href={`${base}/`}><img src={`${base}/logo.svg`} alt="" /><span>Reasonix</span></a>
275 <span>© 2026 Reasonix · MIT</span>
276 <a href={repo}>GitHub ↗</a>
277 </footer>
278
279 <script>
280 import '../scripts/changelog.js';
281 </script>
282 </Base>
283
283 lines Plain Text