diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 4434573e49..e43d935768 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -51,7 +51,7 @@ jobs: run: | yarn run download-extension yarn run rebuild:node - SUPPORT_LOAD_WORKSPACE_BY_HASH=true yarn start & + SUPPORT_LOAD_WORKSPACE_BY_HASH=true yarn start:e2e & bash tools/playwright/scripts/wait.sh && cd tools/playwright && yarn run ui-tests-ci - name: Upload test results diff --git a/package.json b/package.json index 39358f71ba..ab13b0a3ba 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "format": "yarn run lint:fix && prettier \"**/*.{js,jsx,ts,tsx,html,css,less}\" --write", "rebuild:node": "sumi rebuild", "test:module": "cross-env NODE_OPTIONS=--max-old-space-size=5120 tsx ./scripts/module-jest", + "start:e2e": "yarn start --script=start:e2e", "test:ui": "tsx ./scripts/run-ui-tests", "test:ui-ci": "tsx ./scripts/run-ui-tests --ci=true", "test:ui-headful": "tsx ./scripts/run-ui-tests --headful=true", diff --git a/packages/ai-native/src/browser/ai-core.contribution.ts b/packages/ai-native/src/browser/ai-core.contribution.ts index ba825a38c5..f29dbe5805 100644 --- a/packages/ai-native/src/browser/ai-core.contribution.ts +++ b/packages/ai-native/src/browser/ai-core.contribution.ts @@ -11,9 +11,11 @@ import { ComponentRegistry, ContributionProvider, Domain, + IEditorExtensionContribution, KeybindingContribution, KeybindingRegistry, KeybindingScope, + MonacoContribution, PreferenceService, SlotLocation, SlotRendererContribution, @@ -28,8 +30,9 @@ import { AI_INLINE_COMPLETION_VISIBLE, } from '@opensumi/ide-core-browser/lib/ai-native/command'; import { InlineChatIsVisible } from '@opensumi/ide-core-browser/lib/contextkey/ai-native'; -import { LayoutViewSizeConfig } from '@opensumi/ide-core-browser/lib/layout/constants'; +import { DesignLayoutConfig } from '@opensumi/ide-core-browser/lib/layout/constants'; import { + AI_NATIVE_SETTING_GROUP_TITLE, ChatFeatureRegistryToken, ChatRenderRegistryToken, CommandService, @@ -43,16 +46,19 @@ import { IEditor } from '@opensumi/ide-editor'; import { BrowserEditorContribution, IEditorFeatureRegistry } from '@opensumi/ide-editor/lib/browser'; import { IMainLayoutService } from '@opensumi/ide-main-layout'; import { ISettingRegistry, SettingContribution } from '@opensumi/ide-preferences'; +import { EditorContributionInstantiation } from '@opensumi/monaco-editor-core/esm/vs/editor/browser/editorExtensions'; -import { AI_CHAT_CONTAINER_ID, AI_CHAT_VIEW_ID, AI_MENU_BAR_DEBUG_TOOLBAR } from '../common'; +import { AI_CHAT_CONTAINER_ID, AI_CHAT_LOGO_AVATAR_ID, AI_CHAT_VIEW_ID, AI_MENU_BAR_DEBUG_TOOLBAR } from '../common'; import { AIEditorContribution } from './ai-editor.contribution'; import { AINativeService } from './ai-native.service'; import { AIChatView } from './chat/chat.view'; import { AIInlineCompletionsProvider } from './inline-completions/completeProvider'; import { AICompletionsService } from './inline-completions/service/ai-completions.service'; -import { AIChatLayoutConfig, AIMenubarLayoutConfig } from './layout/layout-config'; +import { AIChatLayoutConfig } from './layout/layout-config'; import { AIChatTabRenderer, AILeftTabRenderer, AIRightTabRenderer } from './layout/tabbar.view'; +import { AIChatLogoAvatar } from './layout/view/avatar/avatar.view'; +import { OpenSumiLightBulbWidget } from './light-bulb-widget'; import { AIRunToolbar } from './run/toolbar/run-toolbar'; import { AINativeCoreContribution, @@ -71,6 +77,7 @@ import { KeybindingContribution, ComponentContribution, SlotRendererContribution, + MonacoContribution, ) export class AINativeBrowserContribution implements @@ -80,7 +87,8 @@ export class AINativeBrowserContribution SettingContribution, KeybindingContribution, ComponentContribution, - SlotRendererContribution + SlotRendererContribution, + MonacoContribution { @Autowired(INJECTOR_TOKEN) private readonly injector: Injector; @@ -112,6 +120,9 @@ export class AINativeBrowserContribution @Autowired(AINativeConfigService) private readonly aiNativeConfigService: AINativeConfigService; + @Autowired(DesignLayoutConfig) + private readonly designLayoutConfig: DesignLayoutConfig; + @Autowired(AICompletionsService) private aiCompletionsService: AICompletionsService; @@ -134,11 +145,9 @@ export class AINativeBrowserContribution initialize() { this.aiNativeConfigService.enableCapabilities(); - const { supportsChatAssistant, supportsOpenSumiDesign } = this.aiNativeConfigService.capabilities; - const { useMenubarView } = this.aiNativeConfigService.layout; + const { supportsChatAssistant } = this.aiNativeConfigService.capabilities; let layoutConfig = this.appConfig.layoutConfig; - const layoutViewSize = this.appConfig.layoutViewSize as LayoutViewSizeConfig; if (supportsChatAssistant) { layoutConfig = { @@ -147,22 +156,14 @@ export class AINativeBrowserContribution }; } - if (useMenubarView) { - layoutViewSize.setMenubarHeight(48); - layoutConfig = { - ...layoutConfig, - ...AIMenubarLayoutConfig, - }; - } + this.appConfig.layoutConfig = layoutConfig; + } - if (supportsOpenSumiDesign) { - layoutViewSize.setEditorTabsHeight(36); - layoutViewSize.setStatusBarHeight(36); - layoutViewSize.setAccordionHeaderSizeHeight(36); + registerEditorExtensionContribution(register: IEditorExtensionContribution): void { + const { supportsInlineChat } = this.aiNativeConfigService.capabilities; + if (supportsInlineChat) { + register(OpenSumiLightBulbWidget.ID, OpenSumiLightBulbWidget, EditorContributionInstantiation.Lazy); } - - this.appConfig.layoutConfig = layoutConfig; - this.appConfig.layoutViewSize = layoutViewSize; } onDidStart() { @@ -200,7 +201,7 @@ export class AINativeBrowserContribution registerSetting(registry: ISettingRegistry) { registry.registerSettingGroup({ id: AI_NATIVE_SETTING_GROUP_ID, - title: AI_NATIVE_SETTING_GROUP_ID, + title: AI_NATIVE_SETTING_GROUP_TITLE, iconClass: getIcon('magic-wand'), }); @@ -274,7 +275,7 @@ export class AINativeBrowserContribution registerRenderer(registry: SlotRendererRegistry): void { registry.registerSlotRenderer(AI_CHAT_VIEW_ID, AIChatTabRenderer); - if (this.aiNativeConfigService.layout.useMergeRightWithLeftPanel) { + if (this.designLayoutConfig.useMergeRightWithLeftPanel) { registry.registerSlotRenderer(SlotLocation.left, AILeftTabRenderer); registry.registerSlotRenderer(SlotLocation.right, AIRightTabRenderer); } @@ -289,6 +290,10 @@ export class AINativeBrowserContribution id: AI_MENU_BAR_DEBUG_TOOLBAR, component: AIRunToolbar, }); + registry.register(AI_CHAT_LOGO_AVATAR_ID, { + id: AI_CHAT_LOGO_AVATAR_ID, + component: AIChatLogoAvatar, + }); } registerKeybindings(keybindings: KeybindingRegistry): void { diff --git a/packages/ai-native/src/browser/ai-editor.contribution.ts b/packages/ai-native/src/browser/ai-editor.contribution.ts index 455f5879ce..583246f7e5 100644 --- a/packages/ai-native/src/browser/ai-editor.contribution.ts +++ b/packages/ai-native/src/browser/ai-editor.contribution.ts @@ -4,6 +4,7 @@ import { Autowired, INJECTOR_TOKEN, Injectable, Injector } from '@opensumi/di'; import { AINativeConfigService, IAIInlineChatService, PreferenceService } from '@opensumi/ide-core-browser'; import { IBrowserCtxMenu } from '@opensumi/ide-core-browser/lib/menu/next/renderer/ctxmenu/browser'; import { + AIInlineChatContentWidgetId, AINativeSettingSectionsId, AISerivceType, CancelResponse, @@ -32,8 +33,6 @@ import * as monaco from '@opensumi/ide-monaco'; import { monaco as monacoApi } from '@opensumi/ide-monaco/lib/browser/monaco-api'; import { MonacoTelemetryService } from '@opensumi/ide-monaco/lib/browser/telemetry.service'; -import { AIInlineChatContentWidget } from '../common'; - import { AINativeService } from './ai-native.service'; import { AIInlineCompletionsProvider } from './inline-completions/completeProvider'; import { AICompletionsService } from './inline-completions/service/ai-completions.service'; @@ -239,7 +238,7 @@ export class AIEditorContribution extends Disposable implements IEditorFeatureCo monacoEditor.onMouseUp((event) => { const target = event.target; const detail = (target as any).detail; - if (detail && typeof detail === 'string' && detail === AIInlineChatContentWidget) { + if (detail && typeof detail === 'string' && detail === AIInlineChatContentWidgetId) { needShowInlineChat = false; } else { needShowInlineChat = true; diff --git a/packages/ai-native/src/browser/ai-terminal/component/terminal-inline-chat-controller.tsx b/packages/ai-native/src/browser/ai-terminal/component/terminal-inline-chat-controller.tsx index 98d5de4916..84d19d2b4d 100644 --- a/packages/ai-native/src/browser/ai-terminal/component/terminal-inline-chat-controller.tsx +++ b/packages/ai-native/src/browser/ai-terminal/component/terminal-inline-chat-controller.tsx @@ -21,12 +21,12 @@ export const TerminalInlineWidgetForDetection = ({ actions, onClickItem }: ITerm return (
{ - e.currentTarget.style.backgroundColor = 'rgba(255, 0, 0, 0.1)'; setDisplayAIButton(true); + e.currentTarget.style.backgroundColor = 'rgba(255, 0, 0, 0.1)'; }} onMouseOut={(e) => { - e.currentTarget.style.backgroundColor = ''; setDisplayAIButton(false); + e.currentTarget.style.backgroundColor = ''; }} style={{ width: '100%', diff --git a/packages/ai-native/src/browser/chat/chat.module.less b/packages/ai-native/src/browser/chat/chat.module.less index 774eee9ac7..87d1651ece 100644 --- a/packages/ai-native/src/browser/chat/chat.module.less +++ b/packages/ai-native/src/browser/chat/chat.module.less @@ -2,12 +2,15 @@ display: flex; flex-direction: column; height: 100%; - background-color: var(--panel-background); border-radius: 12px; overflow: hidden; font-size: 14px; user-select: text; + border-top-left-radius: 12px; + border-top-right-radius: 12px; + border-top: none; + .ai_chat_hexagon_box { width: 20px; height: 10px; @@ -124,6 +127,7 @@ .body_container { display: flex; height: calc(100% - 36px); + background-color: var(--panel-background); .left_bar { display: flex; @@ -156,6 +160,9 @@ &::-webkit-scrollbar { width: 4px; + &:hover { + width: 10px; + } } .message_list { diff --git a/packages/ai-native/src/browser/chat/chat.view.tsx b/packages/ai-native/src/browser/chat/chat.view.tsx index 2668465dc9..66fdfc2ca0 100644 --- a/packages/ai-native/src/browser/chat/chat.view.tsx +++ b/packages/ai-native/src/browser/chat/chat.view.tsx @@ -19,13 +19,7 @@ import { MonacoCommandRegistry } from '@opensumi/ide-editor/lib/browser/monaco-c import { IMainLayoutService } from '@opensumi/ide-main-layout'; import 'react-chat-elements/dist/main.css'; -import { - AI_CHAT_VIEW_ID, - ChatMessageRole, - IChatAgentService, - IChatInternalService, - IChatMessageStructure, -} from '../../common'; +import { AI_CHAT_VIEW_ID, IChatAgentService, IChatInternalService, IChatMessageStructure } from '../../common'; import { CodeBlockWrapperInput } from '../components/ChatEditor'; import { ChatInput } from '../components/ChatInput'; import { ChatMarkdown } from '../components/ChatMarkdown'; diff --git a/packages/ai-native/src/browser/components/StreamReplyRender.tsx b/packages/ai-native/src/browser/components/StreamReplyRender.tsx index 86f9761dc2..930385c144 100644 --- a/packages/ai-native/src/browser/components/StreamReplyRender.tsx +++ b/packages/ai-native/src/browser/components/StreamReplyRender.tsx @@ -1,22 +1,19 @@ import hljs from 'highlight.js'; import React, { useCallback, useEffect, useMemo } from 'react'; -import { useInjectable } from '@opensumi/ide-core-browser'; -import { DisposableCollection } from '@opensumi/ide-core-browser'; +import { DisposableCollection, useInjectable } from '@opensumi/ide-core-browser'; import { ChatRenderRegistryToken, IAIReporter, localize } from '@opensumi/ide-core-common'; import { IChatInternalService } from '../../common/index'; import { ChatInternalService } from '../chat/chat.internal.service'; import { ChatRenderRegistry } from '../chat/chat.render.registry'; import { MsgHistoryManager } from '../model/msg-history-manager'; -import { EMsgStreamStatus } from '../model/msg-stream-manager'; -import { IMsgStreamChoices, MsgStreamManager } from '../model/msg-stream-manager'; +import { EMsgStreamStatus, IMsgStreamChoices, MsgStreamManager } from '../model/msg-stream-manager'; import { ChatMarkdown } from './ChatMarkdown'; import { ChatThinking, ChatThinkingResult } from './ChatThinking'; import styles from './components.module.less'; - interface IStreamMsgWrapperProps { sessionId: string; prompt: string; diff --git a/packages/ai-native/src/browser/components/WelcomeMsg.tsx b/packages/ai-native/src/browser/components/WelcomeMsg.tsx index 82fb2683bd..6cb13d621e 100644 --- a/packages/ai-native/src/browser/components/WelcomeMsg.tsx +++ b/packages/ai-native/src/browser/components/WelcomeMsg.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { useInjectable } from '@opensumi/ide-core-browser'; import { Icon, Tooltip } from '@opensumi/ide-core-browser/lib/components'; +import { withPrevented } from '@opensumi/ide-core-browser/lib/dom/event'; import { ChatFeatureRegistryToken, ChatRenderRegistryToken, @@ -20,7 +21,6 @@ import { ChatThinking } from '../components/ChatThinking'; import { extractIcon } from '../components/utils'; import { EMsgStreamStatus } from '../model/msg-stream-manager'; - import styles from './components.module.less'; export const WelcomeMessage = () => { @@ -83,11 +83,10 @@ export const WelcomeMessage = () => { {welcomeSampleQuestions.concat(sampleQuestions).map((data: any, index) => { const node = ( { + onClick={withPrevented(() => { aiChatService.sendMessage(chatAgentService.parseMessage(data.message)); - }} + })} > {data.icon ? : ''} {data.title} diff --git a/packages/ai-native/src/browser/components/components.module.less b/packages/ai-native/src/browser/components/components.module.less index c03a726b31..1f7ec966a8 100644 --- a/packages/ai-native/src/browser/components/components.module.less +++ b/packages/ai-native/src/browser/components/components.module.less @@ -227,6 +227,9 @@ line-height: 16px; &::-webkit-scrollbar { width: 4px; + &:hover { + width: 10px; + } } } diff --git a/packages/ai-native/src/browser/index.ts b/packages/ai-native/src/browser/index.ts index 7e0ebbc66e..b726dc2953 100644 --- a/packages/ai-native/src/browser/index.ts +++ b/packages/ai-native/src/browser/index.ts @@ -26,7 +26,6 @@ import { ChatFeatureRegistry } from './chat/chat.feature.registry'; import { ChatInternalService } from './chat/chat.internal.service'; import { ChatRenderRegistry } from './chat/chat.render.registry'; import { LanguageParserService } from './languages/service'; -import { AIMenuBarContribution } from './layout/menu-bar/menu-bar.contribution'; import { MergeConflictContribution } from './merge-conflict'; import { ResolveConflictRegistry } from './merge-conflict/merge-conflict.feature.registry'; import { RenameCandidatesProviderRegistry } from './rename/rename.feature.registry'; @@ -39,7 +38,6 @@ export class AINativeModule extends BrowserModule { contributionProvider = AINativeCoreContribution; providers: Provider[] = [ AINativeBrowserContribution, - AIMenuBarContribution, TerminalAIContribution, MergeConflictContribution, { diff --git a/packages/ai-native/src/browser/layout/layout-config.ts b/packages/ai-native/src/browser/layout/layout-config.ts index 19bc7043e8..da6f0d48a1 100644 --- a/packages/ai-native/src/browser/layout/layout-config.ts +++ b/packages/ai-native/src/browser/layout/layout-config.ts @@ -1,18 +1,21 @@ -import { SlotLocation } from '@opensumi/ide-core-browser'; +import { DESIGN_MENU_BAR_LEFT, DESIGN_MENU_BAR_RIGHT } from '@opensumi/ide-design'; -import { AI_CHAT_CONTAINER_ID, AI_CHAT_VIEW_ID, AI_MENUBAR_CONTAINER_VIEW_ID } from '../../common'; +import { AI_CHAT_CONTAINER_ID, AI_CHAT_LOGO_AVATAR_ID, AI_CHAT_VIEW_ID } from '../../common'; export const AIChatLayoutConfig = { [AI_CHAT_VIEW_ID]: { modules: [AI_CHAT_CONTAINER_ID], }, -}; - -export const AIMenubarLayoutConfig = { - [SlotLocation.top]: { - modules: [AI_MENUBAR_CONTAINER_VIEW_ID], + [DESIGN_MENU_BAR_RIGHT]: { + modules: [AI_CHAT_LOGO_AVATAR_ID], }, }; -export const AI_MENU_BAR_RIGHT = 'AI_menu_bar_right'; -export const AI_MENU_BAR_LEFT = 'AI_menu_bar_left'; +/** + * @deprecated Use {@link DESIGN_MENU_BAR_RIGHT} instead + */ +export const AI_MENU_BAR_RIGHT = DESIGN_MENU_BAR_RIGHT; +/** + * @deprecated Use {@link DESIGN_MENU_BAR_LEFT} instead + */ +export const AI_MENU_BAR_LEFT = DESIGN_MENU_BAR_LEFT; diff --git a/packages/ai-native/src/browser/layout/layout.module.less b/packages/ai-native/src/browser/layout/layout.module.less index 10d3791e3a..d867d6a362 100644 --- a/packages/ai-native/src/browser/layout/layout.module.less +++ b/packages/ai-native/src/browser/layout/layout.module.less @@ -158,12 +158,23 @@ height: 32px; font-size: 16px; border-radius: 8px; - display: flex; + display: flex !important; align-items: center; justify-content: center; &:hover { - background-color: var(--activityBar-activeBorder); + background-color: var(--badge-background); } } } + +.extra_bottom_icon_container { + display: flex; + flex-direction: column; + align-items: center; + margin-bottom: 4px; +} + +.ai_chat_view_container { + background-color: unset; +} diff --git a/packages/ai-native/src/browser/layout/tabbar.view.tsx b/packages/ai-native/src/browser/layout/tabbar.view.tsx index 01ee75ca5d..0442acbdf4 100644 --- a/packages/ai-native/src/browser/layout/tabbar.view.tsx +++ b/packages/ai-native/src/browser/layout/tabbar.view.tsx @@ -1,13 +1,7 @@ import cls from 'classnames'; import React, { useCallback, useEffect, useMemo } from 'react'; -import { - AINativeConfigService, - ComponentRegistryInfo, - SlotLocation, - useContextMenus, - useInjectable, -} from '@opensumi/ide-core-browser'; +import { ComponentRegistryInfo, SlotLocation, useContextMenus, useInjectable } from '@opensumi/ide-core-browser'; import { EDirection } from '@opensumi/ide-core-browser/lib/components'; import { EnhanceIcon, @@ -15,6 +9,7 @@ import { EnhancePopover, HorizontalVertical, } from '@opensumi/ide-core-browser/lib/components/ai-native'; +import { DesignLayoutConfig } from '@opensumi/ide-core-browser/lib/layout/constants'; import { IMenu } from '@opensumi/ide-core-browser/lib/menu/next'; import { localize } from '@opensumi/ide-core-common'; import { DesignLeftTabRenderer, DesignRightTabRenderer } from '@opensumi/ide-design/lib/browser/layout/tabbar.view'; @@ -61,7 +56,14 @@ export const AIChatTabRenderer = ({ className={cls(className, `${AI_CHAT_VIEW_ID}-slot`)} components={components} TabbarView={() => } - TabpanelView={() => } + TabpanelView={() => ( + + )} /> ); @@ -100,14 +102,19 @@ const AILeftTabbarRenderer: React.FC = () => { renderOtherVisibleContainers={renderOtherVisibleContainers} isRenderExtraTopMenus={false} renderExtraMenus={ - navMenu.length === 0 ? null : ( - - ) +
+ {navMenu.length >= 0 + ? navMenu.map((menu) => ( + + )) + : null} +
} /> ); @@ -121,7 +128,7 @@ export const AIRightTabRenderer = ({ components: ComponentRegistryInfo[]; }) => { const tabbarService: TabbarService = useInjectable(TabbarServiceFactory)(SlotLocation.right); - const aiNativeConfigService: AINativeConfigService = useInjectable(AINativeConfigService); + const designLayoutConfig = useInjectable(DesignLayoutConfig); const handleClose = useCallback(() => { tabbarService.updateCurrentContainerId(''); @@ -151,8 +158,8 @@ export const AIRightTabRenderer = ({ }, []); const rightTabRenderClassName = useMemo( - () => (aiNativeConfigService.layout!.useMergeRightWithLeftPanel ? styles.right_tab_renderer : ''), - [aiNativeConfigService], + () => (designLayoutConfig.useMergeRightWithLeftPanel ? styles.right_tab_renderer : ''), + [designLayoutConfig], ); return ( diff --git a/packages/ai-native/src/browser/layout/view/avatar/avatar.module.less b/packages/ai-native/src/browser/layout/view/avatar/avatar.module.less new file mode 100644 index 0000000000..2b0f9b3f38 --- /dev/null +++ b/packages/ai-native/src/browser/layout/view/avatar/avatar.module.less @@ -0,0 +1,14 @@ +.ai_switch { + height: 16px; + width: 16px; + min-width: 16px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + .avatar_icon_large { + width: 16px; + height: 16px; + font-size: 16px !important; + } +} diff --git a/packages/ai-native/src/browser/layout/view/avatar/avatar.view.tsx b/packages/ai-native/src/browser/layout/view/avatar/avatar.view.tsx new file mode 100644 index 0000000000..dc67f15dba --- /dev/null +++ b/packages/ai-native/src/browser/layout/view/avatar/avatar.view.tsx @@ -0,0 +1,23 @@ +import React from 'react'; + +import { useInjectable } from '@opensumi/ide-core-browser'; +import { AILogoAvatar } from '@opensumi/ide-core-browser/lib/components/ai-native'; +import { IMainLayoutService } from '@opensumi/ide-main-layout'; + +import { AI_CHAT_VIEW_ID } from '../../../../common'; + +import styles from './avatar.module.less'; + +export const AIChatLogoAvatar = () => { + const layoutService = useInjectable(IMainLayoutService); + + const handleChatVisible = React.useCallback(() => { + layoutService.toggleSlot(AI_CHAT_VIEW_ID); + }, [layoutService]); + + return ( +
+ +
+ ); +}; diff --git a/packages/ai-native/src/browser/light-bulb-widget/index.ts b/packages/ai-native/src/browser/light-bulb-widget/index.ts new file mode 100644 index 0000000000..4b075a86fb --- /dev/null +++ b/packages/ai-native/src/browser/light-bulb-widget/index.ts @@ -0,0 +1,41 @@ +import { asClassNameArrayWrapper } from '@opensumi/ide-core-browser'; +import { Sumicon } from '@opensumi/ide-core-common/lib/codicons'; +import { Codicon } from '@opensumi/monaco-editor-core/esm/vs/base/common/codicons'; +import { ThemeIcon } from '@opensumi/monaco-editor-core/esm/vs/base/common/themables'; +import { + LightBulbState, + LightBulbWidget, +} from '@opensumi/monaco-editor-core/esm/vs/editor/contrib/codeAction/browser/lightBulbWidget'; + +export class OpenSumiLightBulbWidget extends LightBulbWidget { + protected override _updateLightBulbTitleAndIcon(): void { + this._domNode.classList.remove(...this._iconClasses); + this._iconClasses = []; + if (this.state.type !== LightBulbState.Type.Showing) { + return; + } + let icon: ThemeIcon; + let autoRun = false; + if (this.state.actions.allAIFixes) { + icon = Sumicon.magicWand; + if (this.state.actions.validActions.length === 1) { + autoRun = true; + } + } else if (this.state.actions.hasAutoFix) { + if (this.state.actions.hasAIFix) { + // icon = Codicon.lightbulbSparkleAutofix; + icon = Sumicon.magicWand; + } else { + icon = Codicon.lightbulbAutofix; + } + } else if (this.state.actions.hasAIFix) { + // icon = Codicon.lightbulbSparkle; + icon = Sumicon.magicWand; + } else { + icon = Codicon.lightBulb; + } + this._updateLightbulbTitle(this.state.actions.hasAutoFix, autoRun); + this._iconClasses = asClassNameArrayWrapper(icon); + this._domNode.classList.add(...this._iconClasses); + } +} diff --git a/packages/ai-native/src/browser/merge-conflict/index.ts b/packages/ai-native/src/browser/merge-conflict/index.ts index 875b4adf88..f05102ccfd 100644 --- a/packages/ai-native/src/browser/merge-conflict/index.ts +++ b/packages/ai-native/src/browser/merge-conflict/index.ts @@ -37,7 +37,7 @@ import { import { IEditor, WorkbenchEditorService } from '@opensumi/ide-editor/lib/browser'; import * as monaco from '@opensumi/ide-monaco'; import { ITextModel } from '@opensumi/ide-monaco'; -import { BaseInlineContentWidget } from '@opensumi/ide-monaco/lib/browser/ai-native/BaseInlineContentWidget'; +import { ReactInlineContentWidget } from '@opensumi/ide-monaco/lib/browser/ai-native/BaseInlineContentWidget'; import { LineRange } from '@opensumi/ide-monaco/lib/browser/contrib/merge-editor/model/line-range'; import { AI_RESOLVE_REGENERATE_ACTIONS, @@ -148,10 +148,10 @@ interface IRequestCancel { } class WidgetFactory implements IWidgetFactory { - private widgetMap: Map; + private widgetMap: Map; constructor( - private contentWidget: ConstructorOf, + private contentWidget: ConstructorOf, private editor: ResultCodeEditor, private injector: Injector, ) { diff --git a/packages/ai-native/src/browser/widget/inline-chat/inline-chat-controller.tsx b/packages/ai-native/src/browser/widget/inline-chat/inline-chat-controller.tsx index b62278b4bb..3cf64833df 100644 --- a/packages/ai-native/src/browser/widget/inline-chat/inline-chat-controller.tsx +++ b/packages/ai-native/src/browser/widget/inline-chat/inline-chat-controller.tsx @@ -13,21 +13,21 @@ import styles from './inline-chat.module.less'; import { AIInlineChatService, EInlineChatStatus } from './inline-chat.service'; export interface IAIInlineOperationProps { - hanldeActions: (id: string) => void; + handleActions: (id: string) => void; onClose?: () => void; } const AIInlineOperation = (props: IAIInlineOperationProps) => { - const { hanldeActions, onClose } = props; + const { handleActions, onClose } = props; const inlineChatFeatureRegistry: InlineChatFeatureRegistry = useInjectable(InlineChatFeatureRegistryToken); const operationList = useMemo(() => inlineChatFeatureRegistry.getEditorActionButtons(), [inlineChatFeatureRegistry]); const handleClickActions = useCallback( (id: string) => { - hanldeActions(id); + handleActions(id); }, - [hanldeActions], + [handleActions], ); const handleClose = useCallback(() => { @@ -166,7 +166,7 @@ export const AIInlineChatController = (props: IAIInlineChatControllerProps) => { ); } - return ; + return ; }, [status]); return
{renderContent()}
; diff --git a/packages/ai-native/src/browser/widget/inline-chat/inline-chat.module.less b/packages/ai-native/src/browser/widget/inline-chat/inline-chat.module.less index fb433df6da..3cb8b49343 100644 --- a/packages/ai-native/src/browser/widget/inline-chat/inline-chat.module.less +++ b/packages/ai-native/src/browser/widget/inline-chat/inline-chat.module.less @@ -6,58 +6,6 @@ line-height: @default_height !important; } -.ai_inline_operation_panel { - display: flex; - align-items: center; - height: @default_height; - padding: 1px 8px; - - .logo_container { - .avatar_icon { - width: 14px; - height: 14px; - background-image: radial-gradient(circle at 21% 21%, #00f6ff 0%, #9c03ff 95%); - border-radius: 12px; - - img { - height: 10px; - } - } - } - - .operate_container, - .close_container { - display: flex; - align-items: center; - height: 100%; - } - - .operate_container { - .operate_btn { - padding: 3px 4px; - border-radius: 4px; - } - .operate_item { - height: 22px; - line-height: 22px; - padding: 0 6px; - border-radius: 4px; - } - - .close_container { - display: none; - } - } - - &:hover { - .operate_container { - .close_container { - display: flex; - } - } - } -} - .more_operation_menu_item { min-width: initial !important; } diff --git a/packages/ai-native/src/browser/widget/inline-chat/inline-content-widget.tsx b/packages/ai-native/src/browser/widget/inline-chat/inline-content-widget.tsx index 6c3835574d..e8ec8ff5dd 100644 --- a/packages/ai-native/src/browser/widget/inline-chat/inline-content-widget.tsx +++ b/packages/ai-native/src/browser/widget/inline-chat/inline-content-widget.tsx @@ -1,16 +1,15 @@ import React from 'react'; import { Autowired, INJECTOR_TOKEN, Injectable, Injector } from '@opensumi/di'; -import { IAIInlineChatService } from '@opensumi/ide-core-browser'; -import { Emitter } from '@opensumi/ide-core-common'; +import { IAIInlineChatService, StackingLevelStr } from '@opensumi/ide-core-browser'; +import { AIInlineChatContentWidgetId, Emitter } from '@opensumi/ide-core-common'; import * as monaco from '@opensumi/ide-monaco'; import { monacoBrowser } from '@opensumi/ide-monaco/lib/browser'; import { - BaseInlineContentWidget, + ReactInlineContentWidget, ShowAIContentOptions, } from '@opensumi/ide-monaco/lib/browser/ai-native/BaseInlineContentWidget'; -import { AIInlineChatContentWidget } from '../../../common/index'; import { AINativeContextKey } from '../../contextkey/ai-native.contextkey.service'; import { AIInlineChatController } from './inline-chat-controller'; @@ -19,7 +18,7 @@ import { AIInlineChatService, EInlineChatStatus } from './inline-chat.service'; import type { ICodeEditor as IMonacoCodeEditor } from '@opensumi/ide-monaco/lib/browser/monaco-api/types'; @Injectable({ multiple: true }) -export class AIInlineContentWidget extends BaseInlineContentWidget { +export class AIInlineContentWidget extends ReactInlineContentWidget { @Autowired(INJECTOR_TOKEN) private readonly injector: Injector; @@ -75,8 +74,8 @@ export class AIInlineContentWidget extends BaseInlineContentWidget { override getDomNode(): HTMLElement { const domNode = super.getDomNode(); domNode.style.padding = '6px'; - domNode.style.zIndex = '999'; - domNode.style.paddingRight = '50px'; + domNode.style.zIndex = StackingLevelStr.OverlayTop; + return domNode; } @@ -112,7 +111,7 @@ export class AIInlineContentWidget extends BaseInlineContentWidget { } id(): string { - return AIInlineChatContentWidget; + return AIInlineChatContentWidgetId; } override getPosition(): monaco.editor.IContentWidgetPosition | null { @@ -127,12 +126,31 @@ export class AIInlineContentWidget extends BaseInlineContentWidget { } const { selection } = this.options; - return selection ? this.computerPosition(selection) : null; + if (!selection) { + return null; + } + const target = this.computePosition(selection); + if (target) { + return target; + } + + return null; } + /** + * 获取指定行的最后一个非空白字符的列数 + */ private safeGetLineLastNonWhitespaceColumn(line: number) { - const model = this.editor.getModel(); - return model!.getLineLastNonWhitespaceColumn(Math.min(Math.max(1, line), model!.getLineCount())); + const model = this.editor.getModel()!; + if (line < 1) { + line = 1; + } + const lineCount = model.getLineCount(); + if (line > lineCount) { + line = lineCount; + } + + return model.getLineLastNonWhitespaceColumn(line); } private toAbovePosition(lineNumber: number, column: number): monaco.editor.IContentWidgetPosition { @@ -212,7 +230,7 @@ export class AIInlineContentWidget extends BaseInlineContentWidget { * 2. 靠近光标处周围没有字符的空白区域作为要显示的区域 * 3. 显示的区域方向在右侧,左侧不考虑 */ - private computerPosition(selection: monaco.Selection): monaco.editor.IContentWidgetPosition | null { + private computePosition(selection: monaco.Selection): monaco.editor.IContentWidgetPosition | null { const startPosition = selection.getStartPosition(); const endPosition = selection.getEndPosition(); const model = this.editor.getModel(); @@ -227,16 +245,20 @@ export class AIInlineContentWidget extends BaseInlineContentWidget { let targetLine: number | null = null; let direction: 'above' | 'below' | null = null; + // 用户只选中了一行 if (startPosition.lineNumber === endPosition.lineNumber) { return this.recheckPosition(cursorPosition.lineNumber, cursorPosition.column); } + // 用户选中了多行,光标在选中的开始位置 if (cursorPosition.equals(startPosition)) { const getMaxLastWhitespaceColumn = Math.max( this.safeGetLineLastNonWhitespaceColumn(cursorPosition.lineNumber - 1), this.safeGetLineLastNonWhitespaceColumn(cursorPosition.lineNumber - 2), ); + // 如果上面两行的最后一个非空白字符的列数小于当前行的最后一个非空白字符的列数 + 10 + // 则直接显示在上面 if (getMaxLastWhitespaceColumn < getCursorLastNonWhitespaceColumn + 10) { return this.toAbovePosition(cursorPosition.lineNumber, getMaxLastWhitespaceColumn + 1); } diff --git a/packages/ai-native/src/common/index.ts b/packages/ai-native/src/common/index.ts index ff701d2986..d181882dc8 100644 --- a/packages/ai-native/src/common/index.ts +++ b/packages/ai-native/src/common/index.ts @@ -1,4 +1,5 @@ import { + AIInlineChatContentWidgetId, AISerivceType, CancellationToken, Event, @@ -7,14 +8,24 @@ import { IMarkdownString, Uri, } from '@opensumi/ide-core-common'; +import { DESIGN_MENUBAR_CONTAINER_VIEW_ID } from '@opensumi/ide-design/lib/common/constants'; export const IAINativeService = Symbol('IAINativeService'); -export const AIInlineChatContentWidget = 'AI_Inline_Chat_Content_Widget'; -export const AI_CHAT_VIEW_ID = 'AI_Chat'; -export const AI_CHAT_CONTAINER_ID = 'AI_Chat_Container'; +/** + * @deprecated Use {@link AIInlineChatContentWidgetId} instead + */ +export const AIInlineChatContentWidget = AIInlineChatContentWidgetId; + +export const AI_CHAT_VIEW_ID = 'AI-Chat'; +export const AI_CHAT_CONTAINER_ID = 'AI-Chat-Container'; +export const AI_CHAT_LOGO_AVATAR_ID = 'AI-Chat-Logo-Avatar'; export const AI_MENU_BAR_DEBUG_TOOLBAR = 'AI_MENU_BAR_DEBUG_TOOLBAR'; -export const AI_MENUBAR_CONTAINER_VIEW_ID = 'AI_menubar'; + +/** + * @deprecated Use {@link DESIGN_MENUBAR_CONTAINER_VIEW_ID} instead + */ +export const AI_MENUBAR_CONTAINER_VIEW_ID = DESIGN_MENUBAR_CONTAINER_VIEW_ID; export const AI_SLASH = '/'; diff --git a/packages/collaboration/src/browser/collaboration.service.ts b/packages/collaboration/src/browser/collaboration.service.ts index d1a279faa7..a8caa375d7 100644 --- a/packages/collaboration/src/browser/collaboration.service.ts +++ b/packages/collaboration/src/browser/collaboration.service.ts @@ -6,7 +6,7 @@ import { Doc as YDoc, Map as YMap, YMapEvent, Text as YText } from 'yjs'; import { Autowired, INJECTOR_TOKEN, Inject, Injectable, Injector } from '@opensumi/di'; import { AppConfig, DisposableCollection } from '@opensumi/ide-core-browser'; -import { Deferred, ILogger, OnEvent, WithEventBus, uuid } from '@opensumi/ide-core-common'; +import { Deferred, DisposableStore, ILogger, OnEvent, WithEventBus, uuid } from '@opensumi/ide-core-common'; import { WorkbenchEditorService } from '@opensumi/ide-editor'; import { EditorDocumentModelCreationEvent, @@ -80,6 +80,7 @@ export class CollaborationService extends WithEventBus implements ICollaboration private bindingReadyMap: Map> = new Map(); protected readonly toDisposableCollection: DisposableCollection = new DisposableCollection(); + protected cssStyleSheetsDisposables = new DisposableStore(); private yMapObserver = (event: YMapEvent) => { const changes = event.changes.keys; @@ -149,11 +150,7 @@ export class CollaborationService extends WithEventBus implements ICollaboration destroy() { this.yWebSocketProvider.awareness.off('update', this.updateCSSManagerWhenAwarenessUpdated); - this.clientIDStyleAddedSet.forEach((clientID) => { - this.cssManager.removeClass(`${Y_REMOTE_SELECTION}-${clientID}`); - this.cssManager.removeClass(`${Y_REMOTE_SELECTION_HEAD}-${clientID}`); - this.cssManager.removeClass(`${Y_REMOTE_SELECTION_HEAD}-${clientID}::after`); - }); + this.cssStyleSheetsDisposables.clear(); this.yTextMap.unobserve(this.yMapObserver); this.yWebSocketProvider.disconnect(); this.bindingMap.forEach((binding) => binding.destroy()); @@ -258,26 +255,32 @@ export class CollaborationService extends WithEventBus implements ICollaboration changes.added.forEach((clientID) => { if (!this.clientIDStyleAddedSet.has(clientID)) { const [foregroundColor, backgroundColor] = getColorByClientID(clientID); - this.cssManager.addClass(`${Y_REMOTE_SELECTION}-${clientID}`, { - backgroundColor, - opacity: '0.25', - color: foregroundColor, - }); - this.cssManager.addClass(`${Y_REMOTE_SELECTION_HEAD}-${clientID}`, { - position: 'absolute', - borderLeft: `${backgroundColor} solid 2px`, - borderBottom: `${backgroundColor} solid 2px`, - borderTop: `${backgroundColor} solid 2px`, - height: '100%', - boxSizing: 'border-box', - }); - this.cssManager.addClass(`${Y_REMOTE_SELECTION_HEAD}-${clientID}::after`, { - position: 'absolute', - content: ' ', - border: `3px solid ${backgroundColor}`, - left: '-4px', - top: '-5px', - }); + this.cssStyleSheetsDisposables.add( + this.cssManager.addClass(`${Y_REMOTE_SELECTION}-${clientID}`, { + backgroundColor, + opacity: '0.25', + color: foregroundColor, + }), + ); + this.cssStyleSheetsDisposables.add( + this.cssManager.addClass(`${Y_REMOTE_SELECTION_HEAD}-${clientID}`, { + position: 'absolute', + borderLeft: `${backgroundColor} solid 2px`, + borderBottom: `${backgroundColor} solid 2px`, + borderTop: `${backgroundColor} solid 2px`, + height: '100%', + boxSizing: 'border-box', + }), + ); + this.cssStyleSheetsDisposables.add( + this.cssManager.addClass(`${Y_REMOTE_SELECTION_HEAD}-${clientID}::after`, { + position: 'absolute', + content: ' ', + border: `3px solid ${backgroundColor}`, + left: '-4px', + top: '-5px', + }), + ); this.clientIDStyleAddedSet.add(clientID); } }); diff --git a/packages/components/src/popover/index.tsx b/packages/components/src/popover/index.tsx index 2996f1c4e4..005ba5319d 100644 --- a/packages/components/src/popover/index.tsx +++ b/packages/components/src/popover/index.tsx @@ -87,8 +87,8 @@ export const Popover: React.FC = ({ if (!contentEl.current || !childEl.current || disable) { return; } - const { left, top, width, height } = childEl.current.getBoundingClientRect() as ClientRect; - const contentRect = contentEl.current.getBoundingClientRect() as ClientRect; + const { left, top, width, height } = childEl.current.getBoundingClientRect() as DOMRect; + const contentRect = contentEl.current.getBoundingClientRect() as DOMRect; if (position === PopoverPosition.top) { const contentLeft = contentRect.right - window.innerWidth > 0 diff --git a/packages/core-browser/__tests__/window/window.service.test.ts b/packages/core-browser/__tests__/window/window.service.test.ts index 97ac81e31c..6f01d81714 100644 --- a/packages/core-browser/__tests__/window/window.service.test.ts +++ b/packages/core-browser/__tests__/window/window.service.test.ts @@ -1,8 +1,8 @@ import { WindowService } from '@opensumi/ide-core-browser/lib/window/window.service'; import { IElectronMainLifeCycleService, IElectronMainUIService } from '@opensumi/ide-core-common/lib/electron'; +import { createBrowserInjector } from '@opensumi/ide-dev-tool/src/injector-helper'; +import { MockInjector } from '@opensumi/ide-dev-tool/src/mock-injector'; -import { createBrowserInjector } from '../../../../tools/dev-tool/src/injector-helper'; -import { MockInjector } from '../../../../tools/dev-tool/src/mock-injector'; import { IWindowService, URI } from '../../src'; import { IExternalUriService } from '../../src/services'; diff --git a/packages/core-browser/src/ai-native/ai-config.service.ts b/packages/core-browser/src/ai-native/ai-config.service.ts index bcb835de1f..42ad9ff1b3 100644 --- a/packages/core-browser/src/ai-native/ai-config.service.ts +++ b/packages/core-browser/src/ai-native/ai-config.service.ts @@ -1,15 +1,18 @@ import { Autowired, Injectable } from '@opensumi/di'; -import { IAINativeCapabilities, IAINativeLayout } from '@opensumi/ide-core-common'; +import { IAINativeCapabilities } from '@opensumi/ide-core-common'; +import { LayoutViewSizeConfig } from '../layout/constants'; import { AppConfig } from '../react-providers/config-provider'; -@Injectable({ multiple: false }) +@Injectable() export class AINativeConfigService { @Autowired(AppConfig) public readonly appConfig: AppConfig; + @Autowired(LayoutViewSizeConfig) + public layoutViewSize: LayoutViewSizeConfig; + private internalCapabilities: Required = { - supportsOpenSumiDesign: false, supportsMarkers: false, supportsChatAssistant: false, supportsInlineChat: false, @@ -20,28 +23,14 @@ export class AINativeConfigService { supportsTerminalCommandSuggest: false, }; - private internalLayout: Required = { - useMergeRightWithLeftPanel: false, - useMenubarView: false, - menubarLogo: '', - }; - private setDefaultCapabilities(value: boolean): void { for (const key in this.internalCapabilities) { - if (this.internalCapabilities.hasOwnProperty(key)) { + if (Object.prototype.hasOwnProperty.call(this.internalCapabilities, key)) { this.internalCapabilities[key] = value; } } } - private setDefaultLayout(value: boolean): void { - for (const key in this.internalLayout) { - if (this.internalLayout.hasOwnProperty(key)) { - this.internalLayout[key] = value; - } - } - } - public enableCapabilities(): void { this.setDefaultCapabilities(true); } @@ -50,14 +39,6 @@ export class AINativeConfigService { this.setDefaultCapabilities(false); } - public enableLayout(): void { - this.setDefaultLayout(true); - } - - public disableLayout(): void { - this.setDefaultLayout(false); - } - public get capabilities(): Required { const { AINativeConfig } = this.appConfig; @@ -67,14 +48,4 @@ export class AINativeConfigService { return this.internalCapabilities; } - - public get layout(): Required { - const { AINativeConfig } = this.appConfig; - - if (AINativeConfig?.layout) { - return { ...this.internalLayout, ...AINativeConfig.layout }; - } - - return this.internalLayout; - } } diff --git a/packages/core-browser/src/bootstrap/app.interface.ts b/packages/core-browser/src/bootstrap/app.interface.ts index 59d7efce9f..7a485d7238 100644 --- a/packages/core-browser/src/bootstrap/app.interface.ts +++ b/packages/core-browser/src/bootstrap/app.interface.ts @@ -6,6 +6,7 @@ import { ClientAppContribution } from '../common/common.define'; import { AppConfig } from '../react-providers'; export type ModuleConstructor = ConstructorOf; + export type ContributionConstructor = ConstructorOf; export type Direction = 'left-to-right' | 'right-to-left' | 'top-to-bottom' | 'bottom-to-top'; diff --git a/packages/core-browser/src/bootstrap/app.ts b/packages/core-browser/src/bootstrap/app.ts index 61eda48598..ba918cca65 100644 --- a/packages/core-browser/src/bootstrap/app.ts +++ b/packages/core-browser/src/bootstrap/app.ts @@ -54,7 +54,7 @@ import { BrowserModule, IClientApp } from '../browser-module'; import { ClientAppContribution } from '../common'; import { CorePreferences, injectCorePreferences } from '../core-preferences'; import { KeybindingRegistry, KeybindingService, NO_KEYBINDING_NAME } from '../keybinding'; -import { LayoutViewSizeConfig } from '../layout/constants'; +import { DesignLayoutConfig, LayoutViewSizeConfig } from '../layout/constants'; import { RenderedEvent } from '../layout/layout.interface'; import { IMenuRegistry, MenuRegistryImpl } from '../menu/next/base'; import { @@ -143,9 +143,15 @@ export class ClientApp implements IClientApp, IDisposable { allowSetDocumentTitleFollowWorkspaceDir, devtools: opts.devtools ?? false, rpcMessageTimeout: opts.rpcMessageTimeout || -1, - layoutViewSize: new LayoutViewSizeConfig(opts.layoutViewSize), }; + const layoutViewSizeConfig = this.injector.get(LayoutViewSizeConfig); + layoutViewSizeConfig.init(opts.layoutViewSize); + this.config.layoutViewSize = layoutViewSizeConfig; + + const designLayoutConfig = this.injector.get(DesignLayoutConfig); + designLayoutConfig.setLayout(opts.designLayout, opts.AINativeConfig?.layout); + this.injector.addProviders({ token: IClientApp, useValue: this }); this.injector.addProviders({ token: AppConfig, useValue: this.config }); diff --git a/packages/core-browser/src/common/common.contribution.ts b/packages/core-browser/src/common/common.contribution.ts index a03ec3f1e1..baec747a57 100644 --- a/packages/core-browser/src/common/common.contribution.ts +++ b/packages/core-browser/src/common/common.contribution.ts @@ -14,6 +14,7 @@ import { IContextKey, IContextKeyService } from '../context-key'; import { corePreferenceSchema } from '../core-preferences'; import { trackFocus } from '../dom'; import { KeybindingContribution, KeybindingRegistry } from '../keybinding'; +import { LayoutViewSizeConfig } from '../layout/constants'; import { IMenuRegistry, MenuContribution } from '../menu/next/base'; import { MenuId } from '../menu/next/menu-id'; import { PreferenceContribution } from '../preferences'; @@ -45,13 +46,16 @@ export class ClientCommonContribution @Autowired(AppConfig) private appConfig: AppConfig; + @Autowired(LayoutViewSizeConfig) + private layoutViewSize: LayoutViewSizeConfig; + schema: PreferenceSchema = corePreferenceSchema; constructor() { const overridePropertiesDefault = { 'application.supportsOpenFolder': !!this.appConfig.isElectronRenderer && !this.appConfig.isRemote, 'application.supportsOpenWorkspace': !!this.appConfig.isElectronRenderer && !this.appConfig.isRemote, - 'debug.toolbar.top': this.appConfig.isElectronRenderer ? 0 : this.appConfig.layoutViewSize!.menubarHeight, + 'debug.toolbar.top': this.appConfig.isElectronRenderer ? 0 : this.layoutViewSize.menubarHeight, }; const keys = Object.keys(this.schema.properties); for (const key of keys) { diff --git a/packages/core-browser/src/components/actions/index.tsx b/packages/core-browser/src/components/actions/index.tsx index 72f2a2f89e..aff222ecdf 100644 --- a/packages/core-browser/src/components/actions/index.tsx +++ b/packages/core-browser/src/components/actions/index.tsx @@ -21,7 +21,8 @@ import { } from '../../menu/next'; import { IMenuRenderProps } from '../../menu/next/renderer/ctxmenu/browser'; import { useInjectable } from '../../react-hooks'; -import { transformLabelWithCodicon, useContextMenus, useDesignStyles, useMenus } from '../../utils'; +import { transformLabelWithCodicon } from '../../utils'; +import { useContextMenus, useDesignStyles, useMenus } from '../../utils/react-hooks'; import placements from './placements'; import styles from './styles.module.less'; diff --git a/packages/core-browser/src/components/ai-native/ai-action/index.module.less b/packages/core-browser/src/components/ai-native/ai-action/index.module.less index 932806327f..9b59d58334 100644 --- a/packages/core-browser/src/components/ai-native/ai-action/index.module.less +++ b/packages/core-browser/src/components/ai-native/ai-action/index.module.less @@ -9,13 +9,16 @@ 0px 6px 16px 0px var(--design-boxShadow-tertiary); font-size: 12px; - span { - color: var(--design-text-foreground); + .ai_action_icon { &:hover { - color: var(--design-title-foreground); + background: var(--design-block-hoverBackground); } } + span { + color: var(--design-text-foreground); + } + .logo-container { .avatar { width: 14px; diff --git a/packages/core-browser/src/components/ai-native/ai-action/index.tsx b/packages/core-browser/src/components/ai-native/ai-action/index.tsx index 38e15523d1..a9f2846cf3 100644 --- a/packages/core-browser/src/components/ai-native/ai-action/index.tsx +++ b/packages/core-browser/src/components/ai-native/ai-action/index.tsx @@ -1,6 +1,10 @@ import React, { useCallback } from 'react'; +import { AIInlineChatContentWidgetId } from '@opensumi/ide-core-common'; + import { MenuNode } from '../../../menu/next/base'; +import { createLayoutEventType } from '../../../monaco'; +import { useChange, useHover } from '../../../react-hooks'; import { AILogoAvatar, EnhanceIcon, EnhanceIconWithCtxMenu } from '../enhanceIcon'; import { LineVertical } from '../line-vertical'; import { EnhancePopover } from '../popover'; @@ -51,20 +55,34 @@ export interface AIActionProps { onClose?: () => void; } +const layoutEventType = createLayoutEventType(AIInlineChatContentWidgetId); +const layoutEvent = new CustomEvent(layoutEventType, { + bubbles: true, +}); + export const AIAction = (props: AIActionProps) => { const { operationList, moreOperation, showClose, onClickItem, onClose } = props; + const containerRef = React.useRef(null); + const [ref, isHovered] = useHover(); + const handleClose = useCallback(() => { if (onClose) { onClose(); } }, [onClose]); + useChange(isHovered, () => { + if (containerRef.current) { + containerRef.current.dispatchEvent(layoutEvent); + } + }); + return ( -
- - -
+
+ + +
{operationList.map(({ name, title, id }, i) => title ? ( @@ -90,8 +108,13 @@ export const AIAction = (props: AIActionProps) => { /> ) : null} {showClose !== false && ( -
- +
+
)} diff --git a/packages/core-browser/src/components/ai-native/enhanceIcon/index.tsx b/packages/core-browser/src/components/ai-native/enhanceIcon/index.tsx index 9b78b95881..beb2efaca8 100644 --- a/packages/core-browser/src/components/ai-native/enhanceIcon/index.tsx +++ b/packages/core-browser/src/components/ai-native/enhanceIcon/index.tsx @@ -1,16 +1,21 @@ import cls from 'classnames'; import React, { useCallback } from 'react'; -import { Icon, IconProps } from '../../../components'; +import { Icon } from '../../../components'; import { MenuNode } from '../../../menu/next/base'; import { IBrowserCtxMenu } from '../../../menu/next/renderer/ctxmenu/browser'; import { useInjectable } from '../../../react-hooks'; import styles from './styles.module.less'; -interface IEnhanceIconProps extends IconProps { +interface IEnhanceIconProps { wrapperStyle?: React.CSSProperties; wrapperClassName?: string; + onClick?: React.MouseEventHandler; + className?: string; + icon?: string; + iconClass?: string; + children?: React.ReactNode; } export const EnhanceIcon = React.forwardRef( @@ -22,11 +27,12 @@ export const EnhanceIcon = React.forwardRef null} style={(props.icon || props.iconClass) && props.children ? { marginRight: 5 } : {}} + icon={props.icon} + iconClass={props.iconClass} /> {props.children && {props.children}}
@@ -42,7 +48,7 @@ interface IEnhanceIconWithCtxMenuProps extends IEnhanceIconProps { * 包含下拉菜单的 icon 组件,可以自定义下拉菜单位置 */ export const EnhanceIconWithCtxMenu = (props: IEnhanceIconWithCtxMenuProps) => { - const { children, menuNodes, skew } = props; + const { children, menuNodes, skew, ...restProps } = props; const ctxMenuRenderer = useInjectable(IBrowserCtxMenu); const [anchor, setAnchor] = React.useState<{ x: number; y: number } | undefined>(undefined); @@ -93,14 +99,14 @@ export const EnhanceIconWithCtxMenu = (props: IEnhanceIconWithCtxMenuProps) => { }, [iconRef.current, menuNodes, anchor]); return ( - + {children} ); }; -export const AILogoAvatar = (props: { iconClassName?: string }) => ( -
+export const AILogoAvatar = (props: { className?: string; iconClassName?: string }) => ( +
); diff --git a/packages/core-browser/src/components/ai-native/enhanceIcon/styles.module.less b/packages/core-browser/src/components/ai-native/enhanceIcon/styles.module.less index e0a305b869..726230dd56 100644 --- a/packages/core-browser/src/components/ai-native/enhanceIcon/styles.module.less +++ b/packages/core-browser/src/components/ai-native/enhanceIcon/styles.module.less @@ -12,9 +12,9 @@ color: var(--design-text-foreground); } &:hover { - background-color: var(--activityBar-activeBorder); + background-color: var(--badge-background); span { - color: var(--activityBar-foreground) !important; + color: var(--badge-foreground); } } diff --git a/packages/core-browser/src/components/layout/box-panel.tsx b/packages/core-browser/src/components/layout/box-panel.tsx index 7ab9ccbd91..ba57fc4b3c 100644 --- a/packages/core-browser/src/components/layout/box-panel.tsx +++ b/packages/core-browser/src/components/layout/box-panel.tsx @@ -1,9 +1,9 @@ import cls from 'classnames'; -import React from 'react'; +import React, { useEffect } from 'react'; import { useInjectable } from '../../react-hooks'; import { AppConfig } from '../../react-providers'; -import { useDesignStyles } from '../../utils'; +import { useDesignStyles } from '../../utils/react-hooks'; import { Layout } from './layout'; import styles from './styles.module.less'; @@ -49,7 +49,7 @@ type ChildComponent = React.ReactElement; /** * 包裹放入其中的元素,并为每个元素创建一个 div * - * 可以通过修改传入的 children 的 props 来定义一些属性,props 的定义可见:{ @link ChildComponent } + * 可以通过修改传入的 children 的 props 来定义一些属性,props 的定义可见:{@link ChildComponent} */ export const BoxPanel: React.FC<{ children?: ChildComponent | ChildComponent[]; @@ -63,37 +63,41 @@ export const BoxPanel: React.FC<{ const appConfig = useInjectable(AppConfig); const styles_box_panel = useDesignStyles(styles['box-panel'], 'box-panel'); + useEffect(() => { + if (appConfig.didRendered) { + appConfig.didRendered(); + } + }, []); + + const directionStyle = Layout.getStyleProperties(direction); return (
{ - if (appConfig.didRendered) { - appConfig.didRendered(); - } - }} {...restProps} className={cls(styles_box_panel, className)} - style={{ flexDirection: Layout.getFlexDirection(direction), zIndex: restProps['z-index'] }} + style={{ + flexDirection: directionStyle.direction, + zIndex: restProps.zIndex || restProps['z-index'], + }} > - {arrayChildren.map((child, index) => ( -
- {child} -
- ))} + {arrayChildren.map((child, index) => { + const props = child['props'] || {}; + return ( +
+ {child} +
+ ); + })}
); }; diff --git a/packages/core-browser/src/components/layout/default-layout.tsx b/packages/core-browser/src/components/layout/default-layout.tsx index a9088bc98f..198b58122c 100644 --- a/packages/core-browser/src/components/layout/default-layout.tsx +++ b/packages/core-browser/src/components/layout/default-layout.tsx @@ -21,11 +21,16 @@ export const getStorageValue = () => { export const DefaultLayout = ToolbarActionBasedLayout; -export function ToolbarActionBasedLayout() { +export function ToolbarActionBasedLayout( + props: { + topSlotDefaultSize?: number; + topSlotZIndex?: number; + } = {}, +) { const { layout } = getStorageValue(); return ( - + = (props) => { ); const ResizeHandle = Layout.getResizeHandle(direction); + const flexStyleProperties = Layout.getStyleProperties(direction); + const childList = React.useMemo(() => React.Children.toArray(children), [children]); + const hasFlexGrow = React.useMemo(() => childList.find((item) => getProp(item, 'flexGrow')), [childList]); + const totalFlexNum = React.useMemo( () => childList.reduce((accumulator, item) => accumulator + getProp(item, 'flex', 1), 0), [childList], @@ -274,6 +278,7 @@ export const SplitPanel: React.FC = (props) => { ); } } + result.push( = (props) => { id={getProp(element, 'id') /* @deprecated: query by data-view-id */} style={{ // 手风琴场景,固定尺寸和 flex 尺寸混合布局;需要在 Resize Flex 模式下禁用 - ...(getProp(element, 'flex') && - !getProp(element, 'savedSize') && - !childList.find((item) => getProp(item, 'flexGrow')) + ...(getProp(element, 'flex') && !getProp(element, 'savedSize') && !hasFlexGrow ? { flex: getProp(element, 'flex') } - : { [Layout.getSizeProperty(direction)]: getElementSize(element, totalFlexNum) }), + : { [flexStyleProperties.size]: getElementSize(element, totalFlexNum) }), // 相对尺寸带来的问题,必须限制最小最大尺寸 - [Layout.getMinSizeProperty(direction)]: propMinSize ? propMinSize + 'px' : '-1px', - [Layout.getMaxSizeProperty(direction)]: maxLocks[index] && propMaxSize ? propMaxSize + 'px' : 'unset', + [flexStyleProperties.minSize]: propMinSize ? propMinSize + 'px' : '-1px', + [flexStyleProperties.maxSize]: maxLocks[index] && propMaxSize ? propMaxSize + 'px' : 'unset', // Resize Flex 模式下应用 flexGrow ...(propFlexGrow !== undefined ? { flexGrow: propFlexGrow } : {}), display: hides[index] ? 'none' : 'block', @@ -345,9 +348,10 @@ export const SplitPanel: React.FC = (props) => { splitPanelService,
(rootRef.current = ele!)} + // 这里的 props 会被设置到 DOM 上,真的要传吗? {...props} className={cls(styles['split-panel'], className)} - style={{ flexDirection: Layout.getFlexDirection(direction), ...style }} + style={{ flexDirection: flexStyleProperties.direction, ...style }} />, elements, props, diff --git a/packages/core-browser/src/design/index.ts b/packages/core-browser/src/design/index.ts index 7d5e1f321e..ec7c82b7cc 100644 --- a/packages/core-browser/src/design/index.ts +++ b/packages/core-browser/src/design/index.ts @@ -1,2 +1,3 @@ export * from './design.style.service'; export * from './types'; +export * from './rule'; diff --git a/packages/core-browser/src/design/rule.ts b/packages/core-browser/src/design/rule.ts new file mode 100644 index 0000000000..03142e2310 --- /dev/null +++ b/packages/core-browser/src/design/rule.ts @@ -0,0 +1,28 @@ +/** + * 定义了 zIndex 的层级 + */ +export const StackingLevel = { + /** + * 基础层级 + */ + Base: 0, + Background: 0, + + Workbench: 1, + WorkbenchEditor: 1, + + Toolbar: 2, + ToolbarDropdown: 10, + + /** + * 一级弹窗 + */ + Popup: 100, + Popover: 100, + Overlay: 800, + OverlayTop: 1000, +} as const; + +export const StackingLevelStr = Object.fromEntries( + Object.entries(StackingLevel).map(([key, value]) => [key, value.toString()]), +) as Record; diff --git a/packages/core-browser/src/dom/event.ts b/packages/core-browser/src/dom/event.ts new file mode 100644 index 0000000000..97cba90cc5 --- /dev/null +++ b/packages/core-browser/src/dom/event.ts @@ -0,0 +1,15 @@ +interface EventShape { + preventDefault(): void; + stopPropagation(): void; +} + +export function withPrevented(action?: (e: T) => void): (e: T) => void { + return (e) => { + e.preventDefault(); + e.stopPropagation(); + + if (action) { + action(e); + } + }; +} diff --git a/packages/core-browser/src/dom/index.ts b/packages/core-browser/src/dom/index.ts index 802b07baba..1c3e64422e 100644 --- a/packages/core-browser/src/dom/index.ts +++ b/packages/core-browser/src/dom/index.ts @@ -1,5 +1,7 @@ import { Event as BaseEvent, Disposable, Emitter, IDisposable, isWebKit } from '@opensumi/ide-core-common'; +export * from './event'; + export const EventType = { // Mouse CLICK: 'click', diff --git a/packages/core-browser/src/index.ts b/packages/core-browser/src/index.ts index 79d531c52e..c0a26a4ea4 100644 --- a/packages/core-browser/src/index.ts +++ b/packages/core-browser/src/index.ts @@ -16,6 +16,7 @@ export * from './toolbar'; export * from './terminal'; export * from './file-decoration'; export * from './ai-native'; +export * from './design'; // 前端工具方法 export * from './logger'; @@ -43,4 +44,3 @@ export * from './markdown'; export * from './extensions'; export * from './static-resource'; -export * from './ai-native'; diff --git a/packages/core-browser/src/layout/constants.ts b/packages/core-browser/src/layout/constants.ts index b69ad06c47..848743e028 100644 --- a/packages/core-browser/src/layout/constants.ts +++ b/packages/core-browser/src/layout/constants.ts @@ -1,3 +1,10 @@ +import merge from 'lodash/merge'; + +import { Injectable } from '@opensumi/di'; +import { IDesignLayoutConfig, isMacintosh } from '@opensumi/ide-core-common'; + +import { electronEnv } from '../utils/electron'; + export interface ILayoutViewSize { menubarHeight: number; editorTabsHeight: number; @@ -18,6 +25,7 @@ export const DEFAULT_LAYOUT_VIEW_SIZE: ILayoutViewSize = { accordionHeaderSizeHeight: 24, }; +@Injectable() export class LayoutViewSizeConfig implements ILayoutViewSize { #menubarHeight: number; #editorTabsHeight: number; @@ -27,17 +35,21 @@ export class LayoutViewSizeConfig implements ILayoutViewSize { #statusBarHeight: number; #accordionHeaderSizeHeight: number; - constructor(private readonly layoutViewSize?: Partial) { - this.#menubarHeight = this.layoutViewSize?.menubarHeight || DEFAULT_LAYOUT_VIEW_SIZE.menubarHeight; - this.#editorTabsHeight = this.layoutViewSize?.editorTabsHeight || DEFAULT_LAYOUT_VIEW_SIZE.editorTabsHeight; - this.#bigSurTitleBarHeight = - this.layoutViewSize?.bigSurTitleBarHeight || DEFAULT_LAYOUT_VIEW_SIZE.bigSurTitleBarHeight; - this.#titleBarHeight = this.layoutViewSize?.titleBarHeight || DEFAULT_LAYOUT_VIEW_SIZE.titleBarHeight; - this.#panelTitleBarHeight = - this.layoutViewSize?.panelTitleBarHeight || DEFAULT_LAYOUT_VIEW_SIZE.panelTitleBarHeight; - this.#statusBarHeight = this.layoutViewSize?.statusBarHeight || DEFAULT_LAYOUT_VIEW_SIZE.statusBarHeight; + private inited = false; + init(layoutViewSize: Partial = {}) { + if (this.inited) { + return; + } + this.inited = true; + + this.#menubarHeight = layoutViewSize.menubarHeight || DEFAULT_LAYOUT_VIEW_SIZE.menubarHeight; + this.#editorTabsHeight = layoutViewSize.editorTabsHeight || DEFAULT_LAYOUT_VIEW_SIZE.editorTabsHeight; + this.#bigSurTitleBarHeight = layoutViewSize.bigSurTitleBarHeight || DEFAULT_LAYOUT_VIEW_SIZE.bigSurTitleBarHeight; + this.#titleBarHeight = layoutViewSize.titleBarHeight || DEFAULT_LAYOUT_VIEW_SIZE.titleBarHeight; + this.#panelTitleBarHeight = layoutViewSize.panelTitleBarHeight || DEFAULT_LAYOUT_VIEW_SIZE.panelTitleBarHeight; + this.#statusBarHeight = layoutViewSize.statusBarHeight || DEFAULT_LAYOUT_VIEW_SIZE.statusBarHeight; this.#accordionHeaderSizeHeight = - this.layoutViewSize?.accordionHeaderSizeHeight || DEFAULT_LAYOUT_VIEW_SIZE.accordionHeaderSizeHeight; + layoutViewSize.accordionHeaderSizeHeight || DEFAULT_LAYOUT_VIEW_SIZE.accordionHeaderSizeHeight; } get menubarHeight(): number { @@ -88,4 +100,46 @@ export class LayoutViewSizeConfig implements ILayoutViewSize { setAccordionHeaderSizeHeight(value: number) { this.#accordionHeaderSizeHeight = value; } + + protected supportNewMacHeaderBar = electronEnv.osRelease ? parseFloat(electronEnv.osRelease) >= 20 : false; + + calcElectronHeaderHeight(): number { + if (isMacintosh) { + // Big Sur increases title bar height + return this.supportNewMacHeaderBar ? this.bigSurTitleBarHeight : this.titleBarHeight; + } + return this.menubarHeight; + } + + calcOnlyTitleBarHeight(): number { + if (isMacintosh && this.supportNewMacHeaderBar) { + return this.bigSurTitleBarHeight; + } + return this.titleBarHeight; + } +} + +@Injectable() +export class DesignLayoutConfig implements IDesignLayoutConfig { + private internalLayout: Required = { + useMergeRightWithLeftPanel: false, + useMenubarView: true, + menubarLogo: '', + }; + + setLayout(...value: (Partial | undefined)[]): void { + this.internalLayout = merge(this.internalLayout, ...value.filter(Boolean)); + } + + get useMergeRightWithLeftPanel(): boolean { + return this.internalLayout.useMergeRightWithLeftPanel; + } + + get useMenubarView(): boolean { + return this.internalLayout.useMenubarView; + } + + get menubarLogo(): string { + return this.internalLayout.menubarLogo; + } } diff --git a/packages/core-browser/src/menu/next/menu-id.ts b/packages/core-browser/src/menu/next/menu-id.ts index cee4b89a3b..8d89e0303d 100644 --- a/packages/core-browser/src/menu/next/menu-id.ts +++ b/packages/core-browser/src/menu/next/menu-id.ts @@ -37,7 +37,7 @@ export enum MenuId { MenubarTerminalMenu = 'menubar/terminal', MenubarViewMenu = 'menubar/view', MenubarCompactMenu = 'menubar/compact/mode', - AIMenuBarTopExtra = 'ai/menubar/top/extra', + DesignMenuBarTopExtra = 'design/menubar/top/extra', TerminalInstanceContext = 'terminal/instance/context', TerminalNewDropdownContext = 'terminal/newDropdown/context', TerminalTabContext = 'terminal/tab/context', diff --git a/packages/core-browser/src/monaco/event.ts b/packages/core-browser/src/monaco/event.ts new file mode 100644 index 0000000000..871bda7a73 --- /dev/null +++ b/packages/core-browser/src/monaco/event.ts @@ -0,0 +1,3 @@ +export function createLayoutEventType(id: string): string { + return `layoutInlineWidget-${id}`; +} diff --git a/packages/core-browser/src/monaco/index.ts b/packages/core-browser/src/monaco/index.ts index 1638be676b..8f185ec735 100644 --- a/packages/core-browser/src/monaco/index.ts +++ b/packages/core-browser/src/monaco/index.ts @@ -23,6 +23,8 @@ import type { import type { IStandaloneEditorConstructionOptions } from '@opensumi/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneCodeEditor'; import type { BrandedService } from '@opensumi/monaco-editor-core/esm/vs/platform/instantiation/common/instantiation'; +export * from './event'; + export enum ServiceNames { CODE_EDITOR_SERVICE = 'codeEditorService', TEXT_MODEL_SERVICE = 'textModelService', diff --git a/packages/core-browser/src/react-hooks/hover.tsx b/packages/core-browser/src/react-hooks/hover.tsx new file mode 100644 index 0000000000..27165fee9f --- /dev/null +++ b/packages/core-browser/src/react-hooks/hover.tsx @@ -0,0 +1,25 @@ +import React, { useEffect } from 'react'; + +export function useHover() { + const ref = React.useRef(null); + + const [isHovered, setHovered] = React.useState(false); + + const handleMouseEnter = () => setHovered(true); + const handleMouseLeave = () => setHovered(false); + + useEffect(() => { + const elem = ref.current; + if (elem) { + elem.addEventListener('mouseenter', handleMouseEnter); + elem.addEventListener('mouseleave', handleMouseLeave); + + return () => { + elem.removeEventListener('mouseenter', handleMouseEnter); + elem.removeEventListener('mouseleave', handleMouseLeave); + }; + } + }, []); + + return [ref, isHovered] as const; +} diff --git a/packages/core-browser/src/react-hooks/index.ts b/packages/core-browser/src/react-hooks/index.ts index b482f8ef31..7f296d17c0 100644 --- a/packages/core-browser/src/react-hooks/index.ts +++ b/packages/core-browser/src/react-hooks/index.ts @@ -1,3 +1,5 @@ export * from './injectable-hooks'; export * from './portal-hooks'; export * from './event'; +export * from './use-change'; +export * from './hover'; diff --git a/packages/core-browser/src/react-hooks/use-change.tsx b/packages/core-browser/src/react-hooks/use-change.tsx new file mode 100644 index 0000000000..57264d576c --- /dev/null +++ b/packages/core-browser/src/react-hooks/use-change.tsx @@ -0,0 +1,12 @@ +import React from 'react'; + +export function useChange(value: T, onChange: (value: T) => void) { + const ref = React.useRef(value); + + React.useEffect(() => { + if (ref.current !== value) { + onChange(value); + ref.current = value; + } + }, [value]); +} diff --git a/packages/core-browser/src/react-providers/config-provider.tsx b/packages/core-browser/src/react-providers/config-provider.tsx index cd97696724..b69617ed9a 100644 --- a/packages/core-browser/src/react-providers/config-provider.tsx +++ b/packages/core-browser/src/react-providers/config-provider.tsx @@ -10,6 +10,7 @@ import type { ExtensionBrowserStyleSheet, ExtensionCandidate, ExtensionConnectOption, + IDesignLayoutConfig, UrlProvider, } from '@opensumi/ide-core-common'; @@ -103,10 +104,7 @@ export interface AppConfig { * 默认值可参考:https://github.com/opensumi/core/blob/58b998d9e1f721928f576579f16ded46b7505e84/packages/core-browser/src/components/layout/default-layout.tsx */ layoutComponent?: React.FC; - /** - * Define the default size (height) of each layout block in the IDE - */ - layoutViewSize?: Partial; + /** * 可基于 `layoutComponent` 配置的基础上 * 定义面板大小,宽度/高度 @@ -267,10 +265,16 @@ export interface AppConfig { * AI Native 相关的配置项 */ AINativeConfig?: IAINativeConfig; + designLayout?: IDesignLayoutConfig; /** * Collaboration Client Options */ collaborationOptions?: ICollaborationClientOpts; + + /** + * Define the default size (height) of each layout block in the IDE + */ + layoutViewSize?: Partial; } export interface ICollaborationClientOpts { diff --git a/packages/core-common/src/ai-native/index.ts b/packages/core-common/src/ai-native/index.ts new file mode 100644 index 0000000000..7929623807 --- /dev/null +++ b/packages/core-common/src/ai-native/index.ts @@ -0,0 +1 @@ +export * from './views'; diff --git a/packages/core-common/src/ai-native/views.ts b/packages/core-common/src/ai-native/views.ts new file mode 100644 index 0000000000..04ca39fe9b --- /dev/null +++ b/packages/core-common/src/ai-native/views.ts @@ -0,0 +1 @@ +export const AIInlineChatContentWidgetId = 'AI-Inline-Chat-Content-Widget'; diff --git a/packages/core-common/src/index.ts b/packages/core-common/src/index.ts index fa62f251a1..f2408cff95 100644 --- a/packages/core-common/src/index.ts +++ b/packages/core-common/src/index.ts @@ -33,3 +33,4 @@ export * from './clipboard'; export * from './mime'; export * from './application.lifecycle'; export * from './extension.schema'; +export * from './ai-native'; diff --git a/packages/core-common/src/settings/ai-native.ts b/packages/core-common/src/settings/ai-native.ts index 5fe32d6470..b4f97f9a32 100644 --- a/packages/core-common/src/settings/ai-native.ts +++ b/packages/core-common/src/settings/ai-native.ts @@ -4,3 +4,4 @@ export enum AINativeSettingSectionsId { CHAT_VISIBLE_TYPE = 'ai.native.chat.visible.type', } export const AI_NATIVE_SETTING_GROUP_ID = 'AI-Native'; +export const AI_NATIVE_SETTING_GROUP_TITLE = 'AI Native'; diff --git a/packages/core-common/src/types/ai-native/index.ts b/packages/core-common/src/types/ai-native/index.ts index c17b78710e..8c67a4dad9 100644 --- a/packages/core-common/src/types/ai-native/index.ts +++ b/packages/core-common/src/types/ai-native/index.ts @@ -4,10 +4,6 @@ import { IAIReportCompletionOption } from './reporter'; export * from './reporter'; export interface IAINativeCapabilities { - /** - * Use opensumi design UI style - */ - supportsOpenSumiDesign?: boolean; /** * Problem panel uses ai capabilities */ @@ -42,18 +38,24 @@ export interface IAINativeCapabilities { supportsTerminalCommandSuggest?: boolean; } -export interface IAINativeLayout { - // Use Merge right panel with left panel +export interface IDesignLayoutConfig { + /** + * merge right panel with left panel + */ useMergeRightWithLeftPanel?: boolean; - // Use ai manubar view + /** + * use new manubar view + */ useMenubarView?: boolean; - // set menubar logo + /** + * set menubar logo + */ menubarLogo?: string; } export interface IAINativeConfig { capabilities?: IAINativeCapabilities; - layout?: IAINativeLayout; + layout?: IDesignLayoutConfig; } export interface IAICompletionResultModel { diff --git a/packages/debug/src/browser/components/floating-click-widget/index.tsx b/packages/debug/src/browser/components/floating-click-widget/index.tsx index b31eb2086c..9a1a16246a 100644 --- a/packages/debug/src/browser/components/floating-click-widget/index.tsx +++ b/packages/debug/src/browser/components/floating-click-widget/index.tsx @@ -3,30 +3,21 @@ import { observer } from 'mobx-react-lite'; import React, { useEffect, useState } from 'react'; import { Button } from '@opensumi/ide-components'; -import { getIcon, localize } from '@opensumi/ide-core-browser'; -import { useInjectable } from '@opensumi/ide-core-browser'; -import { WorkbenchEditorService } from '@opensumi/ide-editor'; -import * as monaco from '@opensumi/ide-monaco'; +import { getIcon, localize, useInjectable } from '@opensumi/ide-core-browser'; +import { EditorContext } from '@opensumi/ide-editor/lib/browser/editor.context'; import { DebugConfigurationService } from '../../view/configuration/debug-configuration.service'; import styles from './index.module.less'; -export const FloatingClickWidget = observer((_: React.HtmlHTMLAttributes) => { +export const FloatingClickWidget = observer(() => { const { addConfiguration, openLaunchEditor, showDynamicQuickPickToInsert, dynamicConfigurations } = useInjectable(DebugConfigurationService); - const editorService = useInjectable(WorkbenchEditorService); + const { minimapWidth } = React.useContext(EditorContext); const [showSmartWidget, setShowSmartWidget] = useState(false); - const [miniMapWidth, setMiniMapWidth] = useState(0); useEffect(() => { - // 获取 Editor 的 minimap 宽度,对整体按钮做一个偏移 - const miniMapWidth = editorService.currentEditor?.monacoEditor.getOption(monaco.editor.EditorOption.layoutInfo) - .minimap.minimapWidth; - if (miniMapWidth) { - setMiniMapWidth(miniMapWidth); - } // 如果没有注册的 Dynamic Configuration Provider,就不显示智能添加配置按钮 if (Array.isArray(dynamicConfigurations) && dynamicConfigurations.length > 0) { setShowSmartWidget(true); @@ -36,7 +27,7 @@ export const FloatingClickWidget = observer((_: React.HtmlHTMLAttributes +
{showSmartWidget && (