Repository navigation
[AI-100] DO NOT MERGE: description assistant prototype on the AI-3 LLM connection - #25245
Draft
jonasjabari wants to merge 54 commits into
Draft
jonasjabari wants to merge 54 commits into
jonasjabari wants to merge 54 commits into
Conversation
Deploying openproject with ⚡ PullPreview
|
Collaborator
Author
Bildschirmaufnahme.2026-09-10.um.17.47.03.mov |
|
Warning Flaky specs
🤖 Ask Copilot to investigateCopy the prompt below into a new comment on this PR to delegate the investigation to GitHub Copilot. It will look into the flakiness and open a separate pull request with you as reviewer. |
jonasjabari
force-pushed
the
demo/ai-100-prototype-pullpreview
branch
from
September 16, 2026 15:51
75c760c to
79b2a3e
Compare
|
Warning Flaky specs
🤖 Ask Copilot to investigateCopy the prompt below into a new comment on this PR to delegate the investigation to GitHub Copilot. It will look into the flakiness and open a separate pull request with you as reviewer. |
|
Warning Flaky specs
🤖 Ask Copilot to investigateCopy the prompt below into a new comment on this PR to delegate the investigation to GitHub Copilot. It will look into the flakiness and open a separate pull request with you as reviewer. |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017HL6QmQW5pV6v8FAig2gJB
Adds a sandbox below the text transform action form that posts text against the run API and polls the run, so the whole stack can be tried in the browser. The representer exposes the stored system prompt for this sandbox only. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017HL6QmQW5pV6v8FAig2gJB
Replaces the environment-configured prototype gateway with a gateway on Llm::Runtime, so the description assistant uses the connection and the model binding an administrator configures in the admin UI. Enables the llm_connection feature flag for PullPreview. Demo only, never to be merged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017HL6QmQW5pV6v8FAig2gJB
RubyLLM sends instructions with the role "developer" unless openai_use_system_role is set. OpenAI-compatible gateways such as the one used for this demo ignore that role, so the whole system prompt was dropped. This is a finding for the AI-3 stack (Llm::Session); patched here for the demo only. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017HL6QmQW5pV6v8FAig2gJB
Primer form with a context selector that explains when a work package or a project and type are needed, selects instead of raw id fields, a spinner with an elapsed timer while the run is polled, and a details dialog with the stored system prompt and the polled events. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017HL6QmQW5pV6v8FAig2gJB
…dbox Replaces the preloaded selects with Primer select panels that fetch up to 20 matches from a small admin-only search endpoint while typing, so the sandbox also works on installations with many work packages and projects. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017HL6QmQW5pV6v8FAig2gJB
A second submit button updates the action and answers with a Turbo Stream that replaces only the form, so the sandbox below keeps its state instead of being lost to the redirect to the index page. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017HL6QmQW5pV6v8FAig2gJB
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017HL6QmQW5pV6v8FAig2gJB
The admin sandbox shows the assembled system prompt in its details dialog. The real API (AI-136) keeps it hidden; this is demo-only. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017HL6QmQW5pV6v8FAig2gJB
…ends Returning to the tab after a run had finished scheduled one more poll, which re-entered finish() and moved finishedAt to the current time, so the timer showed the time away from the tab instead of the run duration. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017HL6QmQW5pV6v8FAig2gJB
Reasoning models stream minutes of chunks without content. The gateway dropped them, so the execution never got a chance to look at the cancel wish or its time budget before the first content chunk. Empty chunks are now passed on as empty deltas. The gateway also restores any error the caller raises inside the stream block, not only the cancellation: Llm::Errors.wrap turned an exceeded budget into a generic upstream error. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017HL6QmQW5pV6v8FAig2gJB
AI-126 asks for a "generation stopped" state when a guardrail blocks the stream. The event vocabulary has no such reason and the real signal of the LLM stack is unknown, so the demo adds the reason "blocked" and a gateway wrapper that produces it, or a connection failure, when the run API is called with the demo-only parameter demoFault. Also holds the locale keys of the editor menu and the result popover. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017HL6QmQW5pV6v8FAig2gJB
…pover Editor demo for AI-126, mode A only (the whole description): - The description editor mounts an AI action menu at the end of its toolbar row. The server renders the Primer ActionMenu shell, the items come from the list endpoints of AI-134. - The result popover runs the chosen action through the run API of AI-136, renders the streamed markdown through the markdown render endpoint and offers cancel, copy, discard, replace and try again. It can be dragged. - The run client (create, cursor polling, cancel) is extracted from the admin sandbox and shared by both. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017HL6QmQW5pV6v8FAig2gJB
- A menu item was a submit button inside the edit field's form, so choosing an action saved the description and closed the editor. Items are plain buttons now, and Replace finds its editor again. - The popover stayed empty: the API v3 render endpoints were removed from dev (OP-19480). A demo endpoint renders the streamed markdown instead. - The prototype controls are gone from the popover. A blocked or failed generation can still be forced with ?ai_demo_fault=blocked|failed in the page URL. The grip moved into the footer as in the mockup. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017HL6QmQW5pV6v8FAig2gJB
Requested after the first UI test: a plain border instead of the popover caret, the drag grip in line with the footer buttons, and a resize handle in the bottom right corner (minimum 320x240, kept inside the viewport). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017HL6QmQW5pV6v8FAig2gJB
…ners Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017HL6QmQW5pV6v8FAig2gJB
With text selected in the description editor, the chosen action receives exactly that selection as markdown and Replace swaps the same range, even if the user kept typing meanwhile. Actions that inject the type template still run on the whole description. Built on the CKEditor model and data APIs of the vendored build: the selection becomes markdown through the build's turndown processor, a marker pins the range, and insertContent puts the answer back with undo intact. Nothing changes in the backend or the run API. The selection is sent as is, not expanded to whole blocks, so structural breakages surface. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017HL6QmQW5pV6v8FAig2gJB
…tion Replace in document mode now goes through insertContent over the root range instead of setData, so the user can undo the AI result like any other edit. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017HL6QmQW5pV6v8FAig2gJB
The pane wraps Primer::Alpha::Overlay instead of the Popover: header with close button, body and footer slots, shown in the browser top layer. It is a manual popover so clicks into the editor do not dismiss it, anchored to a fixed point top right below the app header. Following the design system rule not to change Primer components for a feature, dragging and resizing stay on the wrapper's Stimulus controller. A dragged or resized pane is pinned with important inline styles, because anchored-position re-anchors on every window or document resize. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017HL6QmQW5pV6v8FAig2gJB
anchored-position re-anchors on window and document resizes by assigning inline top and left, which also drops an inline important. The dragged or resized position now lives in CSS variables behind a scoped pinned rule. Also resets the text alignment: the align="end" anchoring attribute on anchored-position is read as the legacy alignment attribute and right- aligned the whole pane. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017HL6QmQW5pV6v8FAig2gJB
The ai-text-transform-pane controller makes the result pane a non-modal floating window: drag by header or footer grip, resize from four handles (min 320x240, 8px viewport margin), keep the pinned geometry when the pane element is replaced, close on Escape through the close form (or remove the pane without one), copy the raw text with short "Copied" feedback and focus the pane on connect. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Attaches ai-text-transform-pane to the pane wrapper with the close form id, copy labels and the Escape action; marks the pane as focusable target, the header and footer grip as drag handles, the resize spans with beginResize, the Copy button label and the raw template. The apply-failed banner gets its targets for AI-176. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Review follow-up: - Copy uses Primer::Beta::ClipboardCopyButton (icon-only, copy to check feedback, live region) instead of an own clipboard call; drop the unused pane.copied locale key. - Gate drag starts with closestDragBlockingElement and the primary button, end pointer actions on pointercancel, track listeners with an AbortController and set touch-action: none on drag surfaces. - Clear max-width while resizing so the left handle keeps the right edge; clamp restored geometry and re-clamp on window resize. - Wire the header close button to the close action. - action helper takes several actions per element. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Apply review suggestions: query the pane, close button and link by role, dispatch events with fireEvent and assert with jest-dom matchers. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…floating-pane-behaviour
Routes under /ai/text_transform_panes for starting, polling, closing, applying and cancelling a result pane run. The contract validates the request id (UUID v4), scope and context ids every pane request carries. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The create action resolves the context, starts a run (or repeats one of the user's own runs) and appends the pane to the body. Unavailable actions render the pane as failed; invalid parameters answer 400. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
show answers polls with updated body and footer sections (204 while nothing changed), destroy cancels the run and removes the pane, apply dispatches the completed text to the editor and cancel stops a run whose pane disappeared. All lookups are scoped to the user's own runs. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Validate action_id, input and retry_of in the contract, reject a work package combined with a project and a project without a type (like the API v3 run endpoint), and accept blank context ids. Only failed runs can be retried. Polls resolve the context only when they render, the pane append renders lazily like the other turbo stream helpers, and new events are detected through TextTransformRun#events_after. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Panes for a new work package have no work package to derive the project from, so project-relative links such as wiki links did not resolve. The pane state now carries the context's project. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…pullpreview # Conflicts: # app/components/ai/text_transforms/result_pane_component.html.erb # app/components/ai/text_transforms/result_pane_component.rb # app/components/ai/text_transforms/result_pane_component.sass # app/components/ai/text_transforms/result_pane_helpers.rb # app/components/ai/text_transforms/result_pane_state.rb # config/locales/en.yml # frontend/src/app/shared/components/editor/components/ckeditor/ckeditor-setup.service.ts # frontend/src/app/shared/components/editor/components/ckeditor/op-ckeditor.component.ts
The demo keeps its own result pane (target architecture with forms and polling); the presentational components that AI-172 merged into dev under the same names are not used here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017HL6QmQW5pV6v8FAig2gJB
Brings the editor side of tangopium's AI-101 into the demo, but only the button: the CKEditor bundle is built from commonmark-ckeditor-build master plus opf/commonmark-ckeditor-build#131, and the core registers the aiActions plugin service with actionsFor and defaultAction as in #25770. The service's run follows the result pane architecture instead of the prototype in #25770: it reads the selection from the editor model, posts to the pane controller through turboRequests and applies the text the pane hands back with model.insertContent (undo keeps working). The demo's own AI menu next to the toolbar and the editor wrapper events are gone; the pane's event names move next to the service. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017HL6QmQW5pV6v8FAig2gJB
CKEditor 49 colours a hovered split button with --ck-color-split-button-hover-background, falling back to --ck-color-base-hover, which stays light in the dark theme. The Assistant button is the first split button in the editor toolbar, so the mapping to the Primer button hover colour was missing until now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017HL6QmQW5pV6v8FAig2gJB
The result pane from AI-172 is on dev. The demo drops its own pane (section component, pane controller, Stimulus controller, routes) and takes the dev files again, so the open AI-173 and AI-175 branches can be merged on top. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017HL6QmQW5pV6v8FAig2gJB
…ane-behaviour' into demo/ai-100-prototype-pullpreview
…e-endpoints' into demo/ai-100-prototype-pullpreview
The pane now comes from AI-172 (dev) with the open AI-173 and AI-175 branches merged in. What those do not cover yet is demo glue: - the footer renders the close, apply and retry forms and a state element (AI-175's component part), wired to AI-173's close action; - a small Stimulus controller on that state element polls while the run generates, waits for the editor's applied answer and cancels the run when the pane disappears mid-run (AI-176's pane part); - the pane controller accepts ?ai_demo_fault= for the demo. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017HL6QmQW5pV6v8FAig2gJB
The CKEditor Assistant button asks the core aiActions plugin service which text transform actions the work package description editor may offer, and which of them to show first: the one run last in this browser, or the first by position. The service resolves the list through the work package endpoint, or the project and type endpoint for a new work package; any failure leaves the list empty, which keeps the button hidden. run is only the seam the plugin calls: it remembers the chosen action. The result pane wiring follows in AI-176. Ported from #25770 without the prototype run. https://community.openproject.org/wp/AI-101 Co-authored-by: Andreas Eiselt <andreas.eiselt@gmail.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Comments, wiki pages, forum messages, meetings, custom fields and admin texts now list the actions scoped "everywhere" through the context-free endpoint. Only the work package description keeps the work package or project endpoint, which adds type-scoped actions and requires the right to edit the work package. https://community.openproject.org/wp/AI-101 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Built from opf/commonmark-ckeditor-build master (83a9f79) plus #131 rebased onto it (213c255). The plugin sits in builtinPlugins, so the full and the constrained editors both show the button once the service lists actions. The label is capped at 12em so an admin-defined name cannot push the button out of the toolbar. https://community.openproject.org/wp/AI-101 Co-authored-by: Andreas Eiselt <andreas.eiselt@gmail.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
CKEditor 49 colours a hovered split button with --ck-color-split-button-hover-background, which falls back to --ck-color-base-hover and stays light in the dark theme: the Assistant button turned light with white text on hover and while its menu was open. It is the first split button in the editor toolbar. https://community.openproject.org/wp/AI-101 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With a ready gateway, the description editor offers the type-scoped and the "everywhere" actions, and the comment editor only the "everywhere" ones. https://community.openproject.org/wp/AI-101 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…0-prototype-pullpreview The demo keeps its pane run in AiActionsService on top of AI-101's actionsFor for every editor; outside the work package description the pane request carries no context. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017HL6QmQW5pV6v8FAig2gJB
CKEditor's config.get deep-clones the value it returns; cloning the whole openProject config fails on the context's HAL resource (read-only getters), so the click on an action threw before the pane request. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017HL6QmQW5pV6v8FAig2gJB
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ticket
https://community.openproject.org/wp/AI-100
What is this?
A demo-only prototype of the AI description assistant (AI-100, AI-126), running on the real LLM connection from the AI-3 stack. Never to be merged: the branch is the open part of the AI-3 stack (PR 7 #24885 up to PR 12 #25349; PRs 1 to 6 are on
devsince 2026-09-30) plusdevplus the demo commits, so the diff shows both. AI-133 to AI-136 are ondev. Rebuilt on 2026-10-02 after the AI-3 migrations were renumbered ondev; a demo-only migration drops the LLM tables of the earlier stack once, so LLM settings entered before have to be entered again. Last synced withdevon 2026-10-23.What it demonstrates:
POST /api/v3/ai_text_transform_runs,GET …/:uuid?after=<seq>,POST …/:uuid/cancel).AI::TextTransforms::LlmGateway, the AI-137 shape:Llm::Runtime.for(:description_assistant)with streaming, zero retries and error translation.Editor demo (AI-126)
Open a work package and click into the description. The "Assistant" split button at the right end of the toolbar is the real CKEditor plugin from AI-101 (opf/commonmark-ckeditor-build#131, bundle built from build-repo master plus that PR). It lists the actions the API offers for the work package (AI-134); the main part runs the last used action, the arrow opens the list.
Only the button is taken from AI-101 (#25770). The result pane is the real implementation: AI-172 from dev plus the open PR branches of AI-173 (#26091, floating behaviour) and AI-175 (#26138, endpoints), merged into this branch; the parts no ticket delivers yet (footer forms, polling, the applied wait) are demo glue until AI-175/AI-176 land. What happens after the click:
AiActionsService.run(action, editor, resource)reads the selection from the editor model and posts toAI::TextTransformPanesControllerthroughturboRequests, like the wiki page link macro requests its dialog. The controller starts the run and appends the result pane (Primer::Beta::BorderBox) to the body.format_text, or204when nothing changed. No separate markdown endpoint; the API v3 run endpoints are not used by the pane.dispatchEventstream action, tied to the request by arequestId, and is applied withmodel.insertContent.With text selected, only the selection is sent and replaced; otherwise the whole description. Replace keeps the editor's undo history. The pane can be dragged and resized from four handles. Append
?ai_demo_fault=blockedor?ai_demo_fault=failedto the page URL to see the "stopped" and "failed" states.Try it in the PullPreview
https://…/v1), API key, save, sync models, enable. If the gateway's models are missing from the list, add them by hand.Findings for AI-3 while building this (found on the stack version of 2026-09-09, not re-checked against the rewrite)
Llm::Sessionsends instructions with thedeveloperrole; OpenAI-compatible gateways drop it silently, so the whole system prompt is lost. Patched here withopenai_use_system_role = true.api_format: openrouterthe model list is registry-backed, not fetched from the server; a bound model may not exist on the gateway./chat/completionsand 404.GET /v1/modelsreturns a bare JSON array, not the OpenAI{ "data": [...] }envelope, which blocked saving the connection. Fixed upstream in [AI-3] Discover the embedding models OpenRouter serves separately #25349; the demo-only patch is gone.The real implementation lands as AI-133 to AI-137.
🤖 Generated with Claude Code