diff --git a/package-lock.json b/package-lock.json index a0048a211..84924afea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,7 @@ "katex": "^0.16.9", "marked": "^9.1.0", "mermaid": "^10.9.1", + "paneforge": "^0.0.6", "pyodide": "^0.26.1", "socket.io-client": "^4.2.0", "sortablejs": "^1.15.2", @@ -6986,6 +6987,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/paneforge": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/paneforge/-/paneforge-0.0.6.tgz", + "integrity": "sha512-jYeN/wdREihja5c6nK3S5jritDQ+EbCqC5NrDo97qCZzZ9GkmEcN5C0ZCjF4nmhBwkDKr6tLIgz4QUKWxLXjAw==", + "dependencies": { + "nanoid": "^5.0.4" + }, + "peerDependencies": { + "svelte": "^4.0.0 || ^5.0.0-next.1" + } + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", diff --git a/package.json b/package.json index 371507789..25c2289a2 100644 --- a/package.json +++ b/package.json @@ -72,6 +72,7 @@ "katex": "^0.16.9", "marked": "^9.1.0", "mermaid": "^10.9.1", + "paneforge": "^0.0.6", "pyodide": "^0.26.1", "socket.io-client": "^4.2.0", "sortablejs": "^1.15.2", diff --git a/src/lib/components/chat/Chat.svelte b/src/lib/components/chat/Chat.svelte index e0ec62b52..de1eb0131 100644 --- a/src/lib/components/chat/Chat.svelte +++ b/src/lib/components/chat/Chat.svelte @@ -2,6 +2,7 @@ import { v4 as uuidv4 } from 'uuid'; import { toast } from 'svelte-sonner'; import mermaid from 'mermaid'; + import { PaneGroup, Pane, PaneResizer } from 'paneforge'; import { getContext, onDestroy, onMount, tick } from 'svelte'; import { goto } from '$app/navigation'; @@ -26,7 +27,8 @@ showControls, showCallOverlay, currentChatPage, - temporaryChatEnabled + temporaryChatEnabled, + mobile } from '$lib/stores'; import { convertMessagesToHistory, @@ -64,12 +66,14 @@ import Navbar from '$lib/components/layout/Navbar.svelte'; import ChatControls from './ChatControls.svelte'; import EventConfirmDialog from '../common/ConfirmDialog.svelte'; + import EllipsisVertical from '../icons/EllipsisVertical.svelte'; const i18n: Writable = getContext('i18n'); export let chatIdProp = ''; let loaded = false; const eventTarget = new EventTarget(); + let controlPane; let stopResponseFlag = false; let autoScroll = true; @@ -1760,117 +1764,117 @@ bind:selectedModels bind:showModelSelector shareEnabled={messages.length > 0} + {controlPane} {chat} {initNewChat} /> - {#if $banners.length > 0 && messages.length === 0 && !$chatId && selectedModels.length <= 1} -
-
- {#each $banners.filter( (b) => (b.dismissible ? !JSON.parse(localStorage.getItem('dismissedBannerIds') ?? '[]').includes(b.id) : true) ) as banner} - { - const bannerId = e.detail; + + + {#if $banners.length > 0 && messages.length === 0 && !$chatId && selectedModels.length <= 1} +
+
+ {#each $banners.filter( (b) => (b.dismissible ? !JSON.parse(localStorage.getItem('dismissedBannerIds') ?? '[]').includes(b.id) : true) ) as banner} + { + const bannerId = e.detail; - localStorage.setItem( - 'dismissedBannerIds', - JSON.stringify( - [ - bannerId, - ...JSON.parse(localStorage.getItem('dismissedBannerIds') ?? '[]') - ].filter((id) => $banners.find((b) => b.id === id)) - ) - ); + localStorage.setItem( + 'dismissedBannerIds', + JSON.stringify( + [ + bannerId, + ...JSON.parse(localStorage.getItem('dismissedBannerIds') ?? '[]') + ].filter((id) => $banners.find((b) => b.id === id)) + ) + ); + }} + /> + {/each} +
+
+ {/if} + +
+ + +
+ { + const model = $models.find((m) => m.id === e); + if (model?.info?.meta?.toolIds ?? false) { + return [...new Set([...a, ...model.info.meta.toolIds])]; + } + return a; + }, [])} + transparentBackground={$settings?.backgroundImageUrl ?? false} + {selectedModels} + {messages} + {submitPrompt} + {stopResponse} + on:call={() => { + showControls.set(true); }} /> - {/each} +
-
- {/if} + -
- - -
- { - const model = $models.find((m) => m.id === e); - if (model?.info?.meta?.toolIds ?? false) { - return [...new Set([...a, ...model.info.meta.toolIds])]; - } - return a; - }, [])} - transparentBackground={$settings?.backgroundImageUrl ?? false} - {selectedModels} - {messages} - {submitPrompt} - {stopResponse} - on:call={() => { - showControls.set(true); - }} - /> -
-
+ { + const model = $models.find((m) => m.id === e); + if (model) { + return [...a, model]; + } + return a; + }, [])} + bind:history + bind:chatFiles + bind:params + bind:files + bind:pane={controlPane} + {submitPrompt} + {stopResponse} + {showMessage} + modelId={selectedModelIds?.at(0) ?? null} + chatId={$chatId} + {eventTarget} + /> +
{/if} - - { - const model = $models.find((m) => m.id === e); - if (model) { - return [...a, model]; - } - return a; - }, [])} - bind:history - bind:chatFiles - bind:params - bind:files - {submitPrompt} - {stopResponse} - {showMessage} - modelId={selectedModelIds?.at(0) ?? null} - chatId={$chatId} - {eventTarget} -/> diff --git a/src/lib/components/chat/ChatControls.svelte b/src/lib/components/chat/ChatControls.svelte index b9268cebc..5c5962d7f 100644 --- a/src/lib/components/chat/ChatControls.svelte +++ b/src/lib/components/chat/ChatControls.svelte @@ -10,6 +10,9 @@ import CallOverlay from './MessageInput/CallOverlay.svelte'; import Drawer from '../common/Drawer.svelte'; import Overview from './Overview.svelte'; + import { Pane, PaneResizer } from 'paneforge'; + import EllipsisVertical from '../icons/EllipsisVertical.svelte'; + import { get } from 'svelte/store'; export let history; export let models = []; @@ -25,7 +28,9 @@ export let files; export let modelId; + export let pane; let largeScreen = false; + onMount(() => { // listen to resize 1024px const mediaQuery = window.matchMedia('(min-width: 1024px)'); @@ -58,33 +63,33 @@ {#if !largeScreen} - {#if $showCallOverlay} -
-
- { - showControls.set(false); - }} - /> -
-
- {:else if $showControls} + {#if $showControls} { showControls.set(false); }} > -
- {#if $showOverview} +
+ {#if $showCallOverlay} +
+ { + showControls.set(false); + }} + /> +
+ {:else if $showOverview} { @@ -107,11 +112,30 @@
{/if} - {:else if $showControls} -
-
+ {:else} + + +
+ +
+
+ { + if (size === 0) { + showControls.set(false); + } else { + if (!$showControls) { + showControls.set(true); + } + localStorage.setItem('chat-controls-size', size); + } + }} + > +
@@ -149,6 +173,6 @@ {/if}
-
+ {/if} diff --git a/src/lib/components/layout/Navbar.svelte b/src/lib/components/layout/Navbar.svelte index e5ec5eb34..fec277a0c 100644 --- a/src/lib/components/layout/Navbar.svelte +++ b/src/lib/components/layout/Navbar.svelte @@ -29,6 +29,7 @@ export let initNewChat: Function; export let title: string = $WEBUI_NAME; export let shareEnabled: boolean = false; + export let controlPane; export let chat; export let selectedModels; @@ -109,8 +110,16 @@