feat: support folder, note, and model drag-and-drop from sidebar as context references (#25771)

* feat(ui): add drag-to-reorder for pinned notes in sidebar

* feat(chat): support folder, note, and model drag-and-drop from sidebar as context references

---------

Co-authored-by: Tim Baek <tim@openwebui.com>
This commit is contained in:
G30 2026-06-29 05:41:16 -04:00 committed by GitHub
parent b617d56c60
commit f53ec857c0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 65 additions and 1 deletions

View file

@ -57,6 +57,8 @@
import { generateAutoCompletion } from '$lib/apis';
import { deleteFileById } from '$lib/apis/files';
import { getChatById } from '$lib/apis/chats';
import { getFolderById } from '$lib/apis/folders';
import { getNoteById } from '$lib/apis/notes';
import { getSessionUser } from '$lib/apis/auths';
import { WEBUI_BASE_URL, WEBUI_API_BASE_URL, PASTED_TEXT_CHARACTER_LIMIT } from '$lib/constants';
@ -874,7 +876,7 @@
e.preventDefault();
console.log(e);
// Check if the dropped data is a sidebar chat item
// Check if the dropped data is a sidebar chat, folder, note, or model item
const textData = e.dataTransfer?.getData('text/plain');
if (textData) {
try {
@ -897,6 +899,49 @@
dragged = false;
e.stopPropagation();
return;
} else if (data.type === 'folder' && data.id) {
// Fetch the folder to get its name, then add as a reference folder
const folder = await getFolderById(localStorage.token, data.id);
if (folder) {
const folderItem = {
type: 'folder',
id: folder.id,
name: folder.name,
status: 'processed'
};
if (!files.find((f) => f.id === folderItem.id)) {
files = [...files, folderItem];
}
}
dragged = false;
e.stopPropagation();
return;
} else if (data.type === 'note' && data.id) {
// Fetch the note to get its title, then add as a reference note
const note = await getNoteById(localStorage.token, data.id);
if (note) {
const noteItem = {
type: 'note',
id: note.id,
name: note.title,
status: 'processed'
};
if (!files.find((f) => f.id === noteItem.id)) {
files = [...files, noteItem];
}
}
dragged = false;
e.stopPropagation();
return;
} else if (data.type === 'model' && data.id) {
// Find the model from the store and set as @-selected model
const model = $models.find((m) => m.id === data.id);
if (model) {
atSelectedModel = model;
}
dragged = false;
e.stopPropagation();
return;
}
} catch (_) {
// Not valid JSON — fall through to file handling

View file

@ -18,6 +18,15 @@
if (pinnedModelsList && !$mobile) {
new Sortable(pinnedModelsList, {
animation: 150,
setData: function (dataTransfer, dragEl) {
dataTransfer.setData(
'text/plain',
JSON.stringify({
type: 'model',
id: dragEl.dataset.id
})
);
},
onUpdate: async (event) => {
const modelId = event.item.dataset.id;
const newIndex = event.newIndex;

View file

@ -54,6 +54,16 @@
<div
class="flex items-center text-gray-800 dark:text-gray-200 cursor-grab relative group rounded-xl px-2.5 py-1.5 hover:bg-gray-100 dark:hover:bg-gray-900 transition"
data-id={note.id}
draggable="true"
on:dragstart={(e) => {
e.dataTransfer.setData(
'text/plain',
JSON.stringify({
type: 'note',
id: note.id
})
);
}}
>
<a
class="grow flex items-center gap-2.5 text-sm"