diff --git a/src/lib/components/admin/Analytics.svelte b/src/lib/components/admin/Analytics.svelte index 4aa6ae86f6..d5e051095f 100644 --- a/src/lib/components/admin/Analytics.svelte +++ b/src/lib/components/admin/Analytics.svelte @@ -18,7 +18,7 @@ {#if loaded} -
+
{/if} diff --git a/src/lib/components/admin/Users.svelte b/src/lib/components/admin/Users.svelte index 47cbdcbaa0..3e6f9a5662 100644 --- a/src/lib/components/admin/Users.svelte +++ b/src/lib/components/admin/Users.svelte @@ -1,6 +1,5 @@ -
- -
- {#if selectedTab === 'overview'} - - {:else if selectedTab === 'groups'} - - {/if} +
+ {#if selectedTab === 'overview'} + + {:else if selectedTab === 'groups'} + + {/if} +
-
+{/if} diff --git a/src/lib/components/admin/Users/Groups.svelte b/src/lib/components/admin/Users/Groups.svelte index e31a27f688..7db8b278e5 100644 --- a/src/lib/components/admin/Users/Groups.svelte +++ b/src/lib/components/admin/Users/Groups.svelte @@ -106,7 +106,7 @@ onSubmit={addGroupHandler} /> -
+
@@ -145,8 +145,10 @@
-
-
+
+
@@ -199,7 +201,7 @@
{#if filteredGroups.length !== 0} -
+
{#each filteredGroups as group} {/each} diff --git a/src/lib/components/admin/Users/Groups/GroupItem.svelte b/src/lib/components/admin/Users/Groups/GroupItem.svelte index 5eaab31768..f7c9c6f9f2 100644 --- a/src/lib/components/admin/Users/Groups/GroupItem.svelte +++ b/src/lib/components/admin/Users/Groups/GroupItem.svelte @@ -67,7 +67,7 @@ /> + + + + +
+ + {#each visibleActions as action (action.id)} + {@const Icon = getActionIcon(action.id)} + {#if action.href} + { + showMenu = false; + }} + > + + {action.label} + + {:else} + + {/if} + {/each} + +
+
+
+{/if} diff --git a/src/lib/components/notes/Notes.svelte b/src/lib/components/notes/Notes.svelte index 2843a64d7c..e8d81d27c6 100644 --- a/src/lib/components/notes/Notes.svelte +++ b/src/lib/components/notes/Notes.svelte @@ -34,6 +34,7 @@ import { createNewNote, deleteNoteById, + getNotes, getNoteById, getNoteList, searchNotes, @@ -46,7 +47,6 @@ import EllipsisHorizontal from '../icons/EllipsisHorizontal.svelte'; import DeleteConfirmDialog from '$lib/components/common/ConfirmDialog.svelte'; import Search from '../icons/Search.svelte'; - import Plus from '../icons/Plus.svelte'; import Spinner from '../common/Spinner.svelte'; import Tooltip from '../common/Tooltip.svelte'; import NoteMenu from './Notes/NoteMenu.svelte'; @@ -55,10 +55,13 @@ import DropdownOptions from '../common/DropdownOptions.svelte'; import Loader from '../common/Loader.svelte'; import SidebarIcon from '../icons/Sidebar.svelte'; + import SplitCreateButton from '../common/SplitCreateButton.svelte'; let loaded = false; let importFiles = ''; + let importDocumentFiles: FileList | null = null; + let notesImportInputElement: HTMLInputElement; let selectedNote = null; let showDeleteConfirm = false; @@ -80,6 +83,15 @@ let itemsLoading = false; let allItemsLoaded = false; + type NoteExportItem = { + title?: string; + data?: { + content?: { + md?: string; + }; + }; + }; + const downloadHandler = async (type) => { // Fetch the full note since the list response may not contain full content const note = await getNoteById(localStorage.token, selectedNote.id).catch((error) => { @@ -116,49 +128,76 @@ } }; - const inputFilesHandler = async (inputFiles) => { - // Check if all the file is a markdown file and extract name and content + const inputFilesHandler = async (inputFiles: File[]) => { + let imported = false; for (const file of inputFiles) { - if (file.type !== 'text/markdown') { - toast.error($i18n.t('Only markdown files are allowed')); + const isSupportedFile = + file.type === 'text/markdown' || + file.type === 'text/plain' || + /\.(md|txt)$/i.test(file.name); + + if (!isSupportedFile) { + toast.error($i18n.t('Only TXT and Markdown files are allowed')); return; } - const reader = new FileReader(); - reader.onload = async (event) => { - const content = event.target.result; - let name = file.name.replace(/\.md$/, ''); + const content = await file.text(); + const name = file.name.replace(/\.(md|txt)$/i, ''); - if (typeof content !== 'string') { - toast.error($i18n.t('Invalid file content')); - return; - } + const res = await createNewNote(localStorage.token, { + title: name, + data: { + content: { + json: null, + html: marked.parse(content ?? ''), + md: content + } + }, + meta: null, + access_grants: [] + }).catch((error) => { + toast.error(`${error}`); + return null; + }); - // Create a new note with the content - const res = await createNewNote(localStorage.token, { - title: name, - data: { - content: { - json: null, - html: marked.parse(content ?? ''), - md: content - } - }, - meta: null, - access_grants: [] - }).catch((error) => { - toast.error(`${error}`); - return null; - }); - - if (res) { - init(); - } - }; - - reader.readAsText(file); + if (res) { + imported = true; + } } + + if (imported) { + init(); + } + }; + + const getNoteExportContent = (notes: NoteExportItem[], type: 'md' | 'txt') => { + return notes + .map((note) => { + const title = note.title ?? $i18n.t('Untitled'); + const content = note.data?.content?.md ?? ''; + + if (type === 'md') { + return `# ${title}\n\n${content}`; + } + + return `${title}\n\n${content}`; + }) + .join(type === 'md' ? '\n\n---\n\n' : '\n\n-----\n\n'); + }; + + const exportNotes = async (type: 'md' | 'txt') => { + const allNotes = await getNotes(localStorage.token, true).catch((error) => { + toast.error(`${error}`); + return null; + }); + + if (!allNotes) return; + + const blob = new Blob([getNoteExportContent(allNotes, type)], { + type: type === 'md' ? 'text/markdown' : 'text/plain' + }); + saveAs(blob, `notes-export-${Date.now()}.${type}`); }; const reset = () => { @@ -326,6 +365,29 @@
{#if loaded} + { + if (!importDocumentFiles || importDocumentFiles.length === 0) return; + + try { + await inputFilesHandler(Array.from(importDocumentFiles)); + toast.success($i18n.t('Imported notes successfully')); + } catch (error) { + toast.error(`${error}`); + } finally { + importDocumentFiles = null; + notesImportInputElement.value = ''; + } + }} + /> +
- + ]} + />
@@ -699,14 +777,14 @@
{:else} -
-
+
+
{$i18n.t('No Notes')}
- {$i18n.t('Create your first note by clicking on the plus button below.')} + {$i18n.t('Create your first note from the Create menu.')}
diff --git a/src/lib/components/workspace/Knowledge.svelte b/src/lib/components/workspace/Knowledge.svelte index 700659c587..c32efb67d4 100644 --- a/src/lib/components/workspace/Knowledge.svelte +++ b/src/lib/components/workspace/Knowledge.svelte @@ -22,7 +22,9 @@ import DeleteConfirmDialog from '../common/ConfirmDialog.svelte'; import ItemMenu from './Knowledge/ItemMenu.svelte'; + import CreateKnowledgeBase from './Knowledge/CreateKnowledgeBase.svelte'; import Badge from '../common/Badge.svelte'; + import Modal from '../common/Modal.svelte'; import Search from '../icons/Search.svelte'; import Spinner from '../common/Spinner.svelte'; import Tooltip from '../common/Tooltip.svelte'; @@ -46,6 +48,7 @@ let loaded = false; let showDeleteConfirm = false; + let showCreateModal = false; let tagsContainerElement: HTMLDivElement; let selectedItem: KnowledgeListItem | null = null; @@ -67,7 +70,9 @@ { id: 'knowledge-new', label: $i18n.t('Create'), - href: '/workspace/knowledge/create' + onClick: () => { + showCreateModal = true; + } } ]); } @@ -197,6 +202,15 @@ }} /> + + { + showCreateModal = false; + }} + /> + +
diff --git a/src/lib/components/workspace/Knowledge/CreateKnowledgeBase.svelte b/src/lib/components/workspace/Knowledge/CreateKnowledgeBase.svelte index 90b891cbbf..a5be843389 100644 --- a/src/lib/components/workspace/Knowledge/CreateKnowledgeBase.svelte +++ b/src/lib/components/workspace/Knowledge/CreateKnowledgeBase.svelte @@ -10,11 +10,19 @@ import AccessControl from '../common/AccessControl.svelte'; import Spinner from '$lib/components/common/Spinner.svelte'; + import XMark from '$lib/components/icons/XMark.svelte'; + + export let modal = false; + /** @type {() => void | Promise} */ + export let onBack = () => goto('/workspace/knowledge'); + /** @type {(knowledge: { id: string }) => void | Promise} */ + export let onCreated = (knowledge) => goto(`/workspace/knowledge/${knowledge.id}`); let loading = false; let name = ''; let description = ''; + /** @type {{ id?: string; principal_type: 'user' | 'group'; principal_id: string; permission: 'read' | 'write' }[]} */ let accessGrants = []; const submitHandler = async () => { @@ -36,7 +44,7 @@ if (res) { toast.success($i18n.t('Knowledge created successfully.')); - goto(`/workspace/knowledge/${res.id}`); + await onCreated(res); } loading = false; @@ -44,39 +52,57 @@
-
-
{$i18n.t('Back')}
- + {:else} + + {/if}
{ submitHandler(); }} >
-
- {$i18n.t('Create a knowledge base')} -
+ {#if !modal} +
+ {$i18n.t('Create a knowledge base')} +
+ {/if}
@@ -103,7 +129,7 @@ bind:value={description} placeholder={$i18n.t('Describe your knowledge base and objectives')} required - /> + >
@@ -120,12 +146,24 @@ />
-
+
+ {#if modal} + + {/if} +
+ { + cloneFrom = null; + showCreateModal = true; + } + }, + { + id: 'automations-import', + label: $i18n.t('Import JSON'), + onClick: () => automationsImportInputElement?.click() + }, + { + id: 'automations-export', + label: $i18n.t('Export JSON'), + onClick: exportAutomations + } + ]} + />
@@ -417,11 +519,13 @@
{#if automations === null || loading} -
+
{:else if (automations ?? []).length === 0} -
+
diff --git a/src/routes/(app)/calendar/+page.svelte b/src/routes/(app)/calendar/+page.svelte index 49cb783c8a..6442896f83 100644 --- a/src/routes/(app)/calendar/+page.svelte +++ b/src/routes/(app)/calendar/+page.svelte @@ -15,7 +15,6 @@ import CalendarEventModal from '$lib/components/calendar/CalendarEventModal.svelte'; import CreateCalendarModal from '$lib/components/calendar/CreateCalendarModal.svelte'; import Spinner from '$lib/components/common/Spinner.svelte'; - import Plus from '$lib/components/icons/Plus.svelte'; import Tooltip from '$lib/components/common/Tooltip.svelte'; import SidebarIcon from '$lib/components/icons/Sidebar.svelte'; import Select from '$lib/components/common/Select.svelte'; @@ -331,24 +330,10 @@
diff --git a/src/routes/(app)/workspace/+layout.svelte b/src/routes/(app)/workspace/+layout.svelte index 9d2e3f49d8..4ec43958e0 100644 --- a/src/routes/(app)/workspace/+layout.svelte +++ b/src/routes/(app)/workspace/+layout.svelte @@ -19,23 +19,15 @@ import { getSkillItems } from '$lib/apis/skills'; import { getToolList } from '$lib/apis/tools'; import Tooltip from '$lib/components/common/Tooltip.svelte'; - import Dropdown from '$lib/components/common/Dropdown.svelte'; - import DropdownMenu from '$lib/components/common/DropdownMenu.svelte'; import Sidebar from '$lib/components/icons/Sidebar.svelte'; - import ChevronDown from '$lib/components/icons/ChevronDown.svelte'; - import DocumentArrowDown from '$lib/components/icons/DocumentArrowDown.svelte'; - import DocumentArrowUp from '$lib/components/icons/DocumentArrowUp.svelte'; - import Link from '$lib/components/icons/Link.svelte'; - import Pencil from '$lib/components/icons/Pencil.svelte'; + import SplitCreateButton from '$lib/components/common/SplitCreateButton.svelte'; const i18n = getContext>('i18n'); let loaded = false; let lastPath = ''; let activeWorkspaceSection = ''; - let showCreateMenu = false; let visibleActions = []; - let primaryCreateAction = null; $: if ($page.url.pathname !== lastPath) { lastPath = $page.url.pathname; @@ -48,28 +40,9 @@ $: activeWorkspaceSection = $page.url.pathname.split('/')[2] ?? ''; $: visibleActions = $workspaceActions.filter((action) => action.visible ?? true); - $: primaryCreateAction = - visibleActions.find((action) => action.id.endsWith('-new')) ?? visibleActions[0] ?? null; const getCount = (res: any) => res?.total ?? (Array.isArray(res) ? res.length : null); - const runWorkspaceAction = async (action) => { - if (!action) return; - if (action.href) { - await goto(action.href); - } else { - await action.onClick?.(); - } - }; - - const getActionIcon = (id: string) => { - if (id.endsWith('-new')) return Pencil; - if (id.endsWith('-import-link')) return Link; - if (id.endsWith('-import')) return DocumentArrowUp; - if (id.endsWith('-export')) return DocumentArrowDown; - return Pencil; - }; - const loadWorkspaceCounts = async () => { const canViewModels = $user?.role === 'admin' || $user?.permissions?.workspace?.models; const canViewKnowledge = $user?.role === 'admin' || $user?.permissions?.workspace?.knowledge; @@ -168,7 +141,7 @@