feat: replace per-cell AI button with global floating panel
The per-cell toolbar AI button (OpenCodeCellActions) is removed. A
single round button is mounted on document.body at the bottom-right of
the JupyterLab shell; clicking it opens a floating panel containing
the same OpenCodeInlinePrompt widget. Clicking again closes it.
The panel resolves the active notebook from app.shell.currentWidget
at open time and re-resolves it whenever currentChanged fires while
the panel is open. The active cell (used by the 📋 插入单元格内容,
↪ Insert, and ⟳ Replace cell actions) is resolved via a new
getActiveCell callback in OpenCodeInlinePrompt, decoupling the prompt
itself from any specific CodeCell instance.
Files:
- src/components/opencode_floating_panel.ts (new) — global panel
- src/components/opencode_cell_actions.ts (deleted) — logic moved up
- src/components/opencode_inline_prompt.ts — getActiveCell option
- src/index.ts — wire floating panel, drop toolbar factory
- style/base.css — .opencode-floating-* styles
- src/__tests__/opencode_floating_panel.spec.ts — new tests (renamed)
All existing functionality preserved: multi-session UI, SSE
streaming, permission/question interaction, history reload.
This commit is contained in:
+23
-41
@@ -3,74 +3,56 @@ import {
|
||||
JupyterFrontEndPlugin
|
||||
} from '@jupyterlab/application';
|
||||
|
||||
import { IToolbarWidgetRegistry } from '@jupyterlab/apputils';
|
||||
import { Cell, CodeCell } from '@jupyterlab/cells';
|
||||
import { Widget } from '@lumino/widgets';
|
||||
|
||||
import {
|
||||
OpenCodeCellActions,
|
||||
setOpenCodeProviders,
|
||||
setOpenCodeServerSettings
|
||||
} from './components/opencode_cell_actions';
|
||||
import { OpenCodeFloatingPanel } from './components/opencode_floating_panel';
|
||||
import { requestAPI } from './request';
|
||||
import { callOpenCodeProviders } from './api/opencode_client';
|
||||
|
||||
/**
|
||||
* Initialization data for the opencode_bridge extension.
|
||||
*
|
||||
* v3-final: no JupyterLab plugin settings. Connection config is read from
|
||||
* environment variables by the server extension; the model is picked
|
||||
* dynamically in the inline prompt.
|
||||
* v5-floating: the per-cell AI button is gone. A single
|
||||
* OpenCodeFloatingPanel is attached to document.body and operates
|
||||
* against the JupyterLab shell's currently-active notebook. The model
|
||||
* is picked dynamically in the inline prompt.
|
||||
*/
|
||||
const plugin: JupyterFrontEndPlugin<void> = {
|
||||
id: 'opencode_bridge:plugin',
|
||||
description: 'A JupyterLab extension bridging the UI to OpenCode Serve.',
|
||||
autoStart: true,
|
||||
optional: [IToolbarWidgetRegistry],
|
||||
activate: (
|
||||
app: JupyterFrontEnd,
|
||||
toolbarRegistry: IToolbarWidgetRegistry | null
|
||||
) => {
|
||||
activate: (app: JupyterFrontEnd) => {
|
||||
console.log('JupyterLab extension opencode_bridge is activated!');
|
||||
|
||||
// Push the Jupyter server settings into the actions module so that
|
||||
// callOpenCodeEdit can build the right base URL.
|
||||
setOpenCodeServerSettings(app.serviceManager.serverSettings);
|
||||
// Mount the floating AI button on the body. The panel itself is
|
||||
// hidden until the user clicks the button.
|
||||
const floating = new OpenCodeFloatingPanel({
|
||||
app,
|
||||
serverSettings: app.serviceManager.serverSettings,
|
||||
providers: null
|
||||
});
|
||||
Widget.attach(floating, document.body);
|
||||
|
||||
// Register the per-cell AI actions into the native Cell toolbar
|
||||
// (top-right of the active cell, next to move up/down). Non-code
|
||||
// cells get an empty widget so they show no AI buttons.
|
||||
if (toolbarRegistry) {
|
||||
toolbarRegistry.addFactory<Cell>(
|
||||
'Cell',
|
||||
'opencode-cell-actions',
|
||||
(cell: Cell) => {
|
||||
if (cell instanceof CodeCell) {
|
||||
return new OpenCodeCellActions(cell);
|
||||
}
|
||||
return new Widget();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Fetch available providers once at activation and cache them for the
|
||||
// inline prompt's model picker. Failure is non-fatal (the picker hides).
|
||||
// Fetch available providers once at activation and cache them for
|
||||
// the inline prompt's model picker. Failure is non-fatal (the
|
||||
// picker hides). Also push the payload into the floating panel so
|
||||
// the prompt can render selects without an extra round trip.
|
||||
void callOpenCodeProviders(app.serviceManager.serverSettings)
|
||||
.then(data => {
|
||||
setOpenCodeProviders(data);
|
||||
floating.setProviders(data);
|
||||
const lines: string[] = [
|
||||
'[opencode_bridge] Available OpenCode providers:'
|
||||
];
|
||||
for (const p of data.providers) {
|
||||
const models = Object.values(p.models).map(m => m.id).join(', ');
|
||||
const models = Object.values(p.models)
|
||||
.map(m => m.id)
|
||||
.join(', ');
|
||||
lines.push(` - ${p.id}: ${models || '(no models)'}`);
|
||||
}
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(lines.join('\n'));
|
||||
})
|
||||
.catch(reason => {
|
||||
// eslint-disable-next-line no-console
|
||||
+ console.warn(
|
||||
console.warn(
|
||||
'[opencode_bridge] Could not fetch providers (is the opencode-bridge server extension enabled and opencode serve running?):',
|
||||
reason
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user