mirror of
https://github.com/open-webui/open-webui.git
synced 2025-12-12 04:15:25 +00:00
feat/enh: create note from input
This commit is contained in:
parent
65d4b22c7c
commit
00c2b6ca40
5 changed files with 93 additions and 20 deletions
|
|
@ -135,7 +135,6 @@ async def search_notes(
|
||||||
async def create_new_note(
|
async def create_new_note(
|
||||||
request: Request, form_data: NoteForm, user=Depends(get_verified_user)
|
request: Request, form_data: NoteForm, user=Depends(get_verified_user)
|
||||||
):
|
):
|
||||||
|
|
||||||
if user.role != "admin" and not has_permission(
|
if user.role != "admin" and not has_permission(
|
||||||
user.id, "features.notes", request.app.state.config.USER_PERMISSIONS
|
user.id, "features.notes", request.app.state.config.USER_PERMISSIONS
|
||||||
):
|
):
|
||||||
|
|
@ -187,8 +186,12 @@ async def get_note_by_id(request: Request, id: str, user=Depends(get_verified_us
|
||||||
status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
|
status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
|
||||||
)
|
)
|
||||||
|
|
||||||
write_access = has_access(
|
write_access = (
|
||||||
user.id, type="write", access_control=note.access_control, strict=False
|
user.role == "admin"
|
||||||
|
or (user.id == note.user_id)
|
||||||
|
or has_access(
|
||||||
|
user.id, type="write", access_control=note.access_control, strict=False
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
return NoteResponse(**note.model_dump(), write_access=write_access)
|
return NoteResponse(**note.model_dump(), write_access=write_access)
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,22 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import DOMPurify from 'dompurify';
|
import DOMPurify from 'dompurify';
|
||||||
import { marked } from 'marked';
|
|
||||||
|
|
||||||
import { toast } from 'svelte-sonner';
|
import { toast } from 'svelte-sonner';
|
||||||
|
|
||||||
|
import { marked } from 'marked';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import { createPicker, getAuthToken } from '$lib/utils/google-drive-picker';
|
import dayjs from '$lib/dayjs';
|
||||||
import { pickAndDownloadFile } from '$lib/utils/onedrive-file-picker';
|
import duration from 'dayjs/plugin/duration';
|
||||||
|
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||||
|
|
||||||
|
dayjs.extend(duration);
|
||||||
|
dayjs.extend(relativeTime);
|
||||||
|
|
||||||
import { onMount, tick, getContext, createEventDispatcher, onDestroy } from 'svelte';
|
import { onMount, tick, getContext, createEventDispatcher, onDestroy } from 'svelte';
|
||||||
|
|
||||||
|
import { createPicker, getAuthToken } from '$lib/utils/google-drive-picker';
|
||||||
|
import { pickAndDownloadFile } from '$lib/utils/onedrive-file-picker';
|
||||||
|
import { KokoroWorker } from '$lib/workers/KokoroWorker';
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
|
@ -49,6 +57,9 @@
|
||||||
|
|
||||||
import { WEBUI_BASE_URL, WEBUI_API_BASE_URL, PASTED_TEXT_CHARACTER_LIMIT } from '$lib/constants';
|
import { WEBUI_BASE_URL, WEBUI_API_BASE_URL, PASTED_TEXT_CHARACTER_LIMIT } from '$lib/constants';
|
||||||
|
|
||||||
|
import { createNoteHandler } from '../notes/utils';
|
||||||
|
import { getSuggestionRenderer } from '../common/RichTextInput/suggestions';
|
||||||
|
|
||||||
import InputMenu from './MessageInput/InputMenu.svelte';
|
import InputMenu from './MessageInput/InputMenu.svelte';
|
||||||
import VoiceRecording from './MessageInput/VoiceRecording.svelte';
|
import VoiceRecording from './MessageInput/VoiceRecording.svelte';
|
||||||
import FilesOverlay from './MessageInput/FilesOverlay.svelte';
|
import FilesOverlay from './MessageInput/FilesOverlay.svelte';
|
||||||
|
|
@ -60,11 +71,9 @@
|
||||||
import Image from '../common/Image.svelte';
|
import Image from '../common/Image.svelte';
|
||||||
|
|
||||||
import XMark from '../icons/XMark.svelte';
|
import XMark from '../icons/XMark.svelte';
|
||||||
import Headphone from '../icons/Headphone.svelte';
|
|
||||||
import GlobeAlt from '../icons/GlobeAlt.svelte';
|
import GlobeAlt from '../icons/GlobeAlt.svelte';
|
||||||
import Photo from '../icons/Photo.svelte';
|
import Photo from '../icons/Photo.svelte';
|
||||||
import Wrench from '../icons/Wrench.svelte';
|
import Wrench from '../icons/Wrench.svelte';
|
||||||
import CommandLine from '../icons/CommandLine.svelte';
|
|
||||||
import Sparkles from '../icons/Sparkles.svelte';
|
import Sparkles from '../icons/Sparkles.svelte';
|
||||||
|
|
||||||
import InputVariablesModal from './MessageInput/InputVariablesModal.svelte';
|
import InputVariablesModal from './MessageInput/InputVariablesModal.svelte';
|
||||||
|
|
@ -74,12 +83,11 @@
|
||||||
import Component from '../icons/Component.svelte';
|
import Component from '../icons/Component.svelte';
|
||||||
import PlusAlt from '../icons/PlusAlt.svelte';
|
import PlusAlt from '../icons/PlusAlt.svelte';
|
||||||
|
|
||||||
import { KokoroWorker } from '$lib/workers/KokoroWorker';
|
|
||||||
|
|
||||||
import { getSuggestionRenderer } from '../common/RichTextInput/suggestions';
|
|
||||||
import CommandSuggestionList from './MessageInput/CommandSuggestionList.svelte';
|
import CommandSuggestionList from './MessageInput/CommandSuggestionList.svelte';
|
||||||
import Knobs from '../icons/Knobs.svelte';
|
import Knobs from '../icons/Knobs.svelte';
|
||||||
import ValvesModal from '../workspace/common/ValvesModal.svelte';
|
import ValvesModal from '../workspace/common/ValvesModal.svelte';
|
||||||
|
import PageEdit from '../icons/PageEdit.svelte';
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
|
|
||||||
|
|
@ -109,6 +117,8 @@
|
||||||
export let webSearchEnabled = false;
|
export let webSearchEnabled = false;
|
||||||
export let codeInterpreterEnabled = false;
|
export let codeInterpreterEnabled = false;
|
||||||
|
|
||||||
|
let inputContent = null;
|
||||||
|
|
||||||
let showInputVariablesModal = false;
|
let showInputVariablesModal = false;
|
||||||
let inputVariablesModalCallback = (variableValues) => {};
|
let inputVariablesModalCallback = (variableValues) => {};
|
||||||
let inputVariables = {};
|
let inputVariables = {};
|
||||||
|
|
@ -730,6 +740,23 @@
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const createNote = async () => {
|
||||||
|
if (inputContent?.md.trim() === '' && inputContent?.html.trim() === '') {
|
||||||
|
toast.error($i18n.t('Cannot create an empty note.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await createNoteHandler(
|
||||||
|
dayjs().format('YYYY-MM-DD'),
|
||||||
|
inputContent?.md,
|
||||||
|
inputContent?.html
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res) {
|
||||||
|
goto(`/notes/${res.id}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const onDragOver = (e) => {
|
const onDragOver = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
|
@ -1195,8 +1222,9 @@
|
||||||
<RichTextInput
|
<RichTextInput
|
||||||
bind:this={chatInputElement}
|
bind:this={chatInputElement}
|
||||||
id="chat-input"
|
id="chat-input"
|
||||||
onChange={(e) => {
|
onChange={(content) => {
|
||||||
prompt = e.md;
|
prompt = content.md;
|
||||||
|
inputContent = content;
|
||||||
command = getCommand();
|
command = getCommand();
|
||||||
}}
|
}}
|
||||||
json={true}
|
json={true}
|
||||||
|
|
@ -1620,13 +1648,13 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="self-end flex space-x-1 mr-1 shrink-0">
|
<div class="self-end flex space-x-1 mr-1 shrink-0 gap-[0.5px]">
|
||||||
{#if (!history?.currentId || history.messages[history.currentId]?.done == true) && ($_user?.role === 'admin' || ($_user?.permissions?.chat?.stt ?? true))}
|
{#if (!history?.currentId || history.messages[history.currentId]?.done == true) && ($_user?.role === 'admin' || ($_user?.permissions?.chat?.stt ?? true))}
|
||||||
<!-- {$i18n.t('Record voice')} -->
|
<!-- {$i18n.t('Record voice')} -->
|
||||||
<Tooltip content={$i18n.t('Dictate')}>
|
<Tooltip content={$i18n.t('Dictate')}>
|
||||||
<button
|
<button
|
||||||
id="voice-input-button"
|
id="voice-input-button"
|
||||||
class=" text-gray-600 dark:text-gray-300 hover:text-gray-700 dark:hover:text-gray-200 transition rounded-full p-1.5 mr-0.5 self-center"
|
class=" text-gray-600 dark:text-gray-300 hover:text-gray-700 dark:hover:text-gray-200 transition rounded-full p-1.5 self-center"
|
||||||
type="button"
|
type="button"
|
||||||
on:click={async () => {
|
on:click={async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -1759,6 +1787,24 @@
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
|
{#if ($config?.features?.enable_notes ?? false) && ($user?.role === 'admin' || ($user?.permissions?.features?.notes ?? true))}
|
||||||
|
<div class=" flex items-center">
|
||||||
|
<Tooltip content={$i18n.t('Create note')}>
|
||||||
|
<button
|
||||||
|
id="send-message-button"
|
||||||
|
class=" text-gray-600 mr-0.5 dark:text-gray-300 hover:text-gray-700 dark:hover:text-gray-200 transition rounded-full p-1.5 self-center"
|
||||||
|
type="button"
|
||||||
|
disabled={prompt === '' && files.length === 0}
|
||||||
|
on:click={() => {
|
||||||
|
createNote();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PageEdit className="size-5" />
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<div class=" flex items-center">
|
<div class=" flex items-center">
|
||||||
<Tooltip content={$i18n.t('Send message')}>
|
<Tooltip content={$i18n.t('Send message')}>
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
24
src/lib/components/icons/PagePlus.svelte
Normal file
24
src/lib/components/icons/PagePlus.svelte
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
<script lang="ts">
|
||||||
|
export let className = 'w-4 h-4';
|
||||||
|
export let strokeWidth = '1.5';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
class={className}
|
||||||
|
aria-hidden="true"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
stroke-width={strokeWidth}
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
><path d="M9 12H12M15 12H12M12 12V9M12 12V15" stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
></path><path
|
||||||
|
d="M4 21.4V2.6C4 2.26863 4.26863 2 4.6 2H16.2515C16.4106 2 16.5632 2.06321 16.6757 2.17574L19.8243 5.32426C19.9368 5.43679 20 5.5894 20 5.74853V21.4C20 21.7314 19.7314 22 19.4 22H4.6C4.26863 22 4 21.7314 4 21.4Z"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
></path><path
|
||||||
|
d="M16 2V5.4C16 5.73137 16.2686 6 16.6 6H20"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
></path></svg
|
||||||
|
>
|
||||||
|
|
@ -337,7 +337,7 @@
|
||||||
>
|
>
|
||||||
<Plus className="size-3" strokeWidth="2.5" />
|
<Plus className="size-3" strokeWidth="2.5" />
|
||||||
|
|
||||||
<div class=" hidden md:block md:ml-1 text-xs">{$i18n.t('New Note')}</div>
|
<div class=" md:ml-1 text-xs">{$i18n.t('New Note')}</div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,7 @@ export const downloadPdf = async (note) => {
|
||||||
pdf.save(`${note.title}.pdf`);
|
pdf.save(`${note.title}.pdf`);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createNoteHandler = async (title: string, content?: string) => {
|
export const createNoteHandler = async (title: string, md?: string, html?: string) => {
|
||||||
// $i18n.t('New Note'),
|
// $i18n.t('New Note'),
|
||||||
const res = await createNewNote(localStorage.token, {
|
const res = await createNewNote(localStorage.token, {
|
||||||
// YYYY-MM-DD
|
// YYYY-MM-DD
|
||||||
|
|
@ -115,8 +115,8 @@ export const createNoteHandler = async (title: string, content?: string) => {
|
||||||
data: {
|
data: {
|
||||||
content: {
|
content: {
|
||||||
json: null,
|
json: null,
|
||||||
html: content ?? '',
|
html: html || md || '',
|
||||||
md: content ?? ''
|
md: md || ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
meta: null,
|
meta: null,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue