[PR #24865] feat(chat): citation modal as global singleton via store #115206

Open
opened 2026-05-18 16:09:30 -05:00 by GiteaMirror · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/open-webui/open-webui/pull/24865
Author: @julenlurnova
Created: 5/18/2026
Status: 🔄 Open

Base: devHead: feat/citation-modal-store


📝 Commits (3)

  • 328474c feat(chat): add citation modal store
  • 9f89645 feat(chat): mount global citation modal in layout
  • ab57a0a refactor(chat): dispatch citation modal via store

📊 Changes

3 files changed (+48 additions, -20 deletions)

View changed files

📝 src/lib/components/chat/Messages/Citations.svelte (+4 -20)
src/lib/stores/citations.ts (+31 -0)
📝 src/routes/(app)/+layout.svelte (+13 -0)

📄 Description

Before submitting, make sure you've checked the following:

  • Target branch: dev
  • Description: Below
  • Changelog: Below
  • Documentation: No user-facing API change, no env vars, no deployment steps
  • Dependencies: No new dependencies
  • Testing: Manual coverage across open/close paths, multi-chat, embed_url branch, readOnly mode (see below)
  • Agentic AI Code: AI-assisted authorship. Code has been reviewed line by line by the author, manually tested across multiple scenarios, and has been running in a downstream fork in production for several days without regression.
  • Code review: Self-reviewed
  • Design & Architecture: Follows the existing embed / showEmbeds store pattern. No new settings, no UX change.
  • Git Hygiene: 3 atomic commits, rebased on dev
  • Title Prefix: feat

Changelog Entry

Description

Citations.svelte mounts <CitationModal> per instance. A chat with N RAG responses has N modal state machines in the DOM, idle until clicked.

This PR applies the same singleton pattern already used by embed / showEmbeds / showControls (writable stores in $lib/stores, sink mounted once at app level, any caller dispatches via store action). Three benefits:

  1. Removes N-instances cost (memory, listeners, lifecycle).
  2. Markdown citation-marker clicks can call openCitation() directly — no bind:this ref hacks back into the right <Citations> instance.
  3. Gives forks an extension point (subscribe to citationModal, render alongside the modal).

Added

  • src/lib/stores/citations.tscitationModal writable store + openCitation / closeCitation actions.
  • Global <CitationModal> mount in src/routes/(app)/+layout.svelte subscribed to the store.

Changed

  • src/lib/components/chat/Messages/Citations.svelte — dispatches via openCitation() instead of a local <CitationModal bind:show> mount. showSourceModal export kept as a thin wrapper for back-compat with external callers (e.g. ResponseMessage.svelte which still calls citationsElement.showSourceModal(id) from a Markdown click handler).

Deprecated

N/A

Removed

  • Per-instance modal mount and local state (showCitationModal, selectedCitation) in Citations.svelte.

Fixed

N/A — pure refactor, no bug fix.

Security

N/A

Breaking Changes

None. Existing consumers of Citations.svelte keep working without modification; showSourceModal external API preserved.


Additional Information

Bridge note. The +layout bridge between the store and <CitationModal bind:show={local}> uses one-way sync (store → local) plus a close-back handler:

let citationModalShow = false;
$: citationModalShow = $citationModal.show;
$: if (!citationModalShow && $citationModal.show) closeCitation();

The naive if ($store.show !== local) { sync } form races itself: the X click writes false to local via bind:show, then the reactive immediately re-syncs from the store (still true), undoing the close. The split form above avoids the loop because the first $: only writes local, never reads it; the second $: only triggers when local transitions to false while the store is still open.

Manual tests performed.

  • Click chip pill → modal opens; X / Esc / backdrop close.
  • Inline Markdown [n] marker click → modal opens (via showSourceModal wrapper, the existing path through ResponseMessage.svelte).
  • embed_url citation → opens right-side embed panel (unchanged path, the showSourceModal wrapper preserves the embed branch).
  • readOnly mode (shared chat) → opens link in new tab (unchanged).
  • Multiple chats in tabs → single global modal instance, no cross-chat leakage.

Follow-ups. The same pattern fits ImagePreview (Image.svelte currently mounts one per image — 7+ call sites in the chat tree) and CodeExecutionModal. Happy to open those as separate small PRs after this is reviewed.

Screenshots or Videos

Refactor with no UX change; visual behavior is identical to current main.

Contributor License Agreement


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/open-webui/open-webui/pull/24865 **Author:** [@julenlurnova](https://github.com/julenlurnova) **Created:** 5/18/2026 **Status:** 🔄 Open **Base:** `dev` ← **Head:** `feat/citation-modal-store` --- ### 📝 Commits (3) - [`328474c`](https://github.com/open-webui/open-webui/commit/328474ca1fd0c3ce68d7ed16348dbfc75df44122) feat(chat): add citation modal store - [`9f89645`](https://github.com/open-webui/open-webui/commit/9f896457628bc411689e7f154f419349b9f5a423) feat(chat): mount global citation modal in layout - [`ab57a0a`](https://github.com/open-webui/open-webui/commit/ab57a0a93cd18cf1c1561d16f26b914aa6da5508) refactor(chat): dispatch citation modal via store ### 📊 Changes **3 files changed** (+48 additions, -20 deletions) <details> <summary>View changed files</summary> 📝 `src/lib/components/chat/Messages/Citations.svelte` (+4 -20) ➕ `src/lib/stores/citations.ts` (+31 -0) 📝 `src/routes/(app)/+layout.svelte` (+13 -0) </details> ### 📄 Description **Before submitting, make sure you've checked the following:** - [x] **Target branch:** `dev` - [x] **Description:** Below - [x] **Changelog:** Below - [x] **Documentation:** No user-facing API change, no env vars, no deployment steps - [x] **Dependencies:** No new dependencies - [x] **Testing:** Manual coverage across open/close paths, multi-chat, embed_url branch, readOnly mode (see below) - [x] **Agentic AI Code:** AI-assisted authorship. Code has been reviewed line by line by the author, manually tested across multiple scenarios, and has been running in a downstream fork in production for several days without regression. - [x] **Code review:** Self-reviewed - [x] **Design & Architecture:** Follows the existing `embed` / `showEmbeds` store pattern. No new settings, no UX change. - [x] **Git Hygiene:** 3 atomic commits, rebased on `dev` - [x] **Title Prefix:** `feat` # Changelog Entry ### Description `Citations.svelte` mounts `<CitationModal>` per instance. A chat with N RAG responses has N modal state machines in the DOM, idle until clicked. This PR applies the same singleton pattern already used by `embed` / `showEmbeds` / `showControls` (writable stores in `$lib/stores`, sink mounted once at app level, any caller dispatches via store action). Three benefits: 1. Removes N-instances cost (memory, listeners, lifecycle). 2. Markdown citation-marker clicks can call `openCitation()` directly — no `bind:this` ref hacks back into the right `<Citations>` instance. 3. Gives forks an extension point (subscribe to `citationModal`, render alongside the modal). ### Added - `src/lib/stores/citations.ts` — `citationModal` writable store + `openCitation` / `closeCitation` actions. - Global `<CitationModal>` mount in `src/routes/(app)/+layout.svelte` subscribed to the store. ### Changed - `src/lib/components/chat/Messages/Citations.svelte` — dispatches via `openCitation()` instead of a local `<CitationModal bind:show>` mount. `showSourceModal` export kept as a thin wrapper for back-compat with external callers (e.g. `ResponseMessage.svelte` which still calls `citationsElement.showSourceModal(id)` from a Markdown click handler). ### Deprecated _N/A_ ### Removed - Per-instance modal mount and local state (`showCitationModal`, `selectedCitation`) in `Citations.svelte`. ### Fixed _N/A — pure refactor, no bug fix._ ### Security _N/A_ ### Breaking Changes None. Existing consumers of `Citations.svelte` keep working without modification; `showSourceModal` external API preserved. --- ### Additional Information **Bridge note.** The `+layout` bridge between the store and `<CitationModal bind:show={local}>` uses one-way sync (store → local) plus a close-back handler: ```svelte let citationModalShow = false; $: citationModalShow = $citationModal.show; $: if (!citationModalShow && $citationModal.show) closeCitation(); ``` The naive `if ($store.show !== local) { sync }` form races itself: the X click writes `false` to `local` via `bind:show`, then the reactive immediately re-syncs from the store (still `true`), undoing the close. The split form above avoids the loop because the first `$:` only writes `local`, never reads it; the second `$:` only triggers when `local` transitions to `false` while the store is still open. **Manual tests performed.** - Click chip pill → modal opens; X / Esc / backdrop close. - Inline Markdown `[n]` marker click → modal opens (via `showSourceModal` wrapper, the existing path through `ResponseMessage.svelte`). - `embed_url` citation → opens right-side embed panel (unchanged path, the `showSourceModal` wrapper preserves the embed branch). - `readOnly` mode (shared chat) → opens link in new tab (unchanged). - Multiple chats in tabs → single global modal instance, no cross-chat leakage. **Follow-ups.** The same pattern fits `ImagePreview` (`Image.svelte` currently mounts one per image — 7+ call sites in the chat tree) and `CodeExecutionModal`. Happy to open those as separate small PRs after this is reviewed. ### Screenshots or Videos Refactor with no UX change; visual behavior is identical to current `main`. ### Contributor License Agreement - [x] By submitting this pull request, I confirm that I have read and fully agree to the [Contributor License Agreement (CLA)](https://github.com/open-webui/open-webui/blob/main/CONTRIBUTOR_LICENSE_AGREEMENT), and I am providing my contributions under its terms. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
GiteaMirror added the pull-request label 2026-05-18 16:09:30 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/open-webui#115206