mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-25 07:00:48 +09:00
feat(web): add About page in settings
Add /settings/about page with version display, GitHub release update checker with markdown-rendered release notes, and external links (GitHub, Docs, Feedback). Remove version section from Profile page.
This commit is contained in:
@@ -52,7 +52,7 @@ import { computed, type Component } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { ChevronLeft, Bot, Boxes, Globe, Brain, Volume2, Mail, AppWindow, ChartLine, User, Store } from 'lucide-vue-next'
|
||||
import { ChevronLeft, Bot, Boxes, Globe, Brain, Volume2, Mail, AppWindow, ChartLine, User, Store, Info } from 'lucide-vue-next'
|
||||
import { useChatSelectionStore } from '@/store/chat-selection'
|
||||
import {
|
||||
Sidebar,
|
||||
@@ -143,5 +143,10 @@ const navItems = computed<{ title: string; name: string; icon: Component }[]>(()
|
||||
name: 'profile',
|
||||
icon: User,
|
||||
},
|
||||
{
|
||||
title: t('sidebar.about'),
|
||||
name: 'about',
|
||||
icon: Info,
|
||||
},
|
||||
])
|
||||
</script>
|
||||
|
||||
@@ -70,7 +70,8 @@
|
||||
"platform": "Platform",
|
||||
"usage": "Usage",
|
||||
"browser": "Browser",
|
||||
"supermarket": "Supermarket"
|
||||
"supermarket": "Supermarket",
|
||||
"about": "About"
|
||||
},
|
||||
"breadcrumb": {
|
||||
"main": "Home"
|
||||
@@ -128,6 +129,18 @@
|
||||
"versionTag": "v{version}",
|
||||
"commitHash": "Commit"
|
||||
},
|
||||
"about": {
|
||||
"checkForUpdates": "Check for Updates",
|
||||
"checking": "Checking...",
|
||||
"upToDate": "You are up to date",
|
||||
"newVersionAvailable": "New version available: v{version}",
|
||||
"releaseNotes": "Release Notes",
|
||||
"viewOnGitHub": "View on GitHub",
|
||||
"checkFailed": "Failed to check for updates",
|
||||
"github": "GitHub",
|
||||
"docs": "Docs",
|
||||
"feedback": "Feedback"
|
||||
},
|
||||
"chat": {
|
||||
"greeting": "Hi! How can I help you today?",
|
||||
"selectBot": "Select a Bot",
|
||||
|
||||
@@ -70,7 +70,8 @@
|
||||
"platform": "接入平台",
|
||||
"usage": "用量统计",
|
||||
"browser": "浏览器",
|
||||
"supermarket": "市场"
|
||||
"supermarket": "市场",
|
||||
"about": "关于"
|
||||
},
|
||||
"breadcrumb": {
|
||||
"main": "主页"
|
||||
@@ -124,6 +125,18 @@
|
||||
"versionTag": "v{version}",
|
||||
"commitHash": "提交"
|
||||
},
|
||||
"about": {
|
||||
"checkForUpdates": "检查更新",
|
||||
"checking": "检查中...",
|
||||
"upToDate": "已是最新版本",
|
||||
"newVersionAvailable": "发现新版本: v{version}",
|
||||
"releaseNotes": "更新日志",
|
||||
"viewOnGitHub": "在 GitHub 上查看",
|
||||
"checkFailed": "检查更新失败",
|
||||
"github": "GitHub",
|
||||
"docs": "文档",
|
||||
"feedback": "反馈"
|
||||
},
|
||||
"chat": {
|
||||
"greeting": "你好!有什么我可以帮你的吗?",
|
||||
"selectBot": "选择一个 Bot",
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
<template>
|
||||
<section class="max-w-7xl mx-auto p-4 pb-12">
|
||||
<div class="max-w-3xl mx-auto space-y-6">
|
||||
<!-- Header: Logo + Version + Check Button -->
|
||||
<div class="flex items-center gap-3">
|
||||
<img
|
||||
src="/logo.png"
|
||||
alt="Memoh"
|
||||
class="size-10 shrink-0 rounded-lg"
|
||||
>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="text-sm font-semibold">
|
||||
Memoh
|
||||
</p>
|
||||
<div class="flex items-center gap-2 mt-0.5">
|
||||
<Badge
|
||||
v-if="serverVersion"
|
||||
variant="secondary"
|
||||
>
|
||||
{{ $t('settings.versionTag', { version: serverVersion }) }}
|
||||
</Badge>
|
||||
<Badge
|
||||
v-if="commitHash"
|
||||
variant="outline"
|
||||
>
|
||||
{{ commitHash }}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
:disabled="checking"
|
||||
@click="checkForUpdates"
|
||||
>
|
||||
<Spinner
|
||||
v-if="checking"
|
||||
class="size-3"
|
||||
/>
|
||||
<RefreshCw
|
||||
v-else
|
||||
class="size-3"
|
||||
/>
|
||||
{{ checking ? $t('about.checking') : $t('about.checkForUpdates') }}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<!-- Update Result -->
|
||||
<template v-if="checkResult">
|
||||
<div
|
||||
v-if="checkResult.isUpToDate"
|
||||
class="flex items-center gap-2 text-xs text-muted-foreground"
|
||||
>
|
||||
<CircleCheck class="size-3.5 text-green-500" />
|
||||
{{ $t('about.upToDate') }}
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<Separator />
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<Badge class="bg-[#8B56E3] text-white hover:bg-[#8B56E3]/90">
|
||||
{{ $t('about.newVersionAvailable', { version: checkResult.latestVersion }) }}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="checkResult.body"
|
||||
class="space-y-2"
|
||||
>
|
||||
<h4 class="text-xs font-medium text-muted-foreground">
|
||||
{{ $t('about.releaseNotes') }}
|
||||
</h4>
|
||||
<div class="prose prose-xs dark:prose-invert max-w-none *:first:mt-0 text-[0.8rem] leading-relaxed">
|
||||
<MarkdownRender
|
||||
:content="checkResult.body"
|
||||
:is-dark="isDark"
|
||||
custom-id="release-notes"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- External Links -->
|
||||
<section>
|
||||
<Separator class="mb-4" />
|
||||
<div class="space-y-1">
|
||||
<a
|
||||
href="https://github.com/memohai/memoh"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="flex items-center gap-3 rounded-lg px-3 py-2.5 text-xs text-foreground hover:bg-accent transition-colors"
|
||||
>
|
||||
<Github class="size-4 text-muted-foreground" />
|
||||
{{ $t('about.github') }}
|
||||
<ExternalLink class="size-3 ml-auto text-muted-foreground" />
|
||||
</a>
|
||||
<a
|
||||
href="https://docs.memoh.ai"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="flex items-center gap-3 rounded-lg px-3 py-2.5 text-xs text-foreground hover:bg-accent transition-colors"
|
||||
>
|
||||
<BookOpen class="size-4 text-muted-foreground" />
|
||||
{{ $t('about.docs') }}
|
||||
<ExternalLink class="size-3 ml-auto text-muted-foreground" />
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/memohai/memoh/issues"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="flex items-center gap-3 rounded-lg px-3 py-2.5 text-xs text-foreground hover:bg-accent transition-colors"
|
||||
>
|
||||
<MessageSquare class="size-4 text-muted-foreground" />
|
||||
{{ $t('about.feedback') }}
|
||||
<ExternalLink class="size-3 ml-auto text-muted-foreground" />
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, onMounted } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { toast } from 'vue-sonner'
|
||||
import { RefreshCw, ExternalLink, Github, BookOpen, MessageSquare, CircleCheck } from 'lucide-vue-next'
|
||||
import { Badge, Button, Separator, Spinner } from '@memohai/ui'
|
||||
import MarkdownRender from 'markstream-vue'
|
||||
import { useCapabilitiesStore } from '@/store/capabilities'
|
||||
import { useSettingsStore } from '@/store/settings'
|
||||
|
||||
const GITHUB_REPO = 'memohai/memoh'
|
||||
|
||||
interface CheckResult {
|
||||
isUpToDate: boolean
|
||||
latestVersion: string
|
||||
body: string
|
||||
htmlUrl: string
|
||||
}
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const capabilitiesStore = useCapabilitiesStore()
|
||||
const { serverVersion, commitHash } = storeToRefs(capabilitiesStore)
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
const isDark = computed(() => settingsStore.theme === 'dark')
|
||||
|
||||
const checking = ref(false)
|
||||
const checkResult = ref<CheckResult | null>(null)
|
||||
|
||||
onMounted(async () => {
|
||||
await capabilitiesStore.load()
|
||||
await checkForUpdates()
|
||||
})
|
||||
|
||||
async function checkForUpdates() {
|
||||
checking.value = true
|
||||
checkResult.value = null
|
||||
try {
|
||||
const res = await fetch(`https://api.github.com/repos/${GITHUB_REPO}/releases/latest`)
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||
const data = await res.json()
|
||||
|
||||
const tagName: string = data.tag_name ?? ''
|
||||
const latestVersion = tagName.replace(/^v/, '')
|
||||
const currentVersion = (serverVersion.value ?? '').replace(/^v/, '')
|
||||
|
||||
checkResult.value = {
|
||||
isUpToDate: latestVersion === currentVersion,
|
||||
latestVersion,
|
||||
body: data.body ?? '',
|
||||
htmlUrl: data.html_url ?? `https://github.com/${GITHUB_REPO}/releases/latest`,
|
||||
}
|
||||
} catch {
|
||||
toast.error(t('about.checkFailed'))
|
||||
} finally {
|
||||
checking.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -188,26 +188,6 @@
|
||||
@generate="onGenerateBindCode"
|
||||
@copy="copyBindCode"
|
||||
/>
|
||||
|
||||
<!-- Version -->
|
||||
<section>
|
||||
<Separator class="mb-4" />
|
||||
<div class="flex items-center gap-2 text-xs text-muted-foreground">
|
||||
<span>{{ $t('settings.version') }}</span>
|
||||
<Badge
|
||||
v-if="serverVersion"
|
||||
variant="secondary"
|
||||
>
|
||||
{{ $t('settings.versionTag', { version: serverVersion }) }}
|
||||
</Badge>
|
||||
<Badge
|
||||
v-if="commitHash"
|
||||
variant="outline"
|
||||
>
|
||||
{{ commitHash }}
|
||||
</Badge>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -243,7 +223,6 @@ import { client } from '@memohai/sdk/client'
|
||||
import type { AccountsAccount, AccountsUpdateProfileRequest, AccountsUpdatePasswordRequest, IdentitiesChannelIdentity } from '@memohai/sdk'
|
||||
import { useUserStore } from '@/store/user'
|
||||
import { useSettingsStore } from '@/store/settings'
|
||||
import { useCapabilitiesStore } from '@/store/capabilities'
|
||||
import type { Locale } from '@/i18n'
|
||||
import { resolveApiErrorMessage } from '@/utils/api-error'
|
||||
import { formatDateTime } from '@/utils/date-time'
|
||||
@@ -271,10 +250,6 @@ const settingsStore = useSettingsStore()
|
||||
const { language, theme } = storeToRefs(settingsStore)
|
||||
const { setLanguage, setTheme } = settingsStore
|
||||
|
||||
// ---- Server version ----
|
||||
const capabilitiesStore = useCapabilitiesStore()
|
||||
const { serverVersion, commitHash } = storeToRefs(capabilitiesStore)
|
||||
|
||||
// ---- User data ----
|
||||
const account = ref<UserAccount | null>(null)
|
||||
const identities = ref<IdentitiesChannelIdentity[]>([])
|
||||
|
||||
@@ -137,6 +137,14 @@ const routes = [
|
||||
breadcrumb: i18nRef('sidebar.supermarket'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'about',
|
||||
path: 'about',
|
||||
component: () => import('@/pages/about/index.vue'),
|
||||
meta: {
|
||||
breadcrumb: i18nRef('sidebar.about'),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user