Commit Graph
9 Commits
Author SHA1 Message Date
tao.chenandClaude Fable 5 cb3576c83a feat: Copy / Insert / Replace buttons on assistant messages
Each assistant message in the inline prompt history now renders a
small toolbar with three action buttons:

  📋 复制  — copy the rendered assistant content (the markdown
             source, not the rendered HTML) to the clipboard via
             navigator.clipboard.writeText, with a fallback to a
             hidden textarea + execCommand for non-secure contexts.
  ↪ 插入  — insert the content at the current editor cursor. Uses
             cell.editor.getCursorPosition() + the cell source to
             compute the offset, then sharedModel.setSource to splice
             the text in. Falls back to appending if the cursor can't
             be read.
  ⟳ 替换  — replace the entire cell source with the assistant content
             via sharedModel.setSource.

User messages and the textarea/send/cancel UI are unchanged; the
toolbar only appears on rendered assistant (markdown) messages.

Implementation:
  - OpenCodeInlinePrompt now stores the cell (private _cell) so the
    button handlers can act on it.
  - New private methods: _copyToClipboard, _insertAtCursor,
    _replaceCell. Each shows a Notification on success/failure.
  - Added import { Notification } from '@jupyterlab/apputils' (the
    prompt file didn't import it before; cell_actions does, so the
    test mock already provides the static methods).
  - style/base.css: .opencode-msg-toolbar (flex row) and
    .opencode-msg-btn (small bordered button) styles.
  - Test fake cell now has an editor stub returning cursor (0,0) so
    the Insert test produces a deterministic offset. jsdom lacks
    navigator.clipboard; the test stubs it globally.

Tests:
  - jest 33/33 (was 29; +4 new: toolbar renders 3 buttons only on
    assistant, Replace sets source, Insert splices at offset 0, Copy
    calls clipboard.writeText with the content).
  - pytest 37/37 (no server changes).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 19:13:28 +08:00
tao.chenandClaude Fable 5 ce59502e97 feat: inline prompt history area — scrollable session messages
The inline prompt now shows the current session's full message
history (user + assistant), scrollable, replacing the single-output
display from the previous commit. The user can scroll back through
prior exchanges in the same notebook's OpenCode session.

Server (opencode_bridge/):
  - New route: GET /opencode-bridge/session-messages?notebook=<path>
    - Resolves notebook to session via SessionManager.peek (no create).
    - If no session: {"messages": []} (no OpenCode call).
    - Else: GET /session/{id}/message on OpenCode Serve, project the
      raw {info, parts}[] into a frontend-friendly {role, content}[].
  - OpenCodeClient.list_session_messages(sid).
  - SessionManager.peek(notebook_path) -> Optional[str] (read without
    creating, to avoid spawning a session just to report emptiness).
  - Wired the new route in setup_route_handlers.

Client (src/):
  - types.ts: OpenCodeMessage { role, content } + OpenCodeMessagesResponse.
  - api/opencode_client.ts: callOpenCodeSessionMessages(notebook, serverSettings).
  - components/opencode_inline_prompt.ts:
    - Replaces the single .opencode-inline-output area with a
      scrollable .opencode-inline-history (max-height 320px, overflow-y
      auto, auto-scrolls to bottom on update).
    - setMessages(messages): renders user messages as plain text,
      assistant messages via marked.parse. No more setOutput/hideOutput.
  - components/opencode_cell_actions.ts:
    - _showPrompt now fires a _refreshHistory(notebookPath) which
      fetches and calls prompt.setMessages.
    - _handleResponse on success also calls _refreshHistory (the new
      assistant message appears as the last item in the history).
    - Cell source is still NOT replaced.
  - api/opencode_client module is mocked in the cell_actions test to
    avoid jsdom network calls.

style/base.css:
  - .opencode-inline-output* rules replaced with .opencode-inline-history
    (max-height 320px, overflow-y auto, border, padding) and
    .opencode-msg / .opencode-msg-user / .opencode-msg-assistant.
  - pre/code/p/h1-3 content styling scoped under .opencode-inline-history.

Tests:
  - pytest 37/37: FakeOpenCodeClient.list_session_messages + 3 new
    session_messages route tests (no session, projects messages, 400).
  - jest 29/29: setMessages renders user/assistant + history area tests.
  - FakeSessionManager.peek (returns None when session_id is falsy).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 18:45:03 +08:00
tao.chenandClaude Fable 5 1d7f5da9d4 feat: inline markdown output box — render AI reply with marked, do not replace cell
Flow (v3-final corrected):
  1. Model call succeeds.
  2. Server passes the AI reply through unchanged as 'markdown' (the
     system prompt allows ```language fences + a brief explanation, so
     the response is real markdown that marked can render into code
     blocks, headings, etc.).
  3. Client OpenCodeCellActions._handleResponse calls prompt.setOutput(
     resp.markdown); the inline prompt widget renders it with
     marked.parse and shows it in a new output area (hidden until a
     response arrives). The cell source is NOT replaced.
  4. User can close the output or cancel the whole prompt.

Server:
  - New unified system prompt: '你是代码助手 ... 按指令修改代码,可附简
    短说明' (allows ```fences``` + explanation; no more 'no markdown
    fences' restriction).
  - EditHandler returns {ok, markdown, sessionId, notebookPath} (raw
    text, fences intact). _strip_code_fence kept as a helper for any
    future apply-to-cell path; no longer called.
  - finalSource field dropped (the cell-apply path is gone).

Client:
  - OpenCodeSuccess: markdown: string (finalSource removed).
  - OpenCodeInlinePrompt: new output area, setOutput(md) renders via
    marked.parse, hideOutput() closes it.
  - OpenCodeCellActions._handleResponse: setOutput(markdown) instead of
    sharedModel.setSource + auto-hide. The prompt stays open so the
    user can read the output.
  - Uses marked@17 (already in node_modules via JupyterLab; no new dep).
  - CSS: output area styling (border, max-height 320px scroll, code/pre
    styling, close button).

Tests: pytest 34/34, jest 29/29. marked is mocked in jest.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 18:29:58 +08:00
tao.chenandClaude Fable 5 04f90ab5a0 feat: dual select (provider + model) with default[provider] model default
The inline prompt now renders two <select> boxes:
  - Provider: lists every provider from the cached /config/providers
    response. Default = first provider.
  - Model: lists that provider's model keys (Object.keys of the Record).
    Default = default[providerID] (from the same response) when that
    modelID is present in the provider's models; otherwise the first
    model. Changing the provider select rebuilds the model select with
    the new provider's models and its own default[provider].

On submit the two select values are read separately and passed as
providerId / modelId to the OpenCodeRequest (no more '|' delimiter).
The /opencode-bridge/edit body shape is unchanged (providerId, modelId).

Also fix the index.ts startup console log (p.models is a Record, use
Object.values), and drop the unused _cell field on the prompt widget
(now an unused param).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 18:13:14 +08:00
tao.chenandClaude Fable 5 e7579d3779 fix: Provider.models is a Record keyed by modelID, not an array
Per the OpenCode server docs (GET /config/providers), each Provider's
'models' is { [modelID: string]: Model } (a Record), so model IDs can be
referenced as strings (matching the 'default' map of providerID ->
modelID). Our code treated it as Model[], which:
  1. crashed with 'p.models is not iterable' on real responses, and
  2. after the previous Array.isArray guard, skipped EVERY provider
     (a Record is never an array), so the inline picker showed nothing.

Update types.ts (OpenCodeProvider.models -> Record, add OpenCodeModel,
add optional 'default' map to the response) and rewrite
flattenProviders to iterate Object.keys(p.models). Update test fixture
to the Record shape.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 18:01:16 +08:00
tao.chenandClaude Fable 5 f7099be060 fix: guard flattenProviders against non-array p.models
Some OpenCode Serve /config/providers responses include providers
without a 'models' field (or with a non-array value). The un-guarded
for-of on p.models then throws 'p.models is not iterable' and crashes
the inline prompt construction. Skip such providers instead.

Also re-assert the button label as 'AI' (matches the df2f3df label
simplification, which the 02cedac revert had bundled away).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 17:58:29 +08:00
tao.chenandClaude Fable 5 df2f3dff95 style: simplify button labels (drop emoji, keep text)
- 🪄 AI 智能编辑 → AI
- 🚀 发送 → 发送
- ✕ 取消 → 取消

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 17:41:38 +08:00
tao.chen 74953d0ab8 feat: fully dynamic model picker in inline prompt 2026-07-23 16:11:20 +08:00
tao.chen 85e16914b5 feat: single AI action button with inline prompt; replace cell source 2026-07-23 12:47:47 +08:00