mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-04-03 15:08:25 +00:00
新增渠道更新
This commit is contained in:
@@ -1401,6 +1401,220 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 版本更新页面
|
||||
.updates-tab {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.updates-hero {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
padding: 20px 22px;
|
||||
border-radius: 18px;
|
||||
border: 1px solid color-mix(in srgb, var(--primary) 18%, var(--border-color));
|
||||
background:
|
||||
linear-gradient(110deg, color-mix(in srgb, var(--bg-primary) 98%, #ffffff 2%), color-mix(in srgb, var(--bg-primary) 97%, var(--primary) 3%));
|
||||
box-shadow: 0 8px 22px color-mix(in srgb, var(--primary) 8%, transparent);
|
||||
}
|
||||
|
||||
.updates-hero-main {
|
||||
min-width: 0;
|
||||
|
||||
h2 {
|
||||
margin: 10px 0 6px;
|
||||
font-size: 30px;
|
||||
line-height: 1;
|
||||
letter-spacing: -0.03em;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
.updates-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 24px;
|
||||
padding: 0 10px;
|
||||
border-radius: 999px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--primary);
|
||||
background: color-mix(in srgb, var(--primary) 12%, transparent);
|
||||
}
|
||||
|
||||
.updates-hero-action {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.updates-card {
|
||||
padding: 18px;
|
||||
border-radius: 16px;
|
||||
border: 1px solid var(--border-color);
|
||||
background: color-mix(in srgb, var(--bg-primary) 96%, var(--bg-secondary));
|
||||
}
|
||||
|
||||
.updates-progress-card {
|
||||
padding: 16px 18px;
|
||||
border-radius: 16px;
|
||||
border: 1px solid color-mix(in srgb, var(--primary) 34%, var(--border-color));
|
||||
background: color-mix(in srgb, var(--bg-primary) 95%, var(--primary) 4%);
|
||||
}
|
||||
|
||||
.updates-progress-header {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
strong {
|
||||
color: var(--primary);
|
||||
font-size: 14px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
.updates-card-header {
|
||||
margin-bottom: 12px;
|
||||
|
||||
h3 {
|
||||
margin: 0 0 4px;
|
||||
font-size: 16px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 12px;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
}
|
||||
|
||||
.update-channel-grid {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.update-channel-card {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 14px;
|
||||
padding: 12px 14px;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
transition: border-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
border-color: color-mix(in srgb, var(--primary) 42%, var(--border-color));
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 8px 24px color-mix(in srgb, var(--primary) 12%, transparent);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: color-mix(in srgb, var(--primary) 60%, var(--border-color));
|
||||
background: color-mix(in srgb, var(--primary) 10%, var(--bg-primary));
|
||||
}
|
||||
}
|
||||
|
||||
.update-channel-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 6px;
|
||||
|
||||
.title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
svg {
|
||||
color: var(--primary);
|
||||
}
|
||||
}
|
||||
|
||||
.update-channel-card .desc {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.updates-progress-track {
|
||||
height: 10px;
|
||||
border-radius: 999px;
|
||||
background: var(--bg-tertiary);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.updates-progress-fill {
|
||||
height: 100%;
|
||||
border-radius: inherit;
|
||||
background: linear-gradient(90deg, var(--primary), color-mix(in srgb, var(--primary) 72%, #ffffff));
|
||||
transition: width 0.3s ease;
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.26), transparent);
|
||||
animation: updatesShimmer 1.7s linear infinite;
|
||||
}
|
||||
}
|
||||
|
||||
.updates-ignore-btn {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
@media (max-width: 920px) {
|
||||
.updates-hero {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.updates-hero-action {
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes updatesShimmer {
|
||||
from {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 关于页面
|
||||
.about-tab {
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
import { Avatar } from '../components/Avatar'
|
||||
import './SettingsPage.scss'
|
||||
|
||||
type SettingsTab = 'appearance' | 'notification' | 'database' | 'models' | 'cache' | 'api' | 'security' | 'about' | 'analytics'
|
||||
type SettingsTab = 'appearance' | 'notification' | 'database' | 'models' | 'cache' | 'api' | 'updates' | 'security' | 'about' | 'analytics'
|
||||
|
||||
const tabs: { id: SettingsTab; label: string; icon: React.ElementType }[] = [
|
||||
{ id: 'appearance', label: '外观', icon: Palette },
|
||||
@@ -24,9 +24,9 @@ const tabs: { id: SettingsTab; label: string; icon: React.ElementType }[] = [
|
||||
{ id: 'models', label: '模型管理', icon: Mic },
|
||||
{ id: 'cache', label: '缓存', icon: HardDrive },
|
||||
{ id: 'api', label: 'API 服务', icon: Globe },
|
||||
|
||||
{ id: 'analytics', label: '分析', icon: BarChart2 },
|
||||
{ id: 'security', label: '安全', icon: ShieldCheck },
|
||||
{ id: 'updates', label: '版本更新', icon: RefreshCw },
|
||||
{ id: 'about', label: '关于', icon: Info }
|
||||
]
|
||||
|
||||
@@ -68,7 +68,6 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
setDownloadProgress,
|
||||
showUpdateDialog,
|
||||
setShowUpdateDialog,
|
||||
setUpdateError
|
||||
} = useAppStore()
|
||||
|
||||
const resetChatStore = useChatStore((state) => state.reset)
|
||||
@@ -141,6 +140,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
const [notificationFilterList, setNotificationFilterList] = useState<string[]>([])
|
||||
const [windowCloseBehavior, setWindowCloseBehavior] = useState<configService.WindowCloseBehavior>('ask')
|
||||
const [quoteLayout, setQuoteLayout] = useState<configService.QuoteLayout>('quote-top')
|
||||
const [updateChannel, setUpdateChannel] = useState<configService.UpdateChannel>('stable')
|
||||
const [filterSearchKeyword, setFilterSearchKeyword] = useState('')
|
||||
const [filterModeDropdownOpen, setFilterModeDropdownOpen] = useState(false)
|
||||
const [positionDropdownOpen, setPositionDropdownOpen] = useState(false)
|
||||
@@ -339,6 +339,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
const savedMessagePushEnabled = await configService.getMessagePushEnabled()
|
||||
const savedWindowCloseBehavior = await configService.getWindowCloseBehavior()
|
||||
const savedQuoteLayout = await configService.getQuoteLayout()
|
||||
const savedUpdateChannel = await configService.getUpdateChannel()
|
||||
|
||||
const savedAuthEnabled = await window.electronAPI.auth.verifyEnabled()
|
||||
const savedAuthUseHello = await configService.getAuthUseHello()
|
||||
@@ -387,6 +388,18 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
setMessagePushEnabled(savedMessagePushEnabled)
|
||||
setWindowCloseBehavior(savedWindowCloseBehavior)
|
||||
setQuoteLayout(savedQuoteLayout)
|
||||
if (savedUpdateChannel) {
|
||||
setUpdateChannel(savedUpdateChannel)
|
||||
} else {
|
||||
const currentVersion = await window.electronAPI.app.getVersion()
|
||||
if (/-preview\.\d+\.\d+$/i.test(currentVersion)) {
|
||||
setUpdateChannel('preview')
|
||||
} else if (/-dev\.\d+\.\d+\.\d+$/i.test(currentVersion) || /(alpha|beta|rc)/i.test(currentVersion)) {
|
||||
setUpdateChannel('dev')
|
||||
} else {
|
||||
setUpdateChannel('stable')
|
||||
}
|
||||
}
|
||||
|
||||
const savedExcludeWords = await configService.getWordCloudExcludeWords()
|
||||
setWordCloudExcludeWords(savedExcludeWords)
|
||||
@@ -512,7 +525,22 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
const handleUpdateChannelChange = async (channel: configService.UpdateChannel) => {
|
||||
if (channel === updateChannel) return
|
||||
|
||||
try {
|
||||
setUpdateChannel(channel)
|
||||
await configService.setUpdateChannel(channel)
|
||||
await configService.setIgnoredUpdateVersion('')
|
||||
setUpdateInfo(null)
|
||||
setShowUpdateDialog(false)
|
||||
const channelLabel = channel === 'stable' ? '稳定版' : channel === 'preview' ? '预览版' : '开发版'
|
||||
showMessage(`已切换到${channelLabel}更新渠道,正在检查更新`, true)
|
||||
await handleCheckUpdate()
|
||||
} catch (e: any) {
|
||||
showMessage(`切换更新渠道失败: ${e}`, false)
|
||||
}
|
||||
}
|
||||
|
||||
const showMessage = (text: string, success: boolean) => {
|
||||
setMessage({ text, success })
|
||||
@@ -1474,6 +1502,16 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
|
||||
const renderDatabaseTab = () => (
|
||||
<div className="tab-content">
|
||||
<div className="form-group">
|
||||
<label>连接测试</label>
|
||||
<span className="form-hint">检测当前数据库配置是否可用</span>
|
||||
<button className="btn btn-secondary" onClick={handleTestConnection} disabled={isLoading || isTesting}>
|
||||
<Plug size={16} /> {isTesting ? '测试中...' : '测试连接'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="divider" />
|
||||
|
||||
<div className="form-group">
|
||||
<label>解密密钥</label>
|
||||
<span className="form-hint">64位十六进制密钥</span>
|
||||
@@ -2400,35 +2438,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
<img src="./logo.png" alt="WeFlow" />
|
||||
</div>
|
||||
<h2 className="about-name">WeFlow</h2>
|
||||
<p className="about-slogan">WeFlow</p>
|
||||
<p className="about-version">v{appVersion || '...'}</p>
|
||||
|
||||
<div className="about-update">
|
||||
{updateInfo?.hasUpdate ? (
|
||||
<>
|
||||
<p className="update-hint">新版 v{updateInfo.version} 可用</p>
|
||||
{isDownloading ? (
|
||||
<div className="update-progress">
|
||||
<div className="progress-bar">
|
||||
<div className="progress-inner" style={{ width: `${(downloadProgress?.percent || 0)}%` }} />
|
||||
</div>
|
||||
<span>{(downloadProgress?.percent || 0).toFixed(0)}%</span>
|
||||
</div>
|
||||
) : (
|
||||
<button className="btn btn-primary" onClick={() => setShowUpdateDialog(true)}>
|
||||
<Download size={16} /> 立即更新
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<div style={{ display: 'flex', gap: '10px', alignItems: 'center' }}>
|
||||
<button className="btn btn-secondary" onClick={handleCheckUpdate} disabled={isCheckingUpdate}>
|
||||
<RefreshCw size={16} className={isCheckingUpdate ? 'spin' : ''} />
|
||||
{isCheckingUpdate ? '检查中...' : '检查更新'}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="about-footer">
|
||||
@@ -2440,7 +2450,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
<span>·</span>
|
||||
<a href="#" onClick={(e) => { e.preventDefault(); window.electronAPI.window.openAgreementWindow() }}>用户协议</a>
|
||||
</div>
|
||||
<p className="copyright">© 2025 WeFlow. All rights reserved.</p>
|
||||
<p className="copyright">© 2026 WeFlow. All rights reserved.</p>
|
||||
|
||||
<div className="log-toggle-line" style={{ marginTop: '16px', justifyContent: 'center' }}>
|
||||
<span style={{ fontSize: '13px', opacity: 0.7 }}>匿名数据收集</span>
|
||||
@@ -2463,6 +2473,82 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
</div>
|
||||
)
|
||||
|
||||
const renderUpdatesTab = () => {
|
||||
const downloadPercent = Math.max(0, Math.min(100, Number(downloadProgress?.percent || 0)))
|
||||
const channelCards: { id: configService.UpdateChannel; title: string; desc: string }[] = [
|
||||
{ id: 'stable', title: '稳定版', desc: '正式发布的版本,适合日常使用' },
|
||||
{ id: 'preview', title: '预览版', desc: '正式发布前的预览体验版本' },
|
||||
{ id: 'dev', title: '开发版', desc: '即刻体验我们的屎山代码' }
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="tab-content updates-tab">
|
||||
<div className="updates-hero">
|
||||
<div className="updates-hero-main">
|
||||
<span className="updates-chip">当前版本</span>
|
||||
<h2>{appVersion || '...'}</h2>
|
||||
<p>{updateInfo?.hasUpdate ? `发现新版本 v${updateInfo.version}` : '当前已是最新版本,可手动检查更新'}</p>
|
||||
</div>
|
||||
<div className="updates-hero-action">
|
||||
{updateInfo?.hasUpdate ? (
|
||||
<button className="btn btn-primary" onClick={() => setShowUpdateDialog(true)}>
|
||||
<Download size={16} /> 立即更新
|
||||
</button>
|
||||
) : (
|
||||
<button className="btn btn-secondary" onClick={handleCheckUpdate} disabled={isCheckingUpdate}>
|
||||
<RefreshCw size={16} className={isCheckingUpdate ? 'spin' : ''} />
|
||||
{isCheckingUpdate ? '检查中...' : '检查更新'}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{(isDownloading || updateInfo?.hasUpdate) && (
|
||||
<div className="updates-progress-card">
|
||||
<div className="updates-progress-header">
|
||||
<h3>{isDownloading ? `正在下载 v${updateInfo?.version || ''}` : `新版本 v${updateInfo?.version} 已就绪`}</h3>
|
||||
{isDownloading ? <strong>{downloadPercent.toFixed(0)}%</strong> : <span>可立即安装</span>}
|
||||
</div>
|
||||
<div className="updates-progress-track">
|
||||
<div className="updates-progress-fill" style={{ width: `${isDownloading ? downloadPercent : 100}%` }} />
|
||||
</div>
|
||||
{updateInfo?.hasUpdate && !isDownloading && (
|
||||
<button className="btn btn-secondary updates-ignore-btn" onClick={handleIgnoreUpdate}>
|
||||
暂不提醒此版本
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="updates-card">
|
||||
<div className="updates-card-header">
|
||||
<h3>更新渠道</h3>
|
||||
<span>切换渠道后会自动重新检查</span>
|
||||
</div>
|
||||
<div className="update-channel-grid">
|
||||
{channelCards.map((channel) => {
|
||||
const active = updateChannel === channel.id
|
||||
return (
|
||||
<button
|
||||
key={channel.id}
|
||||
className={`update-channel-card ${active ? 'active' : ''}`}
|
||||
onClick={() => void handleUpdateChannelChange(channel.id)}
|
||||
disabled={active}
|
||||
>
|
||||
<div className="update-channel-title-row">
|
||||
<span className="title">{channel.title}</span>
|
||||
{active && <Check size={16} />}
|
||||
</div>
|
||||
<span className="desc">{channel.desc}</span>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`settings-modal-overlay ${isClosing ? 'closing' : ''}`} onClick={handleClose}>
|
||||
<div className={`settings-page ${isClosing ? 'closing' : ''}`} onClick={(event) => event.stopPropagation()}>
|
||||
@@ -2510,9 +2596,6 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
<h1>设置</h1>
|
||||
</div>
|
||||
<div className="settings-actions">
|
||||
<button className="btn btn-secondary" onClick={handleTestConnection} disabled={isLoading || isTesting}>
|
||||
<Plug size={16} /> {isTesting ? '测试中...' : '测试连接'}
|
||||
</button>
|
||||
{onClose && (
|
||||
<button type="button" className="settings-close-btn" onClick={handleClose} aria-label="关闭设置">
|
||||
<X size={18} />
|
||||
@@ -2542,6 +2625,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
{activeTab === 'models' && renderModelsTab()}
|
||||
{activeTab === 'cache' && renderCacheTab()}
|
||||
{activeTab === 'api' && renderApiTab()}
|
||||
{activeTab === 'updates' && renderUpdatesTab()}
|
||||
{activeTab === 'analytics' && renderAnalyticsTab()}
|
||||
{activeTab === 'security' && renderSecurityTab()}
|
||||
{activeTab === 'about' && renderAboutTab()}
|
||||
|
||||
Reference in New Issue
Block a user