mirror of
https://github.com/open-webui/open-webui
synced 2025-06-26 18:26:48 +00:00
enh: note versioning
This commit is contained in:
parent
e8b23ef0c1
commit
463ac99e39
@ -43,6 +43,7 @@
|
||||
|
||||
export let json = false;
|
||||
export let raw = false;
|
||||
export let editable = true;
|
||||
|
||||
export let preserveBreaks = false;
|
||||
export let generateAutoCompletion: Function = async () => null;
|
||||
@ -58,6 +59,16 @@
|
||||
throwOnError: false
|
||||
};
|
||||
|
||||
$: if (editor) {
|
||||
editor.setOptions({
|
||||
editable: editable
|
||||
});
|
||||
}
|
||||
|
||||
$: if (value === null && html !== null && editor) {
|
||||
editor.commands.setContent(html);
|
||||
}
|
||||
|
||||
// Function to find the next template in the document
|
||||
function findNextTemplate(doc, from = 0) {
|
||||
const patterns = [{ start: '{{', end: '}}' }];
|
||||
|
19
src/lib/components/icons/ArrowUturnLeft.svelte
Normal file
19
src/lib/components/icons/ArrowUturnLeft.svelte
Normal file
@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
export let className = 'size-4';
|
||||
export let strokeWidth = '1.5';
|
||||
</script>
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width={strokeWidth}
|
||||
stroke="currentColor"
|
||||
class={className}
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M9 15 3 9m0 0 6-6M3 9h12a6 6 0 0 1 0 12h-3"
|
||||
/>
|
||||
</svg>
|
19
src/lib/components/icons/ArrowUturnRight.svelte
Normal file
19
src/lib/components/icons/ArrowUturnRight.svelte
Normal file
@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
export let className = 'size-4';
|
||||
export let strokeWidth = '1.5';
|
||||
</script>
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width={strokeWidth}
|
||||
stroke="currentColor"
|
||||
class={className}
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="m15 15 6-6m0 0-6-6m6 6H9a6 6 0 0 0 0 12h3"
|
||||
/>
|
||||
</svg>
|
@ -9,6 +9,7 @@
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
import { marked } from 'marked';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
import { config, settings, showSidebar } from '$lib/stores';
|
||||
@ -62,6 +63,8 @@
|
||||
import SparklesSolid from '../icons/SparklesSolid.svelte';
|
||||
import Tooltip from '../common/Tooltip.svelte';
|
||||
import Bars3BottomLeft from '../icons/Bars3BottomLeft.svelte';
|
||||
import ArrowUturnLeft from '../icons/ArrowUturnLeft.svelte';
|
||||
import ArrowUturnRight from '../icons/ArrowUturnRight.svelte';
|
||||
|
||||
export let id: null | string = null;
|
||||
|
||||
@ -75,6 +78,7 @@
|
||||
html: '',
|
||||
md: ''
|
||||
},
|
||||
versions: [],
|
||||
files: null
|
||||
},
|
||||
meta: null,
|
||||
@ -83,14 +87,15 @@
|
||||
|
||||
let files = [];
|
||||
|
||||
let selectedVersion = 'note';
|
||||
|
||||
let versionIdx = null;
|
||||
let recording = false;
|
||||
let displayMediaRecord = false;
|
||||
|
||||
let showDeleteConfirm = false;
|
||||
let dragged = false;
|
||||
|
||||
let loading = false;
|
||||
let enhancing = false;
|
||||
|
||||
const init = async () => {
|
||||
loading = true;
|
||||
@ -118,7 +123,7 @@
|
||||
}
|
||||
|
||||
debounceTimeout = setTimeout(async () => {
|
||||
if (!note) {
|
||||
if (!note || enhancing || versionIdx !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -141,7 +146,104 @@
|
||||
init();
|
||||
}
|
||||
|
||||
const versionToggleHandler = () => {};
|
||||
function areContentsEqual(a, b) {
|
||||
return JSON.stringify(a) === JSON.stringify(b);
|
||||
}
|
||||
|
||||
function insertNoteVersion(note) {
|
||||
const current = {
|
||||
json: note.data.content.json,
|
||||
html: note.data.content.html,
|
||||
md: note.data.content.md
|
||||
};
|
||||
const lastVersion = note.data.versions?.at(-1);
|
||||
|
||||
if (!lastVersion || !areContentsEqual(lastVersion, current)) {
|
||||
note.data.versions = (note.data.versions ?? []).concat(current);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async function aiEnhanceContent(content) {
|
||||
const md = content.md + '_ai';
|
||||
const html = marked.parse(md);
|
||||
|
||||
return {
|
||||
json: null,
|
||||
html: html,
|
||||
md: md
|
||||
};
|
||||
}
|
||||
|
||||
async function enhanceNoteHandler() {
|
||||
insertNoteVersion(note);
|
||||
|
||||
enhancing = true;
|
||||
const aiResult = await aiEnhanceContent(note.data.content);
|
||||
|
||||
note.data.content.json = aiResult.json;
|
||||
note.data.content.html = aiResult.html;
|
||||
note.data.content.md = aiResult.md;
|
||||
|
||||
enhancing = false;
|
||||
versionIdx = null;
|
||||
}
|
||||
|
||||
function setContentByVersion(versionIdx) {
|
||||
if (!note.data.versions?.length) return;
|
||||
let idx = versionIdx;
|
||||
|
||||
if (idx === null) idx = note.data.versions.length - 1; // latest
|
||||
const v = note.data.versions[idx];
|
||||
|
||||
note.data.content.json = v.json;
|
||||
note.data.content.html = v.html;
|
||||
note.data.content.md = v.md;
|
||||
|
||||
if (versionIdx === null) {
|
||||
const lastVersion = note.data.versions.at(-1);
|
||||
const currentContent = note.data.content;
|
||||
|
||||
if (areContentsEqual(lastVersion, currentContent)) {
|
||||
// remove the last version
|
||||
note.data.versions = note.data.versions.slice(0, -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Navigation
|
||||
function versionNavigateHandler(direction) {
|
||||
if (!note.data.versions || note.data.versions.length === 0) return;
|
||||
|
||||
if (versionIdx === null) {
|
||||
// Get latest snapshots
|
||||
const lastVersion = note.data.versions.at(-1);
|
||||
const currentContent = note.data.content;
|
||||
|
||||
if (!areContentsEqual(lastVersion, currentContent)) {
|
||||
// If the current content is different from the last version, insert a new version
|
||||
insertNoteVersion(note);
|
||||
versionIdx = note.data.versions.length - 1;
|
||||
} else {
|
||||
versionIdx = note.data.versions.length;
|
||||
}
|
||||
}
|
||||
|
||||
if (direction === 'prev') {
|
||||
if (versionIdx > 0) versionIdx -= 1;
|
||||
} else if (direction === 'next') {
|
||||
if (versionIdx < note.data.versions.length - 1) versionIdx += 1;
|
||||
else versionIdx = null; // Reset to latest
|
||||
|
||||
if (versionIdx === note.data.versions.length - 1) {
|
||||
// If we reach the latest version, reset to null
|
||||
versionIdx = null;
|
||||
}
|
||||
}
|
||||
|
||||
setContentByVersion(versionIdx);
|
||||
}
|
||||
|
||||
const uploadFileHandler = async (file) => {
|
||||
const tempItemId = uuidv4();
|
||||
@ -428,7 +530,7 @@
|
||||
{:else}
|
||||
<div class=" w-full flex flex-col {loading ? 'opacity-20' : ''}">
|
||||
<div class="shrink-0 w-full flex justify-between items-center px-4.5 pt-1 mb-1.5">
|
||||
<div class="w-full flex">
|
||||
<div class="w-full flex items-center">
|
||||
<input
|
||||
class="w-full text-2xl font-medium bg-transparent outline-hidden"
|
||||
type="text"
|
||||
@ -437,7 +539,34 @@
|
||||
required
|
||||
/>
|
||||
|
||||
<div>
|
||||
<div class="flex items-center gap-2">
|
||||
{#if note.data?.versions?.length > 0}
|
||||
<div>
|
||||
<div class="flex items-center gap-0.5 self-center min-w-fit" dir="ltr">
|
||||
<button
|
||||
class="self-center p-1 hover:enabled:bg-black/5 dark:hover:enabled:bg-white/5 dark:hover:enabled:text-white hover:enabled:text-black rounded-md transition disabled:cursor-not-allowed disabled:text-gray-500 disabled:hover:text-gray-500"
|
||||
on:click={() => {
|
||||
versionNavigateHandler('prev');
|
||||
}}
|
||||
disabled={(versionIdx === null && note.data.versions.length === 0) ||
|
||||
versionIdx === 0}
|
||||
>
|
||||
<ArrowUturnLeft className="size-4" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="self-center p-1 hover:enabled:bg-black/5 dark:hover:enabled:bg-white/5 dark:hover:enabled:text-white hover:enabled:text-black rounded-md transition disabled:cursor-not-allowed disabled:text-gray-500 disabled:hover:text-gray-500"
|
||||
on:click={() => {
|
||||
versionNavigateHandler('next');
|
||||
}}
|
||||
disabled={versionIdx >= note.data.versions.length || versionIdx === null}
|
||||
>
|
||||
<ArrowUturnRight className="size-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<NoteMenu
|
||||
onDownload={(type) => {
|
||||
downloadHandler(type);
|
||||
@ -524,6 +653,7 @@
|
||||
placeholder={$i18n.t('Write something...')}
|
||||
html={note.data?.content?.html}
|
||||
json={true}
|
||||
editable={versionIdx === null}
|
||||
onChange={(content) => {
|
||||
note.data.content.html = content.html;
|
||||
note.data.content.md = content.md;
|
||||
@ -609,18 +739,20 @@
|
||||
};
|
||||
}}
|
||||
>
|
||||
<button
|
||||
class="cursor-pointer p-2.5 flex rounded-full border border-gray-50 bg-white dark:border-none dark:bg-gray-850 hover:bg-gray-50 dark:hover:bg-gray-800 transition shadow-xl"
|
||||
type="button"
|
||||
>
|
||||
<MicSolid className="size-4.5" />
|
||||
</button>
|
||||
<Tooltip content={$i18n.t('Record')} placement="top">
|
||||
<button
|
||||
class="cursor-pointer p-2.5 flex rounded-full border border-gray-50 bg-white dark:border-none dark:bg-gray-850 hover:bg-gray-50 dark:hover:bg-gray-800 transition shadow-xl"
|
||||
type="button"
|
||||
>
|
||||
<MicSolid className="size-4.5" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</RecordMenu>
|
||||
|
||||
<div
|
||||
class="cursor-pointer p-0.5 flex gap-0.5 rounded-full border border-gray-50 dark:border-gray-850 dark:bg-gray-850 transition shadow-xl"
|
||||
class="cursor-pointer flex gap-0.5 rounded-full border border-gray-50 dark:border-gray-850 dark:bg-gray-850 transition shadow-xl"
|
||||
>
|
||||
<Tooltip content={$i18n.t('My Notes')} placement="top">
|
||||
<!-- <Tooltip content={$i18n.t('My Notes')} placement="top">
|
||||
<button
|
||||
class="p-2 size-8.5 flex justify-center items-center {selectedVersion === 'note'
|
||||
? 'bg-gray-100 dark:bg-gray-800 '
|
||||
@ -633,16 +765,13 @@
|
||||
>
|
||||
<Bars3BottomLeft />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</Tooltip> -->
|
||||
|
||||
<Tooltip content={$i18n.t('Enhance Notes')} placement="top">
|
||||
<Tooltip content={$i18n.t('Enhance')} placement="top">
|
||||
<button
|
||||
class="p-2 size-8.5 flex justify-center items-center {selectedVersion === 'ai'
|
||||
? 'bg-gray-100 dark:bg-gray-800 '
|
||||
: ' hover:bg-gray-50 dark:hover:bg-gray-800'} rounded-full transition shrink-0"
|
||||
class="p-2.5 flex justify-center items-center hover:bg-gray-50 dark:hover:bg-gray-800 rounded-full transition shrink-0"
|
||||
on:click={() => {
|
||||
selectedVersion = 'ai';
|
||||
versionToggleHandler();
|
||||
enhanceNoteHandler();
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
|
Loading…
Reference in New Issue
Block a user