Skip to content

Commit

Permalink
Merge branch 'v3.4'
Browse files Browse the repository at this point in the history
  • Loading branch information
opensumi[bot] committed Oct 16, 2024
2 parents 33b2de6 + 591891f commit bcf6ec3
Show file tree
Hide file tree
Showing 79 changed files with 634 additions and 312 deletions.
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
"version": "3.4.3"
"version": "3.4.4"
}
2 changes: 1 addition & 1 deletion packages/addons/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@opensumi/ide-addons",
"version": "3.4.3",
"version": "3.4.4",
"files": [
"lib",
"src"
Expand Down
2 changes: 1 addition & 1 deletion packages/ai-native/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@opensumi/ide-ai-native",
"version": "3.4.3",
"version": "3.4.4",
"files": [
"lib",
"src"
Expand Down
9 changes: 4 additions & 5 deletions packages/ai-native/src/browser/chat/chat.view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,10 @@ export const AIChatView = observer(() => {

const scrollToBottom = React.useCallback(() => {
if (containerRef && containerRef.current) {
containerRef.current.scrollTop = Number.MAX_SAFE_INTEGER;
const lastElement = containerRef.current.lastElementChild;
if (lastElement) {
lastElement.scrollIntoView({ behavior: 'smooth', block: 'end' });
}
// 出现滚动条时出现分割线
if (containerRef.current.scrollHeight > containerRef.current.clientHeight) {
containerRef.current.classList.add(SCROLL_CLASSNAME);
Expand All @@ -162,10 +165,6 @@ export const AIChatView = observer(() => {
handleDispatchMessage({ type: 'init', payload: [firstMsg] });
}, []);

React.useEffect(() => {
scrollToBottom();
}, [loading]);

React.useEffect(() => {
const disposer = new Disposable();

Expand Down
92 changes: 42 additions & 50 deletions packages/ai-native/src/browser/components/ChatMarkdown.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import cls from 'classnames';
import React, { useEffect, useRef, useState } from 'react';
import ReactDOM from 'react-dom/client';
import React, { ReactNode, useEffect, useRef, useState } from 'react';

import { MarkdownReactParser, MarkdownReactRenderer } from '@opensumi/ide-components/lib/markdown-react';
import { IMarkedOptions, marked } from '@opensumi/ide-components/lib/utils';
import { AppConfig, ConfigProvider, useInjectable } from '@opensumi/ide-core-browser';
import { defaultGenerator } from '@opensumi/ide-core-common/lib/id-generator';
import { escape } from '@opensumi/ide-utils/lib/strings';
import { IMarkdownString, MarkdownString } from '@opensumi/monaco-editor-core/esm/vs/base/common/htmlContent';

import { CodeEditorWithHighlight } from './ChatEditor';
Expand All @@ -24,52 +22,57 @@ interface MarkdownProps {
export const ChatMarkdown = (props: MarkdownProps) => {
const ref = useRef<HTMLDivElement | null>(null);
const appConfig = useInjectable<AppConfig>(AppConfig);
const [reactParser, setReactParser] = useState<MarkdownReactParser>();
const [tokensList, setTokensList] = useState<marked.TokensList>();

useEffect(() => {
const element = ref.current;
if (!element) {
return;
}
const codeRenderedElements = new Map<string, { container: HTMLDivElement; root: ReactDOM.Root }>();

const markdown: IMarkdownString =
typeof props.markdown === 'string' ? new MarkdownString(props.markdown) : props.markdown;

const renderer = new marked.Renderer();
renderer.link = (href: string | null, title: string | null, text: string): string =>
`<a rel="noopener" target="_blank" href="${href}" target="${href}" title="${title || href}">${text}</a>`;
const renderer: MarkdownReactRenderer = new MarkdownReactRenderer();
renderer.link = (href: string, text: ReactNode): React.ReactElement => (
<a rel='noopener' target='_blank' href={href} title={href}>
{text}
</a>
);
renderer.code = (code, lang) => {
const id = defaultGenerator.nextId();
const container = document.createElement('div');
const language = postProcessCodeBlockLanguageId(lang);
const dom = ReactDOM.createRoot(container);
dom.render(
<ConfigProvider value={appConfig}>
<div className={styles.code_block}>
<div className={styles.code_language}>{language}</div>
<CodeEditorWithHighlight
input={code}
language={language}
relationId={props.relationId || ''}
agentId={props.agentId}
command={props.command}
/>
</div>
</ConfigProvider>,

return (
<div className={styles.code}>
<ConfigProvider value={appConfig}>
<div className={styles.code_block}>
<div className={styles.code_language}>{language}</div>
<CodeEditorWithHighlight
input={code as string}
language={language}
relationId={props.relationId || ''}
agentId={props.agentId}
command={props.command}
/>
</div>
</ConfigProvider>
</div>
);
codeRenderedElements.set(id, { container, root: dom });
return `<div class="code" data-code="${id}">${escape(code)}</div>`;
};
renderer.codespan = (code) => `<code class=${styles.code_inline}>${code}</code>`;
renderer.codespan = (code) => <code className={styles.code_inline}>{code}</code>;

const reactParser = new MarkdownReactParser({ renderer });
const markedOptions = props.markedOptions ?? {};
markedOptions.renderer = renderer;
markedOptions.renderer = reactParser;

let value = markdown.value ?? '';
if (value.length > 100_000) {
value = `${value.slice(0, 100_000)}…`;
}

let renderedMarkdown: string;
let tokensList: marked.TokensList;
if (props.fillInIncompleteTokens) {
const opts = {
...marked.defaults,
Expand All @@ -78,33 +81,22 @@ export const ChatMarkdown = (props: MarkdownProps) => {
const tokens = marked.lexer(value, opts);
const newTokens = fillInIncompleteTokens(tokens);
renderedMarkdown = marked.parser(newTokens, opts);
tokensList = newTokens;
} else {
renderedMarkdown = marked.parse(value, markedOptions);
const tokens = marked.lexer(value, marked.defaults);
renderedMarkdown = marked.parser(tokens, markedOptions);
tokensList = tokens;
}

element.innerHTML = renderedMarkdown;

const codePlaceholderElements = element.querySelectorAll<HTMLDivElement>('div[data-code]');
codePlaceholderElements.forEach((placeholderElement) => {
const renderedElement = codeRenderedElements.get(placeholderElement.dataset['code'] ?? '');
if (renderedElement && renderedElement.container) {
placeholderElement.innerText = '';
placeholderElement.append(renderedElement.container);
}
});

return () => {
if (codeRenderedElements.size > 0) {
codeRenderedElements.forEach(({ root }) => {
requestAnimationFrame(() => {
root.unmount();
});
});
}
};
setTokensList(tokensList);
setReactParser(reactParser);
}, [props.markdown]);

return <div className={cls(styles.markdown_container, props.className)} ref={ref} tabIndex={0} />;
return (
<div className={cls(styles.markdown_container, props.className)} ref={ref} tabIndex={0}>
{tokensList && reactParser && reactParser.parse(tokensList)}
</div>
);
};

export function postProcessCodeBlockLanguageId(lang: string | undefined): string {
Expand Down
22 changes: 16 additions & 6 deletions packages/ai-native/src/browser/components/ChatReply.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
import React, { Fragment, ReactNode, useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react';
import React, {
Fragment,
ReactNode,
startTransition,
useCallback,
useEffect,
useMemo,
useReducer,
useRef,
useState,
} from 'react';

import { Button } from '@opensumi/ide-components/lib/button';
import { BasicRecycleTree, IBasicRecycleTreeHandle, IBasicTreeData } from '@opensumi/ide-components/lib/recycle-tree';
Expand Down Expand Up @@ -192,10 +202,6 @@ export const ChatReply = (props: IChatReplyProps) => {

disposableCollection.push(
request.response.onDidChange(() => {
if (onDidChange) {
onDidChange();
}

history.updateAssistantMessage(msgId, { content: request.response.responseText });

if (request.response.isComplete) {
Expand All @@ -212,7 +218,11 @@ export const ChatReply = (props: IChatReplyProps) => {
agentId,
});
}
update();

startTransition(() => {
onDidChange?.();
update();
});
}),
);

Expand Down
8 changes: 7 additions & 1 deletion packages/ai-native/src/browser/contrib/base/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ export abstract class BaseAIMonacoEditorController extends Disposable implements
this.cancellationTokenSource = new CancellationTokenSource();
}

public featureDisposable = new Disposable();

protected allowedSchemes: string[] = [Schemes.file, Schemes.notebookCell];

constructor(
Expand All @@ -80,7 +82,11 @@ export abstract class BaseAIMonacoEditorController extends Disposable implements

this.addDispose(
this.monacoEditor.onDidChangeModel(({ newModelUrl }) => {
const shouldMount = newModelUrl && this.allowedSchemes.includes(newModelUrl.scheme);
if (!newModelUrl) {
return;
}

const shouldMount = this.allowedSchemes.includes(newModelUrl.scheme);
if (shouldMount !== isMounted) {
isMounted = !!shouldMount;
if (isMounted) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export class IntelligentCompletionsController extends BaseAIMonacoEditorControll
this.aiNativeContextKey = this.injector.get(AINativeContextKey, [this.monacoEditor.contextKeyService]);

this.registerFeature(this.monacoEditor);
return this;
return this.featureDisposable;
}

private handlerAlwaysVisiblePreference(): void {
Expand Down Expand Up @@ -148,7 +148,7 @@ export class IntelligentCompletionsController extends BaseAIMonacoEditorControll
register();
}

this.addDispose(
this.featureDisposable.addDispose(
this.preferenceService.onSpecificPreferenceChange(
AINativeSettingSectionsId.IntelligentCompletionsAlwaysVisible,
({ newValue }) => {
Expand Down Expand Up @@ -356,7 +356,7 @@ export class IntelligentCompletionsController extends BaseAIMonacoEditorControll
}

private registerFeature(monacoEditor: ICodeEditor): void {
this.addDispose(
this.featureDisposable.addDispose(
Event.any<any>(
monacoEditor.onDidChangeCursorPosition,
monacoEditor.onDidChangeModelContent,
Expand All @@ -367,8 +367,8 @@ export class IntelligentCompletionsController extends BaseAIMonacoEditorControll
);

const multiLineEditsIsVisibleKey = new Set([MultiLineEditsIsVisible.raw]);
this.addDispose(this.whenMultiLineEditsVisibleDisposable);
this.addDispose(
this.featureDisposable.addDispose(this.whenMultiLineEditsVisibleDisposable);
this.featureDisposable.addDispose(
this.aiNativeContextKey.contextKeyService!.onDidChangeContext((e) => {
if (e.payload.affectsSome(multiLineEditsIsVisibleKey)) {
const isVisible = this.aiNativeContextKey.multiLineEditsIsVisible.get();
Expand All @@ -379,7 +379,7 @@ export class IntelligentCompletionsController extends BaseAIMonacoEditorControll
}),
);

this.addDispose(
this.featureDisposable.addDispose(
Event.any<any>(
monacoEditor.onDidChangeModel,
monacoEditor.onDidChangeModelContent,
Expand All @@ -391,6 +391,6 @@ export class IntelligentCompletionsController extends BaseAIMonacoEditorControll
);

const inlineCompletionsSource = this.injector.get(InlineCompletionsSource, [this.monacoEditor]);
this.addDispose(inlineCompletionsSource.fetch());
this.featureDisposable.addDispose(inlineCompletionsSource.fetch());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,9 @@ export class ProblemFixController extends BaseAIMonacoEditorController {
mount(): IDisposable {
this.aiNativeContextKey = this.injector.get(AINativeContextKey, [this.monacoEditor.contextKeyService]);

const disposable = new Disposable();

const provider = this.problemFixProviderRegistry.getHoverFixProvider();
if (!provider) {
return disposable;
return Disposable.NULL;
}

// 先去掉 monaco 默认的 MarkerHoverParticipant,以及之前注册的 AIMonacoHoverParticipant
Expand All @@ -82,7 +80,7 @@ export class ProblemFixController extends BaseAIMonacoEditorController {
AIMonacoHoverParticipant.injector = this.injector;
HoverParticipantRegistry.register(AIMonacoHoverParticipant);

disposable.addDispose(
this.featureDisposable.addDispose(
this.problemFixService.onHoverFixTrigger((part) => {
const hoverController = this.monacoEditor?.getContribution<HoverController>(HoverController.ID);
if (hoverController) {
Expand All @@ -93,7 +91,7 @@ export class ProblemFixController extends BaseAIMonacoEditorController {
}),
);

return disposable;
return this.featureDisposable;
}

private async handleHoverFix(part: MarkerHover, provider: IHoverFixHandler) {
Expand Down
Loading

0 comments on commit bcf6ec3

Please sign in to comment.