返回 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, ReleaseTarget } from '../data/releases';
5 import { releasePath, releaseTargetCount } from '../data/releases';
6 import '../styles/changelog.css';
7
8 const { release, releases, canonical } = Astro.props as {
9 release: ReleaseRecord;
10 releases: ReleaseRecord[];
11 canonical?: string;
12 };
13 const base = import.meta.env.BASE_URL.replace(/\/$/, '');
14 const repo = 'https://github.com/esengine/DeepSeek-Reasonix';
15 const prHref = (ref: number) => `${repo}/pull/${ref}`;
16
17 const isPreview = release.channel === 'prerelease';
18 const latestForChannel = releases[0];
19 const downloadHref = isPreview ? 'https://reasonix.io/?download=desktop#start' : release.links.download;
20
21 const kindLabel = (kind: string) => ({
22 new: { en: 'New', zh: '新增' },
23 improved: { en: 'Improved', zh: '改进' },
24 fixed: { en: 'Fixed', zh: '修复' },
25 security: { en: 'Security', zh: '安全' },
26 }[kind] ?? { en: kind, zh: kind });
27
28 const surfaceLabel = (surface: string) => ({
29 agent: { en: 'Agent', zh: 'Agent' },
30 desktop: { en: 'Desktop', zh: '桌面端' },
31 cli: { en: 'CLI', zh: 'CLI' },
32 mcp: { en: 'MCP', zh: 'MCP' },
33 security: { en: 'Security', zh: '安全' },
34 provider: { en: 'Provider', zh: '模型服务' },
35 plugin: { en: 'Plugins', zh: '插件' },
36 site: { en: 'Site', zh: '网站' },
37 service: { en: 'Service', zh: '服务端' },
38 }[surface] ?? { en: surface, zh: surface });
39
40 const targetingEnabled = release.targetingVersion === 1;
41 const changeKinds = ['new', 'improved', 'fixed'] as const;
42 const clientTargets: { id: ReleaseTarget; en: string; zh: string }[] = [
43 { id: 'desktop', en: 'Desktop updates', zh: '桌面端更新' },
44 { id: 'cli', en: 'CLI updates', zh: 'CLI 端更新' },
45 ];
46 const hasTarget = (item: ReleaseItem, target: ReleaseTarget) => item.targets?.includes(target) ?? false;
47 const clientHighlights = (target: ReleaseTarget) => release.highlights.filter((item) => hasTarget(item, target));
48 const clientChanges = (target: ReleaseTarget, kind: typeof changeKinds[number]) =>
49 release.changes[kind].filter((item) => hasTarget(item, target));
50 const hasClientChangeContent = (target: ReleaseTarget) =>
51 clientHighlights(target).length > 0 || changeKinds.some((kind) => clientChanges(target, kind).length > 0);
52 const isOtherOnly = (item: ReleaseItem) =>
53 Boolean(item.targets?.length) && !hasTarget(item, 'desktop') && !hasTarget(item, 'cli');
54 const otherHighlights = release.highlights.filter(isOtherOnly);
55 const otherChanges = Object.fromEntries(
56 changeKinds.map((kind) => [kind, release.changes[kind].filter(isOtherOnly)]),
57 ) as Record<typeof changeKinds[number], ReleaseItem[]>;
58 const hasOtherChanges = otherHighlights.length > 0 || changeKinds.some((kind) => otherChanges[kind].length > 0);
59
60 /* Sections render only when they have content; numbering is sequential. */
61 const sectionDefs = [
62 ...(release.guides.length ? [{ id: 'guides', en: 'Guides', zh: '使用攻略' }] : []),
63 ...(targetingEnabled
64 ? [
65 ...clientTargets,
66 ...(hasOtherChanges ? [{ id: 'other', en: 'Other project updates', zh: '其他项目更新' }] : []),
67 ]
68 : [
69 { id: 'highlights', en: 'Highlights', zh: '重点内容' },
70 ...changeKinds
71 .filter((kind) => release.changes[kind].length)
72 .map((kind) => ({ id: kind, ...kindLabel(kind) })),
73 ]),
74 ...(release.upgrade.length ? [{ id: 'upgrade', en: 'Upgrade notes', zh: '升级提醒' }] : []),
75 ...(release.risks.length ? [{ id: 'risks', en: 'Risk notes', zh: '风险提示' }] : []),
76 ...(release.contributors.length ? [{ id: 'thanks', en: 'Thanks', zh: '致谢' }] : []),
77 ];
78 const sectionNum = new Map(sectionDefs.map((s, i) => [s.id, String(i + 1).padStart(2, '0')]));
79 const tocItems = [...sectionDefs, { id: 'download', en: 'Download', zh: '下载与安装' }];
80
81 /* Version rail: group by minor, show the latest few patches per group,
82 older ones behind a native <details> expander. */
83 const minorKey = (v: string) => v.split('.').slice(0, 2).join('.');
84 const railGroups: { key: string; items: ReleaseRecord[] }[] = [];
85 for (const item of releases) {
86 const key = minorKey(item.version);
87 const last = railGroups[railGroups.length - 1];
88 if (last && last.key === key) last.items.push(item);
89 else railGroups.push({ key, items: [item] });
90 }
91 const RAIL_VISIBLE = 3;
92
93 const titleEn = `Reasonix v${release.version} release notes`;
94 const titleZh = `Reasonix v${release.version} 更新日志`;
95 const description = release.summary.en;
96 ---
97
98 <Base title={titleEn} titleEn={titleEn} titleZh={titleZh} description={description} canonical={canonical} noindex={isPreview}>
99 <SiteHeader active="changelog" scrolled />
100
101 <main class="release-stage">
102 <div class="release-mobile-switcher">
103 <label for="release-version"><span class="l-en">Version</span><span class="l-zh">版本</span></label>
104 <select id="release-version" data-release-version>
105 {releases.map((item) => <option value={item.version} selected={item.version === release.version}>v{item.version}</option>)}
106 </select>
107 </div>
108
109 <div class="release-grid">
110 <aside class="release-rail" aria-label="Release versions">
111 <div class="release-rail__eyebrow"><span class="release-rail__mark"></span><span class="l-en">Release ledger</span><span class="l-zh">版本档案</span></div>
112 <nav>
113 {railGroups.map((group) => {
114 const channelItems = group.items;
115 if (!channelItems.length) return null;
116 const visible = channelItems.slice(0, RAIL_VISIBLE);
117 const overflow = channelItems.slice(RAIL_VISIBLE);
118 return (
119 <div class="release-rail__group">
120 <p class="release-rail__minor">{group.key}.x</p>
121 {visible.map((item) => (
122 <a class:list={["release-version-link", { active: item.version === release.version }]} href={`${base}${releasePath(item.version)}`}>
123 <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>
124 <small>{item.date}</small>
125 </a>
126 ))}
127 {overflow.length > 0 && (
128 <details class="release-rail__more" open={overflow.some((item) => item.version === release.version)}>
129 <summary>
130 <span class="when-closed"><span class="l-en">+{overflow.length} more</span><span class="l-zh">还有 {overflow.length} 个</span></span>
131 <span class="when-open"><span class="l-en">Show less</span><span class="l-zh">收起</span></span>
132 </summary>
133 {overflow.map((item) => (
134 <a class:list={["release-version-link", { active: item.version === release.version }]} href={`${base}${releasePath(item.version)}`}>
135 <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>
136 <small>{item.date}</small>
137 </a>
138 ))}
139 </details>
140 )}
141 </div>
142 );
143 })}
144 </nav>
145 <a class="release-rail__all" href={`${repo}/releases`}><span class="l-en">All GitHub releases ↗</span><span class="l-zh">全部 GitHub Releases ↗</span></a>
146 </aside>
147
148 <article class="release-paper">
149 <header class="release-hero">
150 <div class="release-hero__meta">
151 {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>}
152 <time datetime={release.date}>{release.date}</time>
153 </div>
154 <h1>Reasonix v{release.version}</h1>
155 <div class="release-surfaces">
156 {release.surfaces.map((surface) => {
157 const label = surfaceLabel(surface);
158 return <span><span class="l-en">{label.en}</span><span class="l-zh">{label.zh}</span></span>;
159 })}
160 </div>
161 {release.builds && (
162 <div class="release-builds" aria-label="Published builds">
163 <span><strong>CLI</strong> {release.builds.cli}</span>
164 <span><strong>Desktop</strong> {release.builds.desktop}</span>
165 <span><strong>npm</strong> {release.builds.npm}</span>
166 </div>
167 )}
168 {targetingEnabled && (
169 <div class="release-target-summary" aria-label="Updates by product">
170 <span><strong>Desktop</strong> {releaseTargetCount(release, 'desktop')}</span>
171 <span><strong>CLI</strong> {releaseTargetCount(release, 'cli')}</span>
172 </div>
173 )}
174 <p class="release-lede"><span class="l-en">{release.summary.en}</span><span class="l-zh">{release.summary.zh}</span></p>
175 </header>
176
177 {release.guides.length > 0 && (
178 <section id="guides" class="release-section release-guides">
179 <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>
180 <div class="release-guide-grid">
181 {release.guides.map((guide) => (
182 <a class="release-guide" href={guide.href}>
183 <span class="release-guide__arrow">↗</span>
184 <h3><span class="l-en">{guide.title.en}</span><span class="l-zh">{guide.title.zh}</span></h3>
185 <p><span class="l-en">{guide.body.en}</span><span class="l-zh">{guide.body.zh}</span></p>
186 </a>
187 ))}
188 </div>
189 </section>
190 )}
191
192 {targetingEnabled ? (
193 <>
194 {clientTargets.map((target) => {
195 const highlights = clientHighlights(target.id);
196 const total = releaseTargetCount(release, target.id);
197 return (
198 <section id={target.id} class="release-section release-target-section" data-target={target.id}>
199 <div class="release-section__head"><span>{sectionNum.get(target.id)}</span><h2><span class="l-en">{target.en}</span><span class="l-zh">{target.zh}</span></h2></div>
200 <p class="release-target-section__summary">
201 <span class="l-en">{total} update{total === 1 ? '' : 's'} relevant to {target.id === 'desktop' ? 'Desktop' : 'CLI'}.</span>
202 <span class="l-zh">与{target.id === 'desktop' ? '桌面端' : ' CLI'}相关的更新共 {total} 项。</span>
203 </p>
204
205 {highlights.length > 0 && (
206 <div class="release-target-group">
207 <h3 class="release-target-group__title"><span class="l-en">Highlights</span><span class="l-zh">重点内容</span></h3>
208 <div class="release-highlight-list">
209 {highlights.map((item, index) => {
210 const kind = kindLabel(item.kind);
211 return (
212 <article class="release-highlight" data-kind={item.kind}>
213 <div class="release-highlight__index">{String(index + 1).padStart(2, '0')}</div>
214 <div>
215 <div class="release-item-meta">
216 <div class="release-highlight__kind"><span class="l-en">{kind.en}</span><span class="l-zh">{kind.zh}</span></div>
217 <div class="release-item-targets">{item.targets?.map((itemTarget) => {
218 const label = surfaceLabel(itemTarget);
219 return <span><span class="l-en">{label.en}</span><span class="l-zh">{label.zh}</span></span>;
220 })}</div>
221 </div>
222 <h3><span class="l-en">{item.title.en}</span><span class="l-zh">{item.title.zh}</span></h3>
223 <p><span class="l-en">{item.body.en}</span><span class="l-zh">{item.body.zh}</span></p>
224 {item.refs?.length && <div class="release-refs">{item.refs.map((ref) => <a href={prHref(ref)}>#{ref}</a>)}</div>}
225 </div>
226 </article>
227 );
228 })}
229 </div>
230 </div>
231 )}
232
233 {changeKinds.map((kind) => {
234 const items = clientChanges(target.id, kind);
235 if (!items.length) return null;
236 const label = kindLabel(kind);
237 return (
238 <div class="release-target-group">
239 <h3 class="release-target-group__title"><span class="l-en">{label.en}</span><span class="l-zh">{label.zh}</span></h3>
240 <div class="release-change-list">
241 {items.map((item: ReleaseItem) => (
242 <article>
243 <div class="release-item-targets">{item.targets?.map((itemTarget) => {
244 const targetLabel = surfaceLabel(itemTarget);
245 return <span><span class="l-en">{targetLabel.en}</span><span class="l-zh">{targetLabel.zh}</span></span>;
246 })}</div>
247 <h3><span class="l-en">{item.title.en}</span><span class="l-zh">{item.title.zh}</span></h3>
248 <p><span class="l-en">{item.body.en}</span><span class="l-zh">{item.body.zh}</span></p>
249 {item.refs?.length && <div class="release-refs">{item.refs.map((ref) => <a href={prHref(ref)}>#{ref}</a>)}</div>}
250 </article>
251 ))}
252 </div>
253 </div>
254 );
255 })}
256
257 {!hasClientChangeContent(target.id) && (
258 <div class="release-target-empty">
259 {target.id === 'cli' && total === 0 ? (
260 <><strong><span class="l-en">No CLI changes in this release.</span><span class="l-zh">本版本没有 CLI 相关改动。</span></strong><p><span class="l-en">CLI users may skip this version if preferred.</span><span class="l-zh">CLI 用户可按需跳过这个版本。</span></p></>
261 ) : (
262 <><strong><span class="l-en">No feature or fix entries in this section.</span><span class="l-zh">此板块没有功能或修复条目。</span></strong><p><span class="l-en">See the upgrade and risk notes below for relevant guidance.</span><span class="l-zh">相关说明请查看下方升级提醒和风险提示。</span></p></>
263 )}
264 </div>
265 )}
266 </section>
267 );
268 })}
269
270 {hasOtherChanges && (
271 <section id="other" class="release-section release-target-section" data-target="other">
272 <div class="release-section__head"><span>{sectionNum.get('other')}</span><h2><span class="l-en">Other project updates</span><span class="l-zh">其他项目更新</span></h2></div>
273 <p class="release-target-section__summary"><span class="l-en">Website and service changes that do not require a Desktop or CLI update.</span><span class="l-zh">不要求更新桌面端或 CLI 的网站与服务端变更。</span></p>
274 <div class="release-change-list">
275 {[...otherHighlights, ...changeKinds.flatMap((kind) => otherChanges[kind])].map((item) => (
276 <article>
277 <div class="release-item-targets">{item.targets?.map((itemTarget) => {
278 const label = surfaceLabel(itemTarget);
279 return <span><span class="l-en">{label.en}</span><span class="l-zh">{label.zh}</span></span>;
280 })}</div>
281 <h3><span class="l-en">{item.title.en}</span><span class="l-zh">{item.title.zh}</span></h3>
282 <p><span class="l-en">{item.body.en}</span><span class="l-zh">{item.body.zh}</span></p>
283 {item.refs?.length && <div class="release-refs">{item.refs.map((ref) => <a href={prHref(ref)}>#{ref}</a>)}</div>}
284 </article>
285 ))}
286 </div>
287 </section>
288 )}
289 </>
290 ) : (
291 <>
292 <section id="highlights" class="release-section">
293 <div class="release-section__head"><span>{sectionNum.get('highlights')}</span><h2><span class="l-en">Highlights</span><span class="l-zh">重点内容</span></h2></div>
294 <div class="release-highlight-list">
295 {release.highlights.map((item, index) => {
296 const kind = kindLabel(item.kind);
297 return (
298 <article class="release-highlight" data-kind={item.kind}>
299 <div class="release-highlight__index">{String(index + 1).padStart(2, '0')}</div>
300 <div>
301 <div class="release-highlight__kind"><span class="l-en">{kind.en}</span><span class="l-zh">{kind.zh}</span></div>
302 <h3><span class="l-en">{item.title.en}</span><span class="l-zh">{item.title.zh}</span></h3>
303 <p><span class="l-en">{item.body.en}</span><span class="l-zh">{item.body.zh}</span></p>
304 {item.refs?.length && <div class="release-refs">{item.refs.map((ref) => <a href={prHref(ref)}>#{ref}</a>)}</div>}
305 </div>
306 </article>
307 );
308 })}
309 </div>
310 </section>
311
312 {changeKinds.map((kind) => {
313 const items = release.changes[kind];
314 if (!items.length) return null;
315 const label = kindLabel(kind);
316 return (
317 <section id={kind} class="release-section release-change-section">
318 <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>
319 <div class="release-change-list">
320 {items.map((item: ReleaseItem) => (
321 <article>
322 <h3><span class="l-en">{item.title.en}</span><span class="l-zh">{item.title.zh}</span></h3>
323 <p><span class="l-en">{item.body.en}</span><span class="l-zh">{item.body.zh}</span></p>
324 {item.refs?.length && <div class="release-refs">{item.refs.map((ref) => <a href={prHref(ref)}>#{ref}</a>)}</div>}
325 </article>
326 ))}
327 </div>
328 </section>
329 );
330 })}
331 </>
332 )}
333
334 {release.upgrade.length > 0 && (
335 <section id="upgrade" class="release-section">
336 <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>
337 <div class="release-notice-list">
338 {release.upgrade.map((item) => (
339 <article class:list={["release-notice", `release-notice--${item.level}`]}>
340 <div class="release-notice__icon">{item.level === 'warning' ? '!' : 'i'}</div>
341 <div>
342 {item.targets?.length && <div class="release-item-targets">{item.targets.map((itemTarget) => {
343 const label = surfaceLabel(itemTarget);
344 return <span><span class="l-en">{label.en}</span><span class="l-zh">{label.zh}</span></span>;
345 })}</div>}
346 <h3><span class="l-en">{item.title.en}</span><span class="l-zh">{item.title.zh}</span></h3>
347 <p><span class="l-en">{item.body.en}</span><span class="l-zh">{item.body.zh}</span></p>
348 {item.refs?.length && <div class="release-refs">{item.refs.map((ref) => <a href={prHref(ref)}>#{ref}</a>)}</div>}
349 </div>
350 </article>
351 ))}
352 </div>
353 </section>
354 )}
355
356 {release.risks.length > 0 && (
357 <section id="risks" class="release-section">
358 <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>
359 <div class="release-change-list">
360 {release.risks.map((item) => (
361 <article>
362 {item.targets?.length && <div class="release-item-targets">{item.targets.map((itemTarget) => {
363 const label = surfaceLabel(itemTarget);
364 return <span><span class="l-en">{label.en}</span><span class="l-zh">{label.zh}</span></span>;
365 })}</div>}
366 <h3><span class="l-en">{item.title.en}</span><span class="l-zh">{item.title.zh}</span></h3>
367 <p><span class="l-en">{item.body.en}</span><span class="l-zh">{item.body.zh}</span></p>
368 {item.refs?.length && <div class="release-refs">{item.refs.map((ref) => <a href={prHref(ref)}>#{ref}</a>)}</div>}
369 </article>
370 ))}
371 </div>
372 </section>
373 )}
374
375 {release.contributors.length > 0 && (
376 <section id="thanks" class="release-section release-thanks">
377 <div class="release-section__head"><span>{sectionNum.get('thanks')}</span><h2><span class="l-en">Thanks</span><span class="l-zh">致谢</span></h2></div>
378 <p><span class="l-en">This release was shaped by:</span><span class="l-zh">感谢本版本的贡献者:</span></p>
379 <div>{release.contributors.map((name) => <a href={`https://github.com/${name}`}>@{name}</a>)}</div>
380 </section>
381 )}
382
383 <section id="download" class="release-section release-download">
384 <div>
385 <p class="release-download__kicker"><span class="l-en">Ready when you are</span><span class="l-zh">准备好即可升级</span></p>
386 <h2><span class="l-en">Ship with a safer Reasonix.</span><span class="l-zh">升级到更可靠的 Reasonix。</span></h2>
387 </div>
388 <div class="release-download__actions">
389 <a class="btn btn-dark" href={downloadHref}><span class="l-en">Download</span><span class="l-zh">下载</span></a>
390 <a class="release-compare" href={release.links.compare}><span class="l-en">Full comparison ↗</span><span class="l-zh">完整差异 ↗</span></a>
391 </div>
392 </section>
393 </article>
394
395 <aside class="release-toc">
396 <p><span class="l-en">In v{release.version}</span><span class="l-zh">在 v{release.version} 中</span></p>
397 <nav>{tocItems.map((item) => <a href={`#${item.id}`}><span class="l-en">{item.en}</span><span class="l-zh">{item.zh}</span></a>)}</nav>
398 </aside>
399 </div>
400 </main>
401
402 <footer class="release-footer">
403 <a class="brand" href={`${base}/`}><img src={`${base}/logo.svg`} alt="" /><span>Reasonix</span></a>
404 <span>© 2026 Reasonix · MIT</span>
405 <a href={repo}>GitHub ↗</a>
406 </footer>
407
408 <script>
409 import '../scripts/changelog.js';
410 </script>
411 </Base>
412
412 lines Plain Text