Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
75873e1
Misc UI improvements.
ellisonbg Jul 3, 2025
e6c46ce
Add .env got .gitignore
ellisonbg Oct 21, 2025
e912253
Remove attach button from input as drag and drop now works
ellisonbg Oct 21, 2025
6146017
Improve chat styling
ellisonbg Oct 21, 2025
d0fc141
Clean up css.
ellisonbg Oct 21, 2025
109a198
Add TS logic to track which area the chat is in
ellisonbg Oct 21, 2025
03795d0
Improve attachment visual design and functionality.
ellisonbg Oct 21, 2025
ff0b491
Remove chat input top padding.
ellisonbg Oct 22, 2025
75ad77d
Improve focus UX of chat input
ellisonbg Oct 22, 2025
55abc14
Draft stop button, minor styling of chat input and hr.
ellisonbg Oct 22, 2025
c971d25
Draft of new writers UI
ellisonbg Oct 23, 2025
e1bdf6d
Minor style updates
ellisonbg Oct 23, 2025
564bb04
Work on UI improvements including buttons and message editing.
ellisonbg Oct 24, 2025
2d26c55
More work on button and tooltips.
ellisonbg Oct 24, 2025
807f6c6
Fix input TextField horizontal scrolling
ellisonbg Oct 24, 2025
1bf4155
Make sure that Enter picks item in autocomplete
ellisonbg Oct 24, 2025
5809d8b
Fix autocomplete behavior with Enter
ellisonbg Oct 24, 2025
1cef616
Improve autocomplete styling and behavior
ellisonbg Oct 26, 2025
8ce1005
Simplify autocomplete styling
ellisonbg Oct 26, 2025
2c72372
Allow multiple @ mentions anywhere in a chat message
ellisonbg Oct 26, 2025
3eba5e4
Fix whitespace
ellisonbg Oct 27, 2025
8514c47
Remove border on code toolbar in chat
ellisonbg Oct 27, 2025
9f8c987
Minor change to placeholder text in chat input
ellisonbg Oct 27, 2025
37032b9
Don't render the message header for your own messages
ellisonbg Oct 27, 2025
17ae833
Improve styling for chat UI
ellisonbg Oct 27, 2025
0993b77
Editing of messages is broken, comment out until we can fix the behavior
ellisonbg Oct 27, 2025
0821680
Run linter
ellisonbg Oct 27, 2025
625c562
add back attach & tooltipped buttons
dlqqq Oct 29, 2025
076a7b6
make attach button gray & same size as send button
dlqqq Oct 29, 2025
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,6 @@ Untitled*.ipynb

# Chat files
*.chat

# Ignore secrets in '.env'
.env
99 changes: 64 additions & 35 deletions packages/jupyter-chat/src/components/attachments.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,14 @@
*/

import CloseIcon from '@mui/icons-material/Close';
import { Box } from '@mui/material';
import { Box, Button, Tooltip } from '@mui/material';
import React, { useContext } from 'react';
import { PathExt } from '@jupyterlab/coreutils';
import { UUID } from '@lumino/coreutils';

import { TooltippedButton } from './mui-extras/tooltipped-button';
import { IAttachment } from '../types';
import { AttachmentOpenerContext } from '../context';

const ATTACHMENTS_CLASS = 'jp-chat-attachments';
const ATTACHMENT_CLASS = 'jp-chat-attachment';
const ATTACHMENT_CLICKABLE_CLASS = 'jp-chat-attachment-clickable';
const REMOVE_BUTTON_CLASS = 'jp-chat-attachment-remove';

/**
Expand Down Expand Up @@ -57,7 +53,15 @@ export type AttachmentsProps = {
*/
export function AttachmentPreviewList(props: AttachmentsProps): JSX.Element {
return (
<Box className={ATTACHMENTS_CLASS}>
<Box
sx={{
display: 'flex',
flexWrap: 'wrap',
gap: 1,
rowGap: 1,
columnGap: 2
}}
>
{props.attachments.map(attachment => (
<AttachmentPreview
key={`${PathExt.basename(attachment.value)}-${UUID.uuid4()}`}
Expand All @@ -82,40 +86,65 @@ export type AttachmentProps = AttachmentsProps & {
export function AttachmentPreview(props: AttachmentProps): JSX.Element {
const remove_tooltip = 'Remove attachment';
const attachmentOpenerRegistry = useContext(AttachmentOpenerContext);
const isClickable = !!attachmentOpenerRegistry?.get(props.attachment.type);

return (
<Box className={ATTACHMENT_CLASS}>
<span
className={
attachmentOpenerRegistry?.get(props.attachment.type)
? ATTACHMENT_CLICKABLE_CLASS
: ''
}
onClick={() =>
attachmentOpenerRegistry?.get(props.attachment.type)?.(
props.attachment
)
}
>
{getAttachmentDisplayName(props.attachment)}
</span>
{props.onRemove && (
<TooltippedButton
onClick={() => props.onRemove!(props.attachment)}
tooltip={remove_tooltip}
buttonProps={{
size: 'small',
title: remove_tooltip,
className: REMOVE_BUTTON_CLASS
}}
<Box
sx={{
border: '1px solid var(--jp-border-color1)',
borderRadius: '2px',
px: 1,
py: 0.5,
backgroundColor: 'var(--jp-layout-color2)',
display: 'flex',
alignItems: 'center',
gap: 0.5,
fontSize: '0.8125rem'
}}
>
<Tooltip title={props.attachment.value} placement="top" arrow>
<Box
component="span"
onClick={() =>
attachmentOpenerRegistry?.get(props.attachment.type)?.(
props.attachment
)
}
sx={{
minWidth: 'unset',
padding: '0',
color: 'inherit'
cursor: isClickable ? 'pointer' : 'default',
'&:hover': isClickable
? {
textDecoration: 'underline'
}
: {}
}}
>
<CloseIcon />
</TooltippedButton>
{getAttachmentDisplayName(props.attachment)}
</Box>
</Tooltip>
{props.onRemove && (
<Tooltip title={remove_tooltip} placement="top" arrow>
<span>
<Button
onClick={() => props.onRemove!(props.attachment)}
size="small"
className={REMOVE_BUTTON_CLASS}
aria-label={remove_tooltip}
sx={{
minWidth: 'unset',
padding: 0,
lineHeight: 0,
color: 'var(--jp-ui-font-color2)',
'&:hover': {
color: 'var(--jp-ui-font-color0)',
backgroundColor: 'transparent'
}
}}
>
<CloseIcon fontSize="small" />
</Button>
</span>
</Tooltip>
)}
</Box>
);
Expand Down
17 changes: 13 additions & 4 deletions packages/jupyter-chat/src/components/chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,16 @@ import {
IChatCommandRegistry,
IMessageFooterRegistry
} from '../registers';
import { ChatArea } from '../types';

export function ChatBody(props: Chat.IChatBodyProps): JSX.Element {
const { model } = props;
let { inputToolbarRegistry } = props;
if (!inputToolbarRegistry) {
inputToolbarRegistry = InputToolbarRegistry.defaultToolbarRegistry();
}
// const horizontalPadding = props.area === 'main' ? 8 : 4;
const horizontalPadding = 4;

return (
<AttachmentOpenerContext.Provider value={props.attachmentOpenerRegistry}>
Expand All @@ -42,18 +45,20 @@ export function ChatBody(props: Chat.IChatBodyProps): JSX.Element {
inputToolbarRegistry={inputToolbarRegistry}
messageFooterRegistry={props.messageFooterRegistry}
welcomeMessage={props.welcomeMessage}
area={props.area}
/>
<ChatInput
sx={{
paddingLeft: 4,
paddingRight: 4,
paddingLeft: horizontalPadding,
paddingRight: horizontalPadding,
paddingTop: 0,
paddingBottom: 0,
borderTop: '1px solid var(--jp-border-color1)'
paddingBottom: 0
}}
model={model.input}
chatCommandRegistry={props.chatCommandRegistry}
toolbarRegistry={inputToolbarRegistry}
area={props.area}
chatModel={model}
/>
</AttachmentOpenerContext.Provider>
);
Expand Down Expand Up @@ -141,6 +146,10 @@ export namespace Chat {
* The welcome message.
*/
welcomeMessage?: string;
/**
* The area where the chat is displayed.
*/
area?: ChatArea;
}

/**
Expand Down
84 changes: 56 additions & 28 deletions packages/jupyter-chat/src/components/code-blocks/code-toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@
*/

import { addAboveIcon, addBelowIcon } from '@jupyterlab/ui-components';
import { Box } from '@mui/material';
import { Box, IconButton, Tooltip } from '@mui/material';
import React, { useEffect, useState } from 'react';

import { CopyButton } from './copy-button';
import { TooltippedIconButton } from '../mui-extras/tooltipped-icon-button';
import { IActiveCellManager } from '../../active-cell-manager';
import { replaceCellIcon } from '../../icons';
import { IChatModel } from '../../model';
Expand Down Expand Up @@ -82,8 +81,7 @@ export function CodeToolbar(props: CodeToolbarProps): JSX.Element {
alignItems: 'center',
padding: '2px 2px',
marginBottom: '1em',
border: '1px solid var(--jp-cell-editor-border-color)',
borderTop: 'none'
border: 'none'
}}
className={CODE_TOOLBAR_CLASS}
>
Expand Down Expand Up @@ -116,14 +114,24 @@ function InsertAboveButton(props: ToolbarButtonProps) {
: 'Insert above active cell (no active cell)';

return (
<TooltippedIconButton
className={props.className}
tooltip={tooltip}
onClick={() => props.activeCellManager?.insertAbove(props.content)}
disabled={!props.activeCellAvailable}
>
<addAboveIcon.react height="16px" width="16px" />
</TooltippedIconButton>
<Tooltip title={tooltip} placement="top" arrow>
<span>
<IconButton
className={props.className}
onClick={() => props.activeCellManager?.insertAbove(props.content)}
disabled={!props.activeCellAvailable}
aria-label={tooltip}
sx={{
lineHeight: 0,
'&.Mui-disabled': {
opacity: 0.5
}
}}
>
<addAboveIcon.react height="16px" width="16px" />
</IconButton>
</span>
</Tooltip>
);
}

Expand All @@ -133,14 +141,24 @@ function InsertBelowButton(props: ToolbarButtonProps) {
: 'Insert below active cell (no active cell)';

return (
<TooltippedIconButton
className={props.className}
tooltip={tooltip}
disabled={!props.activeCellAvailable}
onClick={() => props.activeCellManager?.insertBelow(props.content)}
>
<addBelowIcon.react height="16px" width="16px" />
</TooltippedIconButton>
<Tooltip title={tooltip} placement="top" arrow>
<span>
<IconButton
className={props.className}
disabled={!props.activeCellAvailable}
onClick={() => props.activeCellManager?.insertBelow(props.content)}
aria-label={tooltip}
sx={{
lineHeight: 0,
'&.Mui-disabled': {
opacity: 0.5
}
}}
>
<addBelowIcon.react height="16px" width="16px" />
</IconButton>
</span>
</Tooltip>
);
}

Expand Down Expand Up @@ -169,13 +187,23 @@ function ReplaceButton(props: ToolbarButtonProps) {
};

return (
<TooltippedIconButton
className={props.className}
tooltip={tooltip}
disabled={disabled}
onClick={replace}
>
<replaceCellIcon.react height="16px" width="16px" />
</TooltippedIconButton>
<Tooltip title={tooltip} placement="top" arrow>
<span>
<IconButton
className={props.className}
disabled={disabled}
onClick={replace}
aria-label={tooltip}
sx={{
lineHeight: 0,
'&.Mui-disabled': {
opacity: 0.5
}
}}
>
<replaceCellIcon.react height="16px" width="16px" />
</IconButton>
</span>
</Tooltip>
);
}
33 changes: 21 additions & 12 deletions packages/jupyter-chat/src/components/code-blocks/copy-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
import React, { useState, useCallback, useRef } from 'react';

import { copyIcon } from '@jupyterlab/ui-components';

import { TooltippedIconButton } from '../mui-extras/tooltipped-icon-button';
import { IconButton, Tooltip } from '@mui/material';

enum CopyStatus {
None,
Expand Down Expand Up @@ -59,16 +58,26 @@ export function CopyButton(props: CopyButtonProps): JSX.Element {
);
}, [copyStatus, props.value]);

const tooltip = COPYBTN_TEXT_BY_STATUS[copyStatus];

return (
<TooltippedIconButton
disabled={isCopyDisabled}
className={props.className}
tooltip={COPYBTN_TEXT_BY_STATUS[copyStatus]}
placement="top"
onClick={copy}
aria-label="Copy to clipboard"
>
<copyIcon.react height="16px" width="16px" />
</TooltippedIconButton>
<Tooltip title={tooltip} placement="top" arrow>
<span>
<IconButton
disabled={isCopyDisabled}
className={props.className}
onClick={copy}
aria-label="Copy to clipboard"
sx={{
lineHeight: 0,
'&.Mui-disabled': {
opacity: 0.5
}
}}
>
<copyIcon.react height="16px" width="16px" />
</IconButton>
</span>
</Tooltip>
);
}
1 change: 0 additions & 1 deletion packages/jupyter-chat/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,4 @@ export * from './code-blocks';
export * from './input';
export * from './jl-theme-provider';
export * from './messages';
export * from './mui-extras';
export * from './scroll-container';
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,18 @@ export function AttachButton(
tooltip={tooltip}
buttonProps={{
size: 'small',
variant: 'contained',
variant: 'text',
title: tooltip,
className: ATTACH_BUTTON_CLASS
}}
sx={{
width: '24px',
height: '24px',
minWidth: '24px',
color: 'gray'
}}
>
<AttachFileIcon />
<AttachFileIcon sx={{ fontSize: '16px ' }} />
</TooltippedButton>
);
}
Loading
Loading