Revert "feat: AI entry button at the cell's bottom-right + clear input after success"

This reverts commit fd30e53e25.
This commit is contained in:
tao.chen
2026-07-23 19:34:38 +08:00
parent fd30e53e25
commit c0bcbda77f
4 changed files with 40 additions and 110 deletions
+11 -45
View File
@@ -194,11 +194,8 @@ describe('OpenCodeCellActions', () => {
it('renders a single AI button', () => {
const cell = makeFakeCell('x = 1');
// Constructing the actions widget has the side effect of appending
// the floating AI button to cell.node; the widget reference itself
// is not needed in this test.
new OpenCodeCellActions(cell);
const btns = cell.node.querySelectorAll('button.opencode-cell-ai-btn');
const actions = new OpenCodeCellActions(cell);
const btns = actions.node.querySelectorAll('button');
expect(btns.length).toBe(1);
expect(btns[0].textContent).toContain('AI');
});
@@ -213,11 +210,8 @@ describe('OpenCodeCellActions', () => {
contentChanged: { connect: jest.fn(), disconnect: jest.fn() },
stateChanged: { connect: jest.fn(), disconnect: jest.fn() }
};
(cell as any).node = document.createElement('div');
new OpenCodeCellActions(cell);
const btn = cell.node.querySelector(
'button.opencode-cell-ai-btn'
) as HTMLButtonElement;
const actions = new OpenCodeCellActions(cell);
const btn = actions.node.querySelector('button') as HTMLButtonElement;
expect(btn.disabled).toBe(true);
});
@@ -227,7 +221,7 @@ describe('OpenCodeCellActions', () => {
Object.defineProperty(actions, 'parent', { value: cell, configurable: true });
(actions as any).onAfterAttach({} as any);
const btn = cell.node.querySelector('button.opencode-cell-ai-btn') as HTMLButtonElement;
const btn = actions.node.querySelector('button') as HTMLButtonElement;
btn.click();
const prompt = cell.node.querySelector(
@@ -242,7 +236,7 @@ describe('OpenCodeCellActions', () => {
Object.defineProperty(actions, 'parent', { value: cell, configurable: true });
(actions as any).onAfterAttach({} as any);
const btn = cell.node.querySelector('button.opencode-cell-ai-btn') as HTMLButtonElement;
const btn = actions.node.querySelector('button') as HTMLButtonElement;
btn.click();
btn.click();
expect(cell.node.querySelector('.opencode-inline-prompt')).toBeNull();
@@ -280,7 +274,7 @@ describe('OpenCodeCellActions', () => {
Object.defineProperty(actions, 'parent', { value: cell, configurable: true });
(actions as any).onAfterAttach({} as any);
const btn = cell.node.querySelector('button.opencode-cell-ai-btn') as HTMLButtonElement;
const btn = actions.node.querySelector('button') as HTMLButtonElement;
btn.click();
const prompt = cell.node.querySelector(
@@ -325,7 +319,7 @@ describe('OpenCodeCellActions', () => {
Object.defineProperty(actions, 'parent', { value: cell, configurable: true });
(actions as any).onAfterAttach({} as any);
const btn = cell.node.querySelector('button.opencode-cell-ai-btn') as HTMLButtonElement;
const btn = actions.node.querySelector('button') as HTMLButtonElement;
btn.click();
const modelSel = cell.node.querySelector(
@@ -354,7 +348,7 @@ describe('OpenCodeCellActions', () => {
Object.defineProperty(actions, 'parent', { value: cell, configurable: true });
(actions as any).onAfterAttach({} as any);
const btn = cell.node.querySelector('button.opencode-cell-ai-btn') as HTMLButtonElement;
const btn = actions.node.querySelector('button') as HTMLButtonElement;
btn.click();
const prompt = cell.node.querySelector(
@@ -390,7 +384,7 @@ describe('OpenCodeCellActions', () => {
Object.defineProperty(actions, 'parent', { value: cell, configurable: true });
(actions as any).onAfterAttach({} as any);
const btn = cell.node.querySelector('button.opencode-cell-ai-btn') as HTMLButtonElement;
const btn = actions.node.querySelector('button') as HTMLButtonElement;
btn.click();
expect(
@@ -409,7 +403,7 @@ describe('OpenCodeCellActions', () => {
// has a target and the cell source check has a baseline).
Object.defineProperty(actions, 'parent', { value: cell, configurable: true });
(actions as any).onAfterAttach({} as any);
const btn = cell.node.querySelector('button.opencode-cell-ai-btn') as HTMLButtonElement;
const btn = actions.node.querySelector('button') as HTMLButtonElement;
btn.click();
// The history refresh on show is async; spy on _refreshHistory to
@@ -428,34 +422,6 @@ describe('OpenCodeCellActions', () => {
// A history refresh was triggered (to pick up the new assistant msg).
expect(refreshSpy).toHaveBeenCalledWith('foo.ipynb');
});
it('clears the input textarea after a successful response', () => {
setOpenCodeProviders(null);
const cell = makeFakeCell('x = 1');
const actions = new OpenCodeCellActions(cell);
Object.defineProperty(actions, 'parent', { value: cell, configurable: true });
(actions as any).onAfterAttach({} as any);
// Open the prompt and type something into the textarea.
const aiBtn = cell.node.querySelector(
'button.opencode-cell-ai-btn'
) as HTMLButtonElement;
aiBtn.click();
const prompt = (actions as any)._prompt;
expect(prompt).not.toBeNull();
const textarea = prompt._textarea as HTMLTextAreaElement;
textarea.value = 'make it faster';
// A successful response clears the input so the user can type a
// follow-up message.
(actions as any)._handleResponse({
ok: true,
markdown: 'ok',
sessionId: 'ses-1',
notebookPath: 'foo.ipynb'
});
expect(textarea.value).toBe('');
});
});
describe('OpenCodeInlinePrompt', () => {
+11 -37
View File
@@ -50,22 +50,11 @@ export class OpenCodeCellActions extends Widget {
private _context: CellContext | null = null;
private _status: Status = 'idle';
private _prompt: OpenCodeInlinePrompt | null = null;
// Floating "AI" entry button that lives at the cell's bottom-right
// corner (overlaid on the cell, absolutely positioned). The widget's
// own node stays inside the native cell toolbar (as a hidden lifecycle
// holder) and is NOT where the user-visible button renders.
private _floatingBtn: HTMLButtonElement | null = null;
constructor(cell: CodeCell) {
super();
this._cell = cell;
this.addClass('opencode-cell-actions');
// Hide the widget's own node inside the native cell toolbar — the
// actual UI is the floating button below.
this.node.style.display = 'none';
// Anchor the absolutely-positioned floating button to the cell.
this._cell.node.style.position = 'relative';
this._createFloatingButton();
this._cell.model.contentChanged.connect(this._onModelChange, this);
this._cell.model.stateChanged.connect(this._onModelChange, this);
this._render();
@@ -79,26 +68,9 @@ export class OpenCodeCellActions extends Widget {
this._cell.model.contentChanged.disconnect(this._onModelChange, this);
this._cell.model.stateChanged.disconnect(this._onModelChange, this);
this._hidePrompt();
if (this._floatingBtn && this._floatingBtn.parentNode) {
this._floatingBtn.parentNode.removeChild(this._floatingBtn);
}
this._floatingBtn = null;
super.dispose();
}
private _createFloatingButton(): void {
const btn = document.createElement('button');
btn.className = 'opencode-cell-ai-btn';
btn.type = 'button';
btn.textContent = 'AI';
btn.title = '让 AI 修改这个 cell 的代码';
btn.addEventListener('click', () => {
this._togglePrompt();
});
this._cell.node.appendChild(btn);
this._floatingBtn = btn;
}
private _onModelChange(): void {
this._context = extractCellContextFromCell(this._cell);
if (this._prompt) {
@@ -109,18 +81,22 @@ export class OpenCodeCellActions extends Widget {
}
private _render(): void {
// The widget's own node no longer hosts the button (it lives at the
// cell's bottom-right). Just update the floating button's label and
// disabled state.
const btn = this._floatingBtn;
if (!btn) {
return;
}
const node = this.node;
node.textContent = '';
const baseDisabled = !this._context || this._status === 'loading';
const btn = document.createElement('button');
btn.className = 'opencode-btn opencode-btn-ai';
btn.textContent = 'AI';
btn.disabled = baseDisabled;
btn.title = baseDisabled
? 'OpenCode: 等待 cell 上下文…'
: '让 AI 修改这个 cell 的代码';
btn.addEventListener('click', () => {
this._togglePrompt();
});
node.appendChild(btn);
}
private _togglePrompt(): void {
@@ -228,8 +204,6 @@ export class OpenCodeCellActions extends Widget {
if (notebookPath) {
void this._refreshHistory(notebookPath);
}
// Clear the input so the user can type a follow-up message.
this._prompt?.clearInput();
Notification.info(
`OpenCode 完成 (${resp.markdown.length} chars). Session: ${resp.sessionId.slice(0, 8)}`
);
-8
View File
@@ -367,12 +367,4 @@ export class OpenCodeInlinePrompt extends Widget {
cell.model.sharedModel.setSource(text);
Notification.info('已替换单元格内容');
}
/**
* Clear the user input textarea (called by the cell action after a
* successful response, so the user can type a follow-up message).
*/
clearInput(): void {
this._textarea.value = '';
}
}
+18 -20
View File
@@ -4,31 +4,29 @@
https://jupyterlab.readthedocs.io/en/stable/developer/css.html
*/
/* AI entry button: rendered as a floating pill at the bottom-right
corner of the cell. The widget that owns it (opencode-cell-actions)
is registered in the native cell toolbar but its own node is
display:none — only this floating button is visible. */
.opencode-cell-ai-btn {
position: absolute;
bottom: 4px;
right: 4px;
z-index: 10;
border: 1px solid var(--jp-border-color1, #999);
background: var(--jp-layout-color1, #fff);
color: var(--jp-ui-font-color1, #333);
padding: 1px 8px;
border-radius: 10px;
font-size: var(--jp-ui-font-size0, 11px);
line-height: 1.3;
/* Single AI action button inside the native Cell toolbar (active cell, top-right). */
.opencode-cell-actions {
display: flex;
align-items: center;
}
.opencode-cell-actions .opencode-btn {
border: none;
background: transparent;
padding: 0 6px;
font-size: var(--jp-ui-font-size1);
line-height: 20px;
cursor: pointer;
white-space: nowrap;
}
.opencode-cell-ai-btn:hover {
background: var(--jp-layout-color2, #f0f0f0);
.opencode-cell-actions .opencode-btn:hover:enabled {
background: var(--jp-layout-color2);
border-radius: 2px;
}
.opencode-cell-ai-btn:disabled {
opacity: 0.5;
.opencode-cell-actions .opencode-btn:disabled {
opacity: 0.4;
cursor: default;
}