## ๐ 1. ็ณป็ปๆปไฝๆถๆ ้็จ **ๅ็ซฏ UI ๆณจๅ ฅ + ๆฌๅฐ Server Extension ้ไผ ** ็่ฝป้ๅๅฑๆถๆ๏ผ ```text โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ JupyterLab / Notebook v7 (ๅ็ซฏ TS) โ โ โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ โ Cell Component โ โ โ โ [CodeEditor] โโโ (ๆ่ฝฝ) โโโโบ [Cell Action Toolbar] โ โ โ โ โ (็นๅป ๐ช ๆบ่ฝ็ผ่พ) โ โ โ โผ โ โ โ [Inline Prompt Box] โ โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ โ โ โ Context Provider ๆถ้ โ โ (Target Cell, Error, Neighbors) โ โ โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ REST / SSE Stream โผ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ Jupyter Server Extension (ๅ็ซฏ Python) โ โ โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ โ OpenCode Bridge Handler โ โ โ โ - ๆฅๆถ Context & Prompt โ โ โ โ - ๆผ่ฃ ๆ ๅ OpenCode API Payload โ โ โ โ - SSE ๆตๅผ้ไผ ไปฃ็ (Stream Proxy) โ โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ Local Socket / HTTP โผ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ OpenCode Serve ่ฟ็จ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ ``` --- ## ๐จ 2. ไบคไบไธUIๆต็จ่ฎพ่ฎก 1. **ๅ็ Cell Toolbar ้ๆ๏ผ2026-07-23 ไฟฎๆญฃ๏ผ**๏ผไธๅ่ช็ปๆฌๆตฎ toolbarใ`โจ ไผๅ` / `๐ ๆบ่ฝๆ้`๏ผ**ไป ๅฝ Cell ๆ Error Output ๆถไบฎ่ตท**๏ผ/ `๐ช ๆบ่ฝ็ผ่พ` ไธไธชๆ้ฎ้่ฟ `IToolbarWidgetRegistry.addFactory('Cell', 'opencode-cell-actions', ...)` ๆณจๅ ฅ JupyterLab ๅ็ cell toolbar๏ผๆพ็คบๅจ **active cell ๅณไธ่ง**๏ผmove up/down ๆ้ฎๆ๏ผ`rank: 100` ๆๅจ delete ๅณไพง๏ผ๏ผไป `CodeCell` ๆพ็คบ๏ผ้ active cell ๅๆข็งปๅจใ 2. **Inline Prompt ๆก๏ผๆ้ๅฑๅผ๏ผ**๏ผ * ็จๆท็นๅป `๐ช ๆบ่ฝ็ผ่พ` ๅ๏ผๅจๅฝๅ Cell ไธๆนๅนณๆปๅฑๅผไธไธชๅพฎๅ่พๅ ฅๆก๏ผๅธฆ `Textarea` + `๐ ๅ้` ๆ้ฎ๏ผใ 3. **ๆตๅผๆฟๆข/Diff ้ข่ง**๏ผ * ็นๅปๅ้ๅ๏ผๆฏๆ**ๆๅญๆบๅฎๆถๆฟๆข**ๆๅจ Cell ไธๆนๅบ็ฐ `[ Accept ]` / `[ Reject ]` ไธดๆถ Diff ๆฏๅฏน้ขๆฟใ --- ## ๐ 3. ๆฅๅฃๅฅ็บฆ่ฎพ่ฎก (API Contract) ### ๆฅๅฃ๏ผ`POST /opencode/v1/generate` (ๆฏๆ SSE ๆตๅผ่ฟๅ) **Request Payload (ๅ็ซฏ Context Provider ๆๅ ๅ็ป Server Extension)**: ```json { "action": "custom_edit", // custom_edit | fix_error | optimize "prompt": "ๆ่ฟๆฎตไปฃ็ ๆนไธบ็จ plotly ็ปๅถไบคไบๅผๆ็บฟๅพ", "context": { "notebook_path": "analysis/demo.ipynb", "target_cell": { "index": 3, "code": "import matplotlib.pyplot as plt\nplt.plot([1, 2, 3])", "error_output": "ModuleNotFoundError: No module named 'matplotlib'" }, "surrounding_cells": [ { "index": 2, "code": "import pandas as pd" } ] } } ``` **Response (Server Extension ๆตๅผ้ไผ OpenCode Serve ็ SSE ๅๅบ)**: ```text event: chunk data: {"text": "import plotly.express as px\n"} event: chunk data: {"text": "fig = px.line(x=[1, 2, 3], y=[1, 2, 3])\nfig.show()"} event: done data: {"status": "success"} ``` --- ## ๐ ๏ธ 4. ๆ ธๅฟๅ็ซฏๅฎ็ฐ้ป่พ (TypeScript) ไปฅไธๆฏๅ็ซฏๅฎ็ฐโToolbar ๆณจๅ ฅโๅโไธไธๆๆๅโ็ไผชไปฃ็ ้ชจๆถ๏ผๅฏ็ดๆฅไฝไธบๅผๅๅ่๏ผ ### `src/context_provider.ts` (ไธไธๆๆ็ฅๆจกๅ) ```typescript import { Notebook, NotebookTracker } from '@jupyterlab/notebook'; import { Cell } from '@jupyterlab/cells'; export interface CellContext { notebookPath: string; targetCode: string; errorOutput?: string; surroundingCells: string[]; } export function extractCellContext(tracker: NotebookTracker, targetCell: Cell): CellContext | null { const currentWidget = tracker.currentWidget; if (!currentWidget) return null; const notebook: Notebook = currentWidget.content; const cells = notebook.widgets; const targetIndex = cells.findIndex(c => c === targetCell); // 1. ๆๅๅฝๅ Cell ็้่ฏฏ่พๅบ let errorMsg = ''; if (targetCell.model.type === 'code') { const outputs = (targetCell.model as any).outputs; for (let i = 0; i < outputs.length; i++) { const out = outputs.get(i); if (out.type === 'error') { errorMsg += `${out.ename}: ${out.evalue}\n${(out.traceback || []).join('\n')}`; } } } // 2. ๆๅๅไธไธช Cell ็ไปฃ็ ๏ผไธไธๆ๏ผ const prevCode = targetIndex > 0 ? cells[targetIndex - 1].model.sharedModel.getSource() : ''; return { notebookPath: currentWidget.context.path, targetCode: targetCell.model.sharedModel.getSource(), errorOutput: errorMsg, surroundingCells: [prevCode] }; } ``` ### `src/components/cell_toolbar.ts` (ๅทฅๅ ทๆ ไธๅ ๅตๆกๆณจๅ ฅ) ```typescript import { Cell } from '@jupyterlab/cells'; export function injectOpenCodeActionBox(cell: Cell, onSendPrompt: (prompt: string) => void) { // ้ฟๅ ้ๅคๆณจๅ ฅ if (cell.node.querySelector('.opencode-toolbar')) return; // 1. ๅๅปบ Toolbar ๅฎนๅจ const toolbar = document.createElement('div'); toolbar.className = 'opencode-toolbar'; toolbar.innerHTML = ` `; // 2. ็ปๅฎๅฑๅผไบไปถ toolbar.querySelector('.edit-btn')!.addEventListener('click', () => { let promptBox = cell.node.querySelector('.opencode-inline-box') as HTMLDivElement; if (promptBox) { promptBox.style.display = promptBox.style.display === 'none' ? 'block' : 'none'; return; } // ๅจๆๅๅปบ Inline Prompt ่พๅ ฅๆก promptBox = document.createElement('div'); promptBox.className = 'opencode-inline-box'; promptBox.innerHTML = `