Skip to content

Commit

Permalink
add settings in jupyterlab system
Browse files Browse the repository at this point in the history
  • Loading branch information
MarchLiu committed Aug 28, 2024
1 parent f380cae commit 0902e78
Show file tree
Hide file tree
Showing 9 changed files with 305 additions and 67 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,10 @@ See [RELEASE](RELEASE.md)

* installer fixed

### 0.1.4

* add settings

## About Me

My name is Liu Xin, and my English name is Mars Liu and previously used March Liu. I translated the Python
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "jupyter-litchi",
"version": "0.1.3",
"version": "0.1.4",
"description": "litchi is a ai client for jupyter lab",
"keywords": [
"jupyter",
Expand Down Expand Up @@ -61,7 +61,7 @@
"dependencies": {
"@jupyterlab/application": "^4.2.4",
"@jupyterlab/apputils": "^4.3.4",
"@jupyterlab/settingregistry": "^4.0.0",
"@jupyterlab/settingregistry": "^4.2.5",
"@lumino/widgets": "^2.5.0"
},
"devDependencies": {
Expand Down
34 changes: 34 additions & 0 deletions schema/jupyter-litchi.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"title": "Litchi",
"description": "AI Plugin for JupyterLab.",
"jupyter.lab.menus": {
"main": [
{
"id": "litchi-settings",
"label": "Litchi",
"rank": 80,
"items": [
{
"command": "jupyter-litchi:toggle-flag"
}
]
}
]
},
"properties": {
"ollama:host": {
"type": "string",
"title": "Ollama Host:",
"description": "Host or IP address of ollama api.",
"default": "localhost"
},
"ollama:port": {
"type": "integer",
"title": "Ollama Port:",
"description": "Port of ollama api.",
"default": 11434
}
},
"additionalProperties": false,
"type": "object"
}
9 changes: 5 additions & 4 deletions schema/plugin.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
{
"jupyter.lab.shortcuts": [],
"title": "jupyter-litchi",
"description": "litchi settings.",
"title": "Jupyter Litchi",
"description": "AI Plugin for JupyterLab.",
"type": "object",
"properties": {},
"additionalProperties": false
"properties": {
},
"additionalProperties": true
}
3 changes: 1 addition & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
long_description = (this_directory / "README.md").read_text()

__import__("setuptools").setup(name="jupyter-litchi",
version="0.1.3",
version="0.1.4",
description="Litchi is a Jupyterlab extension for AI Client",
long_description=long_description,
long_description_content_type='text/markdown',
Expand All @@ -17,7 +17,6 @@
keywords= ["jupyter", "jupyterlab", "jupyterlab-extension", "ai", "ollama"],
packages=find_packages(),
package_data={
# 将特定于包的资源文件包含进来
'labextension': ['litchi/labextension/*'],
},
)
145 changes: 96 additions & 49 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,21 @@ import {
JupyterFrontEnd,
JupyterFrontEndPlugin
} from '@jupyterlab/application';
import { ICommandPalette } from '@jupyterlab/apputils';
import { ICommandPalette, MainAreaWidget } from '@jupyterlab/apputils';
import { IStateDB } from '@jupyterlab/statedb';
import { chat, IMessage, Message } from './ollama';
import { MarkdownCellModel } from '@jupyterlab/cells'
import { INotebookTracker } from "@jupyterlab/notebook";
import { MarkdownCellModel } from '@jupyterlab/cells';
import { INotebookTracker } from '@jupyterlab/notebook';
import { SettingWidget } from './settings';
import { ISettingRegistry} from '@jupyterlab/settingregistry';

const LITCHI_ID = 'jupyter-litchi:jupyter-litchi';
const ACTIVATE_COMMAND_ID = 'litchi:chat';
const SETTINGS_COMMAND_ID = 'litchi:settings';

namespace CommandIDs {
export const CHAT = ACTIVATE_COMMAND_ID;
export const SETTINGS = SETTINGS_COMMAND_ID;
}
const LITCHI_SESSION = 'litchi:session';
const LITCHI_LATEST = 'litchi:latest';
Expand All @@ -22,76 +27,61 @@ const LITCHI_LATEST = 'litchi:latest';
* The plugin registration information.
*/
const plugin: JupyterFrontEndPlugin<void> = {
id: 'jupyter-litchi',
id: LITCHI_ID,
description: 'Add a widget to the notebook header.',
autoStart: true,
activate: activate,
requires: [ICommandPalette, IStateDB, INotebookTracker]
requires: [ICommandPalette, IStateDB, INotebookTracker, ISettingRegistry],
};

export function activate(
export async function activate(
app: JupyterFrontEnd,
palette: ICommandPalette,
state: IStateDB,
tracker: INotebookTracker,
registry: ISettingRegistry
) {
const widget = new WidgetExtension(app, state);
widget.addClass('jp-litchi-toolbar');

app.docRegistry.addWidgetExtension('Notebook', widget);
// const tracker = new NotebookTracker({ namespace: 'jupyter-litchi' });
console.log('add command litchi:chat');

app.commands.addCommand(CommandIDs.CHAT, {
label: 'Litchi Chat',
execute: async () => {
const session: IMessage[] = await fetchState(state, LITCHI_SESSION, [
Message.startUp()
]);
const cell = tracker.activeCell;
if (cell === null) {
console.error('litchi:chat exit because any cell not been selected');
return;
}
await chatActivate(app, registry, tracker, state);
}
});
// Add the command to the palette.
palette.addItem({ command: CommandIDs.CHAT, category: 'jupyter-Litchi' });

const notebook = tracker.currentWidget?.content;
if (notebook === undefined) {
console.error('litchi:chat exit because the notebook not found');
return;
}
const settingsCreator = () => {
const content = new SettingWidget(LITCHI_ID, app, registry);
const widget = new MainAreaWidget<SettingWidget>({ content });
widget.id = 'litchi-settings';
widget.title.label = 'Litchi Settings';
widget.title.closable = true;
return widget;
};

const content = cell.model.sharedModel.source;
// eslint-disable-next-line eqeqeq
if (content === null) {
console.error('litchi:chat exit because the content of cell is null');
return;
let settingsWidget = settingsCreator();
app.commands.addCommand(CommandIDs.SETTINGS, {
label: 'Litchi Settings',
execute: () => {
// Regenerate the widget if disposed
if (settingsWidget.isDisposed) {
settingsWidget = settingsCreator();
}
const model = (await state.fetch('litchi:model'))?.toString();
if (model === null || model === undefined) {
console.error('litchi:chat exit because not any model selected');
return;
if (!settingsWidget.isAttached) {
// Attach the widget to the main work area if it's not there
app.shell.add(settingsWidget, 'settings editor');
}

const latest = new Message('user', content);
await state.save(LITCHI_LATEST, JSON.stringify(latest));

const message = await chat(session, latest, model!);
console.log(`received message ${JSON.stringify(message)}`);
await state.save(LITCHI_SESSION, JSON.stringify([...session, message]));
const cellModel = new MarkdownCellModel();
cellModel.sharedModel.setSource(message.content);

const { commands } = app;
commands.execute('notebook:insert-cell-below').then(() => {
commands.execute('notebook:change-cell-to-markdown');
});

const newCell = notebook.activeCell!;
newCell.model.sharedModel.setSource(message.content);
// Activate the widget
app.shell.activateById(settingsWidget.id);
}
});

// Add the command to the palette.
palette.addItem({ command: CommandIDs.CHAT, category: 'jupyter-Litchi' });
// palette.addItem({ command: CommandIDs.SETTINGS, category: 'jupyter-Litchi' });
}

async function fetchState<T>(
Expand All @@ -109,6 +99,63 @@ async function fetchState<T>(
}
}

async function chatActivate(
app: JupyterFrontEnd,
registry: ISettingRegistry,
tracker: INotebookTracker,
state: IStateDB
) {
const session: IMessage[] = await fetchState(state, LITCHI_SESSION, [
Message.startUp()
]);
const cell = tracker.activeCell;
if (cell === null) {
console.error('litchi:chat exit because any cell not been selected');
return;
}

const notebook = tracker.currentWidget?.content;
if (notebook === undefined) {
console.error('litchi:chat exit because the notebook not found');
return;
}

const content = cell.model.sharedModel.source;
// eslint-disable-next-line eqeqeq
if (content === null) {
console.error('litchi:chat exit because the content of cell is null');
return;
}
const model = (await state.fetch('litchi:model'))?.toString();
if (model === null || model === undefined) {
console.error('litchi:chat exit because not any model selected');
return;
}

const settings = await registry.load(LITCHI_ID);
const host = settings.get('ollama:host')!.composite!.toString();
const port = Number.parseInt(
settings.get('ollama:port')!.composite!.toString()
);

const latest = new Message('user', content);
await state.save(LITCHI_LATEST, JSON.stringify(latest));

const message = await chat(host, port, session, latest, model!);
console.log(`received message ${JSON.stringify(message)}`);
await state.save(LITCHI_SESSION, JSON.stringify([...session, message]));
const cellModel = new MarkdownCellModel();
cellModel.sharedModel.setSource(message.content);

const { commands } = app;
commands.execute('notebook:insert-cell-below').then(() => {
commands.execute('notebook:change-cell-to-markdown');
});

const newCell = notebook.activeCell!;
newCell.model.sharedModel.setSource(message.content);
}

/**
* Export the plugin as default.
*/
Expand Down
15 changes: 9 additions & 6 deletions src/ollama.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// SendRequestComponent.tsx


export interface IMessage {
role: string;
content: string;
Expand All @@ -15,7 +16,7 @@ export class Message implements IMessage {
}

static startUp(): Message {
return new Message("system", "your are a python and jupyter export");
return new Message('system', 'your are a python and jupyter export');
}
}

Expand Down Expand Up @@ -46,25 +47,27 @@ interface IChatResponse {
}

export async function chat(
host: string,
port: number,
session: Message[],
message: Message,
model: string
): Promise<Message> {
try {
const messages = [...session, message];
const request = new ChatRequest(model, messages);
const resp = await fetch("http://localhost:11434/api/chat", {
method: "POST",

const resp = await fetch(`http://${host}:${port}/api/chat`, {
method: 'POST',
headers: {
"content-type": "application/json"
'content-type': 'application/json'
},
body: JSON.stringify(request)
});
const data = (await resp.json()) as IChatResponse;
console.log(JSON.stringify(data));
return data.message;
} catch (error) {
console.error("Error sending request to server:", error);
console.error('Error sending request to server:', error);
throw error;
}
}
Loading

0 comments on commit 0902e78

Please sign in to comment.