mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-10-25 17:54:34 +08:00
feat(changelog): bundle changelogs locally
This commit is contained in:
1 parent
ab90e06089
commit
9e88dcbd09
6 files changed
+78
-110
No files matched your search
@@ -1,49 +0,0 @@
|
||||
name: Sync changelogs to chatlab.fun
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'changelogs/**'
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout ChatLab
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
sparse-checkout: changelogs
|
||||
|
||||
- name: Checkout chatlab.fun
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: hellodigua/chatlab.fun
|
||||
token: ${{ secrets.CHATLAB_FUN_TOKEN }}
|
||||
path: chatlab-fun
|
||||
|
||||
- name: Sync changelogs
|
||||
run: |
|
||||
rsync -av --delete \
|
||||
changelogs/ chatlab-fun/public/changelogs/
|
||||
|
||||
- name: Commit and push
|
||||
id: push
|
||||
working-directory: chatlab-fun
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git add public/changelogs/
|
||||
if git diff --cached --quiet; then
|
||||
echo "No changes to commit"
|
||||
echo "pushed=false" >> $GITHUB_OUTPUT
|
||||
else
|
||||
git commit -m "sync: update changelogs from ChatLab@${GITHUB_SHA::7}"
|
||||
git push
|
||||
echo "pushed=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Trigger EdgeOne deploy
|
||||
if: steps.push.outputs.pushed == 'true'
|
||||
run: curl -s -X POST "${{ secrets.WEBSITE_DEPLOY_HOOK_URL }}"
|
||||
@@ -3,6 +3,7 @@
|
||||
- 边界:只解决当前需求,不做与需求无关的重构和"顺手优化"
|
||||
- 每次完成任务后,对产生修改的文件进行类型检查、lint检查和format格式化,指定修改的文件路径去执行,确保代码质量
|
||||
- 在执行检查时,如果有其他与本次修改无关的报错,也需要一并修复
|
||||
- 测试文件位置:与单个业务模块强相关的单元测试应就近放在被测文件同目录,命名为 `*.test.ts` / `*.test.js`;跨模块、集成、E2E、测试工具或不明显归属某个业务文件的测试放在根目录 `tests/`;某个 app/package 专属测试放在对应的 `apps/*` 或 `packages/*` 内,遵循该子项目现有约定
|
||||
- 多语言:代码中的日志、注释、AI 工具描述、错误消息等非 UI 文本默认使用英文。当有运行时 locale 可用时(如工具返回结果、AI 看到的文本),应通过 `isChineseLocale(locale)` 等机制支持中英双语。数据清洗中与聊天平台格式匹配的标签(如 `[分享]`、`[图片]`)保持原始语言不变。UI 文案的国际化遵循 `.docs/rules/i18n.md`
|
||||
- 多端复用:维护 Electron 和 CLI Web 的共享业务逻辑时,优先在 `packages/node-runtime/src/services/` 下实现,禁止在路由/IPC handler 中绕过 core 直接写 SQL。详见 `.docs/README.md` 的"多端逻辑复用"章节。
|
||||
- Commit 规范:使用 Conventional Commits。scope 规则——通用改动 scope 随意(如 `ai`、`import`、`sidebar` 等模块名);仅当改动是**平台特有**时才使用平台 scope(`electron`、`cli`、`web`)。
|
||||
Vendored
+6
@@ -77,6 +77,12 @@ interface ChatApi {
|
||||
error?: string
|
||||
diagnostics?: ImportDiagnostics
|
||||
}>
|
||||
importDirectory: (dirPath: string) => Promise<{
|
||||
success: boolean
|
||||
sessionId?: string
|
||||
error?: string
|
||||
diagnostics?: ImportDiagnostics
|
||||
}>
|
||||
importWithOptions: (
|
||||
filePath: string,
|
||||
formatOptions: Record<string, unknown>
|
||||
|
||||
@@ -128,7 +128,6 @@ async function handleDirectoryDrop(dirEntry: FileSystemDirectoryEntry, fileList:
|
||||
// Electron: derive directory path from the native file path
|
||||
if (fileList.length > 0) {
|
||||
try {
|
||||
// @ts-expect-error Electron webUtils
|
||||
const filePath: string | undefined = window.electron?.webUtils?.getPathForFile?.(fileList[0])
|
||||
if (filePath) {
|
||||
// fileList[0] may be the directory itself or a file inside it;
|
||||
@@ -192,7 +191,6 @@ function processFiles(files: File[]) {
|
||||
// 尝试获取文件路径(Electron 环境)
|
||||
for (const file of files) {
|
||||
try {
|
||||
// @ts-ignore - Electron webUtils
|
||||
const path = window.electron?.webUtils?.getPathForFile?.(file)
|
||||
if (path) {
|
||||
paths.push(path)
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { CHATLAB_SITE_BASE, getChatlabSiteLocalePath } from '@/utils/chatlabSiteLocale'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useSettingsStore } from '@/stores/settings'
|
||||
import { sanitizeSummary } from '@/utils/sanitizeSummary'
|
||||
import { getChangeTypeConfig } from './changelogTypeConfig'
|
||||
import { IS_ELECTRON } from '@/utils/platform'
|
||||
import { usePlatformService } from '@/services'
|
||||
import CaptureButton from '@/components/common/CaptureButton.vue'
|
||||
import {
|
||||
getBundledChangelogs,
|
||||
getBundledLatestVersion,
|
||||
hasBundledChangelogVersion,
|
||||
isBundledLatestVersion,
|
||||
normalizeChangelogVersion,
|
||||
type ChangelogItem,
|
||||
} from './changelogData'
|
||||
|
||||
const { t } = useI18n()
|
||||
const settingsStore = useSettingsStore()
|
||||
@@ -47,11 +53,6 @@ function toggleVersion(version: string, index: number) {
|
||||
expandedState.value.set(version, !currentState)
|
||||
}
|
||||
|
||||
// 版本号统一格式,避免 v 前缀造成匹配失败
|
||||
function normalizeVersion(version?: string | null) {
|
||||
return version ? version.trim().replace(/^v/i, '') : null
|
||||
}
|
||||
|
||||
// 判断版本是否展开
|
||||
function isExpanded(version: string, index: number) {
|
||||
// 如果有明确设置的状态,使用该状态
|
||||
@@ -68,52 +69,22 @@ function isExpanded(version: string, index: number) {
|
||||
|
||||
// 判断是否是当前软件版本
|
||||
function isCurrentVersion(version: string) {
|
||||
const current = normalizeVersion(currentAppVersion.value)
|
||||
return current ? normalizeVersion(version) === current : false
|
||||
}
|
||||
|
||||
// Changelog 数据结构
|
||||
interface ChangelogItem {
|
||||
version: string
|
||||
date: string
|
||||
summary: string
|
||||
changes: {
|
||||
type: 'feat' | 'fix' | 'refactor' | 'docs' | 'chore' | 'style'
|
||||
items: string[]
|
||||
}[]
|
||||
const current = normalizeChangelogVersion(currentAppVersion.value)
|
||||
return current ? normalizeChangelogVersion(version) === current : false
|
||||
}
|
||||
|
||||
// Changelog 数据
|
||||
const changelogs = ref<ChangelogItem[]>([])
|
||||
|
||||
function getChangelogUrl(lang: string) {
|
||||
const localePath = getChatlabSiteLocalePath(lang)
|
||||
const langPath = localePath || 'en'
|
||||
return `${CHATLAB_SITE_BASE}/changelogs/${langPath}.json`
|
||||
}
|
||||
|
||||
// 从服务端获取 changelog 数据
|
||||
// 从打包产物中读取 changelog 数据
|
||||
async function fetchChangelogs() {
|
||||
isLoading.value = true
|
||||
loadError.value = null
|
||||
|
||||
try {
|
||||
const url = getChangelogUrl(locale.value)
|
||||
let data: unknown
|
||||
if (IS_ELECTRON) {
|
||||
const result = await usePlatformService().fetchRemoteConfig(url)
|
||||
if (!result.success || !result.data) {
|
||||
throw new Error(result.error || 'Failed to fetch')
|
||||
}
|
||||
data = result.data
|
||||
} else {
|
||||
const res = await fetch(url)
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||
data = await res.json()
|
||||
}
|
||||
changelogs.value = data as ChangelogItem[]
|
||||
changelogs.value = getBundledChangelogs(locale.value)
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch changelogs:', error)
|
||||
console.error('Failed to load bundled changelogs:', error)
|
||||
loadError.value = t('home.changelog.loadError')
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
@@ -161,7 +132,6 @@ function markVersionAsRead(version: string) {
|
||||
|
||||
// 检查是否需要显示新版本日志(冷启动时自动检查)
|
||||
async function checkNewVersion() {
|
||||
if (!IS_ELECTRON) return
|
||||
try {
|
||||
if (!localStorage.getItem(AGREEMENT_KEY)) {
|
||||
return
|
||||
@@ -169,12 +139,12 @@ async function checkNewVersion() {
|
||||
|
||||
// 1. 获取当前软件版本号
|
||||
const rawVersion = await usePlatformService().getVersion()
|
||||
const currentVersion = normalizeVersion(rawVersion)
|
||||
const currentVersion = normalizeChangelogVersion(rawVersion)
|
||||
if (!currentVersion) return
|
||||
|
||||
// 2. 获取 localStorage 中存储的已读版本号
|
||||
const rawReadVersion = localStorage.getItem(CHANGELOG_READ_KEY)
|
||||
const readVersion = normalizeVersion(rawReadVersion)
|
||||
const readVersion = normalizeChangelogVersion(rawReadVersion)
|
||||
|
||||
// 2.1 如果是全新用户(从未设置过该 key),静默标记当前版本为已读,不弹窗
|
||||
if (rawReadVersion === null) {
|
||||
@@ -187,22 +157,19 @@ async function checkNewVersion() {
|
||||
return
|
||||
}
|
||||
|
||||
// 4. readVersion 为空或不等于 currentVersion,需要请求远程 changelog 数据
|
||||
const result = await usePlatformService().fetchRemoteConfig(getChangelogUrl(locale.value))
|
||||
if (!result.success || !result.data) return
|
||||
|
||||
const data = result.data as ChangelogItem[]
|
||||
const latestChangelogVersion = normalizeVersion(data[0]?.version)
|
||||
// 4. readVersion 为空或不等于 currentVersion,需要读取本地打包的 changelog 数据
|
||||
const data = getBundledChangelogs(locale.value)
|
||||
const latestChangelogVersion = normalizeChangelogVersion(getBundledLatestVersion(locale.value))
|
||||
if (!latestChangelogVersion) return
|
||||
|
||||
// 仅当“当前版本就是最新版本”时才弹窗
|
||||
// 避免当前版本较旧时(存在更高版本日志)也弹出阅读
|
||||
if (currentVersion !== latestChangelogVersion) {
|
||||
if (!isBundledLatestVersion(locale.value, currentVersion)) {
|
||||
return
|
||||
}
|
||||
|
||||
// 5. 在 changelog 中查找当前软件版本
|
||||
const currentVersionExists = data.some((log) => normalizeVersion(log.version) === currentVersion)
|
||||
const currentVersionExists = hasBundledChangelogVersion(locale.value, currentVersion)
|
||||
|
||||
// 如果在 changelog 中找不到当前版本,说明日志还没更新到当前版本,不显示弹窗
|
||||
if (!currentVersionExists) {
|
||||
@@ -227,12 +194,10 @@ async function checkNewVersion() {
|
||||
// 手动打开弹窗(用户点击时调用),会自动获取数据
|
||||
async function open() {
|
||||
// 手动打开也标记当前版本,避免标签缺失
|
||||
if (IS_ELECTRON) {
|
||||
try {
|
||||
currentAppVersion.value = normalizeVersion(await usePlatformService().getVersion())
|
||||
} catch {
|
||||
currentAppVersion.value = null
|
||||
}
|
||||
try {
|
||||
currentAppVersion.value = normalizeChangelogVersion(await usePlatformService().getVersion())
|
||||
} catch {
|
||||
currentAppVersion.value = null
|
||||
}
|
||||
expandedState.value.clear()
|
||||
showModal.value = true
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import cnChangelogs from '../../../../changelogs/cn.json'
|
||||
import enChangelogs from '../../../../changelogs/en.json'
|
||||
import jaChangelogs from '../../../../changelogs/ja.json'
|
||||
import twChangelogs from '../../../../changelogs/tw.json'
|
||||
|
||||
export type ChangelogChangeType = 'feat' | 'fix' | 'refactor' | 'docs' | 'chore' | 'style' | 'ci'
|
||||
|
||||
export interface ChangelogItem {
|
||||
version: string
|
||||
date: string
|
||||
summary: string
|
||||
changes: {
|
||||
type: ChangelogChangeType
|
||||
items: string[]
|
||||
}[]
|
||||
}
|
||||
|
||||
const BUNDLED_CHANGELOGS: Record<string, ChangelogItem[]> = {
|
||||
'en-US': enChangelogs as ChangelogItem[],
|
||||
'zh-CN': cnChangelogs as ChangelogItem[],
|
||||
'zh-TW': twChangelogs as ChangelogItem[],
|
||||
'ja-JP': jaChangelogs as ChangelogItem[],
|
||||
}
|
||||
|
||||
export function normalizeChangelogVersion(version?: string | null) {
|
||||
return version ? version.trim().replace(/^v/i, '') : null
|
||||
}
|
||||
|
||||
export function getBundledChangelogs(locale: string): ChangelogItem[] {
|
||||
return BUNDLED_CHANGELOGS[locale] ?? BUNDLED_CHANGELOGS['en-US']
|
||||
}
|
||||
|
||||
export function getBundledLatestVersion(locale: string) {
|
||||
return getBundledChangelogs(locale)[0]?.version || null
|
||||
}
|
||||
|
||||
export function hasBundledChangelogVersion(locale: string, version?: string | null) {
|
||||
const normalizedVersion = normalizeChangelogVersion(version)
|
||||
if (!normalizedVersion) return false
|
||||
return getBundledChangelogs(locale).some((log) => normalizeChangelogVersion(log.version) === normalizedVersion)
|
||||
}
|
||||
|
||||
export function isBundledLatestVersion(locale: string, version?: string | null) {
|
||||
const normalizedVersion = normalizeChangelogVersion(version)
|
||||
const latestVersion = normalizeChangelogVersion(getBundledLatestVersion(locale))
|
||||
return Boolean(normalizedVersion && latestVersion && normalizedVersion === latestVersion)
|
||||
}
|
||||
Reference in new issue
Block a user