From aeeeb4de02605322e7b49558d2a82919b5beaa60 Mon Sep 17 00:00:00 2001 From: SWangHash <88996709+SWangHash@users.noreply.github.com> Date: Mon, 10 Aug 2026 20:07:48 +0800 Subject: [PATCH] fix(ohos): restore system appearance following --- .../components/AboutDialog/AboutDialog.tsx | 2 +- .../components/PersistentFooterActions.tsx | 1 - .../miniapps/views/MiniAppGalleryView.tsx | 1 + .../src/app/scenes/settings/SettingsScene.tsx | 4 +- .../app/scenes/settings/settingsTabI18n.ts | 1 - .../src/app/scenes/welcome/WelcomeScene.tsx | 3 +- .../components/Modal/Modal.tsx | 2 +- .../flow_chat/components/ModelSelector.tsx | 5 +- .../components/modern/ExportImageButton.tsx | 2 + .../components/voice/useComposerVoiceInput.ts | 1 - .../flow-chat-manager/SessionModule.ts | 3 +- .../flow_chat/tool-cards/TerminalToolCard.tsx | 15 ++++- .../appearance/runtime/AppearanceService.ts | 61 ++++++++++++++++++- 13 files changed, 86 insertions(+), 15 deletions(-) diff --git a/src/web-ui/src/app/components/AboutDialog/AboutDialog.tsx b/src/web-ui/src/app/components/AboutDialog/AboutDialog.tsx index 5681bee695..ab88089039 100644 --- a/src/web-ui/src/app/components/AboutDialog/AboutDialog.tsx +++ b/src/web-ui/src/app/components/AboutDialog/AboutDialog.tsx @@ -256,7 +256,7 @@ export const AboutDialog: React.FC = ({

{version.name}

- {t('about.version', { version: formatVersion(version.version, version.isDev) })} + {t('about.version', { version: displayedVersion })}
diff --git a/src/web-ui/src/app/components/NavPanel/components/PersistentFooterActions.tsx b/src/web-ui/src/app/components/NavPanel/components/PersistentFooterActions.tsx index 968fab2213..ca23356f3a 100644 --- a/src/web-ui/src/app/components/NavPanel/components/PersistentFooterActions.tsx +++ b/src/web-ui/src/app/components/NavPanel/components/PersistentFooterActions.tsx @@ -24,7 +24,6 @@ import { useCanvasStore } from '@/app/components/panels/content-canvas/stores'; import { useToolbarModeContext } from '@/flow_chat/components/toolbar-mode/ToolbarModeContext'; import { useNotification } from '@/shared/notification-system'; import { useAccountLoginState } from '@/infrastructure/account/useAccountLoginState'; -import { remoteConnectAPI } from '@/infrastructure/api/service-api/RemoteConnectAPI'; import NotificationButton from '../../TitleBar/NotificationButton'; import GithubStarButton from './GithubStarButton'; import { usePrivacy } from '../../Privacy/PrivacyContext'; diff --git a/src/web-ui/src/app/scenes/miniapps/views/MiniAppGalleryView.tsx b/src/web-ui/src/app/scenes/miniapps/views/MiniAppGalleryView.tsx index 3f24b70d59..f1521153eb 100644 --- a/src/web-ui/src/app/scenes/miniapps/views/MiniAppGalleryView.tsx +++ b/src/web-ui/src/app/scenes/miniapps/views/MiniAppGalleryView.tsx @@ -1,4 +1,5 @@ import React, { useState, useMemo, useCallback, useEffect } from 'react'; +import { open } from '@tauri-apps/plugin-dialog'; import { Box, FolderPlus, diff --git a/src/web-ui/src/app/scenes/settings/SettingsScene.tsx b/src/web-ui/src/app/scenes/settings/SettingsScene.tsx index 830bc217a2..ebdbc8b67a 100644 --- a/src/web-ui/src/app/scenes/settings/SettingsScene.tsx +++ b/src/web-ui/src/app/scenes/settings/SettingsScene.tsx @@ -85,12 +85,12 @@ const SettingsScene: React.FC = () => { const setActiveTab = useSettingsStore(s => s.setActiveTab); const resolvedTab: ConfigTab = - (activeTab as string) === 'session-config' ? 'session-personalization' : activeTab; + (activeTab as string) === 'session-config' ? 'session-permissions' : activeTab; useEffect(() => { /** Legacy merged session settings tab removed in favor of two panels. */ if ((activeTab as string) === 'session-config') { - setActiveTab('session-personalization'); + setActiveTab('session-permissions'); } }, [activeTab, setActiveTab]); diff --git a/src/web-ui/src/app/scenes/settings/settingsTabI18n.ts b/src/web-ui/src/app/scenes/settings/settingsTabI18n.ts index c995ddb7f1..bc14500739 100644 --- a/src/web-ui/src/app/scenes/settings/settingsTabI18n.ts +++ b/src/web-ui/src/app/scenes/settings/settingsTabI18n.ts @@ -26,7 +26,6 @@ export const SETTINGS_TAB_I18N_NAMESPACES: Record { } finally { setIsSelecting(false); } - }, [openWorkspace, openScene, t]); + }, [openWorkspace, openScene]); const handleNewProject = useCallback(() => { window.dispatchEvent(new Event('nav:new-project')); diff --git a/src/web-ui/src/component-library/components/Modal/Modal.tsx b/src/web-ui/src/component-library/components/Modal/Modal.tsx index b778642066..6dcaf87f0c 100644 --- a/src/web-ui/src/component-library/components/Modal/Modal.tsx +++ b/src/web-ui/src/component-library/components/Modal/Modal.tsx @@ -436,7 +436,7 @@ export const Modal: React.FC = ({ ] .filter(Boolean) .join(' ')} - onClick={closeOnOverlayClick ? onClose : undefined} + onClick={closeOnOverlayClick ? handleOverlayClick : undefined} onMouseDown={handleOverlayMouseDown} data-bf-component="modal" data-bf-part="overlay" diff --git a/src/web-ui/src/flow_chat/components/ModelSelector.tsx b/src/web-ui/src/flow_chat/components/ModelSelector.tsx index 0a3e494eab..029d1445bc 100644 --- a/src/web-ui/src/flow_chat/components/ModelSelector.tsx +++ b/src/web-ui/src/flow_chat/components/ModelSelector.tsx @@ -21,7 +21,10 @@ import { type ReasoningCatalogProjection, } from '@/infrastructure/api/service-api/AIApi'; import { ACPClientAPI, type AcpSessionOptions } from '@/infrastructure/api/service-api/ACPClientAPI'; -import { getProviderDisplayName } from '@/infrastructure/config/services/modelConfigs'; +import { + getProviderDisplayName, + getProviderTemplateId, +} from '@/infrastructure/config/services/modelConfigs'; import { globalEventBus } from '@/infrastructure/event-bus'; import type { AIModelConfig, AgentModelDefaultsConfig, DefaultModelsConfig } from '@/infrastructure/config/types'; import { Switch, Tooltip } from '@/component-library'; diff --git a/src/web-ui/src/flow_chat/components/modern/ExportImageButton.tsx b/src/web-ui/src/flow_chat/components/modern/ExportImageButton.tsx index 26f94305cd..24d541fdbc 100644 --- a/src/web-ui/src/flow_chat/components/modern/ExportImageButton.tsx +++ b/src/web-ui/src/flow_chat/components/modern/ExportImageButton.tsx @@ -8,6 +8,7 @@ */ import React, { useState, useCallback, useRef } from 'react'; +import { Image, Loader2 } from 'lucide-react'; import { createRoot } from 'react-dom/client'; import { FlowChatStore } from '../../store/FlowChatStore'; import { notificationService } from '@/shared/notification-system'; @@ -22,6 +23,7 @@ import { withTimeout } from '@/shared/utils/timing'; import { downloadDir, join } from '@tauri-apps/api/path'; import { writeFile } from '@tauri-apps/plugin-fs'; import { ModelThinkingDisplay } from '../../tool-cards/ModelThinkingDisplay'; +import { Tooltip } from '@/component-library'; import './ExportImageButton.scss'; const log = createLogger('ExportImageButton'); diff --git a/src/web-ui/src/flow_chat/components/voice/useComposerVoiceInput.ts b/src/web-ui/src/flow_chat/components/voice/useComposerVoiceInput.ts index 5dd362a8d3..7ed0a9e73d 100644 --- a/src/web-ui/src/flow_chat/components/voice/useComposerVoiceInput.ts +++ b/src/web-ui/src/flow_chat/components/voice/useComposerVoiceInput.ts @@ -9,7 +9,6 @@ import { import { useAIExperienceSettings } from '@/infrastructure/config/hooks'; import { isTauriRuntime } from '@/infrastructure/runtime'; import { useSceneStore } from '@/app/stores/sceneStore'; -import { useSettingsStore } from '@/app/scenes/settings/settingsStore'; import { notificationService } from '@/shared/notification-system'; import { createLogger } from '@/shared/utils/logger'; import { diff --git a/src/web-ui/src/flow_chat/services/flow-chat-manager/SessionModule.ts b/src/web-ui/src/flow_chat/services/flow-chat-manager/SessionModule.ts index e6b56aac3e..14583d867c 100644 --- a/src/web-ui/src/flow_chat/services/flow-chat-manager/SessionModule.ts +++ b/src/web-ui/src/flow_chat/services/flow-chat-manager/SessionModule.ts @@ -22,8 +22,7 @@ import type { SessionHistoryHydrationLocation, } from './types'; import type { Session } from '../../types/flow-chat'; -import { touchSessionActivity, cleanupSaveState, updateSessionMetadata } from './PersistenceModule'; -import { cleanupSessionBuffers } from './TextChunkModule'; +import { touchSessionActivity } from './PersistenceModule'; import { createTextSessionTitleDescriptor, createDefaultSessionTitleDescriptor, diff --git a/src/web-ui/src/flow_chat/tool-cards/TerminalToolCard.tsx b/src/web-ui/src/flow_chat/tool-cards/TerminalToolCard.tsx index 6092ff0149..60ba976a98 100644 --- a/src/web-ui/src/flow_chat/tool-cards/TerminalToolCard.tsx +++ b/src/web-ui/src/flow_chat/tool-cards/TerminalToolCard.tsx @@ -19,6 +19,8 @@ import type { ToolCardProps } from '../types/flow-chat'; import { Terminal, ExternalLink, Square } from 'lucide-react'; import { createTerminalTab } from '@/shared/utils/tabUtils'; import { BaseToolCard, ToolCardHeader } from './BaseToolCard'; +import { CompactToolCard, CompactToolCardHeader } from './CompactToolCard'; +import { ToolCardStatusSlot } from './ToolCardStatusSlot'; import { DotMatrixLoader, IconButton } from '../../component-library'; import { LazyTerminalOutputRenderer } from '@/tools/terminal/components/LazyTerminalOutputRenderer'; import { createLogger } from '@/shared/utils/logger'; @@ -453,11 +455,11 @@ export const TerminalToolCard: React.FC = ({ const handleMouseDown = useCallback(() => { setShouldExpand(true); - }, [toggleExpanded, shouldExpand, setShouldExpand]); + }, []); const handleMouseMove = useCallback(() => { setShouldExpand(false); - }, [toggleExpanded, shouldExpand, setShouldExpand]); + }, []); const handleCardClick = useCallback((e: React.MouseEvent) => { const target = e.target as HTMLElement; @@ -605,6 +607,15 @@ export const TerminalToolCard: React.FC = ({ /> ); + const renderCompactHeader = () => ( + } defaultIcon="tool" />} + action={t('toolCards.terminal.executeCommand')} + content={renderCommandContent('compact')} + rightStatusIcon={renderLoadingStatusIcon()} + /> + ); + const compactSettledPreview = isExpanded && isLastItem === true && diff --git a/src/web-ui/src/infrastructure/appearance/runtime/AppearanceService.ts b/src/web-ui/src/infrastructure/appearance/runtime/AppearanceService.ts index c4fafb16ed..793beb556e 100644 --- a/src/web-ui/src/infrastructure/appearance/runtime/AppearanceService.ts +++ b/src/web-ui/src/infrastructure/appearance/runtime/AppearanceService.ts @@ -1,7 +1,11 @@ import { configAPI } from '@/infrastructure/api/service-api/ConfigAPI'; +import { api } from '@/infrastructure/api/service-api/ApiClient'; +import { workspaceAPI } from '@/infrastructure/api/service-api/WorkspaceAPI'; import { createLogger } from '@/shared/utils/logger'; import { builtinAppearanceCatalog, + DEFAULT_DARK_APPEARANCE_ID, + DEFAULT_LIGHT_APPEARANCE_ID, getBuiltinAppearance, getSystemAppearanceId, } from '../builtins/catalog'; @@ -31,6 +35,7 @@ import type { AppearanceRuntime } from './AppearanceRuntime'; const log = createLogger('AppearanceService'); const APPEARANCE_SELECTION_CONFIG_PATH = 'appearance.selection'; const MAX_SEEN_SYNC_EVENTS = 256; +const SYSTEM_COLOR_SCHEME_CHANGED_EVENT = 'bitfun:system-color-scheme-changed'; interface AppearanceSource { pkg: AppearancePackage; @@ -129,6 +134,8 @@ export class AppearanceService { private initializePromise: Promise | null = null; private mutationQueue: Promise = Promise.resolve(); private systemMedia: MediaQueryList | null = null; + private systemColorSchemeUnsubscribe: (() => void) | null = null; + private ohosSystemAppearanceId: string | null = null; private syncUnsubscribe: (() => void) | null = null; private reconciliationQueued = false; private activeSource: AppearanceSource | null = null; @@ -403,6 +410,7 @@ export class AppearanceService { async dispose(): Promise { this.detachSystemListener(); + this.detachNativeSystemListener(); this.detachReconciliationListeners(); this.syncUnsubscribe?.(); this.syncUnsubscribe = null; @@ -421,6 +429,9 @@ export class AppearanceService { ? configuredSelection.trim() : SYSTEM_APPEARANCE_ID; this.persistedSelectionId = selected; + if (selected === SYSTEM_APPEARANCE_ID) { + await this.refreshNativeSystemAppearance(); + } try { await this.applySelectionTransaction(selected, { persist: false, @@ -450,6 +461,7 @@ export class AppearanceService { }); } this.attachSystemListener(); + this.attachNativeSystemListener(); this.attachReconciliationListeners(); } @@ -457,7 +469,20 @@ export class AppearanceService { selected: AppearanceSelectionId, options: ApplySelectionOptions, ): Promise { - const resolvedId = selected === SYSTEM_APPEARANCE_ID ? getSystemAppearanceId() : selected; + if (selected === SYSTEM_APPEARANCE_ID && !this.ohosSystemAppearanceId) { + await this.refreshNativeSystemAppearance(); + } else if (selected !== SYSTEM_APPEARANCE_ID) { + this.ohosSystemAppearanceId = null; + const selectedPackage = await this.resolvePackage(selected); + if (selectedPackage?.pkg.mode === 'light' || selectedPackage?.pkg.mode === 'dark') { + void workspaceAPI.setThemeMode(selectedPackage.pkg.mode).catch(error => { + log.debug('Native appearance mode sync is unavailable', { error }); + }); + } + } + const resolvedId = selected === SYSTEM_APPEARANCE_ID + ? (this.ohosSystemAppearanceId ?? getSystemAppearanceId()) + : selected; const unchanged = this.snapshot.initialized && this.snapshot.selectedAppearanceId === selected && this.snapshot.resolvedAppearanceId === resolvedId @@ -587,6 +612,40 @@ export class AppearanceService { this.systemMedia.addEventListener('change', this.handleSystemAppearanceChange); } + private attachNativeSystemListener(): void { + if (this.systemColorSchemeUnsubscribe) return; + this.systemColorSchemeUnsubscribe = api.listen<{ scheme?: 'light' | 'dark' }>( + SYSTEM_COLOR_SCHEME_CHANGED_EVENT, + payload => { + if (this.snapshot.selectedAppearanceId !== SYSTEM_APPEARANCE_ID) return; + const scheme = payload?.scheme; + if (scheme !== 'light' && scheme !== 'dark') return; + this.ohosSystemAppearanceId = scheme === 'dark' + ? DEFAULT_DARK_APPEARANCE_ID + : DEFAULT_LIGHT_APPEARANCE_ID; + this.handleSystemAppearanceChange(); + }, + ); + } + + private detachNativeSystemListener(): void { + this.systemColorSchemeUnsubscribe?.(); + this.systemColorSchemeUnsubscribe = null; + } + + private async refreshNativeSystemAppearance(): Promise { + try { + const scheme = await workspaceAPI.setThemeMode('system'); + if (scheme === 'light' || scheme === 'dark') { + this.ohosSystemAppearanceId = scheme === 'dark' + ? DEFAULT_DARK_APPEARANCE_ID + : DEFAULT_LIGHT_APPEARANCE_ID; + } + } catch (error) { + log.debug('Native system appearance discovery is unavailable', { error }); + } + } + private detachSystemListener(): void { this.systemMedia?.removeEventListener('change', this.handleSystemAppearanceChange); this.systemMedia = null;