Skip to content

Dogusata/add code diff to syntax highlighter and file description tooltip #55

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/DATAMODEL.md
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,7 @@ export interface TreeNodeDetails {
status?: 'info' | 'success' | 'warning' | 'error';
icon?: MynahIcons;
label?: string;
description?: string; // Markdown tooltip
}

export interface SourceLink {
Expand Down Expand Up @@ -1348,7 +1349,8 @@ mynahUI.addChatItem(tabId, {
'src/devfile.yaml': {
status: 'error',
label: "Change rejected",
icon: MynahIcons.REVERT
icon: MynahIcons.REVERT,
description: 'Markdown tooltip to show'
}
}
},
Expand Down
24 changes: 15 additions & 9 deletions example/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
exampleRichFollowups,
exampleStreamParts,
sampleMarkdownList,
exampleCodeDiff,
} from './samples/sample-data';
import escapeHTML from 'escape-html';
import './styles/styles.scss';
Expand Down Expand Up @@ -54,18 +55,13 @@ export const createMynahUI = (initialData?: MynahUIDataModel): MynahUI => {
icon: MynahIcons.ELLIPSIS,
items: [
{
id: 'menu-action-1',
text: 'Menu action 1!',
icon: MynahIcons.CHAT,
},
{
id: 'menu-action-2',
text: 'Menu action 2!',
icon: MynahIcons.COMMENT,
id: 'show-code-diff',
text: 'Show code diff!',
icon: MynahIcons.CODE_BLOCK,
},
{
id: 'insert-code',
icon: MynahIcons.CODE_BLOCK,
icon: MynahIcons.CURSOR_INSERT,
text: 'Insert code!',
},
],
Expand All @@ -87,6 +83,11 @@ export const createMynahUI = (initialData?: MynahUIDataModel): MynahUI => {
mynahUI.updateStore(tabId, {
chatItems: [],
});
} else if (buttonId === 'show-code-diff') {
mynahUI.addChatItem(tabId, {
type: ChatItemType.ANSWER,
body: exampleCodeDiff
});
} else if (buttonId === 'insert-code') {
mynahUI.addToUserPrompt(tabId, exampleCodeBlockToInsert, 'code');
}
Expand Down Expand Up @@ -197,6 +198,11 @@ export const createMynahUI = (initialData?: MynahUIDataModel): MynahUI => {
case 'reject-change':
mynahUI.updateChatAnswerWithMessageId(tabId, messageId, exampleFileListChatItemForUpdate);
break;
case 'show-diff':
mynahUI.updateChatAnswerWithMessageId(tabId, messageId, {
body: exampleCodeDiff
});
break;
case 'revert-rejection':
mynahUI.updateChatAnswerWithMessageId(tabId, messageId, {fileList: exampleFileListChatItem.fileList});
break;
Expand Down
13 changes: 13 additions & 0 deletions example/src/samples/sample-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import sampleList2 from './sample-list-2.md';
import sampleList3 from './sample-list-3.md';
import sampleList4 from './sample-list-4.md';
import SampleCode from './sample-code.md';
import SampleDiff from './sample-diff.md';
import { Commands } from '../commands';

export const mynahUIQRImageBase64 =
Expand Down Expand Up @@ -104,6 +105,7 @@ export const exampleStreamParts: Partial<ChatItem>[] = [
];

export const exampleCodeBlockToInsert = SampleCode;
export const exampleCodeDiff = SampleDiff;

export const exampleRichFollowups: ChatItem = {
type: ChatItemType.SYSTEM_PROMPT,
Expand Down Expand Up @@ -199,8 +201,18 @@ export const exampleFileListChatItem: ChatItem = {
name: 'reject-change',
description: 'Reject Change',
},
{
icon: MynahIcons.CODE_BLOCK,
name: 'show-diff',
description: 'Show Diff',
},
],
},
details: {
'src/index.ts': {
description: exampleCodeDiff
},
},
},
};

Expand All @@ -215,6 +227,7 @@ export const exampleFileListChatItemForUpdate: Partial<ChatItem> = {
status: 'error',
label: 'File rejected',
icon: MynahIcons.CANCEL_CIRCLE,
description: exampleCodeDiff
},
},
actions: {
Expand Down
34 changes: 34 additions & 0 deletions example/src/samples/sample-diff.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
### Changes on `index.ts`


> _Between line 42 and 70_
```diff-typescript
const mynahUI = new MynahUI({
tabs: {
'tab-1': {
isSelected: true,
store: {
tabTitle: 'Chat',
chatItems: [
{
type: ChatItemType.ANSWER,
body: 'Welcome to our chat!',
messageId: 'welcome-message'
},
],
- promptInputPlaceholder: 'Write your question',
+ promptInputPlaceholder: 'Type your question',
}
}
},
- onChatPrompt: () => {},
+ onChatPrompt: (tabId: string, prompt: ChatPrompt) => {
+ mynahUI.addChatItem(tabId, {
+ type: ChatItemType.PROMPT,
+ messageId: new Date().getTime().toString(),
+ body: prompt.escapedPrompt
+ });
+ // call your genAI action
+ }
});
```
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@aws/mynah-ui",
"displayName": "AWS Mynah UI",
"version": "4.10.1",
"version": "4.11.0",
"description": "AWS Toolkit VSCode and Intellij IDE Extension Mynah UI",
"publisher": "Amazon Web Services",
"license": "Apache License 2.0",
Expand Down
55 changes: 54 additions & 1 deletion src/components/chat-item/chat-item-tree-file.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { marked } from 'marked';
import { DomBuilder, ExtendedHTMLElement } from '../../helper/dom';
import { MynahUIGlobalEvents, cancelEvent } from '../../helper/events';
import { FileNodeAction, MynahEventNames, TreeNodeDetails } from '../../static';
import { Button } from '../button';
import { Card } from '../card/card';
import { CardBody } from '../card/card-body';
import { Icon, MynahIcons } from '../icon';
import { Overlay, OverlayHorizontalDirection, OverlayVerticalDirection } from '../overlay';

export interface ChatItemTreeFileProps {
tabId: string;
Expand All @@ -15,8 +19,11 @@ export interface ChatItemTreeFileProps {
actions?: FileNodeAction[];
}

const PREVIEW_DELAY = 250;
export class ChatItemTreeFile {
render: ExtendedHTMLElement;
private fileTooltip: Overlay | null;
private fileTooltipTimeout: ReturnType<typeof setTimeout>;
constructor (props: ChatItemTreeFileProps) {
this.render = DomBuilder.getInstance().build({
type: 'div',
Expand All @@ -26,13 +33,23 @@ export class ChatItemTreeFile {
],
events: {
click: () => {
this.hideTooltip();
MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.FILE_CLICK, {
tabId: props.tabId,
messageId: props.messageId,
filePath: props.filePath,
deleted: props.deleted,
});
}
},
...(props.details?.description != null
? {
mouseenter: (e: MouseEvent) => {
const tooltipText = marked(props.details?.description ?? '', { breaks: true }) as string;
this.showTooltip(tooltipText, OverlayVerticalDirection.CENTER, OverlayHorizontalDirection.TO_RIGHT);
},
mouseout: this.hideTooltip
}
: {})
},
children: [
...(props.icon != null
Expand Down Expand Up @@ -83,6 +100,7 @@ export class ChatItemTreeFile {
primary: false,
onClick: (e) => {
cancelEvent(e);
this.hideTooltip();
MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.FILE_ACTION_CLICK, {
tabId: props.tabId,
messageId: props.messageId,
Expand All @@ -96,4 +114,39 @@ export class ChatItemTreeFile {
]
});
}

private readonly showTooltip = (content: string, vDir?: OverlayVerticalDirection, hDir?: OverlayHorizontalDirection): void => {
if (content.trim() !== '') {
clearTimeout(this.fileTooltipTimeout);
this.fileTooltipTimeout = setTimeout(() => {
this.fileTooltip = new Overlay({
background: true,
closeOnOutsideClick: false,
referenceElement: this.render,
dimOutside: false,
removeOtherOverlays: true,
verticalDirection: vDir ?? OverlayVerticalDirection.TO_TOP,
horizontalDirection: hDir ?? OverlayHorizontalDirection.CENTER,
children: [
new Card({
border: false,
children: [
new CardBody({
body: content
}).render
]
}).render
],
});
}, PREVIEW_DELAY);
}
};

public readonly hideTooltip = (): void => {
if (this.fileTooltipTimeout != null) {
clearTimeout(this.fileTooltipTimeout);
}
this.fileTooltip?.close();
this.fileTooltip = null;
};
}
72 changes: 18 additions & 54 deletions src/components/overlay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,65 +131,29 @@ export class Overlay {
'beforeend'
);

const containerRectangle = this.container.getBoundingClientRect();
// Screen edge fixes
const winHeight = Math.max(document.documentElement.clientHeight ?? 0, window.innerHeight ?? 0);
const winWidth = Math.max(document.documentElement.clientWidth ?? 0, window.innerWidth ?? 0);
const lastContainerRect = this.container.getBoundingClientRect();
const effectiveTop = parseFloat(this.container.style.top ?? '0');
const effectiveLeft = parseFloat(this.container.style.left ?? '0');

// if it will open at the center of the reference element or point
// we only need the half of both measurements
const comparingWidth =
horizontalDirection === OverlayHorizontalDirection.CENTER
? containerRectangle.width / 2
: containerRectangle.width;
const comparingHeight =
verticalDirection === OverlayVerticalDirection.CENTER
? containerRectangle.height / 2
: containerRectangle.height;

// if overlay will open to right or at center
// we're checking if it exceeds from the right edge of the window
if (
horizontalDirection !== OverlayHorizontalDirection.TO_LEFT &&
horizontalDirection !== OverlayHorizontalDirection.END_TO_LEFT &&
comparingWidth + OVERLAY_MARGIN + calculatedLeft > winWidth
) {
if (horizontalDirection === OverlayHorizontalDirection.CENTER) {
// Exceed right edge of the window, shift left by the width of exceeding part * 0.5
// to correctly handle the 50% horizontal transform
this.container.style.left =
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
(calculatedLeft - (containerRectangle.width + (OVERLAY_MARGIN * 2) + calculatedLeft - winWidth) * 0.5) + 'px';
} else {
this.container.style.left =
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
calculatedLeft - (containerRectangle.width + OVERLAY_MARGIN + calculatedLeft - winWidth) + 'px';
}
}
// else if the direction is selected as a one that goes to the left,
// we need to check if it is exceeding from the left edge of the window
else if (calculatedLeft + comparingWidth - containerRectangle.width < OVERLAY_MARGIN) {
this.container.style.left =
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
calculatedLeft + (OVERLAY_MARGIN - calculatedLeft + (comparingWidth - containerRectangle.width)) + 'px';
// Vertical edge
// Check top exceeding
if (lastContainerRect.top < OVERLAY_MARGIN) {
this.container.style.top = `${effectiveTop + (OVERLAY_MARGIN - lastContainerRect.top)}px`;
} // Check bottom exceeding
else if (lastContainerRect.top + lastContainerRect.height + OVERLAY_MARGIN > winHeight) {
this.container.style.top = `${effectiveTop - (lastContainerRect.top + lastContainerRect.height + OVERLAY_MARGIN - winHeight)}px`;
}

// if overlay will open to bottom or at center
// we're checking if it exceeds from the bottom edge of the window
if (
verticalDirection !== OverlayVerticalDirection.TO_TOP &&
verticalDirection !== OverlayVerticalDirection.END_TO_TOP &&
comparingHeight + OVERLAY_MARGIN + calculatedTop > winHeight
) {
this.container.style.top =
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
calculatedTop - (comparingHeight + OVERLAY_MARGIN + calculatedTop - winHeight) + 'px';
}
// else if the direction is selected as a one that goes to the top,
// we need to check if it is exceeding from the top edge of the window
else if (calculatedTop + comparingHeight - containerRectangle.height < OVERLAY_MARGIN) {
this.container.style.top =
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
calculatedTop + (OVERLAY_MARGIN - calculatedTop + (comparingHeight - containerRectangle.height)) + 'px';
// Horizontal edge
// Check left exceeding
if (lastContainerRect.left < OVERLAY_MARGIN) {
this.container.style.left = `${effectiveLeft + (OVERLAY_MARGIN - lastContainerRect.left)}px`;
} // Check right exceeding
else if (lastContainerRect.left + lastContainerRect.width + OVERLAY_MARGIN > winWidth) {
this.container.style.left = `${effectiveLeft - (lastContainerRect.left + lastContainerRect.width + OVERLAY_MARGIN - winWidth)}px`;
}

// we need to delay the class toggle
Expand Down
Loading
Loading