mirror of
https://github.com/open-webui/open-webui.git
synced 2025-12-13 12:55:19 +00:00
enh: reference note in chat
This commit is contained in:
parent
f2ee99d760
commit
d5f9bbc7a7
8 changed files with 105 additions and 24 deletions
|
|
@ -18,6 +18,7 @@ from open_webui.retrieval.vector.factory import VECTOR_DB_CLIENT
|
|||
|
||||
from open_webui.models.users import UserModel
|
||||
from open_webui.models.files import Files
|
||||
from open_webui.models.notes import Notes
|
||||
|
||||
from open_webui.retrieval.vector.main import GetResult
|
||||
|
||||
|
|
@ -470,7 +471,15 @@ def get_sources_from_files(
|
|||
"documents": [[doc.get("content") for doc in file.get("docs")]],
|
||||
"metadatas": [[doc.get("metadata") for doc in file.get("docs")]],
|
||||
}
|
||||
elif file.get("context") == "full":
|
||||
elif file.get("type") == "note":
|
||||
# Note Attached
|
||||
note = Notes.get_note_by_id(file.get("id"))
|
||||
|
||||
query_result = {
|
||||
"documents": [[note.data.get("content", {}).get("md", "")]],
|
||||
"metadatas": [[{"file_id": note.id, "name": note.title}]],
|
||||
}
|
||||
elif file.get("context") == "full" and file.get("type") == "file":
|
||||
# Manual Full Mode Toggle
|
||||
query_result = {
|
||||
"documents": [[file.get("file").get("data", {}).get("content")]],
|
||||
|
|
|
|||
|
|
@ -51,7 +51,14 @@ async def get_notes(request: Request, user=Depends(get_verified_user)):
|
|||
return notes
|
||||
|
||||
|
||||
@router.get("/list", response_model=list[NoteUserResponse])
|
||||
class NoteTitleIdResponse(BaseModel):
|
||||
id: str
|
||||
title: str
|
||||
updated_at: int
|
||||
created_at: int
|
||||
|
||||
|
||||
@router.get("/list", response_model=list[NoteTitleIdResponse])
|
||||
async def get_note_list(request: Request, user=Depends(get_verified_user)):
|
||||
|
||||
if user.role != "admin" and not has_permission(
|
||||
|
|
@ -63,12 +70,7 @@ async def get_note_list(request: Request, user=Depends(get_verified_user)):
|
|||
)
|
||||
|
||||
notes = [
|
||||
NoteUserResponse(
|
||||
**{
|
||||
**note.model_dump(),
|
||||
"user": UserResponse(**Users.get_user_by_id(note.user_id).model_dump()),
|
||||
}
|
||||
)
|
||||
NoteTitleIdResponse(**note.model_dump())
|
||||
for note in Notes.get_notes_by_user_id(user.id, "read")
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ export const createNewNote = async (token: string, note: NoteItem) => {
|
|||
return res;
|
||||
};
|
||||
|
||||
export const getNotes = async (token: string = '') => {
|
||||
export const getNotes = async (token: string = '', raw: boolean = false) => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${WEBUI_API_BASE_URL}/notes/`, {
|
||||
|
|
@ -67,6 +67,10 @@ export const getNotes = async (token: string = '') => {
|
|||
throw error;
|
||||
}
|
||||
|
||||
if (raw) {
|
||||
return res; // Return raw response if requested
|
||||
}
|
||||
|
||||
if (!Array.isArray(res)) {
|
||||
return {}; // or throw new Error("Notes response is not an array")
|
||||
}
|
||||
|
|
@ -87,6 +91,37 @@ export const getNotes = async (token: string = '') => {
|
|||
return grouped;
|
||||
};
|
||||
|
||||
export const getNoteList = async (token: string = '') => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${WEBUI_API_BASE_URL}/notes/list`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
authorization: `Bearer ${token}`
|
||||
}
|
||||
})
|
||||
.then(async (res) => {
|
||||
if (!res.ok) throw await res.json();
|
||||
return res.json();
|
||||
})
|
||||
.then((json) => {
|
||||
return json;
|
||||
})
|
||||
.catch((err) => {
|
||||
error = err.detail;
|
||||
console.error(err);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
export const getNoteById = async (token: string, id: string) => {
|
||||
let error = null;
|
||||
|
||||
|
|
|
|||
|
|
@ -1597,9 +1597,8 @@
|
|||
let files = JSON.parse(JSON.stringify(chatFiles));
|
||||
files.push(
|
||||
...(userMessage?.files ?? []).filter((item) =>
|
||||
['doc', 'file', 'collection'].includes(item.type)
|
||||
),
|
||||
...(responseMessage?.files ?? []).filter((item) => ['web_search_results'].includes(item.type))
|
||||
['doc', 'file', 'note', 'collection'].includes(item.type)
|
||||
)
|
||||
);
|
||||
// Remove duplicates
|
||||
files = files.filter(
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
import { tick, getContext, onMount, onDestroy } from 'svelte';
|
||||
import { removeLastWordFromString, isValidHttpUrl } from '$lib/utils';
|
||||
import { knowledge } from '$lib/stores';
|
||||
import { getNoteList, getNotes } from '$lib/apis/notes';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
|
|
@ -75,10 +76,23 @@
|
|||
}
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
onMount(async () => {
|
||||
window.addEventListener('resize', adjustHeight);
|
||||
adjustHeight();
|
||||
|
||||
let notes = await getNoteList(localStorage.token).catch(() => {
|
||||
return [];
|
||||
});
|
||||
|
||||
notes = notes.map((note) => {
|
||||
return {
|
||||
...note,
|
||||
type: 'note',
|
||||
name: note.title,
|
||||
description: dayjs(note.updated_at / 1000000).fromNow()
|
||||
};
|
||||
});
|
||||
|
||||
let legacy_documents = $knowledge
|
||||
.filter((item) => item?.meta?.document)
|
||||
.map((item) => ({
|
||||
|
|
@ -144,14 +158,18 @@
|
|||
]
|
||||
: [];
|
||||
|
||||
items = [...collections, ...collection_files, ...legacy_collections, ...legacy_documents].map(
|
||||
(item) => {
|
||||
return {
|
||||
...item,
|
||||
...(item?.legacy || item?.meta?.legacy || item?.meta?.document ? { legacy: true } : {})
|
||||
};
|
||||
}
|
||||
);
|
||||
items = [
|
||||
...notes,
|
||||
...collections,
|
||||
...collection_files,
|
||||
...legacy_collections,
|
||||
...legacy_documents
|
||||
].map((item) => {
|
||||
return {
|
||||
...item,
|
||||
...(item?.legacy || item?.meta?.legacy || item?.meta?.document ? { legacy: true } : {})
|
||||
};
|
||||
});
|
||||
|
||||
fuse = new Fuse(items, {
|
||||
keys: ['name', 'description']
|
||||
|
|
@ -210,6 +228,12 @@
|
|||
>
|
||||
File
|
||||
</div>
|
||||
{:else if item?.type === 'note'}
|
||||
<div
|
||||
class="bg-blue-500/20 text-blue-700 dark:text-blue-200 rounded-sm uppercase text-xs font-bold px-1 shrink-0"
|
||||
>
|
||||
Note
|
||||
</div>
|
||||
{:else}
|
||||
<div
|
||||
class="bg-green-500/20 text-green-700 dark:text-green-200 rounded-sm uppercase text-xs font-bold px-1 shrink-0"
|
||||
|
|
|
|||
|
|
@ -442,7 +442,10 @@
|
|||
|
||||
const downloadHandler = async (type) => {
|
||||
console.log('downloadHandler', type);
|
||||
if (type === 'md') {
|
||||
if (type === 'txt') {
|
||||
const blob = new Blob([note.data.content.md], { type: 'text/plain' });
|
||||
saveAs(blob, `${note.title}.txt`);
|
||||
} else if (type === 'md') {
|
||||
const blob = new Blob([note.data.content.md], { type: 'text/markdown' });
|
||||
saveAs(blob, `${note.title}.md`);
|
||||
} else if (type === 'pdf') {
|
||||
|
|
|
|||
|
|
@ -302,7 +302,7 @@
|
|||
</div>
|
||||
|
||||
<div
|
||||
class="mb-5 gap-2.5 grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4"
|
||||
class="mb-5 gap-2.5 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5"
|
||||
>
|
||||
{#each notes[timeRange] as note, idx (note.id)}
|
||||
<div
|
||||
|
|
@ -340,7 +340,7 @@
|
|||
</div>
|
||||
|
||||
<div
|
||||
class=" text-xs text-gray-500 dark:text-gray-500 mb-3 line-clamp-5 min-h-18"
|
||||
class=" text-xs text-gray-500 dark:text-gray-500 mb-3 line-clamp-3 min-h-10"
|
||||
>
|
||||
{#if note.data?.content?.md}
|
||||
{note.data?.content?.md}
|
||||
|
|
|
|||
|
|
@ -57,6 +57,15 @@
|
|||
transition={flyAndScale}
|
||||
sideOffset={8}
|
||||
>
|
||||
<DropdownMenu.Item
|
||||
class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
|
||||
on:click={() => {
|
||||
onDownload('txt');
|
||||
}}
|
||||
>
|
||||
<div class="flex items-center line-clamp-1">{$i18n.t('Plain text (.txt)')}</div>
|
||||
</DropdownMenu.Item>
|
||||
|
||||
<DropdownMenu.Item
|
||||
class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
|
||||
on:click={() => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue