@allbluecn/web-app 0.4.10 → 0.4.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allbluecn/web-app",
3
- "version": "0.4.10",
3
+ "version": "0.4.11",
4
4
  "license": "AGPL-3.0-or-later",
5
5
  "type": "module",
6
6
  "files": [
@@ -24,6 +24,8 @@
24
24
  "@base-ui/react": "^1.4.1",
25
25
  "@dnd-kit/react": "^0.5.0",
26
26
  "@floating-ui/react": "^0.27.19",
27
+ "@fontsource-variable/geist": "^5.3.0",
28
+ "@fontsource-variable/geist-mono": "^5.3.0",
27
29
  "@iconify/react": "^6.0.2",
28
30
  "@lobehub/icons": "^5.10.0",
29
31
  "@prisma/client": "^7.8.0",
@@ -133,14 +135,14 @@
133
135
  "three": "^0.180.0",
134
136
  "tw-animate-css": "^1.4.0",
135
137
  "zod": "^4.4.3",
136
- "@allbluecn/database": "0.4.10",
138
+ "@allbluecn/ai-gateway": "0.4.11",
139
+ "@allbluecn/database": "0.4.11",
137
140
  "@allbluecn/kernel": "0.3.0",
138
- "@allbluecn/ai-gateway": "0.4.10",
139
- "@allbluecn/plugins-core": "0.4.10",
141
+ "@allbluecn/plugins-core": "0.4.11",
140
142
  "@allbluecn/prototype": "0.1.1",
141
- "@allbluecn/shared": "0.4.10",
142
- "@allbluecn/tiptap": "0.4.10",
143
- "@allbluecn/ui": "0.4.10"
143
+ "@allbluecn/tiptap": "0.4.11",
144
+ "@allbluecn/shared": "0.4.11",
145
+ "@allbluecn/ui": "0.4.11"
144
146
  },
145
147
  "devDependencies": {
146
148
  "@babel/parser": "^8.0.4",
@@ -0,0 +1,23 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 321.33 50">
3
+ <defs>
4
+ <style>
5
+ .cls-1 {
6
+ fill-rule: evenodd;
7
+ }
8
+
9
+ .cls-1, .cls-2 {
10
+ fill: #121212;
11
+ }
12
+ </style>
13
+ </defs>
14
+ <path class="cls-1" d="M19.03,0c13.76,0,19.03,8.54,19.03,25s-5.27,25-19.03,25S0,41.46,0,25C0,8.54,5.27,0,19.03,0ZM25.32,36.42c-1.79,0-2.76.49-3.55.88-.68.34-1.21.61-2.38.61-1.17,0-1.71-.27-2.38-.61-.78-.39-1.76-.88-3.55-.88-1.79,0-2.76.49-3.55.88-.68.34-1.21.61-2.38.61v2.6c1.79,0,2.76-.49,3.55-.88.68-.34,1.21-.61,2.38-.61s1.71.27,2.38.61c.78.39,1.76.88,3.55.88s2.76-.49,3.55-.88c.68-.34,1.21-.61,2.38-.61s1.71.27,2.38.61c.78.39,1.76.88,3.55.88v-2.6c-1.17,0-1.71-.27-2.38-.61-.78-.39-1.76-.88-3.55-.88ZM26.17,10.31c1.35-1.22,0-3.43-1.69-2.76-.93.37-1.81.91-2.59,1.62-1.23,1.11-2.05,2.52-2.5,4.04-.44-1.53-1.27-2.93-2.5-4.04-.78-.7-1.66-1.25-2.6-1.62-1.69-.67-3.04,1.54-1.69,2.76l4.95,4.49c-1.62-1.1-3.56-1.75-5.67-1.75-1.15,0-2.25.19-3.28.54-1.73.59-1.25,3.19.57,3.19h6.91c-1.52.29-2.97.97-4.18,2.06-.78.7-1.4,1.53-1.87,2.43-.84,1.61,1.22,3.18,2.57,1.97l5.57-5.04-.42,13.87c0,.17-.1.33-.22.46-.18-.08-.35-.16-.53-.26-.78-.39-1.76-.88-3.55-.88s-2.76.49-3.55.88c-.68.34-1.21.61-2.38.61v2.6c1.79,0,2.76-.49,3.55-.88.68-.34,1.21-.61,2.38-.61s1.71.27,2.38.61c.78.39,1.76.88,3.55.88s2.76-.49,3.55-.88c.68-.34,1.21-.61,2.38-.61s1.71.27,2.38.61c.78.39,1.76.88,3.55.88v-2.6c-1.17,0-1.71-.27-2.38-.61h0c-.78-.4-1.76-.89-3.55-.89s-2.76.49-3.55.88c-.2.1-.39.2-.59.28-.15-.13-.24-.3-.25-.48l-.42-13.94,5.65,5.12c1.35,1.22,3.4-.35,2.57-1.97-.46-.89-1.08-1.72-1.87-2.43-1.21-1.1-2.65-1.77-4.18-2.06h6.91c1.83,0,2.3-2.6.57-3.19-1.03-.35-2.13-.54-3.28-.54-2.1,0-4.04.65-5.67,1.75l4.95-4.49Z"/>
15
+ <path class="cls-2" d="M151.71,43.25h-12.45c-.67,0-1.28-.42-1.53-1.04l-2.55-6.55c-.65-1.7-3.15-1.22-3.17.58-.02,1.8-.02,3.53-.02,5.37,0,.9-.74,1.64-1.64,1.64h-10.47c-.92,0-1.64-.72-1.64-1.64V8.34c0-.9.72-1.64,1.64-1.64h12.43c.69-.02,1.28.41,1.53,1.04l2.61,6.84c.65,1.7,3.15,1.25,3.17-.57l.07-5.69c0-.9.74-1.63,1.64-1.63h10.4c.9,0,1.64.74,1.64,1.66v33.26c0,.92-.74,1.64-1.64,1.64l-.02-.02Z"/>
16
+ <path class="cls-2" d="M98.25,37.35l-3.85-.04c-.81,0-1.5.58-1.64,1.38l-.53,3.18c-.12.79-.81,1.38-1.62,1.38h-11.07c-1.07,0-1.85-.99-1.6-2.03l8.38-33.28c.18-.72.85-1.24,1.6-1.24h16.76c.76.02,1.41.53,1.58,1.27l8.4,33.25c.26,1.04-.51,2.05-1.58,2.05l-11.07.04c-.81,0-1.5-.58-1.62-1.38l-.53-3.16c-.12-.79-.81-1.38-1.6-1.38v-.04ZM97.94,27.4l-.85-4.19c-.18-.88-1.44-.88-1.62,0l-.76,4.2c-.19,1.02.62,1.98,1.65,1.94,1.02-.04,1.76-.97,1.57-1.98v.02Z"/>
17
+ <path class="cls-2" d="M69.77,43.23l-10.49.04c-.92,0-1.65-.74-1.65-1.64v-21.76c0-.9-.72-1.64-1.64-1.64h-5.39c-.9,0-1.64-.76-1.64-1.66v-8.2c0-.9.74-1.64,1.65-1.64h27.8c.9,0,1.64.74,1.64,1.64v8.2c0,.9-.72,1.64-1.64,1.64h-5.39c-.9,0-1.64.74-1.64,1.66v21.75c0,.9-.74,1.64-1.64,1.64l.02-.02Z"/>
18
+ <path class="cls-2" d="M311.9,24.92l9.21,15.85c.63,1.1-.14,2.47-1.41,2.47l-10.72.05c-.6,0-1.14-.32-1.44-.83l-4.07-7.21c-.6-1.08-2.24-.65-2.25.58l-.1,5.81c0,.9-.74,1.63-1.64,1.63h-10.47c-.92,0-1.64-.72-1.64-1.64V8.37c0-.9.72-1.64,1.64-1.64h10.49c.9,0,1.62.71,1.64,1.59l.11,4.26c.04,1.27,1.74,1.64,2.29.49l2.59-5.39c.28-.57.85-.94,1.48-.94h10.88c1.27-.02,2.06,1.34,1.43,2.44l-7.97,14.1c-.28.51-.28,1.13,0,1.64h-.02Z"/>
19
+ <path class="cls-2" d="M238.4,37.38l-3.85-.04c-.81,0-1.5.58-1.64,1.38l-.53,3.18c-.12.79-.81,1.38-1.62,1.38h-11.09c-1.07,0-1.85-.99-1.6-2.03l8.38-33.26c.18-.72.85-1.24,1.58-1.24h16.78c.76-.02,1.41.49,1.6,1.22l8.4,33.26c.26,1.04-.51,2.05-1.58,2.05h-11.07c-.81.04-1.5-.55-1.62-1.34l-.53-3.16c-.12-.79-.81-1.38-1.6-1.38v-.02ZM238.07,27.43l-.74-3.67c-.19-.99-1.62-.99-1.81.02l-.67,3.69c-.19,1.02.62,1.98,1.65,1.94,1.02-.04,1.76-.97,1.57-1.98Z"/>
20
+ <path class="cls-2" d="M189.42,33.35c-.99,11.71-17.78,12.35-29.17,8.2-.67-.25-1.11-.9-1.11-1.61v-9.57c0-1.31,1.37-2.14,2.52-1.5,3.68,2.01,7.39,3.44,11.6,4.29,0,0,1.39.11,1.9-.79.51-.9-.46-1.25-.88-1.61-8.01-3.48-17.32-5.53-16.49-14.59.97-10.48,16.83-11.75,27.62-8.39.7.21,1.18.9,1.18,1.64v9.38c0,1.24-1.27,2.07-2.41,1.57-5.16-2.28-12.16-4.29-11.81-2.95.9,3.39,18.06,3.76,17.04,15.9l.02.04Z"/>
21
+ <path class="cls-2" d="M271.32,29.38c2.8,2.33,6.25,2.44,9.22.94,1.41-.71,3.06.25,3.06,1.82l.05,8.37c0,.87-.51,1.68-1.34,1.96-8.08,2.86-17.55,1.24-23.25-5.81-5.7-7.05-5.42-16.91.23-23.62,5.65-6.71,15.03-8.27,23.01-5.55.83.28,1.36,1.1,1.36,1.98l-.05,8.37c0,1.57-1.65,2.53-3.06,1.84-2.94-1.47-6.28-1.47-9.12.79-2.48,1.98-2.75,6.69-.11,8.9Z"/>
22
+ <path class="cls-2" d="M210.7,43.24l-10.49.04c-.92,0-1.65-.74-1.65-1.64v-21.76c0-.9-.72-1.64-1.64-1.64h-5.39c-.9,0-1.64-.76-1.64-1.66v-8.2c0-.9.74-1.64,1.65-1.64h27.79c.9,0,1.64.74,1.64,1.64v8.2c0,.9-.72,1.64-1.64,1.64h-5.39c-.9,0-1.64.74-1.64,1.66v21.75c0,.9-.72,1.64-1.64,1.64l.02-.02Z"/>
23
+ </svg>
@@ -0,0 +1,23 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 321.33 50">
3
+ <defs>
4
+ <style>
5
+ .cls-1 {
6
+ fill-rule: evenodd;
7
+ }
8
+
9
+ .cls-1, .cls-2 {
10
+ fill: #fff;
11
+ }
12
+ </style>
13
+ </defs>
14
+ <path class="cls-1" d="M19.03,0c13.76,0,19.03,8.54,19.03,25s-5.27,25-19.03,25S0,41.46,0,25C0,8.54,5.27,0,19.03,0ZM25.32,36.42c-1.79,0-2.76.49-3.55.88-.68.34-1.21.61-2.38.61-1.17,0-1.71-.27-2.38-.61-.78-.39-1.76-.88-3.55-.88-1.79,0-2.76.49-3.55.88-.68.34-1.21.61-2.38.61v2.6c1.79,0,2.76-.49,3.55-.88.68-.34,1.21-.61,2.38-.61s1.71.27,2.38.61c.78.39,1.76.88,3.55.88s2.76-.49,3.55-.88c.68-.34,1.21-.61,2.38-.61s1.71.27,2.38.61c.78.39,1.76.88,3.55.88v-2.6c-1.17,0-1.71-.27-2.38-.61-.78-.39-1.76-.88-3.55-.88ZM26.17,10.31c1.35-1.22,0-3.43-1.69-2.76-.93.37-1.81.91-2.59,1.62-1.23,1.11-2.05,2.52-2.5,4.04-.44-1.53-1.27-2.93-2.5-4.04-.78-.7-1.66-1.25-2.6-1.62-1.69-.67-3.04,1.54-1.69,2.76l4.95,4.49c-1.62-1.1-3.56-1.75-5.67-1.75-1.15,0-2.25.19-3.28.54-1.73.59-1.25,3.19.57,3.19h6.91c-1.52.29-2.97.97-4.18,2.06-.78.7-1.4,1.53-1.87,2.43-.84,1.61,1.22,3.18,2.57,1.97l5.57-5.04-.42,13.87c0,.17-.1.33-.22.46-.18-.08-.35-.16-.53-.26-.78-.39-1.76-.88-3.55-.88s-2.76.49-3.55.88c-.68.34-1.21.61-2.38.61v2.6c1.79,0,2.76-.49,3.55-.88.68-.34,1.21-.61,2.38-.61s1.71.27,2.38.61c.78.39,1.76.88,3.55.88s2.76-.49,3.55-.88c.68-.34,1.21-.61,2.38-.61s1.71.27,2.38.61c.78.39,1.76.88,3.55.88v-2.6c-1.17,0-1.71-.27-2.38-.61h0c-.78-.4-1.76-.89-3.55-.89s-2.76.49-3.55.88c-.2.1-.39.2-.59.28-.15-.13-.24-.3-.25-.48l-.42-13.94,5.65,5.12c1.35,1.22,3.4-.35,2.57-1.97-.46-.89-1.08-1.72-1.87-2.43-1.21-1.1-2.65-1.77-4.18-2.06h6.91c1.83,0,2.3-2.6.57-3.19-1.03-.35-2.13-.54-3.28-.54-2.1,0-4.04.65-5.67,1.75l4.95-4.49Z"/>
15
+ <path class="cls-2" d="M151.71,43.25h-12.45c-.67,0-1.28-.42-1.53-1.04l-2.55-6.55c-.65-1.7-3.15-1.22-3.17.58-.02,1.8-.02,3.53-.02,5.37,0,.9-.74,1.64-1.64,1.64h-10.47c-.92,0-1.64-.72-1.64-1.64V8.34c0-.9.72-1.64,1.64-1.64h12.43c.69-.02,1.28.41,1.53,1.04l2.61,6.84c.65,1.7,3.15,1.25,3.17-.57l.07-5.69c0-.9.74-1.63,1.64-1.63h10.4c.9,0,1.64.74,1.64,1.66v33.26c0,.92-.74,1.64-1.64,1.64l-.02-.02Z"/>
16
+ <path class="cls-2" d="M98.25,37.35l-3.85-.04c-.81,0-1.5.58-1.64,1.38l-.53,3.18c-.12.79-.81,1.38-1.62,1.38h-11.07c-1.07,0-1.85-.99-1.6-2.03l8.38-33.28c.18-.72.85-1.24,1.6-1.24h16.76c.76.02,1.41.53,1.58,1.27l8.4,33.25c.26,1.04-.51,2.05-1.58,2.05l-11.07.04c-.81,0-1.5-.58-1.62-1.38l-.53-3.16c-.12-.79-.81-1.38-1.6-1.38v-.04ZM97.94,27.4l-.85-4.19c-.18-.88-1.44-.88-1.62,0l-.76,4.2c-.19,1.02.62,1.98,1.65,1.94,1.02-.04,1.76-.97,1.57-1.98v.02Z"/>
17
+ <path class="cls-2" d="M69.77,43.23l-10.49.04c-.92,0-1.65-.74-1.65-1.64v-21.76c0-.9-.72-1.64-1.64-1.64h-5.39c-.9,0-1.64-.76-1.64-1.66v-8.2c0-.9.74-1.64,1.65-1.64h27.8c.9,0,1.64.74,1.64,1.64v8.2c0,.9-.72,1.64-1.64,1.64h-5.39c-.9,0-1.64.74-1.64,1.66v21.75c0,.9-.74,1.64-1.64,1.64l.02-.02Z"/>
18
+ <path class="cls-2" d="M311.9,24.92l9.21,15.85c.63,1.1-.14,2.47-1.41,2.47l-10.72.05c-.6,0-1.14-.32-1.44-.83l-4.07-7.21c-.6-1.08-2.24-.65-2.25.58l-.1,5.81c0,.9-.74,1.63-1.64,1.63h-10.47c-.92,0-1.64-.72-1.64-1.64V8.37c0-.9.72-1.64,1.64-1.64h10.49c.9,0,1.62.71,1.64,1.59l.11,4.26c.04,1.27,1.74,1.64,2.29.49l2.59-5.39c.28-.57.85-.94,1.48-.94h10.88c1.27-.02,2.06,1.34,1.43,2.44l-7.97,14.1c-.28.51-.28,1.13,0,1.64h-.02Z"/>
19
+ <path class="cls-2" d="M238.4,37.38l-3.85-.04c-.81,0-1.5.58-1.64,1.38l-.53,3.18c-.12.79-.81,1.38-1.62,1.38h-11.09c-1.07,0-1.85-.99-1.6-2.03l8.38-33.26c.18-.72.85-1.24,1.58-1.24h16.78c.76-.02,1.41.49,1.6,1.22l8.4,33.26c.26,1.04-.51,2.05-1.58,2.05h-11.07c-.81.04-1.5-.55-1.62-1.34l-.53-3.16c-.12-.79-.81-1.38-1.6-1.38v-.02ZM238.07,27.43l-.74-3.67c-.19-.99-1.62-.99-1.81.02l-.67,3.69c-.19,1.02.62,1.98,1.65,1.94,1.02-.04,1.76-.97,1.57-1.98Z"/>
20
+ <path class="cls-2" d="M189.42,33.35c-.99,11.71-17.78,12.35-29.17,8.2-.67-.25-1.11-.9-1.11-1.61v-9.57c0-1.31,1.37-2.14,2.52-1.5,3.68,2.01,7.39,3.44,11.6,4.29,0,0,1.39.11,1.9-.79.51-.9-.46-1.25-.88-1.61-8.01-3.48-17.32-5.53-16.49-14.59.97-10.48,16.83-11.75,27.62-8.39.7.21,1.18.9,1.18,1.64v9.38c0,1.24-1.27,2.07-2.41,1.57-5.16-2.28-12.16-4.29-11.81-2.95.9,3.39,18.06,3.76,17.04,15.9l.02.04Z"/>
21
+ <path class="cls-2" d="M271.32,29.38c2.8,2.33,6.25,2.44,9.22.94,1.41-.71,3.06.25,3.06,1.82l.05,8.37c0,.87-.51,1.68-1.34,1.96-8.08,2.86-17.55,1.24-23.25-5.81-5.7-7.05-5.42-16.91.23-23.62,5.65-6.71,15.03-8.27,23.01-5.55.83.28,1.36,1.1,1.36,1.98l-.05,8.37c0,1.57-1.65,2.53-3.06,1.84-2.94-1.47-6.28-1.47-9.12.79-2.48,1.98-2.75,6.69-.11,8.9Z"/>
22
+ <path class="cls-2" d="M210.7,43.24l-10.49.04c-.92,0-1.65-.74-1.65-1.64v-21.76c0-.9-.72-1.64-1.64-1.64h-5.39c-.9,0-1.64-.76-1.64-1.66v-8.2c0-.9.74-1.64,1.65-1.64h27.79c.9,0,1.64.74,1.64,1.64v8.2c0,.9-.72,1.64-1.64,1.64h-5.39c-.9,0-1.64.74-1.64,1.66v21.75c0,.9-.72,1.64-1.64,1.64l.02-.02Z"/>
23
+ </svg>
@@ -0,0 +1,382 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // AllBlue - 理想之海
3
+ // Copyright (C) 2026 AllBlue Contributors
4
+ //
5
+ // This program is free software: you can redistribute it and/or modify
6
+ // it under the terms of the GNU Affero General Public License as published by
7
+ // the Free Software Foundation, either version 3 of the License, or
8
+ // (at your option) any later version.
9
+ //
10
+ // This program is distributed in the hope that it will be useful,
11
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ // GNU Affero General Public License for more details.
14
+ //
15
+ // You should have received a copy of the GNU Affero General Public License
16
+ // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+ import { cn } from '@/lib/utils'
19
+ import { ScrollText, AlertTriangle, ArrowLeft } from 'lucide-react'
20
+ import ReactMarkdown from 'react-markdown'
21
+ import { useTranslation } from 'react-i18next'
22
+ import { useMemo, useState } from 'react'
23
+ import { Route } from '@/routes/_nav/changelog/route'
24
+ import type { DeveloperLogEntry } from '@allbluecn/shared'
25
+ import { SDK_GROUPS } from '@/lib/changelog/sdk-versions'
26
+ import { APP_FULL_VERSION } from '@/lib/app-version'
27
+
28
+ const TAG_KEYS = ['all', 'feat', 'fix', 'improvement', 'design', 'refactor', 'breaking', 'docs', 'security', 'perf']
29
+
30
+ const REQUIRES_ACTION_TAGS = new Set(['breaking'])
31
+
32
+ function ChangelogSidebar() {
33
+ const { t } = useTranslation('common')
34
+
35
+ return (
36
+ <aside className="shrink-0 w-150 h-full flex flex-col py-6 pr-12 pl-10 border-r border-border bg-muted/50">
37
+ <div className="flex-1 flex flex-col gap-8 justify-center">
38
+ <div>
39
+ <h1 className="text-4xl font-medium tracking-tight">
40
+ {t('changelog.title')}
41
+ </h1>
42
+ <p className="text-sm text-muted-foreground mt-3 leading-relaxed">
43
+ {t('changelog.description')}
44
+ </p>
45
+ </div>
46
+
47
+ <div className="flex flex-col gap-4">
48
+ <h2 className="text-sm font-semibold tracking-tight">
49
+ {t('changelog.sdkVersions')}
50
+ </h2>
51
+ {SDK_GROUPS.map((group) => (
52
+ <SdkGroupSection key={group.id} group={group} t={t} />
53
+ ))}
54
+ </div>
55
+
56
+ <div>
57
+ <h3 className="text-sm font-semibold tracking-tight">
58
+ {t('changelog.subscribe.title')}
59
+ </h3>
60
+ <p className="text-xs text-muted-foreground mt-1 leading-relaxed">
61
+ {t('changelog.subscribe.subtitle')}
62
+ </p>
63
+ <div className="mt-3 flex gap-1.5">
64
+ <input
65
+ type="email"
66
+ placeholder={t('changelog.subscribe.placeholder')}
67
+ className="flex-1 min-w-0 h-8 rounded-md border border-border bg-background px-3 text-xs placeholder:text-muted-foreground/70 focus:outline-none focus:ring-2 focus:ring-primary/20 transition-shadow"
68
+ />
69
+ <button className="h-8 px-3 rounded-md bg-primary text-primary-foreground text-xs font-medium hover:bg-primary/90 transition-colors shrink-0">
70
+ {t('changelog.subscribe.submit')}
71
+ </button>
72
+ </div>
73
+ </div>
74
+ </div>
75
+
76
+ <div className="pt-4 border-border/50">
77
+ <code className="text-[10px] font-mono text-primary/80 bg-primary/5 px-2 py-0.5 rounded-full">
78
+ {APP_FULL_VERSION}
79
+ </code>
80
+ </div>
81
+ </aside>
82
+ )
83
+ }
84
+
85
+ function TagFilter({
86
+ activeTag,
87
+ onTagChange,
88
+ }: {
89
+ activeTag: string
90
+ onTagChange: (tag: string) => void
91
+ }) {
92
+ const { t } = useTranslation('common')
93
+
94
+ return (
95
+ <div className="flex flex-wrap gap-1.5">
96
+ {TAG_KEYS.map((tag) => {
97
+ const isActive = activeTag === tag
98
+ const label = t(`changelog.tags.${tag}`)
99
+ return (
100
+ <button
101
+ key={tag}
102
+ onClick={() => onTagChange(tag)}
103
+ className={cn(
104
+ 'inline-flex items-center gap-1 px-2.5 py-1 rounded-md text-xs font-medium transition-colors border',
105
+ isActive
106
+ ? 'bg-primary/10 text-primary border-primary/20'
107
+ : 'bg-transparent text-muted-foreground border-border/50 hover:text-foreground hover:border-border'
108
+ )}
109
+ >
110
+ {label}
111
+ {REQUIRES_ACTION_TAGS.has(tag) && (
112
+ <AlertTriangle className="size-3" />
113
+ )}
114
+ </button>
115
+ )
116
+ })}
117
+ </div>
118
+ )
119
+ }
120
+
121
+ function YearHeader({ year, count }: { year: string; count: number }) {
122
+ const { t } = useTranslation('common')
123
+
124
+ return (
125
+ <div className="flex items-center gap-3 py-4 mb-1">
126
+ <h3 className="text-xl font-semibold tracking-tight">{year}</h3>
127
+ <span className="text-xs text-muted-foreground font-mono">
128
+ {count} {t('changelog.entries')}
129
+ </span>
130
+ <div className="flex-1 h-px bg-border/50" />
131
+ </div>
132
+ )
133
+ }
134
+
135
+ function ChangelogEntry({
136
+ entry,
137
+ onClick,
138
+ }: {
139
+ entry: { id: string; date: string; title: string; desc: string; tags: string[] }
140
+ onClick: (id: string) => void
141
+ }) {
142
+ const { t } = useTranslation('common')
143
+
144
+ return (
145
+ <div
146
+ className="group py-3 hover:bg-muted/30 -mx-3 px-3 rounded-lg transition-colors cursor-pointer"
147
+ onClick={() => onClick(entry.id)}
148
+ >
149
+ <div className="flex items-start gap-3">
150
+ <div className="shrink-0 pt-0.5">
151
+ <span className="text-xs text-muted-foreground tabular-nums">
152
+ {entry.date}
153
+ </span>
154
+ </div>
155
+ <div className="flex-1 min-w-0">
156
+ <div className="flex items-start gap-2">
157
+ <h4 className="text-sm font-semibold leading-snug group-hover:text-primary transition-colors">
158
+ {entry.title}
159
+ </h4>
160
+ {entry.tags.includes('breaking') && (
161
+ <span className="inline-flex items-center gap-0.5 px-1.5 py-0.5 rounded text-[10px] font-medium bg-destructive/10 text-destructive shrink-0 mt-0.5">
162
+ <AlertTriangle className="size-2.5" />
163
+ {t('changelog.requiresAction')}
164
+ </span>
165
+ )}
166
+ </div>
167
+ <p className="text-xs text-muted-foreground leading-relaxed mt-1 line-clamp-2">
168
+ {entry.desc}
169
+ </p>
170
+ {entry.tags.length > 0 && (
171
+ <div className="flex flex-wrap gap-1 mt-1.5">
172
+ {entry.tags.map((tag) => (
173
+ <span
174
+ key={tag}
175
+ className="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] text-muted-foreground bg-muted/50"
176
+ >
177
+ {t(`changelog.tags.${tag}`)}
178
+ </span>
179
+ ))}
180
+ </div>
181
+ )}
182
+ </div>
183
+ </div>
184
+ </div>
185
+ )
186
+ }
187
+
188
+ function ChangelogDetail({
189
+ entry,
190
+ onBack,
191
+ }: {
192
+ entry: DeveloperLogEntry
193
+ onBack: () => void
194
+ }) {
195
+ const { t } = useTranslation('common')
196
+
197
+ return (
198
+ <div className="h-full flex justify-center">
199
+ <div className="max-w-4xl flex flex-col py-5">
200
+ <button
201
+ onClick={onBack}
202
+ className="flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors mb-6 shrink-0"
203
+ >
204
+ <ArrowLeft className="size-3.5" />
205
+ {t('changelog.backToList')}
206
+ </button>
207
+
208
+ <div className="flex items-start gap-3 mb-4">
209
+ <span className="text-xs text-muted-foreground font-mono tabular-nums pt-0.5 shrink-0">
210
+ {entry.date}
211
+ </span>
212
+ </div>
213
+
214
+ <h2 className="text-2xl font-semibold tracking-tight mb-4">
215
+ {entry.title}
216
+ </h2>
217
+
218
+ {entry.tags.length > 0 && (
219
+ <div className="flex flex-wrap gap-1.5 mb-6">
220
+ {entry.tags.map((tag) => (
221
+ <span
222
+ key={tag}
223
+ className={cn(
224
+ 'inline-flex items-center gap-1 px-2 py-0.5 rounded text-[10px] font-medium border',
225
+ tag === 'breaking'
226
+ ? 'bg-destructive/10 text-destructive border-destructive/20'
227
+ : 'text-muted-foreground bg-muted/50 border-border/50'
228
+ )}
229
+ >
230
+ {t(`changelog.tags.${tag}`)}
231
+ </span>
232
+ ))}
233
+ </div>
234
+ )}
235
+
236
+ <div className="typeset typeset-article max-w-[37em]">
237
+ <ReactMarkdown>{entry.desc}</ReactMarkdown>
238
+ </div>
239
+
240
+ {entry.commitSha && (
241
+ <div className="mt-6 pt-4 border-t border-border/50">
242
+ <code className="text-[10px] font-mono text-muted-foreground">
243
+ commit: {entry.commitSha}
244
+ </code>
245
+ </div>
246
+ )}
247
+ </div>
248
+ </div>
249
+ )
250
+ }
251
+
252
+ function SdkGroupSection({ group, t }: { group: (typeof SDK_GROUPS)[0]; t: (key: string) => string }) {
253
+ const isTanstack = group.id === 'tanstack'
254
+
255
+ return (
256
+ <div>
257
+ <div
258
+ className="flex items-center gap-1.5 w-full py-1"
259
+ >
260
+ {isTanstack ? (
261
+ <span className="shrink-0">
262
+ <img
263
+ src="/tanstack-landscape-black.svg"
264
+ alt="TanStack"
265
+ className="h-4 dark:hidden"
266
+ />
267
+ <img
268
+ src="/tanstack-landscape-white.svg"
269
+ alt="TanStack"
270
+ className="hidden h-4 dark:block"
271
+ />
272
+ </span>
273
+ ) : (
274
+ <span className="text-base">{group.icon}</span>
275
+ )}
276
+ <span className="text-xs mt-1 font-semibold text-foreground/80">
277
+ {t(`changelog.sdkGroups.${group.label}`)}
278
+ </span>
279
+ </div>
280
+ <ul className="mt-1 flex flex-col">
281
+ {group.items.map((sdk) => (
282
+ <li key={sdk.name}>
283
+ <a
284
+ href={sdk.docsUrl || undefined}
285
+ target={sdk.docsUrl ? '_blank' : undefined}
286
+ rel={sdk.docsUrl ? 'noopener noreferrer' : undefined}
287
+ className="flex items-center justify-between gap-2 py-1.5 group"
288
+ >
289
+ <span className="text-xs font-medium text-muted-foreground group-hover:text-primary transition-colors truncate">
290
+ {sdk.name}
291
+ </span>
292
+ <code className="text-[10px] font-mono text-muted-foreground bg-muted/50 px-1.5 py-0.5 rounded shrink-0 group-hover:text-primary transition-colors">
293
+ {sdk.version}
294
+ </code>
295
+ </a>
296
+ </li>
297
+ ))}
298
+ </ul>
299
+ </div>
300
+ )
301
+ }
302
+
303
+ function ChangelogPage() {
304
+ const { t } = useTranslation('common')
305
+ const [activeTag, setActiveTag] = useState<string>('all')
306
+ const [selectedEntryId, setSelectedEntryId] = useState<string | null>(null)
307
+ const { entries } = Route.useLoaderData()
308
+
309
+ const filteredEntries = useMemo(() => {
310
+ if (activeTag === 'all') return entries
311
+ return entries.filter((entry) => entry.tags.includes(activeTag))
312
+ }, [activeTag, entries])
313
+
314
+ const grouped = useMemo(() => {
315
+ const groups: Record<string, DeveloperLogEntry[]> = {}
316
+ filteredEntries.forEach((entry) => {
317
+ if (!groups[entry.year]) groups[entry.year] = []
318
+ groups[entry.year].push(entry)
319
+ })
320
+ return Object.entries(groups).sort(([a], [b]) => Number(b) - Number(a))
321
+ }, [filteredEntries])
322
+
323
+ const selectedEntry = useMemo(
324
+ () => entries.find((e) => e.id === selectedEntryId) ?? null,
325
+ [entries, selectedEntryId]
326
+ )
327
+
328
+ const handleEntryClick = (id: string) => {
329
+ setSelectedEntryId(id)
330
+ }
331
+
332
+ const handleBack = () => {
333
+ setSelectedEntryId(null)
334
+ }
335
+
336
+ return (
337
+ <div className="h-full flex">
338
+ <ChangelogSidebar />
339
+
340
+ <div className="flex-1 min-w-0 flex flex-col h-full pl-8 pr-6 py-5">
341
+ {selectedEntry ? (
342
+ <ChangelogDetail entry={selectedEntry} onBack={handleBack} />
343
+ ) : (
344
+ <>
345
+ <div className="shrink-0 pb-3">
346
+ <TagFilter activeTag={activeTag} onTagChange={setActiveTag} />
347
+ </div>
348
+
349
+ <div className="flex-1 min-h-0 overflow-y-auto">
350
+ {grouped.length === 0 ? (
351
+ <div className="flex flex-col items-center justify-center h-40 text-center">
352
+ <ScrollText className="size-8 text-muted-foreground/40 mb-2" />
353
+ <p className="text-sm text-muted-foreground">{t('changelog.noMatch')}</p>
354
+ <p className="text-xs text-muted-foreground mt-0.5">{t('changelog.noMatchHint')}</p>
355
+ </div>
356
+ ) : (
357
+ <div className="space-y-4">
358
+ {grouped.map(([year, yearEntries]) => (
359
+ <div key={year}>
360
+ <YearHeader year={year} count={yearEntries.length} />
361
+ <div className="space-y-0">
362
+ {yearEntries.map((entry) => (
363
+ <ChangelogEntry
364
+ key={entry.id}
365
+ entry={entry}
366
+ onClick={handleEntryClick}
367
+ />
368
+ ))}
369
+ </div>
370
+ </div>
371
+ ))}
372
+ </div>
373
+ )}
374
+ </div>
375
+ </>
376
+ )}
377
+ </div>
378
+ </div>
379
+ )
380
+ }
381
+
382
+ export { ChangelogPage }
@@ -39,7 +39,8 @@ import { Badge } from "@/components/ui/badge"
39
39
  import { workspaceRoutes } from "@/config/sidebar-routes";
40
40
  import { notifications } from "@/config/sidebar-notifications";
41
41
  import type { Route } from "@/config/sidebar-routes";
42
- import { SidebarSearchTrigger } from "@/components/layout/sidebar-03/sidebar-search-trigger"
42
+ import { SidebarSearchTrigger } from "@/components/layout/sidebar-03/nav-search-trigger"
43
+ import { NavSecondary } from "@/components/layout/sidebar-03/nav-secondary"
43
44
  import { PluginNavItems } from '@/components/plugin-slots/plugin-nav-items'
44
45
 
45
46
  export function DashboardSidebar() {
@@ -101,6 +102,7 @@ export function DashboardSidebar() {
101
102
  <NavAnyHub />
102
103
  </SidebarContent>
103
104
  <SidebarFooter className="gap-2 px-2 pb-3">
105
+ <NavSecondary />
104
106
  <SidebarSearchTrigger />
105
107
  <UserMenu />
106
108
  </SidebarFooter>
@@ -0,0 +1,93 @@
1
+ import {
2
+ SidebarMenu,
3
+ SidebarMenuButton,
4
+ SidebarMenuItem,
5
+ useSidebar,
6
+ } from "@/components/ui/sidebar"
7
+ import { BookOpen, ScrollText } from "lucide-react"
8
+ import { cn } from "@/lib/utils"
9
+ import { Link } from "@tanstack/react-router"
10
+ import { APP_VERSION_DISPLAY } from "@/lib/app-version"
11
+
12
+ const items = [
13
+ {
14
+ title: "使用文档",
15
+ Icon: BookOpen,
16
+ href: "/docs",
17
+ tooltip: "使用文档",
18
+ external: true,
19
+ },
20
+ {
21
+ title: "开发者日志",
22
+ Icon: ScrollText,
23
+ href: "/changelog",
24
+ tooltip: "开发者日志",
25
+ badge: APP_VERSION_DISPLAY,
26
+ },
27
+ ]
28
+
29
+ export function NavSecondary() {
30
+ const { state } = useSidebar()
31
+ const isCollapsed = state === "collapsed"
32
+
33
+ return (
34
+ <SidebarMenu className={cn("space-y-1", isCollapsed && "px-1")}>
35
+ {!isCollapsed && (
36
+ <div className="px-2 text-xs font-medium text-muted-foreground/70">
37
+ {/* 资源 */}
38
+ </div>
39
+ )}
40
+ {items.map((item) => (
41
+ <SidebarMenuItem key={item.title}>
42
+ <SidebarMenuButton
43
+ asChild
44
+ tooltip={item.tooltip}
45
+ className={cn(
46
+ "flex items-center rounded-lg px-2",
47
+ isCollapsed && "justify-center"
48
+ )}
49
+ >
50
+ {item.external ? (
51
+ <a
52
+ href={item.href}
53
+ className={cn(
54
+ "flex w-full items-center gap-3 text-muted-foreground hover:text-foreground",
55
+ isCollapsed && "justify-center -ml-1"
56
+ )}
57
+ target="_blank"
58
+ rel="noopener noreferrer"
59
+ style={{ width: "100%", height: "100%" }}
60
+ >
61
+ <item.Icon className={cn("text-muted-foreground", !isCollapsed && "size-3.5!")} />
62
+ {!isCollapsed && (
63
+ <span className="text-xs text-muted-foreground">{item.title}</span>
64
+ )}
65
+ </a>
66
+ ) : (
67
+ <Link
68
+ to={item.href}
69
+ className={cn(
70
+ "flex w-full items-center justify-between gap-3 text-muted-foreground hover:text-foreground",
71
+ isCollapsed && "justify-center -ml-1"
72
+ )}
73
+ style={{ width: "100%", height: "100%" }}
74
+ >
75
+ <div className="flex items-center gap-3">
76
+ <item.Icon className={cn("text-muted-foreground", !isCollapsed && "size-3.5!")} />
77
+ {!isCollapsed && (
78
+ <span className="text-xs text-muted-foreground">{item.title}</span>
79
+ )}
80
+ </div>
81
+ {!isCollapsed && item.badge && (
82
+ <code className="text-[10px] font-mono text-muted-foreground/60 bg-muted/50 px-1.5 py-0.5 rounded shrink-0">
83
+ {item.badge}
84
+ </code>
85
+ )}
86
+ </Link>
87
+ )}
88
+ </SidebarMenuButton>
89
+ </SidebarMenuItem>
90
+ ))}
91
+ </SidebarMenu>
92
+ )
93
+ }
@@ -0,0 +1,20 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // AllBlue - 理想之海
3
+ // Copyright (C) 2026 AllBlue Contributors
4
+ //
5
+ // This program is free software: you can redistribute it and/or modify
6
+ // it under the terms of the GNU Affero General Public License as published by
7
+ // the Free Software Foundation, either version 3 of the License, or
8
+ // (at your option) any later version.
9
+ //
10
+ // This program is distributed in the hope that it will be useful,
11
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ // GNU Affero General Public License for more details.
14
+ //
15
+ // You should have received a copy of the GNU Affero General Public License
16
+ // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+ export const APP_VERSION = '0.5.0'
19
+ export const APP_VERSION_DISPLAY = `v${APP_VERSION}`
20
+ export const APP_FULL_VERSION = `AllBlue v${APP_VERSION}`
@@ -0,0 +1,14 @@
1
+ export interface SdkVersion {
2
+ name: string
3
+ version: string
4
+ lastUpdate: string
5
+ source: string
6
+ docsUrl: string | null
7
+ }
8
+
9
+ export interface SdkGroup {
10
+ id: 'tanstack' | 'allblue' | 'core'
11
+ label: string
12
+ icon: string
13
+ items: SdkVersion[]
14
+ }