Skip to content

Commit 4470155

Browse files
JasonWeilldlqqq
andauthored
add JS lint workflow (jupyterlab#230)
* Fixes error and omission in config files * Changes after running jlpm lint once * Modifies eslint config * Ignores dotfiles * Fix lint errors * Fixes or ignores eslint warnings * Add new workflow * Fix error in config * Runs linting only on Linux * Uses standard GitHub action for setup-node * Force yarn used for caching * Simplify test config * Adds reopen trigger * fix lint workflow --------- Co-authored-by: David L. Qiu <[email protected]>
1 parent f32c699 commit 4470155

25 files changed

+175
-134
lines changed

.github/workflows/lint.yml

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
name: JS Lint
2+
on:
3+
- pull_request
4+
5+
jobs:
6+
js_lint:
7+
name: JS Lint
8+
runs-on: ubuntu-latest
9+
steps:
10+
- uses: actions/checkout@v3
11+
- uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
12+
with:
13+
python_version: "3.10.x"
14+
- name: Install JupyterLab
15+
run: pip install jupyterlab~=3.6
16+
- name: Install JS dependencies
17+
run: jlpm
18+
- name: Run JS Lint
19+
run: jlpm lerna run lint:check

packages/jupyter-ai/.eslintignore

+2
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@ tests
66

77
**/__tests__
88
ui-tests
9+
10+
.*.js

packages/jupyter-ai/.eslintrc.js

+6-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ module.exports = {
77
],
88
parser: '@typescript-eslint/parser',
99
parserOptions: {
10+
tsconfigRootDir: __dirname,
1011
project: './tsconfig.json'
1112
},
1213
plugins: ['@typescript-eslint'],
@@ -35,7 +36,9 @@ module.exports = {
3536
eqeqeq: 'error',
3637
'prefer-arrow-callback': 'error'
3738
},
38-
overrides: {
39-
files: ['src/**/*']
40-
}
39+
overrides: [
40+
{
41+
files: ['src/**/*']
42+
}
43+
]
4144
};

packages/jupyter-ai/.stylelintrc

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
],
77
"rules": {
88
"property-no-vendor-prefix": null,
9+
"selector-class-pattern": null,
910
"selector-no-vendor-prefix": null,
1011
"value-no-vendor-prefix": null
1112
}

packages/jupyter-ai/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ First, install [conda](https://conda.io/projects/conda/en/latest/user-guide/inst
3434

3535
If you are using an Apple Silicon-based Mac (M1, M1 Pro, M2, etc.), you need to uninstall the `pip` provided version of `grpcio` and install the version provided by `conda` instead.
3636

37-
$ pip uninstall grpcio; conda install grpcio
37+
$ pip uninstall grpcio; conda install grpcio
3838

3939
## Uninstall
4040

packages/jupyter-ai/src/chat_handler.ts

+24-25
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export class ChatHandler implements IDisposable {
1414
/**
1515
* ID of the connection. Requires `await initialize()`.
1616
*/
17-
id: string = '';
17+
id = '';
1818

1919
/**
2020
* Create a new chat handler.
@@ -29,11 +29,10 @@ export class ChatHandler implements IDisposable {
2929
* resolved when server acknowledges connection and sends the client ID. This
3030
* must be awaited before calling any other method.
3131
*/
32-
public async initialize() {
32+
public async initialize(): Promise<void> {
3333
await this._initialize();
3434
}
3535

36-
3736
/**
3837
* Sends a message across the WebSocket. Promise resolves to the message ID
3938
* when the server sends the same message back, acknowledging receipt.
@@ -141,13 +140,13 @@ export class ChatHandler implements IDisposable {
141140
> = {};
142141

143142
private _onClose(e: CloseEvent, reject: any) {
144-
reject(new Error("Chat UI websocket disconnected"))
145-
console.error("Chat UI websocket disconnected")
143+
reject(new Error('Chat UI websocket disconnected'));
144+
console.error('Chat UI websocket disconnected');
146145
// only attempt re-connect if there was an abnormal closure
147146
// WebSocket status codes defined in RFC 6455: https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1
148147
if (e.code === 1006) {
149-
const delaySeconds = 1
150-
console.info(`Will try to reconnect in ${delaySeconds} s.`)
148+
const delaySeconds = 1;
149+
console.info(`Will try to reconnect in ${delaySeconds} s.`);
151150
setTimeout(async () => await this._initialize(), delaySeconds * 1000);
152151
}
153152
}
@@ -157,28 +156,28 @@ export class ChatHandler implements IDisposable {
157156
if (this.isDisposed) {
158157
return;
159158
}
160-
console.log("Creating a new websocket connection for chat...");
159+
console.log('Creating a new websocket connection for chat...');
161160
const { token, WebSocket, wsUrl } = this.serverSettings;
162161
const url =
163162
URLExt.join(wsUrl, CHAT_SERVICE_URL) +
164163
(token ? `?token=${encodeURIComponent(token)}` : '');
165-
166-
const socket = (this._socket = new WebSocket(url));
167-
socket.onclose = (e) => this._onClose(e, reject);
168-
socket.onerror = (e) => reject(e);
169-
socket.onmessage = msg =>
170-
msg.data && this._onMessage(JSON.parse(msg.data));
171-
172-
const listenForConnection = (message: AiService.Message) => {
173-
if (message.type !== 'connection') {
174-
return;
175-
}
176-
this.id = message.client_id;
177-
resolve();
178-
this.removeListener(listenForConnection);
179-
};
180-
181-
this.addListener(listenForConnection);
164+
165+
const socket = (this._socket = new WebSocket(url));
166+
socket.onclose = e => this._onClose(e, reject);
167+
socket.onerror = e => reject(e);
168+
socket.onmessage = msg =>
169+
msg.data && this._onMessage(JSON.parse(msg.data));
170+
171+
const listenForConnection = (message: AiService.Message) => {
172+
if (message.type !== 'connection') {
173+
return;
174+
}
175+
this.id = message.client_id;
176+
resolve();
177+
this.removeListener(listenForConnection);
178+
};
179+
180+
this.addListener(listenForConnection);
182181
});
183182
}
184183

packages/jupyter-ai/src/components/chat-code-view.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export function ChatCodeView({
8383
inline,
8484
className,
8585
...props
86-
}: ChatCodeViewProps) {
86+
}: ChatCodeViewProps): JSX.Element {
8787
const match = /language-(\w+)/.exec(className || '');
8888
return inline ? (
8989
<ChatCodeInline {...props} />

packages/jupyter-ai/src/components/chat-input.tsx

+32-26
Original file line numberDiff line numberDiff line change
@@ -27,53 +27,59 @@ type ChatInputProps = {
2727
};
2828

2929
export function ChatInput(props: ChatInputProps): JSX.Element {
30-
3130
function handleKeyDown(event: React.KeyboardEvent<HTMLInputElement>) {
32-
if (event.key === 'Enter' && (
33-
(props.sendWithShiftEnter && event.shiftKey)
34-
|| (!props.sendWithShiftEnter && !event.shiftKey)
35-
)) {
31+
if (
32+
event.key === 'Enter' &&
33+
((props.sendWithShiftEnter && event.shiftKey) ||
34+
(!props.sendWithShiftEnter && !event.shiftKey))
35+
) {
3636
props.onSend();
3737
event.stopPropagation();
3838
event.preventDefault();
3939
}
4040
}
4141

4242
// Set the helper text based on whether Shift+Enter is used for sending.
43-
const helperText = props.sendWithShiftEnter
44-
? <span>Press <b>Shift</b>+<b>Enter</b> to send message</span>
45-
: <span>Press <b>Shift</b>+<b>Enter</b> to add a new line</span>;
43+
const helperText = props.sendWithShiftEnter ? (
44+
<span>
45+
Press <b>Shift</b>+<b>Enter</b> to send message
46+
</span>
47+
) : (
48+
<span>
49+
Press <b>Shift</b>+<b>Enter</b> to add a new line
50+
</span>
51+
);
4652

4753
return (
4854
<Box sx={props.sx}>
49-
<Box sx={{ display: 'flex'}}>
55+
<Box sx={{ display: 'flex' }}>
5056
<TextField
5157
value={props.value}
5258
onChange={e => props.onChange(e.target.value)}
5359
fullWidth
5460
variant="outlined"
5561
multiline
5662
onKeyDown={handleKeyDown}
57-
placeholder='Ask Jupyternaut anything'
63+
placeholder="Ask Jupyternaut anything"
5864
InputProps={{
5965
endAdornment: (
60-
<InputAdornment position="end">
61-
<IconButton
62-
size="small"
63-
color="primary"
64-
onClick={props.onSend}
65-
disabled={!props.value.trim().length}
66-
title='Send message (SHIFT+ENTER)'
67-
>
68-
<SendIcon />
69-
</IconButton>
70-
</InputAdornment>
66+
<InputAdornment position="end">
67+
<IconButton
68+
size="small"
69+
color="primary"
70+
onClick={props.onSend}
71+
disabled={!props.value.trim().length}
72+
title="Send message (SHIFT+ENTER)"
73+
>
74+
<SendIcon />
75+
</IconButton>
76+
</InputAdornment>
7177
)
72-
}}
73-
FormHelperTextProps={{
74-
sx: {marginLeft: 'auto', marginRight: 0}
75-
}}
76-
helperText={props.value.length > 2 ? helperText : ' '}
78+
}}
79+
FormHelperTextProps={{
80+
sx: { marginLeft: 'auto', marginRight: 0 }
81+
}}
82+
helperText={props.value.length > 2 ? helperText : ' '}
7783
/>
7884
</Box>
7985
{props.hasSelection && (

packages/jupyter-ai/src/components/chat-messages.tsx

+8-9
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ type ChatMessageHeaderProps = {
2222
sx?: SxProps<Theme>;
2323
};
2424

25-
export function ChatMessageHeader(props: ChatMessageHeaderProps) {
25+
export function ChatMessageHeader(props: ChatMessageHeaderProps): JSX.Element {
2626
const collaborators = useCollaboratorsContext();
2727

2828
const sharedStyles: SxProps<Theme> = {
@@ -101,25 +101,24 @@ export function ChatMessageHeader(props: ChatMessageHeaderProps) {
101101
);
102102
}
103103

104-
export function ChatMessages(props: ChatMessagesProps) {
104+
export function ChatMessages(props: ChatMessagesProps): JSX.Element {
105105
const [timestamps, setTimestamps] = useState<Record<string, string>>({});
106106

107107
/**
108108
* Effect: update cached timestamp strings upon receiving a new message.
109109
*/
110110
useEffect(() => {
111111
const newTimestamps: Record<string, string> = { ...timestamps };
112-
let timestampAdded: boolean = false;
112+
let timestampAdded = false;
113113

114114
for (const message of props.messages) {
115115
if (!(message.id in newTimestamps)) {
116116
// Use the browser's default locale
117-
newTimestamps[message.id] =
118-
new Date(message.time * 1000) // Convert message time to milliseconds
119-
.toLocaleTimeString([], {
120-
hour: 'numeric', // Avoid leading zero for hours; we don't want "03:15 PM"
121-
minute: '2-digit'
122-
});
117+
newTimestamps[message.id] = new Date(message.time * 1000) // Convert message time to milliseconds
118+
.toLocaleTimeString([], {
119+
hour: 'numeric', // Avoid leading zero for hours; we don't want "03:15 PM"
120+
minute: '2-digit'
121+
});
123122

124123
timestampAdded = true;
125124
}

packages/jupyter-ai/src/components/chat-settings.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ function fromRegistryProvider(
7474
* provider is not a registry provider. Otherwise, it is set to
7575
* `<provider-id>:*`.
7676
*/
77-
export function ChatSettings() {
77+
export function ChatSettings(): JSX.Element {
7878
const [state, setState] = useState<ChatSettingsState>(
7979
ChatSettingsState.Loading
8080
);

packages/jupyter-ai/src/components/chat.tsx

+16-13
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,13 @@ import { ScrollContainer } from './scroll-container';
2121

2222
type ChatBodyProps = {
2323
chatHandler: ChatHandler;
24-
setChatView: (view: ChatView) => void
24+
setChatView: (view: ChatView) => void;
2525
};
2626

27-
function ChatBody({ chatHandler, setChatView: chatViewHandler }: ChatBodyProps): JSX.Element {
27+
function ChatBody({
28+
chatHandler,
29+
setChatView: chatViewHandler
30+
}: ChatBodyProps): JSX.Element {
2831
const [messages, setMessages] = useState<AiService.ChatMessage[]>([]);
2932
const [showWelcomeMessage, setShowWelcomeMessage] = useState<boolean>(false);
3033
const [includeSelection, setIncludeSelection] = useState(true);
@@ -106,9 +109,9 @@ function ChatBody({ chatHandler, setChatView: chatViewHandler }: ChatBodyProps):
106109
};
107110

108111
const openSettingsView = () => {
109-
setShowWelcomeMessage(false)
110-
chatViewHandler(ChatView.Settings)
111-
}
112+
setShowWelcomeMessage(false);
113+
chatViewHandler(ChatView.Settings);
114+
};
112115

113116
if (showWelcomeMessage) {
114117
return (
@@ -127,13 +130,13 @@ function ChatBody({ chatHandler, setChatView: chatViewHandler }: ChatBodyProps):
127130
model to chat with from the settings panel. You will also likely
128131
need to provide API credentials, so be sure to have those handy.
129132
</p>
130-
<Button
131-
variant="contained"
132-
startIcon={<SettingsIcon />}
133+
<Button
134+
variant="contained"
135+
startIcon={<SettingsIcon />}
133136
size={'large'}
134137
onClick={() => openSettingsView()}
135-
>
136-
Start Here
138+
>
139+
Start Here
137140
</Button>
138141
</Stack>
139142
</Box>
@@ -175,15 +178,15 @@ export type ChatProps = {
175178
selectionWatcher: SelectionWatcher;
176179
chatHandler: ChatHandler;
177180
globalAwareness: Awareness | null;
178-
chatView?: ChatView
181+
chatView?: ChatView;
179182
};
180183

181184
enum ChatView {
182185
Chat,
183186
Settings
184187
}
185188

186-
export function Chat(props: ChatProps) {
189+
export function Chat(props: ChatProps): JSX.Element {
187190
const [view, setView] = useState<ChatView>(props.chatView || ChatView.Chat);
188191

189192
return (
@@ -221,7 +224,7 @@ export function Chat(props: ChatProps) {
221224
</Box>
222225
{/* body */}
223226
{view === ChatView.Chat && (
224-
<ChatBody chatHandler={props.chatHandler} setChatView={setView}/>
227+
<ChatBody chatHandler={props.chatHandler} setChatView={setView} />
225228
)}
226229
{view === ChatView.Settings && <ChatSettings />}
227230
</Box>

packages/jupyter-ai/src/components/jl-theme-provider.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import React, { useState, useEffect } from 'react';
33
import { Theme, ThemeProvider, createTheme } from '@mui/material/styles';
44
import { getJupyterLabTheme } from '../theme-provider';
55

6-
export function JlThemeProvider(props: { children: React.ReactNode }) {
6+
export function JlThemeProvider(props: {
7+
children: React.ReactNode;
8+
}): JSX.Element {
79
const [theme, setTheme] = useState<Theme>(createTheme());
810

911
useEffect(() => {

0 commit comments

Comments
 (0)