Revert "feat: AI entry button at the cell's bottom-right + clear input after success"
This reverts commit fd30e53e25.
This commit is contained in:
@@ -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', () => {
|
||||
|
||||
@@ -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)}…`
|
||||
);
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user