jupyterlab_ai_code_assistants_extension 0.6.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +29 -0
- package/README.md +44 -0
- package/lib/core/colour.d.ts +57 -0
- package/lib/core/colour.js +190 -0
- package/lib/core/icons.d.ts +12 -0
- package/lib/core/icons.js +99 -0
- package/lib/core/modes.d.ts +13 -0
- package/lib/core/modes.js +36 -0
- package/lib/core/panel.d.ts +233 -0
- package/lib/core/panel.js +1673 -0
- package/lib/core/popup.d.ts +40 -0
- package/lib/core/popup.js +404 -0
- package/lib/core/registry.d.ts +24 -0
- package/lib/core/registry.js +48 -0
- package/lib/core/request.d.ts +25 -0
- package/lib/core/request.js +64 -0
- package/lib/core/terminals.d.ts +129 -0
- package/lib/core/terminals.js +344 -0
- package/lib/core/types.d.ts +269 -0
- package/lib/core/types.js +6 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.js +277 -0
- package/lib/providers/claude.d.ts +3 -0
- package/lib/providers/claude.js +88 -0
- package/lib/providers/codex.d.ts +3 -0
- package/lib/providers/codex.js +79 -0
- package/lib/providers/gemini.d.ts +2 -0
- package/lib/providers/gemini.js +73 -0
- package/lib/providers/index.d.ts +3 -0
- package/lib/providers/index.js +16 -0
- package/lib/providers/kimi.d.ts +23 -0
- package/lib/providers/kimi.js +139 -0
- package/package.json +169 -0
- package/schema/plugin.json +93 -0
- package/src/__tests__/colour.spec.ts +153 -0
- package/src/__tests__/core-neutrality.spec.ts +86 -0
- package/src/__tests__/labels.spec.ts +89 -0
- package/src/__tests__/launch-mode.spec.ts +53 -0
- package/src/__tests__/registry.spec.ts +178 -0
- package/src/core/colour.ts +224 -0
- package/src/core/icons.ts +121 -0
- package/src/core/modes.ts +45 -0
- package/src/core/panel.ts +2020 -0
- package/src/core/popup.ts +475 -0
- package/src/core/registry.ts +60 -0
- package/src/core/request.ts +96 -0
- package/src/core/terminals.ts +446 -0
- package/src/core/types.ts +305 -0
- package/src/index.ts +356 -0
- package/src/providers/claude.ts +102 -0
- package/src/providers/codex.ts +89 -0
- package/src/providers/gemini.ts +80 -0
- package/src/providers/index.ts +21 -0
- package/src/providers/kimi.ts +155 -0
- package/style/base.css +888 -0
- package/style/index.css +1 -0
- package/style/index.js +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Stellars Henson
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# jupyterlab_ai_code_assistants_extension
|
|
2
|
+
|
|
3
|
+
[](https://github.com/stellarshenson/jupyterlab_ai_code_assistants_extension/actions/workflows/build.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/jupyterlab_ai_code_assistants_extension)
|
|
5
|
+
[](https://pypi.org/project/jupyterlab-ai-code-assistants-extension/)
|
|
6
|
+
[](https://pepy.tech/project/jupyterlab-ai-code-assistants-extension)
|
|
7
|
+
[](https://jupyterlab.readthedocs.io/en/stable/)
|
|
8
|
+
[](https://kolomolo.com)
|
|
9
|
+
[](https://www.paypal.com/donate/?hosted_button_id=B4KPBJDLLXTSA)
|
|
10
|
+
|
|
11
|
+
One extension for every AI code assistant in JupyterLab. Replaces the separate `jupyterlab_claude_code_extension`, `jupyterlab_codex_extension` and `jupyterlab_kimi_code_extension` packages with a single install that keeps each assistant's own side panel while sharing one settings page and one session-handling core underneath.
|
|
12
|
+
|
|
13
|
+
> [!NOTE]
|
|
14
|
+
> Early development. The scaffolding, build lifecycle and packaging are in place; the assistant panels are being ported from the standalone extensions.
|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
- **One install, every assistant** - Claude Code, Codex, Kimi and Gemini supported from a single package
|
|
19
|
+
- **Individual right-panel per assistant** - each assistant keeps its own side panel with Favorites, Recent and All projects
|
|
20
|
+
- **Joint settings page** - one settings section covering all assistants, replacing three separate ones
|
|
21
|
+
- **Per-assistant toggles** - switch any assistant's support on or off in settings; all are enabled by default
|
|
22
|
+
- **Shared session core** - resume, branch, switch and clean up conversations through one common mechanism across assistants
|
|
23
|
+
- **Auto-disabled when absent** - an assistant whose CLI is not installed does not show a panel
|
|
24
|
+
|
|
25
|
+
## Requirements
|
|
26
|
+
|
|
27
|
+
- JupyterLab >= 4.0.0
|
|
28
|
+
- Python >= 3.10
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
To install the extension, execute:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install jupyterlab_ai_code_assistants_extension
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Uninstall
|
|
39
|
+
|
|
40
|
+
To remove the extension, execute:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip uninstall jupyterlab_ai_code_assistants_extension
|
|
44
|
+
```
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { ServerConnection } from '@jupyterlab/services';
|
|
2
|
+
import { ColourSource, ISession } from './types';
|
|
3
|
+
/** The colour vocabulary of `jupyterlab_colourful_tab_extension`, in its own
|
|
4
|
+
* order - that extension owns the tab CSS and these six ids; this module only
|
|
5
|
+
* feeds it one of them. The order is load-bearing: its localStorage records a
|
|
6
|
+
* user's menu choice as an INDEX into this list. */
|
|
7
|
+
export declare const TAB_COLOUR_IDS: readonly string[];
|
|
8
|
+
/** Map a conversation id onto one of the six colour ids. FNV-1a (32-bit) - a
|
|
9
|
+
* stable string hash with good avalanche on short hex-ish ids; `Math.imul`
|
|
10
|
+
* keeps the multiplication in 32-bit integer semantics. The same id always
|
|
11
|
+
* yields the same colour, on every reload and in every browser. */
|
|
12
|
+
export declare function fnv1aColour(sessionId: string): string;
|
|
13
|
+
/** Colour the user set by hand on a terminal tab, or null.
|
|
14
|
+
*
|
|
15
|
+
* The colourful-tab extension stores its right-click menu choices as
|
|
16
|
+
* `{ "terminal:<session name>": <index into TAB_COLOUR_IDS> }`. Reading it is
|
|
17
|
+
* what makes "set the colour on the tab" register as the conversation's
|
|
18
|
+
* colour for assistants whose CLI has no colour concept at all. */
|
|
19
|
+
export declare function readUserSetTabColour(terminalName: string): string | null;
|
|
20
|
+
/** Resolve the ladder for one conversation. `userSet` is this extension's own
|
|
21
|
+
* store, `native` the conversation's own colour (only ever populated by a
|
|
22
|
+
* `native` provider). */
|
|
23
|
+
export declare function effectiveColour(sessionId: string | null, source: ColourSource, userSet: string | null, native: string | null | undefined): string | null;
|
|
24
|
+
/** Fall back from an unreadable conversation to the project a terminal sits
|
|
25
|
+
* in: longest matching path wins, so a nested project beats its parent.
|
|
26
|
+
* Never the first resort - a row carries only its representative
|
|
27
|
+
* conversation, so resolving colour by row tints every terminal of a project
|
|
28
|
+
* with the representative's colour. */
|
|
29
|
+
export declare function sessionForCwds(cwds: string[], sessions: ISession[]): ISession | null;
|
|
30
|
+
/**
|
|
31
|
+
* Client for one provider's user-set colour store.
|
|
32
|
+
*
|
|
33
|
+
* The store is server-side and per provider (`providers/<id>/colours`), so
|
|
34
|
+
* colours survive a reload and one assistant's colours can never be read or
|
|
35
|
+
* written through another's routes. Reads are cached in memory and refreshed
|
|
36
|
+
* with the session poll; writes go straight through and update the cache.
|
|
37
|
+
*/
|
|
38
|
+
export declare class ColourStore {
|
|
39
|
+
constructor(providerId: string, serverSettings: ServerConnection.ISettings);
|
|
40
|
+
/** User-set colour for a conversation, from the in-memory cache. */
|
|
41
|
+
get(sessionId: string | null | undefined): string | null;
|
|
42
|
+
/** Reload the whole store. Failures leave the previous cache in place - a
|
|
43
|
+
* momentarily unreachable server must not clear every user colour. */
|
|
44
|
+
load(): Promise<void>;
|
|
45
|
+
/** Record a colour as this conversation's own. Passing null drops the entry,
|
|
46
|
+
* which is also how a deleted conversation leaves no orphan key behind. */
|
|
47
|
+
set(sessionId: string, colour: string | null): Promise<void>;
|
|
48
|
+
/** Drop stored colours for conversations that no longer exist. */
|
|
49
|
+
forget(sessionIds: string[]): Promise<void>;
|
|
50
|
+
/** Copy the parent's EFFECTIVE colour onto a fresh branch, so a branch of a
|
|
51
|
+
* branch inherits whatever its immediate parent actually shows, user-set
|
|
52
|
+
* override included. Called at the fork sites, once the new id is known. */
|
|
53
|
+
inherit(parentColour: string | null, childId: string): Promise<void>;
|
|
54
|
+
private readonly _providerId;
|
|
55
|
+
private readonly _serverSettings;
|
|
56
|
+
private _colours;
|
|
57
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// Terminal tab tint per conversation.
|
|
2
|
+
//
|
|
3
|
+
// Effective colour, highest wins:
|
|
4
|
+
// 1. user-set - the write-back store this module keeps, per provider
|
|
5
|
+
// 2. native - the conversation's own colour, when the assistant has one
|
|
6
|
+
// 3. derived - a stable hash of the conversation id
|
|
7
|
+
// 4. none - no tint
|
|
8
|
+
//
|
|
9
|
+
// Every step of that ladder is pure and exported on its own, because the tint
|
|
10
|
+
// is the part of this panel that regressed twice behind a green suite that
|
|
11
|
+
// never ran the logic.
|
|
12
|
+
import { requestProvider } from './request';
|
|
13
|
+
/** The colour vocabulary of `jupyterlab_colourful_tab_extension`, in its own
|
|
14
|
+
* order - that extension owns the tab CSS and these six ids; this module only
|
|
15
|
+
* feeds it one of them. The order is load-bearing: its localStorage records a
|
|
16
|
+
* user's menu choice as an INDEX into this list. */
|
|
17
|
+
export const TAB_COLOUR_IDS = [
|
|
18
|
+
'rose',
|
|
19
|
+
'peach',
|
|
20
|
+
'lemon',
|
|
21
|
+
'mint',
|
|
22
|
+
'sky',
|
|
23
|
+
'lavender'
|
|
24
|
+
];
|
|
25
|
+
/** Key `jupyterlab_colourful_tab_extension` persists menu-set tab colours
|
|
26
|
+
* under, and the `terminal:<session name>` shape of its entries. Read only -
|
|
27
|
+
* this module never writes into another extension's storage. */
|
|
28
|
+
const TAB_COLOUR_STORAGE_KEY = 'jupyterlab-colourful-tab-colours';
|
|
29
|
+
/** Map a conversation id onto one of the six colour ids. FNV-1a (32-bit) - a
|
|
30
|
+
* stable string hash with good avalanche on short hex-ish ids; `Math.imul`
|
|
31
|
+
* keeps the multiplication in 32-bit integer semantics. The same id always
|
|
32
|
+
* yields the same colour, on every reload and in every browser. */
|
|
33
|
+
export function fnv1aColour(sessionId) {
|
|
34
|
+
let hash = 0x811c9dc5; // FNV offset basis
|
|
35
|
+
for (let i = 0; i < sessionId.length; i++) {
|
|
36
|
+
hash ^= sessionId.charCodeAt(i);
|
|
37
|
+
hash = Math.imul(hash, 0x01000193); // FNV prime
|
|
38
|
+
}
|
|
39
|
+
return TAB_COLOUR_IDS[(hash >>> 0) % TAB_COLOUR_IDS.length];
|
|
40
|
+
}
|
|
41
|
+
/** Colour the user set by hand on a terminal tab, or null.
|
|
42
|
+
*
|
|
43
|
+
* The colourful-tab extension stores its right-click menu choices as
|
|
44
|
+
* `{ "terminal:<session name>": <index into TAB_COLOUR_IDS> }`. Reading it is
|
|
45
|
+
* what makes "set the colour on the tab" register as the conversation's
|
|
46
|
+
* colour for assistants whose CLI has no colour concept at all. */
|
|
47
|
+
export function readUserSetTabColour(terminalName) {
|
|
48
|
+
var _a;
|
|
49
|
+
try {
|
|
50
|
+
const raw = window.localStorage.getItem(TAB_COLOUR_STORAGE_KEY);
|
|
51
|
+
if (!raw) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
const parsed = JSON.parse(raw);
|
|
55
|
+
const index = parsed[`terminal:${terminalName}`];
|
|
56
|
+
if (typeof index !== 'number') {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
return (_a = TAB_COLOUR_IDS[index]) !== null && _a !== void 0 ? _a : null;
|
|
60
|
+
}
|
|
61
|
+
catch (_err) {
|
|
62
|
+
// localStorage unavailable, or another extension's schema changed under
|
|
63
|
+
// us - no user colour is a valid answer, a thrown error is not.
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/** Resolve the ladder for one conversation. `userSet` is this extension's own
|
|
68
|
+
* store, `native` the conversation's own colour (only ever populated by a
|
|
69
|
+
* `native` provider). */
|
|
70
|
+
export function effectiveColour(sessionId, source, userSet, native) {
|
|
71
|
+
if (userSet) {
|
|
72
|
+
return userSet;
|
|
73
|
+
}
|
|
74
|
+
if (source === 'native') {
|
|
75
|
+
return native !== null && native !== void 0 ? native : null;
|
|
76
|
+
}
|
|
77
|
+
if (source === 'derived' && sessionId) {
|
|
78
|
+
return fnv1aColour(sessionId);
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
/** Fall back from an unreadable conversation to the project a terminal sits
|
|
83
|
+
* in: longest matching path wins, so a nested project beats its parent.
|
|
84
|
+
* Never the first resort - a row carries only its representative
|
|
85
|
+
* conversation, so resolving colour by row tints every terminal of a project
|
|
86
|
+
* with the representative's colour. */
|
|
87
|
+
export function sessionForCwds(cwds, sessions) {
|
|
88
|
+
let best = null;
|
|
89
|
+
let bestLen = -1;
|
|
90
|
+
for (const raw of cwds) {
|
|
91
|
+
const cwd = raw.replace(/\/+$/, '');
|
|
92
|
+
for (const s of sessions) {
|
|
93
|
+
const p = s.project_path.replace(/\/+$/, '');
|
|
94
|
+
if ((cwd === p || cwd.startsWith(p + '/')) && p.length > bestLen) {
|
|
95
|
+
best = s;
|
|
96
|
+
bestLen = p.length;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return best;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Client for one provider's user-set colour store.
|
|
104
|
+
*
|
|
105
|
+
* The store is server-side and per provider (`providers/<id>/colours`), so
|
|
106
|
+
* colours survive a reload and one assistant's colours can never be read or
|
|
107
|
+
* written through another's routes. Reads are cached in memory and refreshed
|
|
108
|
+
* with the session poll; writes go straight through and update the cache.
|
|
109
|
+
*/
|
|
110
|
+
export class ColourStore {
|
|
111
|
+
constructor(providerId, serverSettings) {
|
|
112
|
+
this._colours = new Map();
|
|
113
|
+
this._providerId = providerId;
|
|
114
|
+
this._serverSettings = serverSettings;
|
|
115
|
+
}
|
|
116
|
+
/** User-set colour for a conversation, from the in-memory cache. */
|
|
117
|
+
get(sessionId) {
|
|
118
|
+
var _a;
|
|
119
|
+
if (!sessionId) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
return (_a = this._colours.get(sessionId)) !== null && _a !== void 0 ? _a : null;
|
|
123
|
+
}
|
|
124
|
+
/** Reload the whole store. Failures leave the previous cache in place - a
|
|
125
|
+
* momentarily unreachable server must not clear every user colour. */
|
|
126
|
+
async load() {
|
|
127
|
+
var _a;
|
|
128
|
+
try {
|
|
129
|
+
const data = await requestProvider(this._providerId, 'colours', this._serverSettings, { cache: 'no-store' });
|
|
130
|
+
const next = new Map();
|
|
131
|
+
for (const [id, colour] of Object.entries((_a = data.colours) !== null && _a !== void 0 ? _a : {})) {
|
|
132
|
+
if (typeof colour === 'string' && colour) {
|
|
133
|
+
next.set(id, colour);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
this._colours = next;
|
|
137
|
+
}
|
|
138
|
+
catch (_err) {
|
|
139
|
+
// Keep the cache; the next poll retries.
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/** Record a colour as this conversation's own. Passing null drops the entry,
|
|
143
|
+
* which is also how a deleted conversation leaves no orphan key behind. */
|
|
144
|
+
async set(sessionId, colour) {
|
|
145
|
+
if (!sessionId) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (colour) {
|
|
149
|
+
this._colours.set(sessionId, colour);
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
this._colours.delete(sessionId);
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
await requestProvider(this._providerId, 'colours', this._serverSettings, {
|
|
156
|
+
method: 'POST',
|
|
157
|
+
body: JSON.stringify({ session_id: sessionId, colour })
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
catch (_err) {
|
|
161
|
+
// The cache already reflects the intent; the next load reconciles.
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/** Drop stored colours for conversations that no longer exist. */
|
|
165
|
+
async forget(sessionIds) {
|
|
166
|
+
const present = sessionIds.filter(id => this._colours.has(id));
|
|
167
|
+
if (present.length === 0) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
present.forEach(id => this._colours.delete(id));
|
|
171
|
+
try {
|
|
172
|
+
await requestProvider(this._providerId, 'colours', this._serverSettings, {
|
|
173
|
+
method: 'DELETE',
|
|
174
|
+
body: JSON.stringify({ session_ids: present })
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
catch (_err) {
|
|
178
|
+
// Same as `set`: the cache is authoritative until the next load.
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/** Copy the parent's EFFECTIVE colour onto a fresh branch, so a branch of a
|
|
182
|
+
* branch inherits whatever its immediate parent actually shows, user-set
|
|
183
|
+
* override included. Called at the fork sites, once the new id is known. */
|
|
184
|
+
async inherit(parentColour, childId) {
|
|
185
|
+
if (!parentColour || !childId) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
await this.set(childId, parentColour);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { LabIcon } from '@jupyterlab/ui-components';
|
|
2
|
+
export declare const assistantsIcon: LabIcon;
|
|
3
|
+
export declare const starFilledIcon: LabIcon;
|
|
4
|
+
export declare const refreshIcon: LabIcon;
|
|
5
|
+
export declare const removeIcon: LabIcon;
|
|
6
|
+
export declare const warningIcon: LabIcon;
|
|
7
|
+
export declare const addIcon: LabIcon;
|
|
8
|
+
export declare const branchIcon: LabIcon;
|
|
9
|
+
export declare const switchIcon: LabIcon;
|
|
10
|
+
export declare const filterIcon: LabIcon;
|
|
11
|
+
/** Build (or reuse) the LabIcon for a provider's own glyph. */
|
|
12
|
+
export declare function providerIcon(name: string, svgstr: string): LabIcon;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { LabIcon } from '@jupyterlab/ui-components';
|
|
2
|
+
// Shared panel chrome icons. Provider icons are NOT here - each provider
|
|
3
|
+
// carries its own SVG on its descriptor and the registry builds the LabIcon
|
|
4
|
+
// from it, so adding an assistant touches no core file.
|
|
5
|
+
//
|
|
6
|
+
// Material-style glyphs matched to the rest of the sidebar so header and menu
|
|
7
|
+
// icons render at identical size and theme.
|
|
8
|
+
const ICON_PREFIX = 'jupyterlab_ai_code_assistants_extension';
|
|
9
|
+
// The extension's own identity, used by the settings entry - one section for
|
|
10
|
+
// all assistants, so it cannot borrow any one assistant's glyph. Material
|
|
11
|
+
// "smart_toy" outline.
|
|
12
|
+
const assistantsSvgStr = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16">
|
|
13
|
+
<path class="jp-icon3" fill="#616161" d="M20 9V7c0-1.1-.9-2-2-2h-3c0-1.66-1.34-3-3-3S9 3.34 9 5H6c-1.1 0-2 .9-2 2v2c-1.66 0-3 1.34-3 3s1.34 3 3 3v4c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2v-4c1.66 0 3-1.34 3-3s-1.34-3-3-3zm-2 10H6V7h12v12zM9 13.5c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm7.5-1.5c0 .83-.67 1.5-1.5 1.5s-1.5-.67-1.5-1.5.67-1.5 1.5-1.5 1.5.67 1.5 1.5zM8 15h8v2H8v-2z"/>
|
|
14
|
+
</svg>`;
|
|
15
|
+
export const assistantsIcon = new LabIcon({
|
|
16
|
+
name: `${ICON_PREFIX}:assistants`,
|
|
17
|
+
svgstr: assistantsSvgStr
|
|
18
|
+
});
|
|
19
|
+
const starFilledSvgStr = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16">
|
|
20
|
+
<path class="jp-icon3" fill="#616161" d="M12 2.5l2.9 6.4 7 .7-5.3 4.7 1.6 6.9L12 17.7l-6.2 3.5 1.6-6.9-5.3-4.7 7-.7z"/>
|
|
21
|
+
</svg>`;
|
|
22
|
+
export const starFilledIcon = new LabIcon({
|
|
23
|
+
name: `${ICON_PREFIX}:star-filled`,
|
|
24
|
+
svgstr: starFilledSvgStr
|
|
25
|
+
});
|
|
26
|
+
const refreshSvgStr = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16">
|
|
27
|
+
<path class="jp-icon3" fill="#616161" d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/>
|
|
28
|
+
</svg>`;
|
|
29
|
+
export const refreshIcon = new LabIcon({
|
|
30
|
+
name: `${ICON_PREFIX}:refresh`,
|
|
31
|
+
svgstr: refreshSvgStr
|
|
32
|
+
});
|
|
33
|
+
const removeSvgStr = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16">
|
|
34
|
+
<path class="jp-icon3" fill="#616161" d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM8 9h8v10H8V9zm7.5-5l-1-1h-5l-1 1H5v2h14V4h-3.5z"/>
|
|
35
|
+
</svg>`;
|
|
36
|
+
export const removeIcon = new LabIcon({
|
|
37
|
+
name: `${ICON_PREFIX}:remove`,
|
|
38
|
+
svgstr: removeSvgStr
|
|
39
|
+
});
|
|
40
|
+
// Marks the unsafe launch variants. A shield reads as "protected", which is
|
|
41
|
+
// the inverse of what these entries do, so the caution triangle carries them
|
|
42
|
+
// instead. Material "warning" outline.
|
|
43
|
+
const warningSvgStr = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="13" height="13">
|
|
44
|
+
<path class="jp-icon3" fill="#616161" d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/>
|
|
45
|
+
</svg>`;
|
|
46
|
+
export const warningIcon = new LabIcon({
|
|
47
|
+
name: `${ICON_PREFIX}:warning`,
|
|
48
|
+
svgstr: warningSvgStr
|
|
49
|
+
});
|
|
50
|
+
const addSvgStr = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16">
|
|
51
|
+
<path class="jp-icon3" fill="#616161" d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
|
|
52
|
+
</svg>`;
|
|
53
|
+
export const addIcon = new LabIcon({
|
|
54
|
+
name: `${ICON_PREFIX}:add`,
|
|
55
|
+
svgstr: addSvgStr
|
|
56
|
+
});
|
|
57
|
+
// Git-branch glyph (Octicons git-branch-16, MIT) - marks rows carrying
|
|
58
|
+
// parallel conversations and the Branch Session entries.
|
|
59
|
+
const branchSvgStr = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16">
|
|
60
|
+
<path class="jp-icon3" fill="#616161" d="M9.5 3.25a2.25 2.25 0 1 1 3 2.122V6A2.5 2.5 0 0 1 10 8.5H6a1 1 0 0 0-1 1v1.128a2.251 2.251 0 1 1-1.5 0V5.372a2.25 2.25 0 1 1 1.5 0v1.836A2.493 2.493 0 0 1 6 7h4a1 1 0 0 0 1-1v-.628a2.25 2.25 0 0 1-1.5-2.122Zm-6 0a.75.75 0 1 0 1.5 0 .75.75 0 0 0-1.5 0Zm8.25-.75a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5ZM4.25 12a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Z"/>
|
|
61
|
+
</svg>`;
|
|
62
|
+
export const branchIcon = new LabIcon({
|
|
63
|
+
name: `${ICON_PREFIX}:branch`,
|
|
64
|
+
svgstr: branchSvgStr
|
|
65
|
+
});
|
|
66
|
+
// Arrow-switch glyph (Octicons arrow-switch-16, MIT) - the switch-and-manage
|
|
67
|
+
// submenu.
|
|
68
|
+
const switchSvgStr = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16">
|
|
69
|
+
<path class="jp-icon3" fill="#616161" d="M5.22 14.78a.75.75 0 0 0 1.06-1.06L4.56 12h8.69a.75.75 0 0 0 0-1.5H4.56l1.72-1.72a.75.75 0 0 0-1.06-1.06l-3 3a.75.75 0 0 0 0 1.06l3 3Zm5.56-6.5a.75.75 0 1 1-1.06-1.06l1.72-1.72H2.75a.75.75 0 0 1 0-1.5h8.69L9.72 2.28a.75.75 0 0 1 1.06-1.06l3 3a.75.75 0 0 1 0 1.06l-3 3Z"/>
|
|
70
|
+
</svg>`;
|
|
71
|
+
export const switchIcon = new LabIcon({
|
|
72
|
+
name: `${ICON_PREFIX}:switch`,
|
|
73
|
+
svgstr: switchSvgStr
|
|
74
|
+
});
|
|
75
|
+
// Funnel copied verbatim from @jupyterlab/ui-components' `search/filter.svg` -
|
|
76
|
+
// the same image the file browser's filter toggle uses. `class="jp-icon3"`
|
|
77
|
+
// lets the theme drive the fill, so the source `fill="#FFF"` is inert under
|
|
78
|
+
// the standard light/dark themes.
|
|
79
|
+
const filterSvgStr = `<svg xmlns="http://www.w3.org/2000/svg" width="16" viewBox="0 0 24 24">
|
|
80
|
+
<path fill="#FFF" d="M14 12v7.88c.04.3-.06.62-.29.83a.996.996 0 0 1-1.41 0l-2.01-2.01a.99.99 0 0 1-.29-.83V12h-.03L4.21 4.62a1 1 0 0 1 .17-1.4c.19-.14.4-.22.62-.22h14c.22 0 .43.08.62.22a1 1 0 0 1 .17 1.4L14.03 12z" class="jp-icon3"/>
|
|
81
|
+
</svg>`;
|
|
82
|
+
export const filterIcon = new LabIcon({
|
|
83
|
+
name: `${ICON_PREFIX}:filter`,
|
|
84
|
+
svgstr: filterSvgStr
|
|
85
|
+
});
|
|
86
|
+
// Provider glyphs already built, by icon name. LabIcon warns and returns the
|
|
87
|
+
// first registration for a duplicate name, so a provider re-registered by a
|
|
88
|
+
// live settings toggle must resolve to the icon it built the first time
|
|
89
|
+
// rather than construct a second one on every toggle.
|
|
90
|
+
const _providerIcons = new Map();
|
|
91
|
+
/** Build (or reuse) the LabIcon for a provider's own glyph. */
|
|
92
|
+
export function providerIcon(name, svgstr) {
|
|
93
|
+
let icon = _providerIcons.get(name);
|
|
94
|
+
if (!icon) {
|
|
95
|
+
icon = new LabIcon({ name, svgstr });
|
|
96
|
+
_providerIcons.set(name, icon);
|
|
97
|
+
}
|
|
98
|
+
return icon;
|
|
99
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ILaunchMode } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* The single launch-mode token for one action, or undefined for a plain launch.
|
|
4
|
+
*
|
|
5
|
+
* The payload carries at most one mode, because the server maps one token to
|
|
6
|
+
* one flag. `force` wins outright - that is what the menu's variant entries
|
|
7
|
+
* do. Otherwise an enum mode moved off its default takes precedence over any
|
|
8
|
+
* boolean switch: the enum is the explicit statement of intent, so a boolean
|
|
9
|
+
* left on must not silently invert it. A boolean fires only while every enum
|
|
10
|
+
* mode sits at its default. An enum at its default sends nothing - it is what
|
|
11
|
+
* the CLI would do unasked.
|
|
12
|
+
*/
|
|
13
|
+
export declare function resolveLaunchMode(launchModes: ILaunchMode[], values: Record<string, boolean | string>, force?: string): string | undefined;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Launch-mode resolution: which single mode token, if any, one launch carries.
|
|
2
|
+
//
|
|
3
|
+
// Its own module because the panel is the only caller but not the only reader -
|
|
4
|
+
// the precedence between a boolean switch and an enum ladder is a rule worth
|
|
5
|
+
// asserting on its own, and this file pulls in no JupyterLab surface.
|
|
6
|
+
/**
|
|
7
|
+
* The single launch-mode token for one action, or undefined for a plain launch.
|
|
8
|
+
*
|
|
9
|
+
* The payload carries at most one mode, because the server maps one token to
|
|
10
|
+
* one flag. `force` wins outright - that is what the menu's variant entries
|
|
11
|
+
* do. Otherwise an enum mode moved off its default takes precedence over any
|
|
12
|
+
* boolean switch: the enum is the explicit statement of intent, so a boolean
|
|
13
|
+
* left on must not silently invert it. A boolean fires only while every enum
|
|
14
|
+
* mode sits at its default. An enum at its default sends nothing - it is what
|
|
15
|
+
* the CLI would do unasked.
|
|
16
|
+
*/
|
|
17
|
+
export function resolveLaunchMode(launchModes, values, force) {
|
|
18
|
+
if (force) {
|
|
19
|
+
return force;
|
|
20
|
+
}
|
|
21
|
+
for (const mode of launchModes) {
|
|
22
|
+
const value = values[mode.id];
|
|
23
|
+
if (mode.kind !== 'boolean' &&
|
|
24
|
+
typeof value === 'string' &&
|
|
25
|
+
value &&
|
|
26
|
+
value !== mode.default) {
|
|
27
|
+
return `${mode.id}=${value}`;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
for (const mode of launchModes) {
|
|
31
|
+
if (mode.kind === 'boolean' && values[mode.id] === true) {
|
|
32
|
+
return mode.id;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|