This commit is contained in:
cc
2026-03-21 14:50:43 +08:00
10 changed files with 292 additions and 47 deletions

View File

@@ -104,6 +104,44 @@ function App() {
// 数据收集同意状态
const [showAnalyticsConsent, setShowAnalyticsConsent] = useState(false)
const [showWaylandWarning, setShowWaylandWarning] = useState(false)
useEffect(() => {
const checkWaylandStatus = async () => {
try {
// 防止在非客户端环境报错,先检查 API 是否存在
if (!window.electronAPI?.app?.checkWayland) return
// 通过 configService 检查是否已经弹过窗
const hasWarned = await window.electronAPI.config.get('waylandWarningShown')
if (!hasWarned) {
const isWayland = await window.electronAPI.app.checkWayland()
if (isWayland) {
setShowWaylandWarning(true)
}
}
} catch (e) {
console.error('检查 Wayland 状态失败:', e)
}
}
// 只有在协议同意之后并且已经进入主应用流程才检查
if (!isAgreementWindow && !isOnboardingWindow && !agreementLoading) {
checkWaylandStatus()
}
}, [isAgreementWindow, isOnboardingWindow, agreementLoading])
const handleDismissWaylandWarning = async () => {
try {
// 记录到本地配置中,下次不再提示
await window.electronAPI.config.set('waylandWarningShown', true)
} catch (e) {
console.error('保存 Wayland 提示状态失败:', e)
}
setShowWaylandWarning(false)
}
useEffect(() => {
if (location.pathname !== '/settings') {
settingsBackgroundRef.current = location
@@ -432,6 +470,8 @@ function App() {
checkLock()
}, [isAgreementWindow, isOnboardingWindow, isVideoPlayerWindow])
// 独立协议窗口
if (isAgreementWindow) {
return <AgreementPage />
@@ -614,6 +654,33 @@ function App() {
</div>
)}
{showWaylandWarning && (
<div className="agreement-overlay">
<div className="agreement-modal">
<div className="agreement-header">
<Shield size={32} />
<h2> (Wayland)</h2>
</div>
<div className="agreement-content">
<div className="agreement-text">
<p>使 <strong>Wayland</strong> </p>
<p> Wayland <strong></strong></p>
<p></p>
<br />
<p>使</p>
<p>1. <strong>X11 (Xorg)</strong> </p>
<p>2. (WM/DE) </p>
</div>
</div>
<div className="agreement-footer">
<div className="agreement-actions">
<button className="btn btn-primary" onClick={handleDismissWaylandWarning}></button>
</div>
</div>
</div>
</div>
)}
{/* 更新提示对话框 */}
<UpdateDialog
open={showUpdateDialog}

View File

@@ -7611,6 +7611,12 @@ function MessageBubble({
const [voiceWaveform, setVoiceWaveform] = useState<number[]>([])
const voiceAutoDecryptTriggered = useRef(false)
const [systemAlert, setSystemAlert] = useState<{
title: string;
message: React.ReactNode;
} | null>(null)
// 转账消息双方名称
const [transferPayerName, setTransferPayerName] = useState<string | undefined>(undefined)
const [transferReceiverName, setTransferReceiverName] = useState<string | undefined>(undefined)
@@ -8290,9 +8296,9 @@ function MessageBubble({
}
const result = await window.electronAPI.chat.getVoiceTranscript(
session.username,
String(message.localId),
message.createTime
session.username,
String(message.localId),
message.createTime
)
if (result.success) {
@@ -8300,6 +8306,21 @@ function MessageBubble({
voiceTranscriptCache.set(voiceTranscriptCacheKey, transcriptText)
setVoiceTranscript(transcriptText)
} else {
if (result.error === 'SEGFAULT_ERROR') {
console.warn('[ChatPage] 捕获到语音引擎底层段错误');
setSystemAlert({
title: '引擎崩溃提示',
message: (
<>
(Segmentation Fault)<br /><br />
使 Linux <code>sherpa-onnx</code> ( glibc )
</>
)
});
}
setVoiceTranscriptError(true)
voiceTranscriptRequestedRef.current = false
}
@@ -9699,6 +9720,31 @@ function MessageBubble({
{isSelected && <Check size={14} strokeWidth={3} />}
</div>
)}
{systemAlert && createPortal(
<div className="modal-overlay" onClick={() => setSystemAlert(null)} style={{ zIndex: 99999 }}>
<div className="delete-confirm-card" onClick={(e) => e.stopPropagation()} style={{ maxWidth: '400px' }}>
<div className="confirm-icon">
<AlertCircle size={32} color="var(--danger)" />
</div>
<div className="confirm-content">
<h3>{systemAlert.title}</h3>
<p style={{ marginTop: '12px', lineHeight: '1.6', fontSize: '14px', color: 'var(--text-secondary)' }}>
{systemAlert.message}
</p>
</div>
<div className="confirm-actions" style={{ justifyContent: 'center', marginTop: '24px' }}>
<button
className="btn-primary"
onClick={() => setSystemAlert(null)}
style={{ padding: '8px 32px' }}
>
</button>
</div>
</div>
</div>,
document.body
)}
</div>
</>
)

View File

@@ -175,6 +175,21 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
const isClearingCache = isClearingAnalyticsCache || isClearingImageCache || isClearingAllCache
const [isWayland, setIsWayland] = useState(false)
useEffect(() => {
const checkWaylandStatus = async () => {
if (window.electronAPI?.app?.checkWayland) {
try {
const wayland = await window.electronAPI.app.checkWayland()
setIsWayland(wayland)
} catch (e) {
console.error('检查 Wayland 状态失败:', e)
}
}
}
checkWaylandStatus()
}, [])
// 检查 Hello 可用性
useEffect(() => {
if (window.PublicKeyCredential) {
@@ -1169,6 +1184,11 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
<div className="form-group">
<label></label>
<span className="form-hint"></span>
{isWayland && (
<span className="form-hint" style={{ color: '#ff4d4f', marginTop: '4px', display: 'block' }}>
Wayland
</span>
)}
<div className="custom-select">
<div
className={`custom-select-trigger ${positionDropdownOpen ? 'open' : ''}`}
@@ -1652,34 +1672,49 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
)
const renderCacheTab = () => (
<div className="tab-content">
<p className="section-desc"></p>
<div className="form-group">
<label> <span className="optional">()</span></label>
<span className="form-hint">使</span>
<input
type="text"
placeholder="留空使用默认目录"
value={cachePath}
onChange={(e) => {
const value = e.target.value
setCachePath(value)
scheduleConfigSave('cachePath', () => configService.setCachePath(value))
}}
/>
<div className="btn-row">
<button className="btn btn-secondary" onClick={handleSelectCachePath}><FolderOpen size={16} /> </button>
<button
className="btn btn-secondary"
onClick={async () => {
setCachePath('')
await configService.setCachePath('')
}}
>
<RotateCcw size={16} />
</button>
<div className="tab-content">
<p className="section-desc"></p>
<div className="form-group">
<label> <span className="optional">()</span></label>
<span className="form-hint">使</span>
<input
type="text"
placeholder="留空使用默认目录"
value={cachePath}
onChange={(e) => {
const value = e.target.value
setCachePath(value)
scheduleConfigSave('cachePath', () => configService.setCachePath(value))
}}
/>
<div style={{ marginTop: '8px', fontSize: '13px', color: 'var(--text-secondary)' }}>
<code style={{
background: 'var(--bg-secondary)',
padding: '3px 6px',
borderRadius: '4px',
userSelect: 'all',
wordBreak: 'break-all',
marginLeft: '4px'
}}>
{cachePath || (isMac ? '~/Documents/WeFlow' : isLinux ? '~/Documents/WeFlow' : '系统 文档\\WeFlow 目录')}
</code>
</div>
<div className="btn-row" style={{ marginTop: '12px' }}>
<button className="btn btn-secondary" onClick={handleSelectCachePath}><FolderOpen size={16} /> </button>
<button
className="btn btn-secondary"
onClick={async () => {
setCachePath('')
await configService.setCachePath('')
}}
>
<RotateCcw size={16} />
</button>
</div>
</div>
</div>
<div className="btn-row">
<button className="btn btn-secondary" onClick={handleClearAnalyticsCache} disabled={isClearingCache}>

View File

@@ -61,6 +61,7 @@ export interface ElectronAPI {
ignoreUpdate: (version: string) => Promise<{ success: boolean }>
onDownloadProgress: (callback: (progress: number) => void) => () => void
onUpdateAvailable: (callback: (info: { version: string; releaseNotes: string }) => void) => () => void
checkWayland: () => Promise<boolean>
}
notification: {
show: (data: { title: string; content: string; avatarUrl?: string; sessionId: string }) => Promise<{ success?: boolean; error?: string } | void>