Merge branch 'hicccc77:dev' into dev

This commit is contained in:
xuncha
2026-02-28 20:00:06 +08:00
committed by GitHub
13 changed files with 351 additions and 722 deletions

View File

@@ -257,7 +257,8 @@ export function GlobalSessionMonitor() {
const handleActiveSessionRefresh = async (sessionId: string) => {
// 从 ChatPage 复制/调整的逻辑,以保持集中
const state = useChatStore.getState()
const lastMsg = state.messages[state.messages.length - 1]
const msgs = state.messages || []
const lastMsg = msgs[msgs.length - 1]
const minTime = lastMsg?.createTime || 0
try {

View File

@@ -48,18 +48,26 @@
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
// 确保背景完全不透明(通知是独立窗口,透明背景会穿透)
background: var(--bg-secondary-solid, var(--bg-secondary, #2c2c2c));
color: var(--text-primary, #ffffff);
// 独立通知窗口:默认使用浅色模式硬编码值,确保不依赖 <html> 上的主题属性
background: #ffffff;
color: #3d3d3d;
--text-primary: #3d3d3d;
--text-secondary: #666666;
--text-tertiary: #999999;
--border-light: rgba(0, 0, 0, 0.08);
// 色模式强制完全不透明白色背景
[data-mode="light"] &,
:not([data-mode]) & {
background: #ffffff !important;
// 色模式覆盖
[data-mode="dark"] & {
background: var(--bg-secondary-solid, #282420);
color: var(--text-primary, #F0EEE9);
--text-primary: #F0EEE9;
--text-secondary: #b3b0aa;
--text-tertiary: #807d78;
--border-light: rgba(255, 255, 255, 0.1);
}
box-shadow: none !important; // NO SHADOW
border: 1px solid var(--border-light, rgba(255, 255, 255, 0.1));
border: 1px solid var(--border-light);
display: flex;
padding: 16px;

View File

@@ -768,7 +768,7 @@ function ChatPage(_props: ChatPageProps) {
setIsRefreshingMessages(true)
// 找出当前已渲染消息中的最大时间戳(使用 getState 获取最新状态,避免闭包过时导致重复)
const currentMessages = useChatStore.getState().messages
const currentMessages = useChatStore.getState().messages || []
const lastMsg = currentMessages[currentMessages.length - 1]
const minTime = lastMsg?.createTime || 0
@@ -782,7 +782,7 @@ function ChatPage(_props: ChatPageProps) {
if (result.success && result.messages && result.messages.length > 0) {
// 过滤去重:必须对比实时的状态,防止在 handleRefreshMessages 运行期间导致的冲突
const latestMessages = useChatStore.getState().messages
const latestMessages = useChatStore.getState().messages || []
const existingKeys = new Set(latestMessages.map(getMessageKey))
const newOnes = result.messages.filter(m => !existingKeys.has(getMessageKey(m)))
@@ -823,7 +823,7 @@ function ChatPage(_props: ChatPageProps) {
return
}
// 使用实时状态进行去重对比
const latestMessages = useChatStore.getState().messages
const latestMessages = useChatStore.getState().messages || []
const existing = new Set(latestMessages.map(getMessageKey))
const lastMsg = latestMessages[latestMessages.length - 1]
const lastTime = lastMsg?.createTime ?? 0
@@ -1751,7 +1751,7 @@ function ChatPage(_props: ChatPageProps) {
// Range selection with Shift key
if (isShiftKey && lastSelectedIdRef.current !== null && lastSelectedIdRef.current !== localId) {
const currentMsgs = useChatStore.getState().messages
const currentMsgs = useChatStore.getState().messages || []
const idx1 = currentMsgs.findIndex(m => m.localId === lastSelectedIdRef.current)
const idx2 = currentMsgs.findIndex(m => m.localId === localId)
@@ -1821,7 +1821,7 @@ function ChatPage(_props: ChatPageProps) {
const dbPathHint = (msg as any)._db_path
const result = await (window as any).electronAPI.chat.deleteMessage(currentSessionId, msg.localId, msg.createTime, dbPathHint)
if (result.success) {
const currentMessages = useChatStore.getState().messages
const currentMessages = useChatStore.getState().messages || []
const newMessages = currentMessages.filter(m => m.localId !== msg.localId)
useChatStore.getState().setMessages(newMessages)
} else {
@@ -1882,7 +1882,7 @@ function ChatPage(_props: ChatPageProps) {
try {
const result = await (window as any).electronAPI.chat.updateMessage(currentSessionId, editingMessage.message.localId, editingMessage.message.createTime, finalContent)
if (result.success) {
const currentMessages = useChatStore.getState().messages
const currentMessages = useChatStore.getState().messages || []
const newMessages = currentMessages.map(m => {
if (m.localId === editingMessage.message.localId) {
return { ...m, parsedContent: finalContent, content: finalContent, rawContent: finalContent }
@@ -1924,7 +1924,7 @@ function ChatPage(_props: ChatPageProps) {
cancelDeleteRef.current = false
try {
const currentMessages = useChatStore.getState().messages
const currentMessages = useChatStore.getState().messages || []
const selectedIds = Array.from(selectedMessages)
const deletedIds = new Set<number>()
@@ -1948,7 +1948,7 @@ function ChatPage(_props: ChatPageProps) {
setDeleteProgress({ current: i + 1, total: selectedIds.length })
}
const finalMessages = useChatStore.getState().messages.filter(m => !deletedIds.has(m.localId))
const finalMessages = (useChatStore.getState().messages || []).filter(m => !deletedIds.has(m.localId))
useChatStore.getState().setMessages(finalMessages)
setIsSelectionMode(false)
@@ -2137,7 +2137,7 @@ function ChatPage(_props: ChatPageProps) {
<div className="empty-sessions">
<MessageSquare />
<p></p>
<p className="hint"></p>
<p className="hint"></p>
</div>
)}
</div>
@@ -2342,7 +2342,7 @@ function ChatPage(_props: ChatPageProps) {
</div>
)}
{messages.map((msg, index) => {
{(messages || []).map((msg, index) => {
const prevMsg = index > 0 ? messages[index - 1] : undefined
const showDateDivider = shouldShowDateDivider(msg, prevMsg)

View File

@@ -1,11 +1,9 @@
import { useEffect, useState, useRef } from 'react'
import { NotificationToast, type NotificationData } from '../components/NotificationToast'
import { useThemeStore } from '../stores/themeStore'
import '../components/NotificationToast.scss'
import './NotificationWindow.scss'
export default function NotificationWindow() {
const { currentTheme, themeMode } = useThemeStore()
const [notification, setNotification] = useState<NotificationData | null>(null)
const [prevNotification, setPrevNotification] = useState<NotificationData | null>(null)
@@ -19,12 +17,6 @@ export default function NotificationWindow() {
const notificationRef = useRef<NotificationData | null>(null)
// 应用主题到通知窗口
useEffect(() => {
document.documentElement.setAttribute('data-theme', currentTheme)
document.documentElement.setAttribute('data-mode', themeMode)
}, [currentTheme, themeMode])
useEffect(() => {
notificationRef.current = notification
}, [notification])

View File

@@ -774,7 +774,7 @@ function SettingsPage() {
}
setIsFetchingImageKey(true);
setImageKeyPercent(0)
setImageKeyStatus('正在初始化多核爆破引擎...');
setImageKeyStatus('正在初始化...');
setImageKeyProgress(0); // 重置进度
try {
@@ -1377,19 +1377,19 @@ function SettingsPage() {
<Plug size={14} /> {isFetchingImageKey ? '获取中...' : '自动获取图片密钥'}
</button>
{isFetchingImageKey ? (
<div className="brute-force-progress">
<div className="status-header">
<span className="status-text">{imageKeyStatus || '正在启动多核爆破引擎...'}</span>
{imageKeyPercent !== null && <span className="percent">{imageKeyPercent.toFixed(1)}%</span>}
</div>
{imageKeyPercent !== null && (
<div className="progress-bar-container">
<div className="fill" style={{ width: `${imageKeyPercent}%` }}></div>
</div>
)}
<div className="brute-force-progress">
<div className="status-header">
<span className="status-text">{imageKeyStatus || '正在启动...'}</span>
{imageKeyPercent !== null && <span className="percent">{imageKeyPercent.toFixed(1)}%</span>}
</div>
{imageKeyPercent !== null && (
<div className="progress-bar-container">
<div className="fill" style={{ width: `${imageKeyPercent}%` }}></div>
</div>
)}
</div>
) : (
imageKeyStatus && <div className="form-hint status-text" style={{ marginTop: '8px' }}>{imageKeyStatus}</div>
imageKeyStatus && <div className="form-hint status-text" style={{ marginTop: '8px' }}>{imageKeyStatus}</div>
)}
</div>
@@ -2113,8 +2113,8 @@ function SettingsPage() {
<label></label>
<span className="form-hint">{
isLockMode ? '已开启' :
authEnabled ? '旧版模式 — 请重新设置密码以升级为新模式提高安全性' :
'未开启 — 请设置密码以开启'
authEnabled ? '旧版模式 — 请重新设置密码以升级为新模式提高安全性' :
'未开启 — 请设置密码以开启'
}</span>
</div>
{authEnabled && !showDisableLockInput && (

View File

@@ -746,52 +746,52 @@ function WelcomePage({ standalone = false }: WelcomePageProps) {
)}
{currentStep.id === 'image' && (
<div className="form-group">
<div className="grid-2">
<div>
<label className="field-label"> XOR </label>
<input
type="text"
className="field-input"
placeholder="0x..."
value={imageXorKey}
onChange={(e) => setImageXorKey(e.target.value)}
/>
</div>
<div>
<label className="field-label"> AES </label>
<input
type="text"
className="field-input"
placeholder="16位密钥"
value={imageAesKey}
onChange={(e) => setImageAesKey(e.target.value)}
/>
</div>
<div className="form-group">
<div className="grid-2">
<div>
<label className="field-label"> XOR </label>
<input
type="text"
className="field-input"
placeholder="0x..."
value={imageXorKey}
onChange={(e) => setImageXorKey(e.target.value)}
/>
</div>
<div>
<label className="field-label"> AES </label>
<input
type="text"
className="field-input"
placeholder="16位密钥"
value={imageAesKey}
onChange={(e) => setImageAesKey(e.target.value)}
/>
</div>
<button className="btn btn-secondary btn-block mt-4" onClick={handleAutoGetImageKey} disabled={isFetchingImageKey}>
{isFetchingImageKey ? '获取中...' : '自动获取图片密钥'}
</button>
{isFetchingImageKey ? (
<div className="brute-force-progress">
<div className="status-header">
<span className="status-text">{imageKeyStatus || '正在启动多核爆破引擎...'}</span>
{imageKeyPercent !== null && <span className="percent">{imageKeyPercent.toFixed(1)}%</span>}
</div>
{imageKeyPercent !== null && (
<div className="progress-bar-container">
<div className="fill" style={{ width: `${imageKeyPercent}%` }}></div>
</div>
)}
</div>
) : (
imageKeyStatus && <div className="status-message" style={{ marginTop: '12px' }}>{imageKeyStatus}</div>
)}
<div className="field-hint"></div>
</div>
<button className="btn btn-secondary btn-block mt-4" onClick={handleAutoGetImageKey} disabled={isFetchingImageKey}>
{isFetchingImageKey ? '获取中...' : '自动获取图片密钥'}
</button>
{isFetchingImageKey ? (
<div className="brute-force-progress">
<div className="status-header">
<span className="status-text">{imageKeyStatus || '正在启动...'}</span>
{imageKeyPercent !== null && <span className="percent">{imageKeyPercent.toFixed(1)}%</span>}
</div>
{imageKeyPercent !== null && (
<div className="progress-bar-container">
<div className="fill" style={{ width: `${imageKeyPercent}%` }}></div>
</div>
)}
</div>
) : (
imageKeyStatus && <div className="status-message" style={{ marginTop: '12px' }}>{imageKeyStatus}</div>
)}
<div className="field-hint"></div>
</div>
)}
</div>

View File

@@ -86,15 +86,16 @@ export const useChatStore = create<ChatState>((set, get) => ({
if (m.localId && m.localId > 0) return `l:${m.localId}`
return `t:${m.createTime}:${m.sortSeq || 0}:${m.serverId || 0}`
}
const existingKeys = new Set(state.messages.map(getMsgKey))
const currentMessages = state.messages || []
const existingKeys = new Set(currentMessages.map(getMsgKey))
const filtered = newMessages.filter(m => !existingKeys.has(getMsgKey(m)))
if (filtered.length === 0) return state
return {
messages: prepend
? [...filtered, ...state.messages]
: [...state.messages, ...filtered]
? [...filtered, ...currentMessages]
: [...currentMessages, ...filtered]
}
}),