import { type AnalyticsEventParams, GOOGLE_ANALYTICS_MEASUREMENT_ID } from '@shared/analytics' import * as defaults from '@shared/defaults' import type { Config, Settings, ShortcutSetting } from '@shared/types' import localforage from 'localforage' import { v4 as uuidv4 } from 'uuid' import { parseLocale } from '@/i18n/parser' import { type ImageGenerationStorage, IndexedDBImageGenerationStorage } from '@/storage/ImageGenerationStorage' import { IndexedDBSessionMetaStorage, type SessionMetaStorage } from '@/storage/SessionMetaStorage' import { getBrowser, getOS } from '../packages/navigator' import type { Platform, PlatformType } from './interfaces' import type { KnowledgeBaseController } from './knowledge-base/interface' import type { SessionAttachmentRagController } from './session-attachment-rag/interface' import { IndexedDBStorage } from './storages' import WebExporter from './web_exporter' import webLogger from './web_logger' import { parseFileLocallyInBrowser } from './web_platform_utils' export default class WebPlatform extends IndexedDBStorage implements Platform { public type: PlatformType = 'web' public readonly isDesktopLike = false public exporter = new WebExporter() private imageGenerationStorage: ImageGenerationStorage | null = null private sessionMetaStorage: SessionMetaStorage | null = null constructor() { super() webLogger.init().catch((e) => console.error('Failed to init web logger:', e)) } public async getVersion(): Promise { return 'web' } public async getPlatform(): Promise { return 'web' } public async getArch(): Promise { return 'web' } public async shouldUseDarkColors(): Promise { return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches } public onSystemThemeChange(callback: () => void): () => void { window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', callback) return () => { window.matchMedia('(prefers-color-scheme: dark)').removeEventListener('change', callback) } } public onWindowShow(callback: () => void): () => void { return () => null } public onWindowFocused(callback: () => void): () => void { const onFocus = () => callback() const onVisibilityChange = () => { if (document.visibilityState === 'visible') { callback() } } window.addEventListener('focus', onFocus) document.addEventListener('visibilitychange', onVisibilityChange) return () => { window.removeEventListener('focus', onFocus) document.removeEventListener('visibilitychange', onVisibilityChange) } } public async isWindowFocused(): Promise { return document.visibilityState === 'visible' && document.hasFocus() } public onUpdateDownloaded(callback: () => void): () => void { return () => null } public async openLink(url: string): Promise { window.open(url) } public async getDeviceName(): Promise { // Web 平台返回浏览器名称 return await Promise.resolve(getBrowser()!) } public async getInstanceName(): Promise { return `${getOS()} / ${getBrowser()}` } public async getLocale() { const lang = window.navigator.language return parseLocale(lang) } public async ensureShortcutConfig(config: ShortcutSetting): Promise { return } public async ensureProxyConfig(config: { proxy?: string }): Promise { return } public async relaunch(): Promise { location.reload() } public async getConfig(): Promise { let value: Config = await this.getStoreValue('configs') if (value === undefined || value === null) { value = defaults.newConfigs() await this.setStoreValue('configs', value) } return value } public async getSettings(): Promise { let value: Settings = await this.getStoreValue('settings') if (value === undefined || value === null) { value = defaults.settings() await this.setStoreValue('settings', value) } return value } public async getStoreBlob(key: string): Promise { return localforage.getItem(key) } public async setStoreBlob(key: string, value: string): Promise { await localforage.setItem(key, value) } public async delStoreBlob(key: string) { return localforage.removeItem(key) } public async listStoreBlobKeys(): Promise { return localforage.keys() } public async initTracking() { const conf = await this.getConfig() window.gtag('config', GOOGLE_ANALYTICS_MEASUREMENT_ID, { app_name: 'chatbox', user_id: conf.uuid, client_id: conf.uuid, app_version: await this.getVersion(), chatbox_platform_type: 'web', chatbox_platform: await this.getPlatform(), app_platform: await this.getPlatform(), }) } public trackingEvent(name: string, params: AnalyticsEventParams) { window.gtag('event', name, params) } public async shouldShowAboutDialogWhenStartUp(): Promise { return false } public async appLog(level: string, message: string): Promise { webLogger.log(level, message) } public async exportLogs(): Promise { return webLogger.exportLogs() } public async clearLogs(): Promise { return webLogger.clearLogs() } public async ensureAutoLaunch(enable: boolean) { return } async parseFileLocally(file: File): Promise<{ key?: string; isSupported: boolean; errorCode?: string }> { const result = await parseFileLocallyInBrowser(file) if (!result.isSupported) { return { isSupported: false, errorCode: result.errorCode } } const key = `parseFile-` + uuidv4() await this.setStoreBlob(key, result.text) return { key, isSupported: true } } getLocalFilePath(file: File): string { return file.path || '' } public async parseUrl(url: string): Promise<{ key: string; title: string }> { throw new Error('Not implemented') } public async isFullscreen() { return true } public async setFullscreen(enabled: boolean): Promise { return } installUpdate(): Promise { throw new Error('Method not implemented.') } public getKnowledgeBaseController(): KnowledgeBaseController { throw new Error('Method not implemented.') } public getSessionAttachmentRagController(): SessionAttachmentRagController { throw new Error('Session attachment RAG is not implemented on web.') } public getImageGenerationStorage(): ImageGenerationStorage { if (!this.imageGenerationStorage) { this.imageGenerationStorage = new IndexedDBImageGenerationStorage() } return this.imageGenerationStorage } public getSessionMetaStorage(): SessionMetaStorage { if (!this.sessionMetaStorage) { this.sessionMetaStorage = new IndexedDBSessionMetaStorage() } return this.sessionMetaStorage } public minimize() { return Promise.resolve() } public maximize() { return Promise.resolve() } public unmaximize() { return Promise.resolve() } public closeWindow() { return Promise.resolve() } public isMaximized() { return Promise.resolve(true) } public onMaximizedChange() { return () => null } }