mirror of
https://github.com/open-webui/open-webui.git
synced 2026-08-24 02:34:13 -05:00
refac
This commit is contained in:
Generated
-31
@@ -77,7 +77,6 @@
|
||||
"mammoth": "^1.11.0",
|
||||
"marked": "^9.1.0",
|
||||
"mermaid": "^11.10.1",
|
||||
"paneforge": "^0.0.6",
|
||||
"panzoom": "^9.4.3",
|
||||
"pdfjs-dist": "^5.4.149",
|
||||
"prosemirror-collab": "^1.3.1",
|
||||
@@ -11188,24 +11187,6 @@
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "5.1.7",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.7.tgz",
|
||||
"integrity": "sha512-ua3NDgISf6jdwezAheMOk4mbE1LXjm1DfMUDMuJf4AqxLFK3ccGpgWizwa5YV7Yz9EpXwEaWoRXSb/BnV0t5dQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18 || >=20"
|
||||
}
|
||||
},
|
||||
"node_modules/natural-compare": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
|
||||
@@ -11502,18 +11483,6 @@
|
||||
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
|
||||
"license": "(MIT AND Zlib)"
|
||||
},
|
||||
"node_modules/paneforge": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/paneforge/-/paneforge-0.0.6.tgz",
|
||||
"integrity": "sha512-jYeN/wdREihja5c6nK3S5jritDQ+EbCqC5NrDo97qCZzZ9GkmEcN5C0ZCjF4nmhBwkDKr6tLIgz4QUKWxLXjAw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nanoid": "^5.0.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"svelte": "^4.0.0 || ^5.0.0-next.1"
|
||||
}
|
||||
},
|
||||
"node_modules/panzoom": {
|
||||
"version": "9.4.3",
|
||||
"resolved": "https://registry.npmjs.org/panzoom/-/panzoom-9.4.3.tgz",
|
||||
|
||||
@@ -121,7 +121,6 @@
|
||||
"mammoth": "^1.11.0",
|
||||
"marked": "^9.1.0",
|
||||
"mermaid": "^11.10.1",
|
||||
"paneforge": "^0.0.6",
|
||||
"panzoom": "^9.4.3",
|
||||
"pdfjs-dist": "^5.4.149",
|
||||
"prosemirror-collab": "^1.3.1",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { Pane, PaneGroup, PaneResizer } from 'paneforge';
|
||||
|
||||
import { onDestroy, onMount, tick } from 'svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
@@ -20,6 +19,7 @@
|
||||
import MessageInput from './MessageInput.svelte';
|
||||
import Navbar from './Navbar.svelte';
|
||||
import Drawer from '../common/Drawer.svelte';
|
||||
import ResizableSidePanel from '../common/ResizableSidePanel.svelte';
|
||||
import EllipsisVertical from '../icons/EllipsisVertical.svelte';
|
||||
import Thread from './Thread.svelte';
|
||||
import i18n from '$lib/i18n';
|
||||
@@ -35,11 +35,11 @@
|
||||
|
||||
let top = false;
|
||||
|
||||
let channel = null;
|
||||
let channel: any = null;
|
||||
let messages = null;
|
||||
|
||||
let replyToMessage = null;
|
||||
let threadId = null;
|
||||
let threadId: any = null;
|
||||
|
||||
let typingUsers = [];
|
||||
let typingUsersTimeout = {};
|
||||
@@ -265,6 +265,11 @@
|
||||
|
||||
let mediaQuery;
|
||||
let largeScreen = false;
|
||||
let threadPanelWidth = 420;
|
||||
|
||||
const handleMediaQuery = (e) => {
|
||||
largeScreen = e.matches;
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
if ($chatId) {
|
||||
@@ -275,14 +280,6 @@
|
||||
|
||||
mediaQuery = window.matchMedia('(min-width: 1024px)');
|
||||
|
||||
const handleMediaQuery = async (e) => {
|
||||
if (e.matches) {
|
||||
largeScreen = true;
|
||||
} else {
|
||||
largeScreen = false;
|
||||
}
|
||||
};
|
||||
|
||||
mediaQuery.addEventListener('change', handleMediaQuery);
|
||||
handleMediaQuery(mediaQuery);
|
||||
});
|
||||
@@ -292,6 +289,7 @@
|
||||
updateLastReadAt(id);
|
||||
_channelId.set(null);
|
||||
$socket?.off('events:channel', channelEventHandler);
|
||||
mediaQuery?.removeEventListener('change', handleMediaQuery);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -325,8 +323,8 @@
|
||||
: ''} w-full max-w-full flex flex-col"
|
||||
id="channel-container"
|
||||
>
|
||||
<PaneGroup direction="horizontal" class="w-full h-full">
|
||||
<Pane defaultSize={50} minSize={50} class="h-full flex flex-col w-full relative">
|
||||
<div class="w-full h-full flex">
|
||||
<div class="h-full flex flex-col min-w-0 flex-1 relative">
|
||||
<Navbar
|
||||
{channel}
|
||||
onPin={pinHandler}
|
||||
@@ -407,7 +405,7 @@
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</Pane>
|
||||
</div>
|
||||
|
||||
{#if !largeScreen}
|
||||
{#if threadId !== null}
|
||||
@@ -430,16 +428,13 @@
|
||||
</Drawer>
|
||||
{/if}
|
||||
{:else if threadId !== null}
|
||||
<PaneResizer
|
||||
class="relative flex items-center justify-center group border-l border-gray-50 dark:border-gray-850/30 hover:border-gray-200 dark:hover:border-gray-800 transition z-20"
|
||||
id="controls-resizer"
|
||||
<ResizableSidePanel
|
||||
open={true}
|
||||
bind:width={threadPanelWidth}
|
||||
minWidth={320}
|
||||
maxWidth={640}
|
||||
className="h-full"
|
||||
>
|
||||
<div
|
||||
class=" absolute -left-1.5 -right-1.5 -top-0 -bottom-0 z-20 cursor-col-resize bg-transparent"
|
||||
/>
|
||||
</PaneResizer>
|
||||
|
||||
<Pane defaultSize={50} minSize={30} class="h-full w-full">
|
||||
<div class="h-full w-full shadow-xl">
|
||||
<Thread
|
||||
{threadId}
|
||||
@@ -450,7 +445,7 @@
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Pane>
|
||||
</ResizableSidePanel>
|
||||
{/if}
|
||||
</PaneGroup>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { PaneGroup, Pane, PaneResizer } from 'paneforge';
|
||||
|
||||
import { getContext, onDestroy, onMount, tick } from 'svelte';
|
||||
import { fade } from 'svelte/transition';
|
||||
@@ -147,8 +146,6 @@
|
||||
$: messageInputDropzoneId = embedded ? 'note-chat-input-dropzone' : 'chat-pane';
|
||||
|
||||
const eventTarget = new EventTarget();
|
||||
let controlPane: Pane | undefined;
|
||||
let controlPaneComponent: ChatControls | undefined;
|
||||
|
||||
let messageInput: MessageInput | undefined;
|
||||
let messagesRef: Messages | undefined;
|
||||
@@ -1556,20 +1553,7 @@
|
||||
stopAudio();
|
||||
});
|
||||
|
||||
const showControlsSubscribe = showControls.subscribe(async (value) => {
|
||||
await tick();
|
||||
if (controlPane && !$mobile) {
|
||||
try {
|
||||
if (value) {
|
||||
controlPaneComponent?.openPane();
|
||||
} else {
|
||||
controlPane.collapse();
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
const showControlsSubscribe = showControls.subscribe((value) => {
|
||||
if (!value) {
|
||||
showCallOverlay.set(false);
|
||||
showArtifacts.set(false);
|
||||
@@ -4249,8 +4233,8 @@
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<PaneGroup direction="horizontal" class="w-full h-full">
|
||||
<Pane defaultSize={50} minSize={30} class="h-full flex relative max-w-full flex-col">
|
||||
<div class="w-full h-full flex">
|
||||
<div class="h-full flex relative max-w-full min-w-0 flex-1 flex-col">
|
||||
<FilesOverlay show={dragged} />
|
||||
{#if embedded}
|
||||
<div
|
||||
@@ -4595,16 +4579,14 @@
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Pane>
|
||||
</div>
|
||||
|
||||
{#if !embedded}
|
||||
<ChatControls
|
||||
bind:this={controlPaneComponent}
|
||||
bind:history
|
||||
bind:chatFiles
|
||||
bind:params
|
||||
bind:files
|
||||
bind:pane={controlPane}
|
||||
chatId={$chatId}
|
||||
chatUser={chatOwner}
|
||||
modelId={selectedModelIds?.at(0) ?? null}
|
||||
@@ -4620,10 +4602,9 @@
|
||||
{showMessage}
|
||||
{eventTarget}
|
||||
{codeInterpreterEnabled}
|
||||
containerId={chatContainerId}
|
||||
/>
|
||||
{/if}
|
||||
</PaneGroup>
|
||||
</div>
|
||||
</div>
|
||||
{:else if loading}
|
||||
<div class=" flex items-center justify-center h-full w-full">
|
||||
|
||||
@@ -5,10 +5,9 @@
|
||||
<script lang="ts">
|
||||
import { SvelteFlowProvider } from '@xyflow/svelte';
|
||||
import { slide } from 'svelte/transition';
|
||||
import { Pane, PaneResizer } from 'paneforge';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { onDestroy, onMount, tick, getContext } from 'svelte';
|
||||
import { onMount, tick, getContext } from 'svelte';
|
||||
import {
|
||||
config,
|
||||
terminalServers,
|
||||
@@ -29,6 +28,7 @@
|
||||
import Controls from './Controls/Controls.svelte';
|
||||
import CallOverlay from './MessageInput/CallOverlay.svelte';
|
||||
import Drawer from '../common/Drawer.svelte';
|
||||
import ResizableSidePanel from '../common/ResizableSidePanel.svelte';
|
||||
import Artifacts from './Artifacts.svelte';
|
||||
import Embeds from './ChatControls/Embeds.svelte';
|
||||
import FileNav from './FileNav.svelte';
|
||||
@@ -56,13 +56,10 @@
|
||||
|
||||
export let codeInterpreterEnabled = false;
|
||||
|
||||
export let pane: Pane | null = null;
|
||||
export let containerId = 'chat-container';
|
||||
|
||||
let largeScreen = false;
|
||||
let dragged = false;
|
||||
let minSize = 0;
|
||||
let paneReady = false;
|
||||
let mounted = false;
|
||||
let controlsWidth = 350;
|
||||
|
||||
// Tab state for Controls+Files panel
|
||||
let activeTab = savedTab;
|
||||
@@ -92,11 +89,10 @@
|
||||
chatContextAvailable(selectedSystemTerminal) &&
|
||||
!(chatContextNeedsSavedChat(selectedSystemTerminal) && !isSavedChatId(chatId));
|
||||
$: terminalFilesAvailable = !!(
|
||||
($selectedTerminalId &&
|
||||
(selectedSystemTerminalAvailable ||
|
||||
(!selectedSystemTerminal &&
|
||||
($user?.role === 'admin' ||
|
||||
($user?.permissions?.features?.direct_tool_servers ?? true)))))
|
||||
$selectedTerminalId &&
|
||||
(selectedSystemTerminalAvailable ||
|
||||
(!selectedSystemTerminal &&
|
||||
($user?.role === 'admin' || ($user?.permissions?.features?.direct_tool_servers ?? true))))
|
||||
);
|
||||
$: showFilesTab =
|
||||
terminalFilesAvailable ||
|
||||
@@ -179,18 +175,6 @@
|
||||
}
|
||||
};
|
||||
|
||||
export const openPane = () => {
|
||||
if (parseInt(localStorage?.chatControlsSize)) {
|
||||
const container = document.getElementById(containerId);
|
||||
let size = Math.floor(
|
||||
(parseInt(localStorage?.chatControlsSize) / container.clientWidth) * 100
|
||||
);
|
||||
pane.resize(size);
|
||||
} else {
|
||||
pane.resize(minSize);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMediaQuery = async (e) => {
|
||||
if (e.matches) {
|
||||
largeScreen = true;
|
||||
@@ -206,7 +190,6 @@
|
||||
await tick();
|
||||
showCallOverlay.set(true);
|
||||
}
|
||||
pane = null;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -222,45 +205,16 @@
|
||||
mediaQuery.addEventListener('change', handleMediaQuery);
|
||||
handleMediaQuery(mediaQuery);
|
||||
|
||||
let resizeObserver: ResizeObserver | null = null;
|
||||
let isDestroyed = false;
|
||||
|
||||
// Wait for Svelte to render the Pane after largeScreen changed
|
||||
const init = async () => {
|
||||
await tick();
|
||||
|
||||
if (isDestroyed) return;
|
||||
|
||||
// If controls were persisted as open, set the pane to the saved size
|
||||
if ($showControls && pane) {
|
||||
openPane();
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
paneReady = true;
|
||||
mounted = true;
|
||||
}, 0);
|
||||
|
||||
const container = document.getElementById(containerId) as HTMLElement;
|
||||
if (!container) return;
|
||||
|
||||
minSize = Math.floor((350 / container.clientWidth) * 100);
|
||||
resizeObserver = new ResizeObserver((entries) => {
|
||||
for (let entry of entries) {
|
||||
const width = entry.contentRect.width;
|
||||
minSize = Math.floor((350 / width) * 100);
|
||||
if ($showControls) {
|
||||
if (pane && pane.isExpanded() && pane.getSize() < minSize) {
|
||||
pane.resize(minSize);
|
||||
} else {
|
||||
let size = Math.floor(
|
||||
(parseInt(localStorage?.chatControlsSize) / container.clientWidth) * 100
|
||||
);
|
||||
if (size < minSize && pane) pane.resize(minSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
resizeObserver.observe(container);
|
||||
};
|
||||
init();
|
||||
|
||||
@@ -269,8 +223,7 @@
|
||||
|
||||
return () => {
|
||||
isDestroyed = true;
|
||||
paneReady = false;
|
||||
resizeObserver?.disconnect();
|
||||
mounted = false;
|
||||
if (!largeScreen) {
|
||||
showControls.set(false);
|
||||
}
|
||||
@@ -289,7 +242,7 @@
|
||||
if ($showCallOverlay) showCallOverlay.set(false);
|
||||
};
|
||||
|
||||
$: if (paneReady && !chatId) closeHandler();
|
||||
$: if (mounted && !chatId) closeHandler();
|
||||
|
||||
// Helper: is a "special" full-screen panel active?
|
||||
$: specialPanel = $showCallOverlay || $showArtifacts || $showEmbeds;
|
||||
@@ -409,154 +362,129 @@
|
||||
</Drawer>
|
||||
{/if}
|
||||
{:else}
|
||||
{#if $showControls}
|
||||
<PaneResizer
|
||||
class="relative flex items-center justify-center group border-l border-gray-50 dark:border-gray-850/30 hover:border-gray-200 dark:hover:border-gray-800 transition z-20"
|
||||
id="controls-resizer"
|
||||
>
|
||||
<div
|
||||
class="absolute -left-1.5 -right-1.5 -top-0 -bottom-0 z-20 cursor-col-resize bg-transparent"
|
||||
/>
|
||||
</PaneResizer>
|
||||
{/if}
|
||||
|
||||
<Pane
|
||||
bind:pane
|
||||
defaultSize={0}
|
||||
onResize={(size) => {
|
||||
if ($showControls && pane.isExpanded()) {
|
||||
if (size < minSize) pane.resize(minSize);
|
||||
if (size < minSize) {
|
||||
localStorage.chatControlsSize = 0;
|
||||
} else {
|
||||
const container = document.getElementById(containerId);
|
||||
localStorage.chatControlsSize = Math.floor((size / 100) * container.clientWidth);
|
||||
}
|
||||
}
|
||||
}}
|
||||
onCollapse={() => {
|
||||
if (paneReady) showControls.set(false);
|
||||
}}
|
||||
collapsible={true}
|
||||
class="z-10 bg-white dark:bg-gray-900"
|
||||
<ResizableSidePanel
|
||||
open={$showControls}
|
||||
bind:width={controlsWidth}
|
||||
minWidth={350}
|
||||
maxWidth={640}
|
||||
storageKey="chatControlsSize"
|
||||
className="h-full z-10 bg-white dark:bg-gray-900"
|
||||
>
|
||||
{#if $showControls}
|
||||
<div class="flex max-h-full min-h-full">
|
||||
<div
|
||||
class="w-full {specialPanel && !$showCallOverlay
|
||||
? ' '
|
||||
: 'bg-white dark:bg-gray-900'} z-40 pointer-events-auto {activeTab === 'files'
|
||||
? ''
|
||||
: 'overflow-y-auto'} scrollbar-hidden"
|
||||
id="controls-container"
|
||||
>
|
||||
{#if $showCallOverlay}
|
||||
<div class="w-full h-full flex justify-center">
|
||||
<CallOverlay
|
||||
bind:files
|
||||
{submitPrompt}
|
||||
{stopResponse}
|
||||
{modelId}
|
||||
{chatId}
|
||||
{eventTarget}
|
||||
on:close={() => showControls.set(false)}
|
||||
/>
|
||||
</div>
|
||||
{:else if $showEmbeds}
|
||||
<Embeds overlay={dragged} />
|
||||
{:else if $showArtifacts}
|
||||
<Artifacts {history} overlay={dragged} />
|
||||
{:else}
|
||||
<!-- Controls + Files tabs -->
|
||||
<div class="flex flex-col h-full min-h-0">
|
||||
<!-- Tab bar -->
|
||||
<div class="flex items-center justify-between px-2 pt-2 pb-2 shrink-0">
|
||||
<div class="flex gap-1 min-w-0 overflow-x-auto scrollbar-hidden">
|
||||
{#if showControlsTab}
|
||||
<button
|
||||
class="px-2.5 py-1 text-sm rounded-lg transition whitespace-nowrap {activeTab ===
|
||||
'controls'
|
||||
? 'bg-gray-100/40 dark:bg-gray-800/25 font-normal text-gray-700 dark:text-gray-200'
|
||||
: 'text-gray-500 dark:text-gray-400 hover:bg-gray-100/30 dark:hover:bg-gray-800/20 hover:text-gray-600 dark:hover:text-gray-300'}"
|
||||
on:click={() => (activeTab = 'controls')}
|
||||
>
|
||||
{$i18n.t('Controls')}
|
||||
</button>
|
||||
{/if}
|
||||
{#if showFilesTab}
|
||||
<button
|
||||
class="px-2.5 py-1 text-sm rounded-lg transition whitespace-nowrap {activeTab ===
|
||||
'files'
|
||||
? 'bg-gray-100/40 dark:bg-gray-800/25 font-normal text-gray-700 dark:text-gray-200'
|
||||
: 'text-gray-500 dark:text-gray-400 hover:bg-gray-100/30 dark:hover:bg-gray-800/20 hover:text-gray-600 dark:hover:text-gray-300'}"
|
||||
on:click={() => (activeTab = 'files')}
|
||||
>
|
||||
{$i18n.t('Files')}
|
||||
</button>
|
||||
{/if}
|
||||
{#if showOverviewTab}
|
||||
<button
|
||||
class="px-2.5 py-1 text-sm rounded-lg transition whitespace-nowrap {activeTab ===
|
||||
'overview'
|
||||
? 'bg-gray-100/40 dark:bg-gray-800/25 font-normal text-gray-700 dark:text-gray-200'
|
||||
: 'text-gray-500 dark:text-gray-400 hover:bg-gray-100/30 dark:hover:bg-gray-800/20 hover:text-gray-600 dark:hover:text-gray-300'}"
|
||||
on:click={() => (activeTab = 'overview')}
|
||||
>
|
||||
{$i18n.t('Overview')}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
<button
|
||||
class="p-1 rounded-lg text-gray-500 dark:text-gray-400"
|
||||
on:click={() => showControls.set(false)}
|
||||
aria-label={$i18n.t('Close')}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
class="size-4"
|
||||
<div class="flex h-full max-h-full min-h-full">
|
||||
<div
|
||||
class="w-full {specialPanel && !$showCallOverlay
|
||||
? ' '
|
||||
: 'bg-white dark:bg-gray-900'} z-40 pointer-events-auto {activeTab === 'files'
|
||||
? ''
|
||||
: 'overflow-y-auto'} scrollbar-hidden"
|
||||
id="controls-container"
|
||||
>
|
||||
{#if $showCallOverlay}
|
||||
<div class="w-full h-full flex justify-center">
|
||||
<CallOverlay
|
||||
bind:files
|
||||
{submitPrompt}
|
||||
{stopResponse}
|
||||
{modelId}
|
||||
{chatId}
|
||||
{eventTarget}
|
||||
on:close={() => showControls.set(false)}
|
||||
/>
|
||||
</div>
|
||||
{:else if $showEmbeds}
|
||||
<Embeds overlay={dragged} />
|
||||
{:else if $showArtifacts}
|
||||
<Artifacts {history} overlay={dragged} />
|
||||
{:else}
|
||||
<!-- Controls + Files tabs -->
|
||||
<div class="flex flex-col h-full min-h-0">
|
||||
<!-- Tab bar -->
|
||||
<div class="flex items-center justify-between px-2 pt-2 pb-2 shrink-0">
|
||||
<div class="flex gap-1 min-w-0 overflow-x-auto scrollbar-hidden">
|
||||
{#if showControlsTab}
|
||||
<button
|
||||
class="px-2.5 py-1 text-sm rounded-lg transition whitespace-nowrap {activeTab ===
|
||||
'controls'
|
||||
? 'bg-gray-100/40 dark:bg-gray-800/25 font-normal text-gray-700 dark:text-gray-200'
|
||||
: 'text-gray-500 dark:text-gray-400 hover:bg-gray-100/30 dark:hover:bg-gray-800/20 hover:text-gray-600 dark:hover:text-gray-300'}"
|
||||
on:click={() => (activeTab = 'controls')}
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex-1 min-h-0 {activeTab === 'overview'
|
||||
? 'h-full'
|
||||
: activeTab === 'controls'
|
||||
? 'overflow-y-auto px-3 pt-1'
|
||||
: ''}"
|
||||
>
|
||||
{#if activeTab === 'overview'}
|
||||
<Overview
|
||||
{history}
|
||||
{chatUser}
|
||||
onNodeClick={(e) => {
|
||||
const node = e.node;
|
||||
if (node?.data?.message?.favorite) {
|
||||
history.messages[node.data.message.id].favorite = true;
|
||||
} else {
|
||||
history.messages[node.data.message.id].favorite = null;
|
||||
}
|
||||
showMessage(node.data.message, true);
|
||||
}}
|
||||
/>
|
||||
{:else if activeTab === 'files' && terminalFilesAvailable && $selectedTerminalId}
|
||||
<FileNav onAttach={handleTerminalAttach} overlay={dragged} {chatId} />
|
||||
{:else if activeTab === 'files' && codeInterpreterEnabled}
|
||||
<PyodideFileNav overlay={dragged} />
|
||||
{:else}
|
||||
<Controls embed={true} {models} bind:chatFiles bind:params />
|
||||
{$i18n.t('Controls')}
|
||||
</button>
|
||||
{/if}
|
||||
{#if showFilesTab}
|
||||
<button
|
||||
class="px-2.5 py-1 text-sm rounded-lg transition whitespace-nowrap {activeTab ===
|
||||
'files'
|
||||
? 'bg-gray-100/40 dark:bg-gray-800/25 font-normal text-gray-700 dark:text-gray-200'
|
||||
: 'text-gray-500 dark:text-gray-400 hover:bg-gray-100/30 dark:hover:bg-gray-800/20 hover:text-gray-600 dark:hover:text-gray-300'}"
|
||||
on:click={() => (activeTab = 'files')}
|
||||
>
|
||||
{$i18n.t('Files')}
|
||||
</button>
|
||||
{/if}
|
||||
{#if showOverviewTab}
|
||||
<button
|
||||
class="px-2.5 py-1 text-sm rounded-lg transition whitespace-nowrap {activeTab ===
|
||||
'overview'
|
||||
? 'bg-gray-100/40 dark:bg-gray-800/25 font-normal text-gray-700 dark:text-gray-200'
|
||||
: 'text-gray-500 dark:text-gray-400 hover:bg-gray-100/30 dark:hover:bg-gray-800/20 hover:text-gray-600 dark:hover:text-gray-300'}"
|
||||
on:click={() => (activeTab = 'overview')}
|
||||
>
|
||||
{$i18n.t('Overview')}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
<button
|
||||
class="p-1 rounded-lg text-gray-500 dark:text-gray-400"
|
||||
on:click={() => showControls.set(false)}
|
||||
aria-label={$i18n.t('Close')}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
class="size-4"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex-1 min-h-0 {activeTab === 'overview'
|
||||
? 'h-full'
|
||||
: activeTab === 'controls'
|
||||
? 'overflow-y-auto px-3 pt-1'
|
||||
: ''}"
|
||||
>
|
||||
{#if activeTab === 'overview'}
|
||||
<Overview
|
||||
{history}
|
||||
{chatUser}
|
||||
onNodeClick={(e) => {
|
||||
const node = e.node;
|
||||
if (node?.data?.message?.favorite) {
|
||||
history.messages[node.data.message.id].favorite = true;
|
||||
} else {
|
||||
history.messages[node.data.message.id].favorite = null;
|
||||
}
|
||||
showMessage(node.data.message, true);
|
||||
}}
|
||||
/>
|
||||
{:else if activeTab === 'files' && terminalFilesAvailable && $selectedTerminalId}
|
||||
<FileNav onAttach={handleTerminalAttach} overlay={dragged} {chatId} />
|
||||
{:else if activeTab === 'files' && codeInterpreterEnabled}
|
||||
<PyodideFileNav overlay={dragged} />
|
||||
{:else}
|
||||
<Controls embed={true} {models} bind:chatFiles bind:params />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</Pane>
|
||||
</div>
|
||||
</ResizableSidePanel>
|
||||
{/if}
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
<script lang="ts">
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
|
||||
export let open = false;
|
||||
export let side: 'left' | 'right' = 'right';
|
||||
export let width = 350;
|
||||
export let minWidth = 300;
|
||||
export let maxWidth = 640;
|
||||
export let storageKey = '';
|
||||
export let className = '';
|
||||
export let resizerId = 'controls-resizer';
|
||||
|
||||
let isResizing = false;
|
||||
let startClientX = 0;
|
||||
let startWidth = 0;
|
||||
|
||||
const clamp = (value: number) => Math.min(maxWidth, Math.max(minWidth, value));
|
||||
|
||||
const persistWidth = () => {
|
||||
if (storageKey) {
|
||||
localStorage.setItem(storageKey, String(width));
|
||||
}
|
||||
};
|
||||
|
||||
const resizeStartHandler = (e: MouseEvent) => {
|
||||
if (!open) return;
|
||||
|
||||
isResizing = true;
|
||||
startClientX = e.clientX;
|
||||
startWidth = width;
|
||||
document.body.style.userSelect = 'none';
|
||||
};
|
||||
|
||||
const resizeEndHandler = () => {
|
||||
if (!isResizing) return;
|
||||
|
||||
isResizing = false;
|
||||
document.body.style.userSelect = '';
|
||||
persistWidth();
|
||||
};
|
||||
|
||||
const resizeHandler = (endClientX: number) => {
|
||||
const dx = endClientX - startClientX;
|
||||
width = clamp(side === 'right' ? startWidth - dx : startWidth + dx);
|
||||
};
|
||||
|
||||
const resizeKeyHandler = (e: KeyboardEvent) => {
|
||||
if (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight') return;
|
||||
|
||||
e.preventDefault();
|
||||
const delta = e.key === 'ArrowLeft' ? -10 : 10;
|
||||
width = clamp(width + (side === 'right' ? -delta : delta));
|
||||
persistWidth();
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
if (!storageKey) return;
|
||||
|
||||
const storedWidth = Number(localStorage.getItem(storageKey));
|
||||
if (!Number.isNaN(storedWidth)) {
|
||||
width = clamp(storedWidth);
|
||||
}
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
if (isResizing) {
|
||||
document.body.style.userSelect = '';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:window
|
||||
on:mousemove={(e) => {
|
||||
if (!isResizing) return;
|
||||
resizeHandler(e.clientX);
|
||||
}}
|
||||
on:mouseup={resizeEndHandler}
|
||||
/>
|
||||
|
||||
{#if open}
|
||||
{#if side === 'right'}
|
||||
<!-- svelte-ignore a11y_no_noninteractive_element_interactions, a11y_no_noninteractive_tabindex -->
|
||||
<div
|
||||
class="relative flex items-center justify-center group border-l border-gray-50 dark:border-gray-850/30 hover:border-gray-200 dark:hover:border-gray-800 transition z-20 bg-transparent p-0 appearance-none"
|
||||
id={resizerId}
|
||||
on:mousedown={resizeStartHandler}
|
||||
on:keydown={resizeKeyHandler}
|
||||
role="separator"
|
||||
tabindex="0"
|
||||
aria-label="Resize panel"
|
||||
aria-orientation="vertical"
|
||||
>
|
||||
<span
|
||||
class="absolute -left-1.5 -right-1.5 -top-0 -bottom-0 z-20 cursor-col-resize bg-transparent"
|
||||
></span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class={className} style="width: {width}px; flex: 0 0 {width}px;">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
{#if side === 'left'}
|
||||
<!-- svelte-ignore a11y_no_noninteractive_element_interactions, a11y_no_noninteractive_tabindex -->
|
||||
<div
|
||||
class="relative flex items-center justify-center group border-r border-gray-50 dark:border-gray-850/30 hover:border-gray-200 dark:hover:border-gray-800 transition z-20 bg-transparent p-0 appearance-none"
|
||||
id={resizerId}
|
||||
on:mousedown={resizeStartHandler}
|
||||
on:keydown={resizeKeyHandler}
|
||||
role="separator"
|
||||
tabindex="0"
|
||||
aria-label="Resize panel"
|
||||
aria-orientation="vertical"
|
||||
>
|
||||
<span
|
||||
class="absolute -left-1.5 -right-1.5 -top-0 -bottom-0 z-20 cursor-col-resize bg-transparent"
|
||||
></span>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
@@ -21,8 +21,6 @@
|
||||
dayjs.extend(duration);
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
import { PaneGroup, Pane, PaneResizer } from 'paneforge';
|
||||
|
||||
import { compressImage, copyToClipboard, convertHeicToJpeg } from '$lib/utils';
|
||||
import { WEBUI_BASE_URL } from '$lib/constants';
|
||||
import { getFileById, uploadFile } from '$lib/apis/files';
|
||||
@@ -1006,8 +1004,8 @@ ${content}
|
||||
</div>
|
||||
</DeleteConfirmDialog>
|
||||
|
||||
<PaneGroup direction="horizontal" class="w-full h-full">
|
||||
<Pane defaultSize={70} minSize={30} class="h-full flex flex-col w-full relative">
|
||||
<div class="w-full h-full flex">
|
||||
<div class="h-full flex flex-col min-w-0 flex-1 relative">
|
||||
<div class="relative flex-1 w-full h-full flex justify-center pt-2" id="note-editor">
|
||||
{#if loading}
|
||||
<div class=" absolute top-0 bottom-0 left-0 right-0 flex">
|
||||
@@ -1468,7 +1466,7 @@ ${content}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</Pane>
|
||||
</div>
|
||||
<NotePanel bind:show={showNoteChat}>
|
||||
{#if noteChatLoading}
|
||||
<div class="flex h-full items-center justify-center">
|
||||
@@ -1511,4 +1509,4 @@ ${content}
|
||||
/>
|
||||
{/if}
|
||||
</NotePanel>
|
||||
</PaneGroup>
|
||||
</div>
|
||||
|
||||
@@ -1,63 +1,27 @@
|
||||
<script lang="ts">
|
||||
import { onDestroy, onMount, tick } from 'svelte';
|
||||
import { Pane, PaneResizer } from 'paneforge';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
|
||||
import Drawer from '../common/Drawer.svelte';
|
||||
import ResizableSidePanel from '../common/ResizableSidePanel.svelte';
|
||||
|
||||
export let show = false;
|
||||
export let pane = null;
|
||||
|
||||
export let containerId = 'note-container';
|
||||
|
||||
let mediaQuery;
|
||||
let mediaQuery: MediaQueryList;
|
||||
let largeScreen = false;
|
||||
let panelWidth = 350;
|
||||
|
||||
let minSize = 0;
|
||||
|
||||
const handleMediaQuery = async (e) => {
|
||||
if (e.matches) {
|
||||
largeScreen = true;
|
||||
} else {
|
||||
largeScreen = false;
|
||||
pane = null;
|
||||
}
|
||||
const handleMediaQuery = (e: MediaQueryListEvent | MediaQueryList) => {
|
||||
largeScreen = e.matches;
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
// listen to resize 1000px
|
||||
mediaQuery = window.matchMedia('(min-width: 1000px)');
|
||||
|
||||
mediaQuery.addEventListener('change', handleMediaQuery);
|
||||
handleMediaQuery(mediaQuery);
|
||||
|
||||
// Select the container element you want to observe
|
||||
const container = document.getElementById(containerId);
|
||||
|
||||
// initialize the minSize based on the container width
|
||||
minSize = Math.floor((350 / container.clientWidth) * 100);
|
||||
|
||||
// Create a new ResizeObserver instance
|
||||
const resizeObserver = new ResizeObserver((entries) => {
|
||||
for (let entry of entries) {
|
||||
const width = entry.contentRect.width;
|
||||
const percentage = (350 / width) * 100;
|
||||
// set the minSize to the percentage, must be an integer
|
||||
minSize = Math.floor(percentage);
|
||||
|
||||
if (show) {
|
||||
if (pane && pane.isExpanded() && pane.getSize() < minSize) {
|
||||
pane.resize(minSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Start observing the container's size changes
|
||||
resizeObserver.observe(container);
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
mediaQuery.removeEventListener('change', handleMediaQuery);
|
||||
mediaQuery?.removeEventListener('change', handleMediaQuery);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -69,39 +33,25 @@
|
||||
show = false;
|
||||
}}
|
||||
>
|
||||
<div class=" h-screen max-h-dvh flex flex-col">
|
||||
<div class="h-screen max-h-dvh flex flex-col">
|
||||
<slot />
|
||||
</div>
|
||||
</Drawer>
|
||||
{/if}
|
||||
{:else if show}
|
||||
<PaneResizer
|
||||
class="relative flex items-center justify-center group border-l border-gray-50 dark:border-gray-850/30 hover:border-gray-200 dark:hover:border-gray-800 transition z-20"
|
||||
id="controls-resizer"
|
||||
{:else}
|
||||
<ResizableSidePanel
|
||||
bind:open={show}
|
||||
bind:width={panelWidth}
|
||||
minWidth={350}
|
||||
maxWidth={640}
|
||||
className="h-full z-10"
|
||||
>
|
||||
<div
|
||||
class=" absolute -left-1.5 -right-1.5 -top-0 -bottom-0 z-20 cursor-col-resize bg-transparent"
|
||||
/>
|
||||
</PaneResizer>
|
||||
|
||||
<Pane
|
||||
bind:pane
|
||||
defaultSize={Math.max(20, minSize)}
|
||||
{minSize}
|
||||
onCollapse={() => {
|
||||
show = false;
|
||||
}}
|
||||
collapsible={true}
|
||||
class=" z-10 "
|
||||
>
|
||||
{#if show}
|
||||
<div class="flex max-h-full min-h-full">
|
||||
<div
|
||||
class="w-full bg-white dark:bg-gray-900 z-40 pointer-events-auto overflow-hidden scrollbar-hidden flex flex-col"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
<div class="flex h-full max-h-full min-h-full">
|
||||
<div
|
||||
class="w-full bg-white dark:bg-gray-900 z-40 pointer-events-auto overflow-hidden scrollbar-hidden flex flex-col"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
{/if}
|
||||
</Pane>
|
||||
</div>
|
||||
</ResizableSidePanel>
|
||||
{/if}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import Fuse from 'fuse.js';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { PaneGroup, Pane, PaneResizer } from 'paneforge';
|
||||
|
||||
import { onMount, getContext, onDestroy, tick } from 'svelte';
|
||||
import type { Writable } from 'svelte/store';
|
||||
@@ -77,11 +76,6 @@
|
||||
import Pagination from '$lib/components/common/Pagination.svelte';
|
||||
import AttachWebpageModal from '$lib/components/chat/MessageInput/AttachWebpageModal.svelte';
|
||||
|
||||
let largeScreen = true;
|
||||
|
||||
let pane;
|
||||
let showSidepanel = true;
|
||||
|
||||
let showAddWebpageModal = false;
|
||||
let showAddTextContentModal = false;
|
||||
let showNewDirectoryModal = false;
|
||||
@@ -92,7 +86,6 @@
|
||||
let showAccessControlModal = false;
|
||||
let showResetConfirm = false;
|
||||
|
||||
let minSize = 0;
|
||||
type DirectoryFileEntry = { path: string; filename: string; file: File };
|
||||
type DirectoryManifestEntry = DirectoryFileEntry & { checksum: string; size: number };
|
||||
|
||||
@@ -917,7 +910,6 @@
|
||||
};
|
||||
|
||||
let debounceTimeout = null;
|
||||
let mediaQuery;
|
||||
|
||||
let dragged = false;
|
||||
let isSaving = false;
|
||||
@@ -980,14 +972,6 @@
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
const handleMediaQuery = async (e) => {
|
||||
if (e.matches) {
|
||||
largeScreen = true;
|
||||
} else {
|
||||
largeScreen = false;
|
||||
}
|
||||
};
|
||||
|
||||
const readDirectoryEntries = async (reader: any) => {
|
||||
const entries: any[] = [];
|
||||
|
||||
@@ -1096,42 +1080,6 @@
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
// listen to resize 1024px
|
||||
mediaQuery = window.matchMedia('(min-width: 1024px)');
|
||||
|
||||
mediaQuery.addEventListener('change', handleMediaQuery);
|
||||
handleMediaQuery(mediaQuery);
|
||||
|
||||
// Select the container element you want to observe
|
||||
const container = document.getElementById('collection-container');
|
||||
|
||||
// initialize the minSize based on the container width
|
||||
minSize = !largeScreen ? 100 : Math.floor((300 / container.clientWidth) * 100);
|
||||
|
||||
// Create a new ResizeObserver instance
|
||||
const resizeObserver = new ResizeObserver((entries) => {
|
||||
for (let entry of entries) {
|
||||
const width = entry.contentRect.width;
|
||||
// calculate the percentage of 300
|
||||
const percentage = (300 / width) * 100;
|
||||
// set the minSize to the percentage, must be an integer
|
||||
minSize = !largeScreen ? 100 : Math.floor(percentage);
|
||||
|
||||
if (showSidepanel) {
|
||||
if (pane && pane.isExpanded() && pane.getSize() < minSize) {
|
||||
pane.resize(minSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Start observing the container's size changes
|
||||
resizeObserver.observe(container);
|
||||
|
||||
if (pane) {
|
||||
pane.expand();
|
||||
}
|
||||
|
||||
id = $page.params.id;
|
||||
const res = await getKnowledgeById(localStorage.token, id).catch((e) => {
|
||||
toast.error(`${e}`);
|
||||
@@ -1160,7 +1108,6 @@
|
||||
clearInterval(pendingPollTimer);
|
||||
pendingPollTimer = null;
|
||||
}
|
||||
mediaQuery?.removeEventListener('change', handleMediaQuery);
|
||||
const dropZone = document.querySelector('body');
|
||||
dropZone?.removeEventListener('dragover', onDragOver);
|
||||
dropZone?.removeEventListener('drop', onDrop);
|
||||
|
||||
Reference in New Issue
Block a user