feat: enhance theme management and image caching functionality
This commit is contained in:
@@ -9,6 +9,7 @@ import { extractText, isToolOnlyMessage, isToolResultRole, isInternalMessage } f
|
||||
import { hostApiFetch } from '@lib/host-api'
|
||||
import { gatewayRpc, onGatewayEvent } from '@lib/gateway-client'
|
||||
import { useProviderStore } from '@stores/providers'
|
||||
import { IPC_EVENTS, CONFIG_KEYS } from '@lib/constants'
|
||||
|
||||
// ── Constants ───────────────────────────────────────────────────
|
||||
const DEFAULT_SESSION_KEY = 'agent:main:main'
|
||||
@@ -32,7 +33,6 @@ let _lastLoadSessionsAt = 0
|
||||
const _historyLoadInFlight = new Map<string, Promise<void>>()
|
||||
const _lastHistoryLoadAtBySession = new Map<string, number>()
|
||||
const _chatEventDedupe = new Map<string, number>()
|
||||
const IMAGE_CACHE_KEY = 'zn-ai:image-cache'
|
||||
const IMAGE_CACHE_MAX = 100
|
||||
|
||||
// ── Helpers: Timers ─────────────────────────────────────────────
|
||||
@@ -90,29 +90,35 @@ function isDuplicateChatEvent(eventState: string, event: Record<string, unknown>
|
||||
}
|
||||
|
||||
// ── Helpers: Image Cache ────────────────────────────────────────
|
||||
function loadImageCache(): Map<string, AttachedFileMeta> {
|
||||
let _imageCache: Map<string, AttachedFileMeta> = new Map()
|
||||
let _imageCacheInitialized = false
|
||||
|
||||
async function initImageCache(): Promise<void> {
|
||||
if (_imageCacheInitialized) return
|
||||
_imageCache = await loadImageCache()
|
||||
_imageCacheInitialized = true
|
||||
}
|
||||
|
||||
async function loadImageCache(): Promise<Map<string, AttachedFileMeta>> {
|
||||
try {
|
||||
const raw = localStorage.getItem(IMAGE_CACHE_KEY)
|
||||
if (raw) {
|
||||
const entries = JSON.parse(raw) as Array<[string, AttachedFileMeta]>
|
||||
return new Map(entries)
|
||||
const raw = await window.api.invoke(IPC_EVENTS.GET_CONFIG, CONFIG_KEYS.IMAGE_CACHE)
|
||||
if (Array.isArray(raw)) {
|
||||
return new Map(raw as Array<[string, AttachedFileMeta]>)
|
||||
}
|
||||
} catch { /* ignore */ }
|
||||
return new Map()
|
||||
}
|
||||
|
||||
function saveImageCache(cache: Map<string, AttachedFileMeta>): void {
|
||||
async function saveImageCache(cache: Map<string, AttachedFileMeta>): Promise<void> {
|
||||
try {
|
||||
const entries = Array.from(cache.entries())
|
||||
const trimmed = entries.length > IMAGE_CACHE_MAX
|
||||
? entries.slice(entries.length - IMAGE_CACHE_MAX)
|
||||
: entries
|
||||
localStorage.setItem(IMAGE_CACHE_KEY, JSON.stringify(trimmed))
|
||||
await window.api.invoke(IPC_EVENTS.SET_CONFIG, CONFIG_KEYS.IMAGE_CACHE, trimmed)
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
const _imageCache = loadImageCache()
|
||||
|
||||
// ── Helpers: Timestamp ──────────────────────────────────────────
|
||||
function toMs(ts: number): number {
|
||||
return ts < 1e12 ? ts * 1000 : ts
|
||||
@@ -918,6 +924,7 @@ export const useChatStore = defineStore('chat', {
|
||||
try {
|
||||
// Cache image attachments
|
||||
if (attachments && attachments.length > 0) {
|
||||
await initImageCache()
|
||||
for (const a of attachments) {
|
||||
_imageCache.set(a.stagedPath, {
|
||||
fileName: a.fileName,
|
||||
@@ -926,7 +933,7 @@ export const useChatStore = defineStore('chat', {
|
||||
preview: a.preview,
|
||||
})
|
||||
}
|
||||
saveImageCache(_imageCache)
|
||||
await saveImageCache(_imageCache)
|
||||
}
|
||||
|
||||
let messageContent = trimmed
|
||||
|
||||
@@ -2,9 +2,7 @@ import { defineStore } from 'pinia';
|
||||
import { i18n, setLanguage, getLanguage, type LanguageCode } from '@src/i18n';
|
||||
import { SUPPORTED_LANGUAGES, SUPPORTED_LANGUAGE_CODES } from '@src/i18n/constants';
|
||||
import { resolveSupportedLanguage, detectSystemLanguage } from '@src/i18n/resolver';
|
||||
|
||||
// 持久化键
|
||||
const STORAGE_KEY = 'zn-language';
|
||||
import { IPC_EVENTS, CONFIG_KEYS } from '@lib/constants';
|
||||
|
||||
interface LocaleState {
|
||||
language: LanguageCode;
|
||||
@@ -37,8 +35,8 @@ export const useLocaleStore = defineStore('locale', {
|
||||
if (this.initialized) return;
|
||||
|
||||
try {
|
||||
// 1. 尝试从 localStorage 读取持久化设置
|
||||
const saved = localStorage.getItem(STORAGE_KEY);
|
||||
// 1. 尝试从 electron-store 读取持久化设置
|
||||
const saved = await window.api.invoke(IPC_EVENTS.GET_CONFIG, CONFIG_KEYS.LANGUAGE);
|
||||
let lang: LanguageCode = 'zh';
|
||||
|
||||
if (saved && SUPPORTED_LANGUAGE_CODES.includes(saved as LanguageCode)) {
|
||||
@@ -63,12 +61,12 @@ export const useLocaleStore = defineStore('locale', {
|
||||
/**
|
||||
* 设置语言
|
||||
* @param language 目标语言代码
|
||||
* @param persist 是否持久化到 localStorage(默认为 true)
|
||||
* @param persist 是否持久化到 electron-store(默认为 true)
|
||||
*/
|
||||
async setLanguage(language: LanguageCode, persist: boolean = true) {
|
||||
// 验证语言代码有效性
|
||||
const resolvedLang = resolveSupportedLanguage(language);
|
||||
|
||||
|
||||
if (resolvedLang === this.language) {
|
||||
return; // 语言未变化
|
||||
}
|
||||
@@ -80,14 +78,14 @@ export const useLocaleStore = defineStore('locale', {
|
||||
// 2. 更新 store 状态
|
||||
this.language = resolvedLang;
|
||||
|
||||
// 3. 持久化到 localStorage
|
||||
// 3. 持久化到 electron-store
|
||||
if (persist) {
|
||||
localStorage.setItem(STORAGE_KEY, resolvedLang);
|
||||
await window.api.invoke(IPC_EVENTS.SET_CONFIG, CONFIG_KEYS.LANGUAGE, resolvedLang);
|
||||
}
|
||||
|
||||
// 4. 触发语言变化事件(供其他组件监听)
|
||||
window.dispatchEvent(new CustomEvent('language-changed', {
|
||||
detail: { language: resolvedLang }
|
||||
window.dispatchEvent(new CustomEvent('language-changed', {
|
||||
detail: { language: resolvedLang }
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('Failed to set language:', error);
|
||||
|
||||
@@ -42,18 +42,12 @@ const applyThemeToDom = (theme: 'light' | 'dark') => {
|
||||
};
|
||||
|
||||
export const useThemeStore = defineStore('zn-ai-theme', {
|
||||
state: (): ThemeState => {
|
||||
// 从 localStorage 恢复缓存的主题,确保在初始化前 UI 不会闪烁
|
||||
const cachedTheme = typeof localStorage !== 'undefined' ? localStorage.getItem('zn-ai-theme-cache') as Theme : null;
|
||||
const initialTheme = (cachedTheme === 'light' || cachedTheme === 'dark' || cachedTheme === 'system') ? cachedTheme : 'system';
|
||||
|
||||
return {
|
||||
theme: initialTheme,
|
||||
isDark: false,
|
||||
systemTheme: 'light',
|
||||
initialized: false,
|
||||
};
|
||||
},
|
||||
state: (): ThemeState => ({
|
||||
theme: 'system',
|
||||
isDark: false,
|
||||
systemTheme: 'light',
|
||||
initialized: false,
|
||||
}),
|
||||
|
||||
actions: {
|
||||
async init() {
|
||||
@@ -83,12 +77,7 @@ export const useThemeStore = defineStore('zn-ai-theme', {
|
||||
this.isDark = isDark;
|
||||
this.systemTheme = systemTheme;
|
||||
this.initialized = true;
|
||||
|
||||
// 缓存到 localStorage
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
localStorage.setItem('zn-ai-theme-cache', savedTheme);
|
||||
}
|
||||
|
||||
|
||||
// 6. 监听系统主题变化
|
||||
if (typeof window !== 'undefined') {
|
||||
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
@@ -159,12 +148,7 @@ export const useThemeStore = defineStore('zn-ai-theme', {
|
||||
// 4. 更新 store 状态
|
||||
this.theme = theme;
|
||||
this.isDark = isDark;
|
||||
|
||||
// 5. 缓存到 localStorage
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
localStorage.setItem('zn-ai-theme-cache', theme);
|
||||
}
|
||||
|
||||
|
||||
console.log('Theme changed:', { theme, appliedTheme, isDark });
|
||||
} catch (error) {
|
||||
console.error('Failed to set theme:', error);
|
||||
|
||||
Reference in New Issue
Block a user