mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-10-25 17:54:34 +08:00
feat(update): add in-app update notice
This commit is contained in:
1 parent
218c3c9ee9
commit
9030d6b0c3
17 files changed
+328
-50
No files matched your search
+20
-1
@@ -31,11 +31,30 @@ const program = new Command()
|
||||
|
||||
program.name('chatlab').description('ChatLab - Chat history analysis tool').version(getVersion(), '-v, --version')
|
||||
|
||||
program.hook('preAction', async () => {
|
||||
program.hook('preAction', async (_thisCommand, actionCommand) => {
|
||||
if (actionCommand.name() === 'update') return
|
||||
const { checkForUpdatesInteractive } = await import('./update-checker')
|
||||
await checkForUpdatesInteractive()
|
||||
})
|
||||
|
||||
program
|
||||
.command('update')
|
||||
.description('Update ChatLab CLI to the latest version')
|
||||
.action(async () => {
|
||||
const { performCliSelfUpdate } = await import('./update-checker')
|
||||
const result = await performCliSelfUpdate({
|
||||
write: (text) => process.stderr.write(text),
|
||||
})
|
||||
|
||||
if (result.success) {
|
||||
console.error(' Updated successfully. Please restart chatlab to use the new version.\n')
|
||||
return
|
||||
}
|
||||
|
||||
console.error(` Update failed: ${result.error || 'unknown error'}\n`)
|
||||
process.exitCode = 1
|
||||
})
|
||||
|
||||
program
|
||||
.command('sessions')
|
||||
.description('List all imported chat sessions')
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
import * as os from 'os'
|
||||
import * as path from 'path'
|
||||
import type { FastifyInstance } from 'fastify'
|
||||
import type { PathProvider } from '@openchatlab/core'
|
||||
import { isNewerStableVersion, type PathProvider } from '@openchatlab/core'
|
||||
import type {
|
||||
DatabaseManager,
|
||||
AIConversationManager,
|
||||
@@ -55,27 +55,6 @@ export interface AiContextOptions {
|
||||
runAgentStream?: HttpRouteContext['runAgentStream']
|
||||
}
|
||||
|
||||
/**
|
||||
* Semver comparison: returns true if `latest` is strictly newer than `current`.
|
||||
* Handles pre-release tags (e.g. 0.22.0-beta.1 < 0.22.0).
|
||||
*/
|
||||
function isNewerVersion(latest: string, current: string): boolean {
|
||||
const parse = (v: string) => {
|
||||
const [core, pre] = v.split('-', 2)
|
||||
const parts = core.split('.').map(Number)
|
||||
return { major: parts[0] || 0, minor: parts[1] || 0, patch: parts[2] || 0, pre }
|
||||
}
|
||||
const l = parse(latest)
|
||||
const c = parse(current)
|
||||
if (l.major !== c.major) return l.major > c.major
|
||||
if (l.minor !== c.minor) return l.minor > c.minor
|
||||
if (l.patch !== c.patch) return l.patch > c.patch
|
||||
// Same core version: stable > pre-release
|
||||
// Design note: prerelease-to-prerelease updates are intentionally not surfaced.
|
||||
if (c.pre && !l.pre) return true
|
||||
return false
|
||||
}
|
||||
|
||||
export function registerWebRoutes(
|
||||
server: FastifyInstance,
|
||||
dbManager: DatabaseManager,
|
||||
@@ -161,18 +140,17 @@ export function registerWebRoutes(
|
||||
|
||||
server.get('/_web/system/check-update', async () => {
|
||||
const currentVersion = getVersion()
|
||||
const packageName = 'chatlab-cli'
|
||||
try {
|
||||
const resp = await fetch(`https://registry.npmjs.org/${packageName}/latest`, {
|
||||
const resp = await fetch('https://chatlab.fun/latest-version', {
|
||||
headers: { Accept: 'application/json' },
|
||||
signal: AbortSignal.timeout(10_000),
|
||||
})
|
||||
if (!resp.ok) {
|
||||
return { hasUpdate: false, currentVersion, error: `npm registry HTTP ${resp.status}` }
|
||||
return { hasUpdate: false, currentVersion, error: `latest-version HTTP ${resp.status}` }
|
||||
}
|
||||
const data = (await resp.json()) as { version?: string }
|
||||
const latestVersion = data.version || currentVersion
|
||||
const hasUpdate = isNewerVersion(latestVersion, currentVersion)
|
||||
const hasUpdate = isNewerStableVersion(latestVersion, currentVersion)
|
||||
return { hasUpdate, currentVersion, latestVersion }
|
||||
} catch (err) {
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { describe, it } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { performCliSelfUpdate } from './update-checker'
|
||||
|
||||
describe('performCliSelfUpdate', () => {
|
||||
it('installs the latest chatlab-cli package globally', async () => {
|
||||
const calls: Array<{ command: string; args: string[] }> = []
|
||||
|
||||
const result = await performCliSelfUpdate({
|
||||
runCommand: async (command, args) => {
|
||||
calls.push({ command, args })
|
||||
return { success: true }
|
||||
},
|
||||
write: () => {},
|
||||
platform: 'darwin',
|
||||
})
|
||||
|
||||
assert.deepEqual(calls, [
|
||||
{
|
||||
command: 'npm',
|
||||
args: ['install', '-g', 'chatlab-cli@latest'],
|
||||
},
|
||||
])
|
||||
assert.deepEqual(result, { success: true })
|
||||
})
|
||||
})
|
||||
@@ -23,6 +23,17 @@ interface UpdateCache {
|
||||
skippedVersion?: string
|
||||
}
|
||||
|
||||
export interface UpdateCommandResult {
|
||||
success: boolean
|
||||
error?: string
|
||||
}
|
||||
|
||||
export interface PerformCliSelfUpdateOptions {
|
||||
runCommand?: (command: string, args: string[]) => Promise<UpdateCommandResult>
|
||||
write?: (text: string) => void
|
||||
platform?: NodeJS.Platform
|
||||
}
|
||||
|
||||
function readCache(): UpdateCache | null {
|
||||
try {
|
||||
if (fs.existsSync(CACHE_FILE)) {
|
||||
@@ -115,28 +126,35 @@ function promptUser(question: string, choices: string[]): Promise<number> {
|
||||
})
|
||||
}
|
||||
|
||||
function runNpmUpdate(): Promise<{ success: boolean; error?: string }> {
|
||||
function runNpmUpdateCommand(
|
||||
command: string,
|
||||
args: string[],
|
||||
write: (text: string) => void
|
||||
): Promise<UpdateCommandResult> {
|
||||
return new Promise((resolve) => {
|
||||
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm'
|
||||
process.stderr.write(`\n Running: npm install -g ${PACKAGE_NAME}@latest\n\n`)
|
||||
|
||||
const child = execFile(
|
||||
npmCmd,
|
||||
['install', '-g', `${PACKAGE_NAME}@latest`],
|
||||
{ timeout: 120_000 },
|
||||
(err, _stdout, stderr) => {
|
||||
if (err) {
|
||||
resolve({ success: false, error: stderr || err.message })
|
||||
} else {
|
||||
resolve({ success: true })
|
||||
}
|
||||
const child = execFile(command, args, { timeout: 120_000 }, (err, _stdout, stderr) => {
|
||||
if (err) {
|
||||
resolve({ success: false, error: stderr || err.message })
|
||||
} else {
|
||||
resolve({ success: true })
|
||||
}
|
||||
)
|
||||
child.stdout?.pipe(process.stderr)
|
||||
child.stderr?.pipe(process.stderr)
|
||||
})
|
||||
child.stdout?.on('data', (chunk) => write(String(chunk)))
|
||||
child.stderr?.on('data', (chunk) => write(String(chunk)))
|
||||
})
|
||||
}
|
||||
|
||||
export function performCliSelfUpdate(options: PerformCliSelfUpdateOptions = {}): Promise<UpdateCommandResult> {
|
||||
const platformName = options.platform ?? process.platform
|
||||
const npmCmd = platformName === 'win32' ? 'npm.cmd' : 'npm'
|
||||
const args = ['install', '-g', `${PACKAGE_NAME}@latest`]
|
||||
const write = options.write ?? ((text) => process.stderr.write(text))
|
||||
const runCommand = options.runCommand ?? ((command, commandArgs) => runNpmUpdateCommand(command, commandArgs, write))
|
||||
|
||||
write(`\n Running: ${npmCmd} ${args.join(' ')}\n\n`)
|
||||
return runCommand(npmCmd, args)
|
||||
}
|
||||
|
||||
function isDevEnvironment(): boolean {
|
||||
if (process.env.CHATLAB_SKIP_UPDATE_CHECK) return true
|
||||
if (process.env.NODE_ENV === 'development') return true
|
||||
@@ -208,7 +226,7 @@ export async function checkForUpdatesInteractive(): Promise<void> {
|
||||
])
|
||||
|
||||
if (choice === 1) {
|
||||
const result = await runNpmUpdate()
|
||||
const result = await performCliSelfUpdate()
|
||||
if (result.success) {
|
||||
process.stderr.write(
|
||||
` \x1b[32m🎉 Updated successfully! Please restart chatlab to use the new version.\x1b[0m\n\n`
|
||||
|
||||
@@ -180,6 +180,10 @@ export type {
|
||||
TableSchema,
|
||||
} from './query'
|
||||
|
||||
// 版本比较
|
||||
export { isNewerStableVersion, isStableVersion } from './version'
|
||||
export type { ParsedStableVersion } from './version'
|
||||
|
||||
// NLP(平台无关的类型、数据和工具函数)
|
||||
export {
|
||||
POS_TAG_DEFINITIONS,
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { describe, it } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { isNewerStableVersion, isStableVersion } from './version'
|
||||
|
||||
describe('stable version comparison', () => {
|
||||
it('accepts only stable semver versions', () => {
|
||||
assert.equal(isStableVersion('0.24.0'), true)
|
||||
assert.equal(isStableVersion('v0.24.0'), true)
|
||||
assert.equal(isStableVersion('0.24.0-beta.1'), false)
|
||||
assert.equal(isStableVersion('0.24.0-rc.1'), false)
|
||||
assert.equal(isStableVersion('latest'), false)
|
||||
})
|
||||
|
||||
it('reports newer stable versions only when both sides are stable', () => {
|
||||
assert.equal(isNewerStableVersion('0.25.0', '0.24.0'), true)
|
||||
assert.equal(isNewerStableVersion('0.24.1', '0.24.0'), true)
|
||||
assert.equal(isNewerStableVersion('0.24.0', '0.24.0'), false)
|
||||
assert.equal(isNewerStableVersion('0.23.9', '0.24.0'), false)
|
||||
assert.equal(isNewerStableVersion('0.25.0-beta.1', '0.24.0'), false)
|
||||
assert.equal(isNewerStableVersion('0.25.0', '0.25.0-beta.1'), false)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,31 @@
|
||||
export interface ParsedStableVersion {
|
||||
major: number
|
||||
minor: number
|
||||
patch: number
|
||||
}
|
||||
|
||||
function parseStableVersion(version: string): ParsedStableVersion | null {
|
||||
const normalized = version.trim().replace(/^v/i, '')
|
||||
const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(normalized)
|
||||
if (!match) return null
|
||||
|
||||
return {
|
||||
major: Number(match[1]),
|
||||
minor: Number(match[2]),
|
||||
patch: Number(match[3]),
|
||||
}
|
||||
}
|
||||
|
||||
export function isStableVersion(version: string): boolean {
|
||||
return parseStableVersion(version) !== null
|
||||
}
|
||||
|
||||
export function isNewerStableVersion(latest: string, current: string): boolean {
|
||||
const latestVersion = parseStableVersion(latest)
|
||||
const currentVersion = parseStableVersion(current)
|
||||
if (!latestVersion || !currentVersion) return false
|
||||
|
||||
if (latestVersion.major !== currentVersion.major) return latestVersion.major > currentVersion.major
|
||||
if (latestVersion.minor !== currentVersion.minor) return latestVersion.minor > currentVersion.minor
|
||||
return latestVersion.patch > currentVersion.patch
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { storeToRefs } from 'pinia'
|
||||
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { isNewerStableVersion } from '@openchatlab/core'
|
||||
import type { AnalysisSession } from '@/types/base'
|
||||
import SidebarButton from './sidebar/SidebarButton.vue'
|
||||
import SidebarFooter from './sidebar/SidebarFooter.vue'
|
||||
@@ -15,6 +16,9 @@ import { IS_ELECTRON } from '@/utils/platform'
|
||||
import logoSvg from '@/assets/images/logo.svg'
|
||||
|
||||
const { t } = useI18n()
|
||||
const LATEST_VERSION_URL = 'https://chatlab.fun/latest-version'
|
||||
const UPDATE_CHECK_CACHE_KEY = 'chatlab:latest-version-check'
|
||||
const UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000
|
||||
|
||||
const sessionStore = useSessionStore()
|
||||
const layoutStore = useLayoutStore()
|
||||
@@ -39,6 +43,10 @@ const deleteTarget = ref<AnalysisSession | null>(null)
|
||||
|
||||
// 版本号
|
||||
const version = ref('')
|
||||
const latestVersion = ref('')
|
||||
const hasUpdate = ref(false)
|
||||
const showUpdateModal = ref(false)
|
||||
const isStartingUpdate = ref(false)
|
||||
|
||||
// 搜索相关状态
|
||||
const showSearch = ref(false)
|
||||
@@ -74,6 +82,7 @@ onMounted(async () => {
|
||||
sessionStore.loadSessions()
|
||||
try {
|
||||
version.value = await usePlatformService().getVersion()
|
||||
void checkUpdateNotice()
|
||||
} catch (e) {
|
||||
console.error('Failed to get version', e)
|
||||
}
|
||||
@@ -94,6 +103,96 @@ function handleImport() {
|
||||
router.push('/')
|
||||
}
|
||||
|
||||
function readUpdateCheckCache(): { lastCheckTime: number; latestVersion: string | null } | null {
|
||||
try {
|
||||
const raw = window.localStorage.getItem(UPDATE_CHECK_CACHE_KEY)
|
||||
if (!raw) return null
|
||||
const data = JSON.parse(raw) as { lastCheckTime?: unknown; latestVersion?: unknown }
|
||||
if (typeof data.lastCheckTime !== 'number') return null
|
||||
return {
|
||||
lastCheckTime: data.lastCheckTime,
|
||||
latestVersion: typeof data.latestVersion === 'string' ? data.latestVersion : null,
|
||||
}
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function writeUpdateCheckCache(nextLatestVersion: string | null) {
|
||||
try {
|
||||
window.localStorage.setItem(
|
||||
UPDATE_CHECK_CACHE_KEY,
|
||||
JSON.stringify({
|
||||
lastCheckTime: Date.now(),
|
||||
latestVersion: nextLatestVersion,
|
||||
})
|
||||
)
|
||||
} catch {
|
||||
// localStorage 不可用时忽略,更新提醒是非关键能力。
|
||||
}
|
||||
}
|
||||
|
||||
function setLatestVersion(nextLatestVersion: string | null) {
|
||||
latestVersion.value = nextLatestVersion || ''
|
||||
hasUpdate.value = Boolean(nextLatestVersion && isNewerStableVersion(nextLatestVersion, version.value))
|
||||
}
|
||||
|
||||
function parseLatestVersionPayload(data: unknown): string | null {
|
||||
if (!data || typeof data !== 'object') return null
|
||||
const versionValue = (data as { version?: unknown }).version
|
||||
return typeof versionValue === 'string' ? versionValue : null
|
||||
}
|
||||
|
||||
async function fetchLatestVersion(): Promise<string | null> {
|
||||
const platformService = usePlatformService()
|
||||
if (IS_ELECTRON) {
|
||||
const result = await platformService.fetchRemoteConfig(LATEST_VERSION_URL)
|
||||
if (!result.success) return null
|
||||
return parseLatestVersionPayload(result.data)
|
||||
}
|
||||
|
||||
const result = await platformService.checkUpdate()
|
||||
if (!result || result.error) return null
|
||||
return result.latestVersion || null
|
||||
}
|
||||
|
||||
async function checkUpdateNotice() {
|
||||
const cache = readUpdateCheckCache()
|
||||
if (cache) {
|
||||
setLatestVersion(cache.latestVersion)
|
||||
if (Date.now() - cache.lastCheckTime < UPDATE_CHECK_INTERVAL_MS) return
|
||||
}
|
||||
|
||||
try {
|
||||
const nextLatestVersion = await fetchLatestVersion()
|
||||
writeUpdateCheckCache(nextLatestVersion)
|
||||
setLatestVersion(nextLatestVersion)
|
||||
} catch (error) {
|
||||
console.debug('Update notice check failed:', error)
|
||||
}
|
||||
}
|
||||
|
||||
function openUpdateModal() {
|
||||
showUpdateModal.value = true
|
||||
}
|
||||
|
||||
async function confirmUpdate() {
|
||||
if (!IS_ELECTRON) {
|
||||
showUpdateModal.value = false
|
||||
return
|
||||
}
|
||||
|
||||
isStartingUpdate.value = true
|
||||
try {
|
||||
await usePlatformService().checkUpdate()
|
||||
showUpdateModal.value = false
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
isStartingUpdate.value = false
|
||||
}, 3000)
|
||||
}
|
||||
}
|
||||
|
||||
// 打开重命名弹窗
|
||||
function openRenameModal(session: AnalysisSession) {
|
||||
renameTarget.value = session
|
||||
@@ -253,6 +352,15 @@ function getAvatarColorClass(session: AnalysisSession, isActive: boolean) {
|
||||
{{ t('layout.brand') }}
|
||||
</div>
|
||||
<span class="ml-2 text-xs text-gray-400">v{{ version }}</span>
|
||||
<button
|
||||
v-if="hasUpdate"
|
||||
type="button"
|
||||
class="ml-1.5 rounded-full bg-red-500 px-1.5 py-0.5 text-[10px] font-bold leading-none text-white shadow-sm shadow-red-500/20 transition hover:bg-red-600"
|
||||
style="-webkit-app-region: no-drag"
|
||||
@click="openUpdateModal"
|
||||
>
|
||||
{{ t('layout.updateNotice.tag') }}
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
@@ -460,6 +568,42 @@ function getAvatarColorClass(session: AnalysisSession, isActive: boolean) {
|
||||
</template>
|
||||
</UModal>
|
||||
|
||||
<!-- Update Notice Modal -->
|
||||
<UModal v-model:open="showUpdateModal" :ui="{ content: 'z-50' }">
|
||||
<template #content>
|
||||
<div class="p-4">
|
||||
<div class="mb-3 flex items-center gap-2">
|
||||
<div class="flex h-8 w-8 items-center justify-center rounded-lg bg-red-50 text-red-500 dark:bg-red-950/30">
|
||||
<UIcon name="i-lucide-sparkles" class="h-4 w-4" />
|
||||
</div>
|
||||
<h3 class="font-semibold text-gray-900 dark:text-white">
|
||||
{{ t('layout.updateNotice.title') }}
|
||||
</h3>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||
{{ t('layout.updateNotice.message', { version: latestVersion }) }}
|
||||
</p>
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ IS_ELECTRON ? t('layout.updateNotice.desktopHint') : t('layout.updateNotice.cliHint') }}
|
||||
</p>
|
||||
<div
|
||||
v-if="!IS_ELECTRON"
|
||||
class="mt-3 rounded-md border border-gray-200 bg-gray-50 px-3 py-2 font-mono text-xs text-gray-700 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-200"
|
||||
>
|
||||
clb update
|
||||
</div>
|
||||
<div class="mt-4 flex justify-end gap-2">
|
||||
<UButton variant="soft" :disabled="isStartingUpdate" @click="showUpdateModal = false">
|
||||
{{ t('layout.updateNotice.later') }}
|
||||
</UButton>
|
||||
<UButton v-if="IS_ELECTRON" color="primary" :loading="isStartingUpdate" @click="confirmUpdate">
|
||||
{{ t('layout.updateNotice.updateNow') }}
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</UModal>
|
||||
|
||||
<!-- Footer -->
|
||||
<SidebarFooter />
|
||||
</div>
|
||||
|
||||
@@ -21,6 +21,15 @@
|
||||
"title": "Confirm Delete",
|
||||
"message": "Are you sure you want to delete \"{name}\"? This action cannot be undone."
|
||||
},
|
||||
"updateNotice": {
|
||||
"tag": "New",
|
||||
"title": "Update Available",
|
||||
"message": "The latest version is v{version}.",
|
||||
"desktopHint": "Click Update Now to start checking and downloading the desktop update.",
|
||||
"cliHint": "Run the following command in your terminal, then restart the service.",
|
||||
"updateNow": "Update Now",
|
||||
"later": "Later"
|
||||
},
|
||||
"filter": {
|
||||
"all": "All",
|
||||
"private": "Private",
|
||||
|
||||
@@ -393,7 +393,7 @@
|
||||
"upToDate": "You are up to date",
|
||||
"newVersionAvailable": "New version {version} available",
|
||||
"updateNow": "Update Now",
|
||||
"webUpdateHint": "Run npm install -g chatlab-cli@latest in your terminal, then restart the service.",
|
||||
"webUpdateHint": "Run clb update in your terminal, then restart the service.",
|
||||
"updating": "Updating...",
|
||||
"updateSuccess": "Update successful, please restart the service to apply",
|
||||
"updateFailed": "Update failed: {error}",
|
||||
|
||||
@@ -21,6 +21,15 @@
|
||||
"title": "削除の確認",
|
||||
"message": "チャット記録「{name}」を削除しますか?この操作は取り消せません。"
|
||||
},
|
||||
"updateNotice": {
|
||||
"tag": "New",
|
||||
"title": "新しいバージョンがあります",
|
||||
"message": "最新バージョンは v{version} です。",
|
||||
"desktopHint": "今すぐ更新をクリックすると、デスクトップ版の更新確認とダウンロードを開始します。",
|
||||
"cliHint": "ターミナルで次のコマンドを実行し、更新後にサービスを再起動してください。",
|
||||
"updateNow": "今すぐ更新",
|
||||
"later": "後で"
|
||||
},
|
||||
"filter": {
|
||||
"all": "すべて",
|
||||
"private": "個人",
|
||||
|
||||
@@ -393,7 +393,7 @@
|
||||
"upToDate": "最新バージョンです",
|
||||
"newVersionAvailable": "新しいバージョン {version} があります",
|
||||
"updateNow": "今すぐ更新",
|
||||
"webUpdateHint": "ターミナルで npm install -g chatlab-cli@latest を実行し、サービスを再起動してください。",
|
||||
"webUpdateHint": "ターミナルで clb update を実行し、サービスを再起動してください。",
|
||||
"updating": "更新中...",
|
||||
"updateSuccess": "更新が完了しました。サービスを再起動してください",
|
||||
"updateFailed": "更新に失敗しました:{error}",
|
||||
|
||||
@@ -21,6 +21,15 @@
|
||||
"title": "确认删除",
|
||||
"message": "确定要删除聊天记录 \"{name}\" 吗?此操作无法撤销。"
|
||||
},
|
||||
"updateNotice": {
|
||||
"tag": "New",
|
||||
"title": "发现新版本",
|
||||
"message": "最新版本是 v{version}。",
|
||||
"desktopHint": "点击立即更新后,ChatLab 会开始检查并下载桌面端更新。",
|
||||
"cliHint": "请在终端运行以下命令更新 CLI,更新后重启服务。",
|
||||
"updateNow": "立即更新",
|
||||
"later": "稍后"
|
||||
},
|
||||
"filter": {
|
||||
"all": "全部",
|
||||
"private": "私聊",
|
||||
|
||||
@@ -393,7 +393,7 @@
|
||||
"upToDate": "已是最新版本",
|
||||
"newVersionAvailable": "发现新版本 {version}",
|
||||
"updateNow": "立即更新",
|
||||
"webUpdateHint": "请在终端运行 npm install -g chatlab-cli@latest 后重启服务。",
|
||||
"webUpdateHint": "请在终端运行 clb update 后重启服务。",
|
||||
"updating": "更新中...",
|
||||
"updateSuccess": "更新成功,请重启服务以生效",
|
||||
"updateFailed": "更新失败:{error}",
|
||||
|
||||
@@ -21,6 +21,15 @@
|
||||
"title": "確認刪除",
|
||||
"message": "確定要刪除聊天紀錄「{name}」嗎?此操作無法復原。"
|
||||
},
|
||||
"updateNotice": {
|
||||
"tag": "New",
|
||||
"title": "發現新版本",
|
||||
"message": "最新版本是 v{version}。",
|
||||
"desktopHint": "點擊立即更新後,ChatLab 會開始檢查並下載桌面端更新。",
|
||||
"cliHint": "請在終端機執行以下命令更新 CLI,更新後重新啟動服務。",
|
||||
"updateNow": "立即更新",
|
||||
"later": "稍後"
|
||||
},
|
||||
"filter": {
|
||||
"all": "全部",
|
||||
"private": "私聊",
|
||||
|
||||
@@ -393,7 +393,7 @@
|
||||
"upToDate": "已是最新版本",
|
||||
"newVersionAvailable": "發現新版本 {version}",
|
||||
"updateNow": "立即更新",
|
||||
"webUpdateHint": "請在終端機執行 npm install -g chatlab-cli@latest 後重啟服務。",
|
||||
"webUpdateHint": "請在終端機執行 clb update 後重啟服務。",
|
||||
"updating": "更新中...",
|
||||
"updateSuccess": "更新成功,請重啟服務以生效",
|
||||
"updateFailed": "更新失敗:{error}",
|
||||
|
||||
@@ -76,7 +76,7 @@ export class WebPlatformAdapter implements PlatformAdapter {
|
||||
async performUpdate(): Promise<PerformUpdateResult> {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Web update is unavailable. Run npm install -g chatlab-cli@latest in your terminal.',
|
||||
error: 'Web update is unavailable. Run clb update in your terminal.',
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user