diff --git a/packages/core/src/core/diff-viewer/common.ts b/packages/core/src/core/diff-viewer/common.ts index b7cc4cdd..7e8b3871 100644 --- a/packages/core/src/core/diff-viewer/common.ts +++ b/packages/core/src/core/diff-viewer/common.ts @@ -4,6 +4,7 @@ import { IPartialEditEvent } from '@opensumi/ide-ai-native/lib/browser/widget/in import { Event, URI } from '@opensumi/ide-core-common'; import { IResourceOpenOptions } from '@opensumi/ide-editor'; import { ITheme } from '@opensumi/ide-theme'; +import { SumiReadableStream } from '@opensumi/ide-utils/lib/stream'; import { IAppInstance } from '../../editor'; import { LandingProps } from '../types'; @@ -45,6 +46,12 @@ export interface IDiffViewerHandle { newContent: string, options?: IResourceOpenDiffOptions, ) => Promise; + openDiffInTabByStream: ( + filePath: string, + oldContent: string, + stream: SumiReadableStream, + options?: IResourceOpenOptions, + ) => Promise; /** * 打开标签页 */ diff --git a/packages/core/src/core/diff-viewer/internal/base.ts b/packages/core/src/core/diff-viewer/internal/base.ts index 354821d5..330892f9 100644 --- a/packages/core/src/core/diff-viewer/internal/base.ts +++ b/packages/core/src/core/diff-viewer/internal/base.ts @@ -8,6 +8,7 @@ import { Domain, Emitter, Event, + IChatProgress, ILogger, Sequencer, URI, @@ -16,11 +17,13 @@ import { IResourceOpenOptions, WorkbenchEditorService } from '@opensumi/ide-edit import { Selection, SelectionDirection } from '@opensumi/ide-monaco'; import { Autowired } from '@opensumi/di'; +import { InlineChatController } from '@opensumi/ide-ai-native/lib/browser/widget/inline-chat/inline-chat-controller'; import { LiveInlineDiffPreviewer } from '@opensumi/ide-ai-native/lib/browser/widget/inline-diff/inline-diff-previewer'; import { InlineDiffHandler } from '@opensumi/ide-ai-native/lib/browser/widget/inline-diff/inline-diff.handler'; import { EResultKind } from '@opensumi/ide-ai-native/lib/common'; import { IMenuRegistry, MenuContribution } from '@opensumi/ide-core-browser/lib/menu/next'; import { IEditor, IEditorDocumentModelService } from '@opensumi/ide-editor/lib/browser'; +import { listenReadable, SumiReadableStream } from '@opensumi/ide-utils/lib/stream'; import path from 'path'; import { IDiffViewerProps, IDiffViewerTab, IExtendPartialEditEvent, ITabChangedEvent } from '../common'; import { removeStart } from '../utils'; @@ -135,6 +138,63 @@ export class DiffViewerContribution implements CommandContribution, ClientAppCon previewer.revealFirstDiff(); }; + const openDiffInTabByStream = async ( + filePath: string, + oldContent: string, + stream: SumiReadableStream, + options?: IResourceOpenOptions, + ) => { + const { uri, result: openResourceResult } = await openFileInTab(filePath, oldContent, { + ...options, + preview: false, + }); + + if (!openResourceResult) { + throw new Error('Failed to open file in tab: ' + filePath); + } + + const editor = openResourceResult.group.codeEditor; + + const model = this.editorDocumentModelService.getModelReference(uri); + if (!model || !model.instance) { + throw new Error('Failed to get model reference: ' + filePath); + } + + const monacoModel = model.instance.getMonacoModel(); + + monacoModel.setValue(oldContent); + const fullRange = monacoModel.getFullModelRange(); + + const controller = new InlineChatController(); + const newStream = new SumiReadableStream(); + controller.mountReadable(newStream); + listenReadable(stream, { + onData(data) { + newStream.emitData({ + kind: 'content', + content: data, + }); + }, + onEnd() { + newStream.end(); + }, + onError(error) { + newStream.emitError(error); + }, + }); + + this.inlineDiffHandler.showPreviewerByStream( + editor.monacoEditor, + { + crossSelection: Selection.fromRange(fullRange, SelectionDirection.LTR), + chatResponse: controller, + previewerOptions: { + disposeWhenEditorClosed: false, + }, + }, + ) as LiveInlineDiffPreviewer; + }; + const getFilePathForEditor = (editor: IEditor) => { return this.stripDirectory(editor.currentUri!.codeUri.fsPath); }; @@ -184,6 +244,7 @@ export class DiffViewerContribution implements CommandContribution, ClientAppCon openDiffInTab: async (filePath, oldContent, newContent, options?: IResourceOpenOptions) => { await sequencer.queue(() => openDiffInTab(filePath, oldContent, newContent, options)); }, + openDiffInTabByStream, openFileInTab: async (filePath: string, content: string, options?: IResourceOpenOptions) => { const { uri } = await openFileInTab(filePath, content, options); return uri; diff --git a/packages/startup/package.json b/packages/startup/package.json index 713d4385..12334b43 100644 --- a/packages/startup/package.json +++ b/packages/startup/package.json @@ -25,7 +25,8 @@ }, "devDependencies": { "@types/react": "^18.2.0", - "@types/react-dom": "^18.2.0" + "@types/react-dom": "^18.2.0", + "split-retain": "^1.0.1" }, "peerDependencies": { "react": "^18.2.0", diff --git a/packages/startup/src/diff-viewer/index.tsx b/packages/startup/src/diff-viewer/index.tsx index 97ea2e3d..d8ba0108 100644 --- a/packages/startup/src/diff-viewer/index.tsx +++ b/packages/startup/src/diff-viewer/index.tsx @@ -4,6 +4,8 @@ import { createRoot } from 'react-dom/client'; import '../index.css'; import { DiffViewerRenderer } from '@codeblitzjs/ide-core/lib/api/renderDiffViewer'; import { IDiffViewerHandle } from '@codeblitzjs/ide-core/lib/core/diff-viewer'; +import { SumiReadableStream } from '@opensumi/ide-utils/lib/stream'; +import splitRetain from 'split-retain'; import jsonData from './data.json'; const data = [ @@ -30,6 +32,24 @@ const data = [ data.push(...jsonData); +function createMockStream(data: string) { + const streamData = splitRetain(data, '\n'); + const length = streamData.length; + const chatReadableStream = new SumiReadableStream(); + + streamData.forEach((chunk, index) => { + setTimeout(() => { + chatReadableStream.emitData(chunk.toString()); + + if (length - 1 === index) { + chatReadableStream.end(); + } + }, index * 100); + }); + + return chatReadableStream; +} + const App = () => { const handleRef = useRef(null); const [eventInfo, setEventInfo] = React.useState(null); @@ -37,19 +57,17 @@ const App = () => { const memo = useMemo(() => (
hello
, + // component: () =>
代码生成中
, }} appConfig={{ - layoutViewSize: { - editorTabsHeight: 50 - } + layoutViewSize: {}, }} onWillApplyTheme={() => { return { 'editorGroupHeader.tabsBackground': '#ECF1FE', 'editor.background': '#fff', 'aiNative.inlineDiffAddedRange': '#26bf6d1f', - 'aiNative.inlineDiffRemovedRange': "#ff4d4f1e", + 'aiNative.inlineDiffRemovedRange': '#ff4d4f1e', 'aiNative.inlineDiffAcceptPartialEdit': '#26bf6d80', 'aiNative.inlineDiffDiscardPartialEdit': '#ff4d4f80', 'aiNative.inlineDiffAcceptPartialEdit.foreground': '#000', @@ -155,6 +173,15 @@ const App = () => { > reset + {data.map((item, index) => { return ( diff --git a/yarn.lock b/yarn.lock index a2b4553b..c10a85a0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -706,6 +706,7 @@ __metadata: "@types/react-dom": "npm:^18.2.0" antd: "npm:^4.0.0" lodash: "npm:^4.17.21" + split-retain: "npm:^1.0.1" tslib: "npm:^2.2.0" peerDependencies: react: ^18.2.0 @@ -14765,6 +14766,13 @@ __metadata: languageName: node linkType: hard +"split-retain@npm:^1.0.1": + version: 1.0.1 + resolution: "split-retain@npm:1.0.1" + checksum: 10/8fe5217e0e2953141ddbce755cc68a5bb2db4f071e2a56991c57b9aabb999ea2d74868e166e288e9247d1920b611d4477bb81fab666157db433066de0d787631 + languageName: node + linkType: hard + "split-string@npm:^3.0.1, split-string@npm:^3.0.2": version: 3.1.0 resolution: "split-string@npm:3.1.0"