From d13e18bf40124d6c439114541a4a1c327aa5930d Mon Sep 17 00:00:00 2001 From: wgqqqqq Date: Wed, 11 Mar 2026 16:56:51 +0800 Subject: [PATCH] fix web ui typecheck and enforce in ci --- .github/workflows/ci.yml | 3 + .github/workflows/desktop-package.yml | 3 + .github/workflows/nightly.yml | 3 + package.json | 3 +- .../src/app/components/NavPanel/MainNav.tsx | 4 +- .../NavPanel/components/NavItem.tsx | 1 + .../components/PersistentFooterActions.tsx | 1 - .../sections/sessions/SessionsSection.tsx | 1 + .../sections/workspaces/WorkspaceItem.tsx | 1 - .../app/components/TitleBar/GlobalSearch.tsx | 4 - .../src/app/components/TitleBar/TitleBar.tsx | 5 +- .../components/panels/BranchSelectModal.tsx | 13 +- .../src/app/components/panels/FilesPanel.tsx | 6 +- .../components/panels/base/FlexiblePanel.tsx | 5 +- .../src/app/components/panels/base/types.ts | 6 +- .../src/app/components/panels/base/utils.ts | 26 +- .../panels/content-canvas/ContentCanvas.tsx | 10 +- .../content-canvas/anchor-zone/AnchorZone.tsx | 5 - .../content-canvas/editor-area/EditorArea.tsx | 6 +- .../editor-area/EditorGroup.tsx | 1 - .../editor-area/SplitHandle.tsx | 1 - .../hooks/useKeyboardShortcuts.ts | 2 - .../content-canvas/hooks/useLayoutState.ts | 3 +- .../hooks/usePanelTabCoordinator.ts | 4 - .../content-canvas/hooks/useTabLifecycle.ts | 14 +- .../mission-control/MissionControl.tsx | 7 +- .../mission-control/ThumbnailCard.tsx | 2 +- .../content-canvas/stores/canvasStore.ts | 9 + .../panels/content-canvas/tab-bar/Tab.tsx | 4 - .../panels/content-canvas/tab-bar/TabBar.tsx | 2 +- .../tab-bar/TabOverflowMenu.tsx | 4 - .../panels/content-canvas/types/content.ts | 42 +- src/web-ui/src/app/hooks/useApp.ts | 4 +- src/web-ui/src/app/hooks/useSceneManager.ts | 8 +- src/web-ui/src/app/layout/AppLayout.tsx | 44 +- .../src/app/layout/FloatingMiniChat.tsx | 5 +- src/web-ui/src/app/scenes/SceneViewport.tsx | 4 +- .../src/app/scenes/git/views/BranchesView.tsx | 13 +- .../app/scenes/profile/views/PersonaView.tsx | 10 +- src/web-ui/src/app/scenes/shell/ShellNav.tsx | 2 - .../src/app/scenes/toolbox/MiniAppScene.tsx | 3 +- .../scenes/toolbox/hooks/useMiniAppBridge.ts | 8 +- .../components/Alert/Alert.tsx | 4 +- .../components/Card/Card.tsx | 2 +- .../components/Checkbox/Checkbox.tsx | 4 +- .../FlowChatCards/SearchCard/SearchCard.tsx | 4 +- .../SnapshotCard/SnapshotCard.tsx | 4 +- .../components/Input/Input.tsx | 14 +- .../components/Search/Search.tsx | 26 +- .../components/StreamText/StreamText.tsx | 9 +- .../component-library/components/Tag/Tag.tsx | 6 +- .../src/component-library/components/index.ts | 2 - .../components/steps/CompletionStep.tsx | 2 +- .../onboarding/components/steps/ThemeStep.tsx | 2 +- .../onboarding/hooks/useOnboarding.ts | 3 - .../flow_chat/components/FlowTextBlock.tsx | 6 +- .../components/InlineDiffPreview.tsx | 24 -- .../flow_chat/components/ModelSelector.tsx | 5 +- .../TaskDetailPanel/TaskDetailPanel.tsx | 5 +- .../flow_chat/components/TurnHistoryPanel.tsx | 6 +- .../modern/ExploreGroupRenderer.tsx | 6 +- .../components/modern/FlowChatContext.tsx | 3 +- .../components/modern/ModelRoundItem.tsx | 11 +- .../modern/ModernFlowChatContainer.tsx | 2 +- .../modern/SessionFileModificationsBar.tsx | 3 +- .../components/modern/SessionFilesBadge.tsx | 2 +- .../components/modern/VirtualItemRenderer.tsx | 4 +- .../components/modern/VirtualMessageList.tsx | 4 +- .../RecommendationRegistry.ts | 5 - .../components/toolbar-mode/ToolbarMode.tsx | 8 +- src/web-ui/src/flow_chat/hooks/useFlowChat.ts | 7 +- .../src/flow_chat/hooks/useTemplateEditor.ts | 3 +- .../src/flow_chat/services/FlowChatManager.ts | 5 - .../flow-chat-manager/EventHandlerModule.ts | 6 +- .../flow-chat-manager/MessageModule.ts | 6 +- .../flow-chat-manager/SubagentModule.ts | 2 +- .../flow-chat-manager/TextChunkModule.ts | 8 +- .../flow-chat-manager/ToolEventModule.ts | 4 +- .../services/flow-chat-manager/types.ts | 1 - .../src/flow_chat/store/FlowChatStore.ts | 67 +-- .../flow_chat/tool-cards/CompactToolCard.tsx | 13 +- .../tool-cards/FileOperationToolCard.tsx | 20 - .../tool-cards/GetFileDiffDisplay.tsx | 1 - .../flow_chat/tool-cards/GitToolDisplay.tsx | 2 - .../tool-cards/GlobSearchDisplay.tsx | 4 - .../tool-cards/GrepSearchDisplay.tsx | 4 - .../tool-cards/IdeControlToolCard.tsx | 7 +- .../src/flow_chat/tool-cards/LSDisplay.tsx | 4 - .../tool-cards/MermaidInteractiveDisplay.tsx | 18 - .../flow_chat/tool-cards/ReadFileDisplay.tsx | 4 - .../flow_chat/tool-cards/TaskToolDisplay.tsx | 4 +- .../flow_chat/tool-cards/TerminalToolCard.tsx | 4 +- .../flow_chat/tool-cards/WebSearchCard.tsx | 2 +- src/web-ui/src/flow_chat/types/flow-chat.ts | 11 +- .../src/infrastructure/api/adapters/index.ts | 5 - .../api/service-api/AgentAPI.ts | 25 ++ .../infrastructure/api/service-api/ToolAPI.ts | 3 +- .../config/components/AIRulesMemoryConfig.tsx | 1 - .../config/components/DefaultModelConfig.tsx | 21 +- .../config/components/MCPResourceBrowser.tsx | 5 +- .../config/components/ThemeConfig.tsx | 7 +- .../src/infrastructure/config/hooks/index.ts | 3 - .../infrastructure/config/hooks/useConfig.ts | 145 ------- src/web-ui/src/infrastructure/config/index.ts | 3 - .../src/infrastructure/i18n/hooks/useI18n.ts | 4 +- .../src/infrastructure/i18n/types/index.ts | 6 +- src/web-ui/src/infrastructure/index.ts | 7 - .../infrastructure/providers/CoreProvider.tsx | 4 - .../services/PromptTemplateService.ts | 4 +- .../infrastructure/services/api/aiService.ts | 6 +- .../services/api/contextService.ts | 173 -------- .../services/business/agentService.ts | 49 --- .../infrastructure/services/business/index.ts | 5 - .../src/infrastructure/services/index.ts | 1 - .../state-management/GlobalStore.ts | 384 ------------------ .../infrastructure/state-management/Store.ts | 332 --------------- .../infrastructure/state-management/index.ts | 183 --------- .../infrastructure/state-management/types.ts | 244 ----------- .../infrastructure/theme/hooks/useTheme.ts | 5 +- .../theme/presets/china-style-theme.ts | 3 +- .../theme/utils/ThemeValidator.ts | 13 +- .../commands/BaseCommand.ts | 2 +- .../commands/CommandExecutor.ts | 7 +- .../core/ContextResolver.ts | 12 - .../context-menu-system/core/MenuBuilder.ts | 3 +- .../providers/EditorMenuProvider.ts | 5 - .../providers/FileExplorerMenuProvider.ts | 2 - .../providers/TerminalMenuProvider.ts | 4 - .../types/command.types.ts | 4 +- .../components/ContextCard/ContextCard.tsx | 12 +- .../core/types/CodeSnippetContextImpl.tsx | 5 +- .../core/types/FileContextImpl.tsx | 5 +- .../core/types/ImageContextImpl.tsx | 5 +- .../drag-drop/ContextDropZone.tsx | 7 +- .../drag-drop/FileTreeDragSource.ts | 8 +- src/web-ui/src/shared/index.ts | 1 - .../components/NotificationCenter.tsx | 4 - .../store/NotificationStore.ts | 6 - .../src/shared/services/FileTabManager.ts | 7 +- .../src/shared/services/SnapshotEventBus.ts | 197 --------- .../src/shared/services/agent-service.ts | 11 +- .../src/shared/services/ide-control/api.ts | 5 - .../shared/services/tool-execution-service.ts | 7 +- .../src/shared/stores/PanelStateManager.ts | 6 - src/web-ui/src/shared/stores/contextStore.ts | 14 +- src/web-ui/src/shared/stores/index.ts | 6 - src/web-ui/src/shared/stores/useAppStore.ts | 109 ----- src/web-ui/src/shared/types/global-state.ts | 86 +++- .../src/shared/utils/configConverter.ts | 7 - .../src/shared/utils/contextGenerator.ts | 1 + src/web-ui/src/shared/utils/eventManager.ts | 12 +- src/web-ui/src/shared/utils/helpers.ts | 221 ---------- src/web-ui/src/shared/utils/index.ts | 2 - .../src/shared/utils/pathCompression.ts | 4 +- src/web-ui/src/shared/utils/tabUtils.ts | 6 +- .../tools/editor/components/CodeEditor.tsx | 2 - .../tools/editor/components/DiffEditor.tsx | 2 - .../editor/components/EditorBreadcrumb.tsx | 2 - .../src/tools/editor/components/index.ts | 4 +- src/web-ui/src/tools/editor/config/presets.ts | 10 + .../tools/editor/core/MonacoEditorCore.tsx | 2 +- .../tools/editor/extensions/LspExtension.ts | 74 ++-- .../src/tools/editor/hooks/useEditorConfig.ts | 41 +- .../tools/editor/hooks/useLspIntegration.ts | 3 +- .../editor/meditor/components/IREditor.tsx | 2 - .../meditor/utils/rehype-local-images.ts | 5 +- .../editor/meditor/utils/rehype-mermaid.ts | 5 +- .../src/tools/editor/services/DiffService.ts | 7 +- .../tools/file-system/components/FileTree.tsx | 76 +--- .../file-system/hooks/useFileTreeGitSync.ts | 4 +- .../file-system/services/DirectoryCache.ts | 4 - .../src/tools/file-system/types/index.ts | 2 +- .../CreateBranchDialog/CreateBranchDialog.tsx | 5 +- .../GitDiffEditor/GitDiffEditor.tsx | 150 +------ .../src/tools/git/hooks/useGitAdvanced.ts | 3 +- .../src/tools/git/hooks/useGitOperations.ts | 5 +- src/web-ui/src/tools/git/hooks/useGitState.ts | 7 +- src/web-ui/src/tools/git/index.ts | 4 +- .../src/tools/git/services/GitEventService.ts | 15 +- .../src/tools/git/services/gitDiffService.ts | 3 +- src/web-ui/src/tools/git/services/index.ts | 8 +- src/web-ui/src/tools/git/types/operations.ts | 4 - src/web-ui/src/tools/index.ts | 1 - .../LspPluginList/LspPluginList.tsx | 6 +- .../ReferencesPanel/ReferencesPanel.tsx | 10 - src/web-ui/src/tools/lsp/index.ts | 3 +- .../src/tools/lsp/services/LspDiagnostics.ts | 3 +- .../tools/lsp/services/MonacoLspAdapter.ts | 18 +- .../components/MermaidEditor.tsx | 4 +- .../components/MermaidPreview.tsx | 2 +- .../tools/mermaid-editor/hooks/usePanZoom.ts | 4 - .../mermaid-editor/services/MermaidService.ts | 2 +- .../src/tools/mermaid-editor/types/index.ts | 3 +- .../components/TerminalOutputRenderer.tsx | 4 +- src/web-ui/tsconfig.json | 12 + 195 files changed, 591 insertions(+), 3049 deletions(-) delete mode 100644 src/web-ui/src/infrastructure/config/hooks/index.ts delete mode 100644 src/web-ui/src/infrastructure/config/hooks/useConfig.ts delete mode 100644 src/web-ui/src/infrastructure/services/api/contextService.ts delete mode 100644 src/web-ui/src/infrastructure/services/business/index.ts delete mode 100644 src/web-ui/src/infrastructure/state-management/GlobalStore.ts delete mode 100644 src/web-ui/src/infrastructure/state-management/Store.ts delete mode 100644 src/web-ui/src/infrastructure/state-management/index.ts delete mode 100644 src/web-ui/src/infrastructure/state-management/types.ts delete mode 100644 src/web-ui/src/shared/services/SnapshotEventBus.ts delete mode 100644 src/web-ui/src/shared/stores/index.ts delete mode 100644 src/web-ui/src/shared/stores/useAppStore.ts delete mode 100644 src/web-ui/src/shared/utils/helpers.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 26f2f941..9094a1ee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -85,6 +85,9 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Type-check web UI + run: pnpm run type-check:web + - name: Build web UI run: pnpm run build:web diff --git a/.github/workflows/desktop-package.yml b/.github/workflows/desktop-package.yml index fd7a26f6..53957ce6 100644 --- a/.github/workflows/desktop-package.yml +++ b/.github/workflows/desktop-package.yml @@ -116,6 +116,9 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Type-check web UI + run: pnpm run type-check:web + - name: Build desktop app run: ${{ matrix.platform.build_command }} diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 291451b6..016d7c91 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -110,6 +110,9 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Type-check web UI + run: pnpm run type-check:web + - name: Patch nightly version shell: bash env: diff --git a/package.json b/package.json index 3f902f55..34881a77 100644 --- a/package.json +++ b/package.json @@ -17,8 +17,9 @@ "dev:web": "pnpm --dir src/web-ui dev", "prebuild": "pnpm run prebuild:web", "prebuild:web": "pnpm run copy-assets --silent && pnpm run generate-all --silent", + "type-check:web": "pnpm --dir src/web-ui run type-check", "build": "pnpm run build:web", - "build:web": "pnpm --dir src/web-ui build", + "build:web": "pnpm run type-check:web && pnpm --dir src/web-ui build", "build:mobile-web": "pnpm --dir src/mobile-web build", "preview": "pnpm --dir src/web-ui preview", "desktop:dev": "node scripts/dev.cjs desktop", diff --git a/src/web-ui/src/app/components/NavPanel/MainNav.tsx b/src/web-ui/src/app/components/NavPanel/MainNav.tsx index d1cccf8d..7b07a062 100644 --- a/src/web-ui/src/app/components/NavPanel/MainNav.tsx +++ b/src/web-ui/src/app/components/NavPanel/MainNav.tsx @@ -485,6 +485,8 @@ const MainNav: React.FC = ({ const sectionDir = getSectionDepartDir(section.id); const sectionDepartCls = sectionDir ? ` is-departing-${sectionDir}` : ''; + const sectionSceneId = section.sceneId; + return (
{section.label && ( @@ -493,7 +495,7 @@ const MainNav: React.FC = ({ collapsible={isCollapsible} isOpen={isSectionOpen} onToggle={() => toggleSection(section.id)} - onSceneOpen={section.sceneId ? () => openScene(section.sceneId) : undefined} + onSceneOpen={sectionSceneId ? () => openScene(sectionSceneId) : undefined} actions={section.id === 'workspace' ? (
diff --git a/src/web-ui/src/app/components/TitleBar/GlobalSearch.tsx b/src/web-ui/src/app/components/TitleBar/GlobalSearch.tsx index 773dc9e9..d63615be 100644 --- a/src/web-ui/src/app/components/TitleBar/GlobalSearch.tsx +++ b/src/web-ui/src/app/components/TitleBar/GlobalSearch.tsx @@ -18,11 +18,7 @@ import { useI18n } from '@/infrastructure/i18n'; import { useWorkspaceContext } from '../../../infrastructure/contexts/WorkspaceContext'; import { useFileSearch } from '@/hooks'; import type { FileSearchResult } from '../../../infrastructure/api/service-api/tauri-commands'; -import { createLogger } from '@/shared/utils/logger'; import './GlobalSearch.scss'; - -const log = createLogger('GlobalSearch'); - // Initial result count and load-more batch size const INITIAL_DISPLAY_COUNT = 50; const LOAD_MORE_COUNT = 50; diff --git a/src/web-ui/src/app/components/TitleBar/TitleBar.tsx b/src/web-ui/src/app/components/TitleBar/TitleBar.tsx index 85bd2c77..1afccc91 100644 --- a/src/web-ui/src/app/components/TitleBar/TitleBar.tsx +++ b/src/web-ui/src/app/components/TitleBar/TitleBar.tsx @@ -16,12 +16,11 @@ import './TitleBar.scss'; import { Button, WindowControls, Tooltip } from '@/component-library'; import { WorkspaceManager } from '../../../tools/workspace'; -import { CurrentSessionTitle, useToolbarModeContext } from '../../../flow_chat'; +import { CurrentSessionTitle } from '../../../flow_chat'; import { createConfigCenterTab } from '@/shared/utils/tabUtils'; import { workspaceAPI } from '@/infrastructure/api'; import { NewProjectDialog } from '../NewProjectDialog'; import { AboutDialog } from '../AboutDialog'; -import { GlobalSearch } from './GlobalSearch'; import { AgentOrb } from './AgentOrb'; import NotificationButton from './NotificationButton'; import { createLogger } from '@/shared/utils/logger'; @@ -65,8 +64,6 @@ const TitleBar: React.FC = ({ ); }, []); - const { enableToolbarMode } = useToolbarModeContext(); - const lastMouseDownTimeRef = React.useRef(0); const handleHeaderMouseDown = useCallback((e: React.MouseEvent) => { diff --git a/src/web-ui/src/app/components/panels/BranchSelectModal.tsx b/src/web-ui/src/app/components/panels/BranchSelectModal.tsx index 1cfc1823..5bbbbc59 100644 --- a/src/web-ui/src/app/components/panels/BranchSelectModal.tsx +++ b/src/web-ui/src/app/components/panels/BranchSelectModal.tsx @@ -14,6 +14,11 @@ import './BranchSelectModal.scss'; const log = createLogger('BranchSelectModal'); +type SelectableBranch = GitBranchType & { + isCurrent?: boolean; + hasWorktree?: boolean; +}; + export interface BranchSelectResult { branch: string; isNew: boolean; @@ -93,7 +98,7 @@ export const BranchSelectModal: React.FC = ({ } }; - const filteredBranches = useMemo(() => { + const filteredBranches = useMemo(() => { let result = branches; if (searchTerm.trim()) { @@ -103,8 +108,8 @@ export const BranchSelectModal: React.FC = ({ ); } - const availableBranches: GitBranchType[] = []; - const unavailableBranches: GitBranchType[] = []; + const availableBranches: SelectableBranch[] = []; + const unavailableBranches: SelectableBranch[] = []; const existingWorktreeSet = new Set(existingWorktreeBranches); result.forEach(branch => { @@ -217,9 +222,7 @@ export const BranchSelectModal: React.FC = ({ )} {filteredBranches.map((branch) => { - // @ts-ignore const isDisabled = branch.isCurrent || branch.hasWorktree; - // @ts-ignore const hasWorktree = branch.hasWorktree; return ( diff --git a/src/web-ui/src/app/components/panels/FilesPanel.tsx b/src/web-ui/src/app/components/panels/FilesPanel.tsx index 306f90d3..e850a24e 100644 --- a/src/web-ui/src/app/components/panels/FilesPanel.tsx +++ b/src/web-ui/src/app/components/panels/FilesPanel.tsx @@ -82,7 +82,6 @@ const FilesPanel: React.FC = ({ selectedFile, expandedFolders, loading, - silentRefreshing, error, loadFileTree, selectFile, @@ -97,13 +96,12 @@ const FilesPanel: React.FC = ({ enableAutoWatch: true, enableLazyLoad: true }); - const handleTreeUpdate = useCallback((updatedTree: FileSystemNode[]) => { log.debug('File tree updated', { nodeCount: updatedTree.length }); setFileTree(updatedTree); }, [setFileTree]); - const handleNodeExpandLazy = useCallback((path: string, expanded: boolean) => { + const handleNodeExpandLazy = useCallback((path: string) => { expandFolderLazy(path); }, [expandFolderLazy]); @@ -682,4 +680,4 @@ const FilesPanel: React.FC = ({ ); }; -export default FilesPanel; \ No newline at end of file +export default FilesPanel; diff --git a/src/web-ui/src/app/components/panels/base/FlexiblePanel.tsx b/src/web-ui/src/app/components/panels/base/FlexiblePanel.tsx index 4bf9ff02..4b838194 100644 --- a/src/web-ui/src/app/components/panels/base/FlexiblePanel.tsx +++ b/src/web-ui/src/app/components/panels/base/FlexiblePanel.tsx @@ -172,8 +172,6 @@ const FlexiblePanel: React.FC = memo(({ if (content?.type !== 'mermaid-editor') return null; const mermaidData = content.data || {}; - const metadata = content.metadata || {}; - return { initialSourceCode: mermaidData.sourceCode || t('flexiblePanel.fallback.mermaidDefaultCode'), onSave: async (sourceCode: string) => { @@ -514,7 +512,7 @@ const FlexiblePanel: React.FC = memo(({ onDirtyStateChange(hasChanges); } }} - onSave={(savedContent) => { + onSave={() => { if (onDirtyStateChange) { onDirtyStateChange(false); } @@ -793,4 +791,3 @@ const FlexiblePanel: React.FC = memo(({ FlexiblePanel.displayName = 'FlexiblePanel'; export default FlexiblePanel; - diff --git a/src/web-ui/src/app/components/panels/base/types.ts b/src/web-ui/src/app/components/panels/base/types.ts index 73d8bc70..06fa64a6 100644 --- a/src/web-ui/src/app/components/panels/base/types.ts +++ b/src/web-ui/src/app/components/panels/base/types.ts @@ -21,6 +21,9 @@ export type PanelContentType = | 'git-branch-history' | 'ai-session' | 'planner' + | 'ui-editor' + | 'ui-relation-graph' + | 'design-tokens' | 'task-detail' | 'plan-viewer' | 'terminal'; @@ -67,9 +70,8 @@ export interface TabbedFlexiblePanelRef { export interface PanelContentConfig { type: PanelContentType; displayName: string; - icon: React.ComponentType<{ size?: number }>; + icon: React.ComponentType<{ size?: string | number }>; supportsCopy: boolean; supportsDownload: boolean; showHeader: boolean; } - diff --git a/src/web-ui/src/app/components/panels/base/utils.ts b/src/web-ui/src/app/components/panels/base/utils.ts index 131f3a08..8fc63ff8 100644 --- a/src/web-ui/src/app/components/panels/base/utils.ts +++ b/src/web-ui/src/app/components/panels/base/utils.ts @@ -10,7 +10,6 @@ import { GitBranch, Eye, Edit3, - Terminal as TerminalIcon, BookOpen, Settings, ClipboardList, @@ -125,6 +124,22 @@ export const PANEL_CONTENT_CONFIGS: Record supportsDownload: false, showHeader: false }, + 'git-graph': { + type: 'git-graph', + displayName: 'Git Graph', + icon: GitBranch, + supportsCopy: false, + supportsDownload: false, + showHeader: false + }, + 'git-branch-history': { + type: 'git-branch-history', + displayName: 'Git Branch History', + icon: GitBranch, + supportsCopy: false, + supportsDownload: false, + showHeader: false + }, 'ai-session': { type: 'ai-session', displayName: 'AI Session', @@ -180,6 +195,14 @@ export const PANEL_CONTENT_CONFIGS: Record supportsCopy: false, supportsDownload: false, showHeader: false + }, + 'terminal': { + type: 'terminal', + displayName: 'Terminal', + icon: Code, + supportsCopy: false, + supportsDownload: false, + showHeader: false } }; @@ -282,4 +305,3 @@ export const generateFileName = (type: PanelContentType, title: string): string return `${baseName}.txt`; } }; - diff --git a/src/web-ui/src/app/components/panels/content-canvas/ContentCanvas.tsx b/src/web-ui/src/app/components/panels/content-canvas/ContentCanvas.tsx index 20e47754..0fd0002b 100644 --- a/src/web-ui/src/app/components/panels/content-canvas/ContentCanvas.tsx +++ b/src/web-ui/src/app/components/panels/content-canvas/ContentCanvas.tsx @@ -3,7 +3,7 @@ * Core component for the right panel, aggregating submodules. */ -import React, { useEffect, useCallback, useMemo } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { EditorArea } from './editor-area'; import { AnchorZone } from './anchor-zone'; import { MissionControl } from './mission-control'; @@ -11,11 +11,7 @@ import { EmptyState } from './empty-state'; import { useCanvasStore } from './stores'; import { useTabLifecycle, useKeyboardShortcuts, usePanelTabCoordinator } from './hooks'; import type { AnchorPosition } from './types'; -import { createLogger } from '@/shared/utils/logger'; import './ContentCanvas.scss'; - -const log = createLogger('ContentCanvas'); - export interface ContentCanvasProps { /** Workspace path */ workspacePath?: string; @@ -31,12 +27,10 @@ export const ContentCanvas: React.FC = ({ workspacePath, mode = 'agent', onInteraction, - onBeforeClose, }) => { // Store state const { primaryGroup, - secondaryGroup, layout, isMissionControlOpen, setAnchorPosition, @@ -44,7 +38,6 @@ export const ContentCanvas: React.FC = ({ closeMissionControl, openMissionControl, } = useCanvasStore(); - // Initialize hooks const { handleCloseWithDirtyCheck, handleCloseAllWithDirtyCheck } = useTabLifecycle({ mode }); useKeyboardShortcuts({ enabled: true, handleCloseWithDirtyCheck }); @@ -137,7 +130,6 @@ export const ContentCanvas: React.FC = ({
); }; - ContentCanvas.displayName = 'ContentCanvas'; export default ContentCanvas; diff --git a/src/web-ui/src/app/components/panels/content-canvas/anchor-zone/AnchorZone.tsx b/src/web-ui/src/app/components/panels/content-canvas/anchor-zone/AnchorZone.tsx index 2d34c6cc..a753ec7d 100644 --- a/src/web-ui/src/app/components/panels/content-canvas/anchor-zone/AnchorZone.tsx +++ b/src/web-ui/src/app/components/panels/content-canvas/anchor-zone/AnchorZone.tsx @@ -35,7 +35,6 @@ export const AnchorZone: React.FC = ({ size, isMaximized = false, onSizeChange, - onPositionChange, onClose, onToggleMaximize, children, @@ -97,10 +96,6 @@ export const AnchorZone: React.FC = ({ }, [isCollapsed]); // Toggle position - const togglePosition = useCallback(() => { - onPositionChange(isBottom ? 'right' : 'bottom'); - }, [isBottom, onPositionChange]); - return (
void; @@ -49,6 +45,7 @@ export const EditorArea: React.FC = ({ reorderTab, handleDrop, setSplitRatio, + setSplitRatio2, setActiveGroup, updateTabContent, setTabDirty, @@ -190,7 +187,6 @@ export const EditorArea: React.FC = ({ } if (splitMode === 'grid') { - const setSplitRatio2 = (r: number) => useCanvasStore.setState((s) => ({ layout: { ...s.layout, splitRatio2: r } })); return (
diff --git a/src/web-ui/src/app/components/panels/content-canvas/editor-area/EditorGroup.tsx b/src/web-ui/src/app/components/panels/content-canvas/editor-area/EditorGroup.tsx index d899b007..28aeb19b 100644 --- a/src/web-ui/src/app/components/panels/content-canvas/editor-area/EditorGroup.tsx +++ b/src/web-ui/src/app/components/panels/content-canvas/editor-area/EditorGroup.tsx @@ -15,7 +15,6 @@ import type { DropPosition, PanelContent, SplitMode, - CanvasTab, } from '../types'; import './EditorGroup.scss'; diff --git a/src/web-ui/src/app/components/panels/content-canvas/editor-area/SplitHandle.tsx b/src/web-ui/src/app/components/panels/content-canvas/editor-area/SplitHandle.tsx index 1370a3a9..209b2c4a 100644 --- a/src/web-ui/src/app/components/panels/content-canvas/editor-area/SplitHandle.tsx +++ b/src/web-ui/src/app/components/panels/content-canvas/editor-area/SplitHandle.tsx @@ -6,7 +6,6 @@ import React, { useState, useCallback, useRef, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { Tooltip } from '@/component-library'; -import type { SplitMode } from '../types'; import { LAYOUT_CONFIG, clampSplitRatio } from '../types'; import './SplitHandle.scss'; diff --git a/src/web-ui/src/app/components/panels/content-canvas/hooks/useKeyboardShortcuts.ts b/src/web-ui/src/app/components/panels/content-canvas/hooks/useKeyboardShortcuts.ts index 1ef75e27..62eeadb7 100644 --- a/src/web-ui/src/app/components/panels/content-canvas/hooks/useKeyboardShortcuts.ts +++ b/src/web-ui/src/app/components/panels/content-canvas/hooks/useKeyboardShortcuts.ts @@ -72,7 +72,6 @@ export const useKeyboardShortcuts = (options: UseKeyboardShortcutsOptions = {}) secondaryGroup, activeGroupId, layout, - isMissionControlOpen, closeTab, switchToTab, reopenClosedTab, @@ -81,7 +80,6 @@ export const useKeyboardShortcuts = (options: UseKeyboardShortcutsOptions = {}) toggleMaximize, toggleMissionControl, } = useCanvasStore(); - // Execute shortcut action const executeAction = useCallback((action: ShortcutAction) => { const activeGroup = activeGroupId === 'primary' ? primaryGroup : secondaryGroup; diff --git a/src/web-ui/src/app/components/panels/content-canvas/hooks/useLayoutState.ts b/src/web-ui/src/app/components/panels/content-canvas/hooks/useLayoutState.ts index 2aa0e2ce..58f3e4d5 100644 --- a/src/web-ui/src/app/components/panels/content-canvas/hooks/useLayoutState.ts +++ b/src/web-ui/src/app/components/panels/content-canvas/hooks/useLayoutState.ts @@ -6,8 +6,7 @@ import { useCallback } from 'react'; import { useCanvasStore } from '../stores'; import type { SplitMode, AnchorPosition } from '../types'; -import { LAYOUT_CONFIG, clampSplitRatio, clampAnchorSize } from '../types'; - +import { LAYOUT_CONFIG } from '../types'; interface UseLayoutStateReturn { // State splitMode: SplitMode; diff --git a/src/web-ui/src/app/components/panels/content-canvas/hooks/usePanelTabCoordinator.ts b/src/web-ui/src/app/components/panels/content-canvas/hooks/usePanelTabCoordinator.ts index 274bd564..fbcbdc4a 100644 --- a/src/web-ui/src/app/components/panels/content-canvas/hooks/usePanelTabCoordinator.ts +++ b/src/web-ui/src/app/components/panels/content-canvas/hooks/usePanelTabCoordinator.ts @@ -13,10 +13,6 @@ import { useCanvasStore } from '../stores'; import { useApp } from '@/app/hooks/useApp'; import { TAB_EVENTS } from '../types'; import { loadPanelWidth, STORAGE_KEYS, RIGHT_PANEL_CONFIG } from '@/app/layout/panelConfig'; -import { createLogger } from '@/shared/utils/logger'; - -const log = createLogger('usePanelTabCoordinator'); - interface UsePanelTabCoordinatorOptions { /** Auto-collapse when all tabs are closed */ autoCollapseOnEmpty?: boolean; diff --git a/src/web-ui/src/app/components/panels/content-canvas/hooks/useTabLifecycle.ts b/src/web-ui/src/app/components/panels/content-canvas/hooks/useTabLifecycle.ts index 421ee18b..d8809cfd 100644 --- a/src/web-ui/src/app/components/panels/content-canvas/hooks/useTabLifecycle.ts +++ b/src/web-ui/src/app/components/panels/content-canvas/hooks/useTabLifecycle.ts @@ -12,12 +12,8 @@ import { useCallback, useEffect } from 'react'; import { useCanvasStore, useAgentCanvasStore, useProjectCanvasStore, useGitCanvasStore } from '../stores'; import type { EditorGroupId, PanelContent, CreateTabEventDetail } from '../types'; import { TAB_EVENTS } from '../types'; -import { createLogger } from '@/shared/utils/logger'; import { useI18n } from '@/infrastructure/i18n'; import { drainPendingTabs } from '@/shared/services/pendingTabQueue'; - -const log = createLogger('useTabLifecycle'); - interface UseTabLifecycleOptions { /** App mode / target canvas */ mode?: 'agent' | 'project' | 'git'; @@ -136,13 +132,10 @@ export const useTabLifecycle = (options: UseTabLifecycleOptions = {}): UseTabLif if (tab.isDirty) { // Show confirmation and ensure correct return handling - const resultPromise = window.confirm( + const result = window.confirm( t('tabs.confirmCloseWithDirty', { title: tab.title }) ); - // Await if confirm returns a Promise - const result = resultPromise instanceof Promise ? await resultPromise : resultPromise; - if (!result) { return false; } @@ -166,13 +159,10 @@ export const useTabLifecycle = (options: UseTabLifecycleOptions = {}): UseTabLif // Show confirmation with list of dirty files const fileList = dirtyTabs.map(t => ` - ${t.title}`).join('\n'); - const resultPromise = window.confirm( + const result = window.confirm( t('tabs.confirmCloseAllWithDirty', { count: dirtyTabs.length, fileList }) ); - // Await if confirm returns a Promise - const result = resultPromise instanceof Promise ? await resultPromise : resultPromise; - if (!result) { return false; } diff --git a/src/web-ui/src/app/components/panels/content-canvas/mission-control/MissionControl.tsx b/src/web-ui/src/app/components/panels/content-canvas/mission-control/MissionControl.tsx index 4e23a48e..a748948f 100644 --- a/src/web-ui/src/app/components/panels/content-canvas/mission-control/MissionControl.tsx +++ b/src/web-ui/src/app/components/panels/content-canvas/mission-control/MissionControl.tsx @@ -29,8 +29,7 @@ export const MissionControl: React.FC = ({ const { t } = useTranslation('components'); const [searchQuery, setSearchQuery] = useState(''); const [selectedGroups, setSelectedGroups] = useState>(new Set(['primary', 'secondary', 'tertiary'])); - const [draggingTabId, setDraggingTabId] = useState(null); - + const [, setDraggingTabId] = useState(null); const { primaryGroup, secondaryGroup, @@ -40,10 +39,8 @@ export const MissionControl: React.FC = ({ switchToTab, closeTab, togglePinTab, - closeMissionControl, setSplitMode, } = useCanvasStore(); - // Organize tabs by group const organizedTabs = useMemo(() => { const primary = primaryGroup.tabs @@ -144,7 +141,7 @@ export const MissionControl: React.FC = ({ }, [togglePinTab]); // Drag start - const handleDragStart = useCallback((tabId: string) => (e: React.DragEvent) => { + const handleDragStart = useCallback((tabId: string) => (_e: React.DragEvent) => { setDraggingTabId(tabId); }, []); diff --git a/src/web-ui/src/app/components/panels/content-canvas/mission-control/ThumbnailCard.tsx b/src/web-ui/src/app/components/panels/content-canvas/mission-control/ThumbnailCard.tsx index c6d63458..a41a7f25 100644 --- a/src/web-ui/src/app/components/panels/content-canvas/mission-control/ThumbnailCard.tsx +++ b/src/web-ui/src/app/components/panels/content-canvas/mission-control/ThumbnailCard.tsx @@ -7,7 +7,7 @@ import React, { useCallback, useMemo } from 'react'; import { X, Pin, FileCode, FileText, Image, Terminal, GitBranch } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { Tooltip } from '@/component-library'; -import type { CanvasTab, EditorGroupId, TabState } from '../types'; +import type { CanvasTab, EditorGroupId } from '../types'; import { isFileViewerType } from '../types'; import './ThumbnailCard.scss'; diff --git a/src/web-ui/src/app/components/panels/content-canvas/stores/canvasStore.ts b/src/web-ui/src/app/components/panels/content-canvas/stores/canvasStore.ts index 5ff15301..8e7d7761 100644 --- a/src/web-ui/src/app/components/panels/content-canvas/stores/canvasStore.ts +++ b/src/web-ui/src/app/components/panels/content-canvas/stores/canvasStore.ts @@ -111,6 +111,9 @@ interface CanvasStoreActions { /** Set split ratio */ setSplitRatio: (ratio: number) => void; + + /** Set secondary split ratio used by grid top row */ + setSplitRatio2: (ratio: number) => void; /** Set anchor position */ setAnchorPosition: (position: AnchorPosition) => void; @@ -986,6 +989,12 @@ const createCanvasStoreHook = () => create()( draft.layout.splitRatio = clampSplitRatio(ratio); }); }, + + setSplitRatio2: (ratio) => { + set((draft) => { + draft.layout.splitRatio2 = clampSplitRatio(ratio); + }); + }, setAnchorPosition: (position) => { set((draft) => { diff --git a/src/web-ui/src/app/components/panels/content-canvas/tab-bar/Tab.tsx b/src/web-ui/src/app/components/panels/content-canvas/tab-bar/Tab.tsx index 8f8c0fc3..16628cad 100644 --- a/src/web-ui/src/app/components/panels/content-canvas/tab-bar/Tab.tsx +++ b/src/web-ui/src/app/components/panels/content-canvas/tab-bar/Tab.tsx @@ -8,11 +8,7 @@ import { X, Pin, Split } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { Tooltip } from '@/component-library'; import type { CanvasTab, EditorGroupId, TabState } from '../types'; -import { createLogger } from '@/shared/utils/logger'; import './Tab.scss'; - -const log = createLogger('Tab'); - export interface TabProps { /** Tab data */ tab: CanvasTab; diff --git a/src/web-ui/src/app/components/panels/content-canvas/tab-bar/TabBar.tsx b/src/web-ui/src/app/components/panels/content-canvas/tab-bar/TabBar.tsx index 4354f014..7f8e2c5a 100644 --- a/src/web-ui/src/app/components/panels/content-canvas/tab-bar/TabBar.tsx +++ b/src/web-ui/src/app/components/panels/content-canvas/tab-bar/TabBar.tsx @@ -227,7 +227,7 @@ export const TabBar: React.FC = ({ const overflowTabs = visibleTabs.slice(visibleTabsCount); // Handle tab drag start - const handleTabDragStart = useCallback((tab: CanvasTab) => (e: React.DragEvent) => { + const handleTabDragStart = useCallback((tab: CanvasTab) => (_e: React.DragEvent) => { onDragStart({ tabId: tab.id, sourceGroupId: groupId, diff --git a/src/web-ui/src/app/components/panels/content-canvas/tab-bar/TabOverflowMenu.tsx b/src/web-ui/src/app/components/panels/content-canvas/tab-bar/TabOverflowMenu.tsx index 815c7304..7d78a0ed 100644 --- a/src/web-ui/src/app/components/panels/content-canvas/tab-bar/TabOverflowMenu.tsx +++ b/src/web-ui/src/app/components/panels/content-canvas/tab-bar/TabOverflowMenu.tsx @@ -11,12 +11,8 @@ import { createPortal } from 'react-dom'; import { LayoutGrid, ChevronDown, X } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { Tooltip } from '@/component-library'; -import { createLogger } from '@/shared/utils/logger'; import type { CanvasTab } from '../types'; import './TabOverflowMenu.scss'; - -const log = createLogger('TabOverflowMenu'); - export interface TabOverflowMenuProps { /** Overflow tabs */ overflowTabs: CanvasTab[]; diff --git a/src/web-ui/src/app/components/panels/content-canvas/types/content.ts b/src/web-ui/src/app/components/panels/content-canvas/types/content.ts index 1f1f9809..45cb6f82 100644 --- a/src/web-ui/src/app/components/panels/content-canvas/types/content.ts +++ b/src/web-ui/src/app/components/panels/content-canvas/types/content.ts @@ -1,46 +1,10 @@ /** * Content-related type definitions. - * Reuses existing FlexiblePanel content types. + * Reuses the shared FlexiblePanel content contract. */ -/** - * Panel content types. - */ -export type PanelContentType = - | 'empty' - | 'code-preview' - | 'code-viewer' - | 'code-editor' - | 'markdown-viewer' - | 'markdown-editor' - | 'mermaid-editor' - | 'text-viewer' - | 'file-viewer' - | 'image-viewer' - | 'diff-code-editor' - | 'git-diff' - | 'git-settings' - | 'git-graph' - | 'git-branch-history' - | 'ai-session' - | 'planner' - | 'task-detail' - | 'plan-viewer' - | 'terminal'; - -/** - * Panel content interface. - */ -export interface PanelContent { - /** Content type */ - type: PanelContentType; - /** Display title */ - title: string; - /** Content data */ - data?: any; - /** Metadata (dedupe, navigation, etc.) */ - metadata?: Record; -} +export type { PanelContentType, PanelContent } from '../../base/types'; +import type { PanelContentType } from '../../base/types'; /** * File viewer types (code, markdown, images, etc.). diff --git a/src/web-ui/src/app/hooks/useApp.ts b/src/web-ui/src/app/hooks/useApp.ts index 0f70533e..ddc12e3a 100644 --- a/src/web-ui/src/app/hooks/useApp.ts +++ b/src/web-ui/src/app/hooks/useApp.ts @@ -7,8 +7,6 @@ import { useState, useEffect, useCallback } from 'react'; import { UseAppReturn, AppState, - AppEvent, - Agent, AgentConfig, ChatSession, TabInfo, @@ -24,7 +22,7 @@ export const useApp = (): UseAppReturn => { // Listen for app state changes useEffect(() => { - const unsubscribe = appManager.addEventListener((event: AppEvent) => { + const unsubscribe = appManager.addEventListener(() => { // Update state on each event setState(appManager.getState()); }); diff --git a/src/web-ui/src/app/hooks/useSceneManager.ts b/src/web-ui/src/app/hooks/useSceneManager.ts index 09c86f1c..a1f20502 100644 --- a/src/web-ui/src/app/hooks/useSceneManager.ts +++ b/src/web-ui/src/app/hooks/useSceneManager.ts @@ -6,7 +6,7 @@ */ import { SCENE_TAB_REGISTRY, getMiniAppSceneDef } from '../scenes/registry'; -import type { SceneTabDef } from '../components/SceneBar/types'; +import type { SceneTabDef, SceneTabId } from '../components/SceneBar/types'; import { useSceneStore } from '../stores/sceneStore'; import { useToolboxStore } from '../scenes/toolbox/toolboxStore'; @@ -14,9 +14,9 @@ export interface UseSceneManagerReturn { openTabs: ReturnType['openTabs']; activeTabId: ReturnType['activeTabId']; tabDefs: SceneTabDef[]; - activateScene: (id: string) => void; - openScene: (id: string) => void; - closeScene: (id: string) => void; + activateScene: (id: SceneTabId) => void; + openScene: (id: SceneTabId) => void; + closeScene: (id: SceneTabId) => void; } export function useSceneManager(): UseSceneManagerReturn { diff --git a/src/web-ui/src/app/layout/AppLayout.tsx b/src/web-ui/src/app/layout/AppLayout.tsx index 5762b695..a13d8a91 100644 --- a/src/web-ui/src/app/layout/AppLayout.tsx +++ b/src/web-ui/src/app/layout/AppLayout.tsx @@ -23,7 +23,6 @@ import { NewProjectDialog } from '../components/NewProjectDialog'; import { AboutDialog } from '../components/AboutDialog'; import { WorkspaceManager } from '../../tools/workspace'; import { workspaceAPI } from '@/infrastructure/api'; -import { appManager } from '../'; import { createLogger } from '@/shared/utils/logger'; import { useI18n } from '@/infrastructure/i18n'; import './AppLayout.scss'; @@ -48,10 +47,8 @@ const AppLayout: React.FC = ({ className = '' }) => { const isAgentScene = activeSceneId === 'session'; const isWelcomeScene = activeSceneId === 'welcome'; - const [isTransitioning, setIsTransitioning] = useState(false); - const [isSweepGlowing, setIsSweepGlowing] = useState(false); - const [showStartupOverlay, setShowStartupOverlay] = useState(false); - const [transitionDir, setTransitionDir] = useState(null); + const isTransitioning = false; + const transitionDir: TransitionDirection = null; // Auto-open last workspace on startup const autoOpenAttemptedRef = useRef(false); @@ -121,43 +118,6 @@ const AppLayout: React.FC = ({ className = '' }) => { return () => { unlistenFns.forEach(fn => fn()); unlistenFns = []; }; }, [isMacOS, openWorkspace, handleNewProject, handleShowAbout]); - const handleWorkspaceSelected = useCallback(async (workspacePath: string, projectDescription?: string) => { - try { - log.info('Workspace selected', { workspacePath }); - - if (projectDescription && projectDescription.trim()) { - sessionStorage.setItem('pendingProjectDescription', projectDescription.trim()); - } - - setTransitionDir('entering'); - setIsTransitioning(true); - setShowStartupOverlay(true); - await openWorkspace(workspacePath); - - appManager.updateLayout({ - leftPanelCollapsed: false, - rightPanelCollapsed: true, - }); - - setIsSweepGlowing(true); - setTimeout(() => setIsSweepGlowing(false), 1200); - setTimeout(() => { - setShowStartupOverlay(false); - setIsTransitioning(false); - setTransitionDir(null); - }, 700); - - } catch (error) { - log.error('Failed to open workspace', error); - setIsTransitioning(false); - - import('@/shared/notification-system').then(({ notificationService }) => { - const errorMessage = error instanceof Error ? error.message : String(error); - notificationService.error(errorMessage || t('appLayout.workspaceOpenFailed'), { duration: 5000 }); - }); - } - }, [openWorkspace, t]); - // Initialize FlowChatManager React.useEffect(() => { const initializeFlowChat = async () => { diff --git a/src/web-ui/src/app/layout/FloatingMiniChat.tsx b/src/web-ui/src/app/layout/FloatingMiniChat.tsx index e6862408..efabba8b 100644 --- a/src/web-ui/src/app/layout/FloatingMiniChat.tsx +++ b/src/web-ui/src/app/layout/FloatingMiniChat.tsx @@ -26,6 +26,9 @@ import { ModernFlowChatContainer } from '../../flow_chat/components/modern/Moder import { Tooltip, Input } from '@/component-library'; import './FloatingMiniChat.scss'; +const getSessionTimestamp = (updatedAt?: number, lastActiveAt?: number) => + updatedAt ?? lastActiveAt ?? 0; + export const FloatingMiniChat: React.FC = () => { const { t } = useTranslation('flow-chat'); const { toolbarState } = useToolbarModeContext(); @@ -55,7 +58,7 @@ export const FloatingMiniChat: React.FC = () => { const sessions = useMemo(() => { return Array.from(flowChatState.sessions.values()) - .sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()) + .sort((a, b) => getSessionTimestamp(b.updatedAt, b.lastActiveAt) - getSessionTimestamp(a.updatedAt, a.lastActiveAt)) .slice(0, 10); }, [flowChatState]); diff --git a/src/web-ui/src/app/scenes/SceneViewport.tsx b/src/web-ui/src/app/scenes/SceneViewport.tsx index 012c0975..ae5dc737 100644 --- a/src/web-ui/src/app/scenes/SceneViewport.tsx +++ b/src/web-ui/src/app/scenes/SceneViewport.tsx @@ -99,7 +99,7 @@ function renderScene(id: SceneTabId, workspacePath?: string, isEntering?: boolea case 'session': return ; case 'terminal': - return ; + return ; case 'git': return ; case 'settings': @@ -107,7 +107,7 @@ function renderScene(id: SceneTabId, workspacePath?: string, isEntering?: boolea case 'file-viewer': return ; case 'profile': - return ; + return ; case 'agents': return ; case 'skills': diff --git a/src/web-ui/src/app/scenes/git/views/BranchesView.tsx b/src/web-ui/src/app/scenes/git/views/BranchesView.tsx index 4c4b207c..a511c1e6 100644 --- a/src/web-ui/src/app/scenes/git/views/BranchesView.tsx +++ b/src/web-ui/src/app/scenes/git/views/BranchesView.tsx @@ -20,8 +20,7 @@ import { gitService } from '@/tools/git/services'; import { useGitOperations } from '@/tools/git/hooks'; import { useNotification } from '@/shared/notification-system'; import { CreateBranchDialog } from '@/tools/git/components/CreateBranchDialog'; -import type { GitBranch as GitBranchType } from '@/infrastructure/api/service-api/GitAPI'; -import type { GitCommit as GitCommitType } from '@/infrastructure/api/service-api/GitAPI'; +import type { GitBranch as GitBranchType, GitCommit as GitCommitType, GitFileChange } from '@/tools/git/types/repository'; import './BranchesView.scss'; interface BranchesViewProps { @@ -103,7 +102,7 @@ const BranchesView: React.FC = ({ workspacePath }) => { const filteredCommits = commitSearchQuery.trim() ? commits.filter( c => - (c.summary ?? (c as any).message ?? '').toLowerCase().includes(commitSearchQuery.toLowerCase()) || + (c.message ?? '').toLowerCase().includes(commitSearchQuery.toLowerCase()) || ((c as any).author?.name ?? (c as any).author ?? '').toLowerCase().includes(commitSearchQuery.toLowerCase()) || (c.hash ?? '').toLowerCase().includes(commitSearchQuery.toLowerCase()) ) @@ -304,11 +303,11 @@ const BranchesView: React.FC = ({ workspacePath }) => { ) : ( filteredCommits.map((commit, idx) => { const isExpanded = expandedCommits.has(commit.hash); - const msg = (commit as any).message ?? commit.summary ?? ''; + const msg = commit.message ?? ''; const summary = msg.split('\n')[0]; const body = msg.split('\n').slice(1).join('\n').trim(); const author = (commit as any).author?.name ?? (commit as any).author ?? t('common.unknown'); - const files = (commit as any).files; + const files = commit.files; return (
= ({ workspacePath }) => { {t('commit.changedFiles', { count: files.length })}
    - {(files as { path?: string }[]).map((f, i) => ( -
  • {f.path ?? f}
  • + {(files as GitFileChange[]).map((file, i) => ( +
  • {file.path}
  • ))}
diff --git a/src/web-ui/src/app/scenes/profile/views/PersonaView.tsx b/src/web-ui/src/app/scenes/profile/views/PersonaView.tsx index 9962322b..441a8d45 100644 --- a/src/web-ui/src/app/scenes/profile/views/PersonaView.tsx +++ b/src/web-ui/src/app/scenes/profile/views/PersonaView.tsx @@ -215,11 +215,10 @@ interface ModelPillProps { slotDesc: string; currentId: string; models: AIModelConfig[]; - defaultModels: DefaultModelsConfig | null; onChange: (id: string) => void; } const ModelPill: React.FC = ({ - slotKey, slotLabel, slotDesc, currentId, models, defaultModels, onChange, + slotKey, slotLabel, slotDesc, currentId, models, onChange, }) => { const { t } = useTranslation('scenes/profile'); @@ -293,7 +292,6 @@ const ModelPill: React.FC = ({
); }; - const PersonaView: React.FC<{ workspacePath: string }> = ({ workspacePath }) => { const { t } = useTranslation('scenes/profile'); @@ -311,7 +309,7 @@ const PersonaView: React.FC<{ workspacePath: string }> = ({ workspacePath }) => const descInputRef = useRef(null); const [models, setModels] = useState([]); - const [defaultModels, setDefaultModels] = useState(null); + const [, setDefaultModels] = useState(null); const [funcAgentModels, setFuncAgentModels] = useState>({}); const [rules, setRules] = useState([]); const [memories, setMemories] = useState([]); @@ -1061,7 +1059,6 @@ const PersonaView: React.FC<{ workspacePath: string }> = ({ workspacePath }) => slotDesc={t(`modelSlots.${key}.desc`)} currentId={slotIds[key]} models={models} - defaultModels={defaultModels} onChange={id => handleModelChange(key, id)} /> ))} @@ -1076,7 +1073,6 @@ const PersonaView: React.FC<{ workspacePath: string }> = ({ workspacePath }) => slotDesc={t(`modelSlots.${key}.desc`)} currentId={slotIds[key]} models={models} - defaultModels={defaultModels} onChange={id => handleModelChange(key, id)} /> ))} @@ -1231,7 +1227,7 @@ const PersonaView: React.FC<{ workspacePath: string }> = ({ workspacePath }) => ); })} -
diff --git a/src/web-ui/src/app/scenes/shell/ShellNav.tsx b/src/web-ui/src/app/scenes/shell/ShellNav.tsx index 89d8e09d..6ddb08b7 100644 --- a/src/web-ui/src/app/scenes/shell/ShellNav.tsx +++ b/src/web-ui/src/app/scenes/shell/ShellNav.tsx @@ -54,7 +54,6 @@ const ShellNav: React.FC = () => { createHubTerminal, promoteToHub, openTerminal, - startTerminal, stopTerminal, deleteTerminal, openEditModal, @@ -62,7 +61,6 @@ const ShellNav: React.FC = () => { closeWorktreeTerminals, removeWorktreeConfig, } = useShellEntries(); - const { workspacePath, isGitRepo, diff --git a/src/web-ui/src/app/scenes/toolbox/MiniAppScene.tsx b/src/web-ui/src/app/scenes/toolbox/MiniAppScene.tsx index a9fa5f07..fc6b8843 100644 --- a/src/web-ui/src/app/scenes/toolbox/MiniAppScene.tsx +++ b/src/web-ui/src/app/scenes/toolbox/MiniAppScene.tsx @@ -13,6 +13,7 @@ import { useCurrentWorkspace } from '@/infrastructure/contexts/WorkspaceContext' import { createLogger } from '@/shared/utils/logger'; import { IconButton, Button } from '@/component-library'; import { useSceneManager } from '@/app/hooks/useSceneManager'; +import type { SceneTabId } from '@/app/components/SceneBar/types'; import './MiniAppScene.scss'; const log = createLogger('MiniAppScene'); @@ -64,7 +65,7 @@ const MiniAppScene: React.FC = ({ appId }) => { }, [appId, themeType, workspacePath]); useEffect(() => { - const tabId = `miniapp:${appId}`; + const tabId = `miniapp:${appId}` as SceneTabId; const shouldHandle = (payload?: { id?: string }) => payload?.id === appId; const unlistenUpdated = api.listen<{ id?: string }>('miniapp-updated', (payload) => { diff --git a/src/web-ui/src/app/scenes/toolbox/hooks/useMiniAppBridge.ts b/src/web-ui/src/app/scenes/toolbox/hooks/useMiniAppBridge.ts index ef83336b..6abf7cf7 100644 --- a/src/web-ui/src/app/scenes/toolbox/hooks/useMiniAppBridge.ts +++ b/src/web-ui/src/app/scenes/toolbox/hooks/useMiniAppBridge.ts @@ -73,20 +73,20 @@ export function useMiniAppBridge( return; } if (method === 'dialog.open') { - reply(await dialogOpen(params as Parameters[0])); + reply(await dialogOpen(params as unknown as Parameters[0])); return; } if (method === 'dialog.save') { - reply(await dialogSave(params as Parameters[0])); + reply(await dialogSave(params as unknown as Parameters[0])); return; } if (method === 'dialog.message') { - reply(await dialogMessage(params as Parameters[0])); + reply(await dialogMessage(params as unknown as Parameters[0])); return; } replyError(`Unknown method: ${method}`); } catch (error) { - replyError(String(error)); + replyError(typeof error === 'string' ? error : String(error)); } }; window.addEventListener('message', handler); diff --git a/src/web-ui/src/component-library/components/Alert/Alert.tsx b/src/web-ui/src/component-library/components/Alert/Alert.tsx index ba4f4f63..9405f1bf 100644 --- a/src/web-ui/src/component-library/components/Alert/Alert.tsx +++ b/src/web-ui/src/component-library/components/Alert/Alert.tsx @@ -2,7 +2,7 @@ * Alert component */ -import React, { useState, useEffect, forwardRef, ReactNode } from 'react'; +import React, { useState, forwardRef, ReactNode } from 'react'; import { useI18n } from '@/infrastructure/i18n'; import './Alert.scss'; @@ -118,4 +118,4 @@ export const Alert = forwardRef(({ ); }); -Alert.displayName = 'Alert'; \ No newline at end of file +Alert.displayName = 'Alert'; diff --git a/src/web-ui/src/component-library/components/Card/Card.tsx b/src/web-ui/src/component-library/components/Card/Card.tsx index bceacfa4..1a34d76d 100644 --- a/src/web-ui/src/component-library/components/Card/Card.tsx +++ b/src/web-ui/src/component-library/components/Card/Card.tsx @@ -51,7 +51,7 @@ export const Card = forwardRef(({ Card.displayName = 'Card'; -export interface CardHeaderProps extends React.HTMLAttributes { +export interface CardHeaderProps extends Omit, 'title'> { /** Title */ title?: React.ReactNode; /** Subtitle */ diff --git a/src/web-ui/src/component-library/components/Checkbox/Checkbox.tsx b/src/web-ui/src/component-library/components/Checkbox/Checkbox.tsx index c45dbdaa..af37382c 100644 --- a/src/web-ui/src/component-library/components/Checkbox/Checkbox.tsx +++ b/src/web-ui/src/component-library/components/Checkbox/Checkbox.tsx @@ -3,7 +3,7 @@ import './Checkbox.scss'; export interface CheckboxProps extends Omit, 'size'> { /** Checkbox label */ - label?: string; + label?: React.ReactNode; /** Description text */ description?: string; /** Size */ @@ -92,4 +92,4 @@ export const Checkbox = forwardRef( } ); -Checkbox.displayName = 'Checkbox'; \ No newline at end of file +Checkbox.displayName = 'Checkbox'; diff --git a/src/web-ui/src/component-library/components/FlowChatCards/SearchCard/SearchCard.tsx b/src/web-ui/src/component-library/components/FlowChatCards/SearchCard/SearchCard.tsx index 8f54e1ed..e75734d3 100644 --- a/src/web-ui/src/component-library/components/FlowChatCards/SearchCard/SearchCard.tsx +++ b/src/web-ui/src/component-library/components/FlowChatCards/SearchCard/SearchCard.tsx @@ -57,7 +57,7 @@ export const SearchCard: React.FC = ({ const stats = useMemo(() => { if (searchMatches.length === 0) return { matches: 0, files: 0 }; - const files = new Set(searchMatches.map(match => + const files = new Set(searchMatches.map((match: any) => match.file || match.filename || match.path || match )).size; @@ -70,7 +70,7 @@ export const SearchCard: React.FC = ({ const topFiles = useMemo(() => { const fileMap = new Map(); - searchMatches.forEach(match => { + searchMatches.forEach((match: any) => { const file = match.file || match.filename || match.path || match; if (file && typeof file === 'string') { fileMap.set(file, (fileMap.get(file) || 0) + 1); diff --git a/src/web-ui/src/component-library/components/FlowChatCards/SnapshotCard/SnapshotCard.tsx b/src/web-ui/src/component-library/components/FlowChatCards/SnapshotCard/SnapshotCard.tsx index 321c447b..e355d61c 100644 --- a/src/web-ui/src/component-library/components/FlowChatCards/SnapshotCard/SnapshotCard.tsx +++ b/src/web-ui/src/component-library/components/FlowChatCards/SnapshotCard/SnapshotCard.tsx @@ -3,7 +3,7 @@ * Used to show file operations (Write/Edit/Delete) changes and provide confirmation actions */ -import React, { useState } from 'react'; +import React from 'react'; import { CheckCircle, XCircle, Maximize2, FileText, Loader2, Clock } from 'lucide-react'; import { useI18n } from '@/infrastructure/i18n'; import { BaseToolCard, BaseToolCardProps } from '../BaseToolCard'; @@ -40,8 +40,6 @@ export const SnapshotCard: React.FC = ({ ...baseProps }) => { const { t } = useI18n('components'); - const [isExpanded, setIsExpanded] = useState(false); - const resolvedFilePath = filePath || input?.file_path || input?.target_file || input?.path || t('flowChatCards.snapshotCard.unspecifiedFile'); const fileName = resolvedFilePath.split(/[/\\]/).pop() || t('flowChatCards.snapshotCard.file'); diff --git a/src/web-ui/src/component-library/components/Input/Input.tsx b/src/web-ui/src/component-library/components/Input/Input.tsx index 3b687fa2..ddb9ba8b 100644 --- a/src/web-ui/src/component-library/components/Input/Input.tsx +++ b/src/web-ui/src/component-library/components/Input/Input.tsx @@ -5,32 +5,37 @@ import React, { forwardRef } from 'react'; import './Input.scss'; -export interface InputProps extends Omit, 'size'> { +export interface InputProps extends Omit, 'size' | 'prefix'> { variant?: 'default' | 'filled' | 'outlined'; inputSize?: 'small' | 'medium' | 'large'; + size?: 'small' | 'medium' | 'large'; error?: boolean; errorMessage?: string; prefix?: React.ReactNode; suffix?: React.ReactNode; label?: string; + hint?: React.ReactNode; } export const Input = forwardRef(({ variant = 'default', inputSize = 'medium', + size, error = false, errorMessage, prefix, suffix, label, + hint, className = '', disabled, ...props }, ref) => { + const resolvedInputSize = size ?? inputSize; const classNames = [ 'bitfun-input-wrapper', `bitfun-input-wrapper--${variant}`, - `bitfun-input-wrapper--${inputSize}`, + `bitfun-input-wrapper--${resolvedInputSize}`, error && 'bitfun-input-wrapper--error', disabled && 'bitfun-input-wrapper--disabled', className @@ -51,6 +56,9 @@ export const Input = forwardRef(({ /> {suffix && {suffix}}
+ {!error && hint && ( + {hint} + )} {error && errorMessage && ( {errorMessage} )} @@ -58,4 +66,4 @@ export const Input = forwardRef(({ ); }); -Input.displayName = 'Input'; \ No newline at end of file +Input.displayName = 'Input'; diff --git a/src/web-ui/src/component-library/components/Search/Search.tsx b/src/web-ui/src/component-library/components/Search/Search.tsx index 8550b3dd..21d7a12a 100644 --- a/src/web-ui/src/component-library/components/Search/Search.tsx +++ b/src/web-ui/src/component-library/components/Search/Search.tsx @@ -2,7 +2,7 @@ * Search input component */ -import React, { useState, useRef, useEffect, useCallback } from 'react'; +import React, { useState, useRef, useEffect, useCallback, forwardRef } from 'react'; import { useI18n } from '@/infrastructure/i18n'; import './Search.scss'; @@ -32,7 +32,7 @@ export interface SearchProps { suffixContent?: React.ReactNode; } -export const Search: React.FC = ({ +export const Search = forwardRef(({ value, defaultValue = '', placeholder, @@ -56,7 +56,7 @@ export const Search: React.FC = ({ searchButtonText, showSearchButton = false, suffixContent, -}) => { +}, ref) => { const { t } = useI18n('components'); // Resolve i18n default values @@ -68,7 +68,17 @@ export const Search: React.FC = ({ const [isFocused, setIsFocused] = useState(false); const [isHovered, setIsHovered] = useState(false); - const inputRef = useRef(null); + const inputRef = useRef(null); + + const setForwardedRef = useCallback((node: HTMLInputElement | null) => { + if (typeof ref === 'function') { + ref(node); + return; + } + if (ref) { + (ref as React.MutableRefObject).current = node; + } + }, [ref]); useEffect(() => { if (value !== undefined) { @@ -201,7 +211,10 @@ export const Search: React.FC = ({ { + inputRef.current = node; + setForwardedRef(node); + }} type="text" className="search__input" value={inputValue} @@ -258,5 +271,6 @@ export const Search: React.FC = ({ )} ); -}; +}); +Search.displayName = 'Search'; diff --git a/src/web-ui/src/component-library/components/StreamText/StreamText.tsx b/src/web-ui/src/component-library/components/StreamText/StreamText.tsx index 5869542e..911efaae 100644 --- a/src/web-ui/src/component-library/components/StreamText/StreamText.tsx +++ b/src/web-ui/src/component-library/components/StreamText/StreamText.tsx @@ -54,7 +54,6 @@ const StreamTextComponent: React.FC = ({ const [currentIndex, setCurrentIndex] = useState(0); const [isStreaming, setIsStreaming] = useState(false); const [isComplete, setIsComplete] = useState(false); - const [charStates, setCharStates] = useState([]); const timeoutRef = useRef(null); const pausedIndexRef = useRef(0); const hasStartedRef = useRef(false); @@ -71,8 +70,6 @@ const StreamTextComponent: React.FC = ({ clearTimeout(timeoutRef.current); } - setCharStates([]); - if (isComplete) { setDisplayedText(text); setCurrentIndex(text.length); @@ -130,10 +127,6 @@ const StreamTextComponent: React.FC = ({ setDisplayedText(prev => prev + text[index]); setCurrentIndex(index + 1); - if (charAnimation) { - setCharStates(prev => [...prev, Date.now()]); - } - const progress = ((index + 1) / text.length) * 100; onProgress?.(progress); @@ -207,4 +200,4 @@ export const StreamText = React.memo(StreamTextComponent, (prevProps, nextProps) prevProps.showCursor === nextProps.showCursor && prevProps.className === nextProps.className ); -}); \ No newline at end of file +}); diff --git a/src/web-ui/src/component-library/components/Tag/Tag.tsx b/src/web-ui/src/component-library/components/Tag/Tag.tsx index 06a25784..86993b8d 100644 --- a/src/web-ui/src/component-library/components/Tag/Tag.tsx +++ b/src/web-ui/src/component-library/components/Tag/Tag.tsx @@ -9,6 +9,7 @@ export interface TagProps { children: React.ReactNode; color?: 'blue' | 'green' | 'red' | 'yellow' | 'purple' | 'gray'; size?: 'small' | 'medium' | 'large'; + title?: string; closable?: boolean; onClose?: () => void; className?: string; @@ -19,6 +20,7 @@ export const Tag: React.FC = ({ children, color = 'blue', size = 'medium', + title, closable = false, onClose, className = '', @@ -35,7 +37,7 @@ export const Tag: React.FC = ({ .join(' '); return ( - + {children} {closable && (