mirror of
https://github.com/open-webui/open-webui
synced 2025-03-23 22:31:38 +00:00
feat: chat overview
This commit is contained in:
parent
bb087a5989
commit
d1dbb9a3be
@ -156,3 +156,7 @@ input[type='number'] {
|
||||
font-weight: 600;
|
||||
@apply rounded-md dark:bg-gray-800 bg-gray-100 mx-0.5;
|
||||
}
|
||||
|
||||
.svelte-flow {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
@ -23,6 +23,7 @@
|
||||
banners,
|
||||
user,
|
||||
socket,
|
||||
showControls,
|
||||
showCallOverlay,
|
||||
currentChatPage,
|
||||
temporaryChatEnabled
|
||||
@ -70,7 +71,6 @@
|
||||
let loaded = false;
|
||||
const eventTarget = new EventTarget();
|
||||
|
||||
let showControls = false;
|
||||
let stopResponseFlag = false;
|
||||
let autoScroll = true;
|
||||
let processing = '';
|
||||
@ -1703,7 +1703,6 @@
|
||||
{title}
|
||||
bind:selectedModels
|
||||
bind:showModelSelector
|
||||
bind:showControls
|
||||
shareEnabled={messages.length > 0}
|
||||
{chat}
|
||||
{initNewChat}
|
||||
@ -1713,7 +1712,7 @@
|
||||
<div
|
||||
class="absolute top-[4.25rem] w-full {$showSidebar
|
||||
? 'md:max-w-[calc(100%-260px)]'
|
||||
: ''} {showControls ? 'lg:pr-[24rem]' : ''} z-20"
|
||||
: ''} {$showControls ? 'lg:pr-[24rem]' : ''} z-20"
|
||||
>
|
||||
<div class=" flex flex-col gap-1 w-full">
|
||||
{#each $banners.filter( (b) => (b.dismissible ? !JSON.parse(localStorage.getItem('dismissedBannerIds') ?? '[]').includes(b.id) : true) ) as banner}
|
||||
@ -1740,7 +1739,7 @@
|
||||
|
||||
<div class="flex flex-col flex-auto z-10">
|
||||
<div
|
||||
class=" pb-2.5 flex flex-col justify-between w-full flex-auto overflow-auto h-0 max-w-full z-10 scrollbar-hidden {showControls
|
||||
class=" pb-2.5 flex flex-col justify-between w-full flex-auto overflow-auto h-0 max-w-full z-10 scrollbar-hidden {$showControls
|
||||
? 'lg:pr-[24rem]'
|
||||
: ''}"
|
||||
id="messages-container"
|
||||
@ -1770,7 +1769,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class={showControls ? 'lg:pr-[24rem]' : ''}>
|
||||
<div class={$showControls ? 'lg:pr-[24rem]' : ''}>
|
||||
<MessageInput
|
||||
bind:files
|
||||
bind:prompt
|
||||
@ -1791,7 +1790,7 @@
|
||||
{submitPrompt}
|
||||
{stopResponse}
|
||||
on:call={() => {
|
||||
showControls = true;
|
||||
showControls.set(true);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@ -1807,7 +1806,7 @@
|
||||
}
|
||||
return a;
|
||||
}, [])}
|
||||
bind:show={showControls}
|
||||
bind:history
|
||||
bind:chatFiles
|
||||
bind:params
|
||||
bind:files
|
||||
|
@ -1,14 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { SvelteFlowProvider } from '@xyflow/svelte';
|
||||
import { slide } from 'svelte/transition';
|
||||
|
||||
import { onMount } from 'svelte';
|
||||
import { mobile, showControls, showCallOverlay, showOverview } from '$lib/stores';
|
||||
|
||||
import Modal from '../common/Modal.svelte';
|
||||
import Controls from './Controls/Controls.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import { mobile, showCallOverlay } from '$lib/stores';
|
||||
import CallOverlay from './MessageInput/CallOverlay.svelte';
|
||||
import Drawer from '../common/Drawer.svelte';
|
||||
import Overview from './Overview.svelte';
|
||||
|
||||
export let show = false;
|
||||
|
||||
export let history;
|
||||
export let models = [];
|
||||
|
||||
export let chatId = null;
|
||||
@ -44,46 +47,13 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if !largeScreen}
|
||||
{#if $showCallOverlay}
|
||||
<div class=" absolute w-full h-screen max-h-[100dvh] flex z-[999] overflow-hidden">
|
||||
<div
|
||||
class="absolute w-full h-screen max-h-[100dvh] bg-white text-gray-700 dark:bg-black dark:text-gray-300 flex justify-center"
|
||||
>
|
||||
<CallOverlay
|
||||
bind:files
|
||||
{submitPrompt}
|
||||
{stopResponse}
|
||||
{modelId}
|
||||
{chatId}
|
||||
{eventTarget}
|
||||
on:close={() => {
|
||||
show = false;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{:else if show}
|
||||
<Drawer bind:show>
|
||||
<div class=" px-6 py-4 h-full">
|
||||
<Controls
|
||||
on:close={() => {
|
||||
show = false;
|
||||
}}
|
||||
{models}
|
||||
bind:chatFiles
|
||||
bind:params
|
||||
/>
|
||||
</div>
|
||||
</Drawer>
|
||||
{/if}
|
||||
{:else if show}
|
||||
<div class=" absolute bottom-0 right-0 z-20 h-full pointer-events-none">
|
||||
<div class="pr-4 pt-14 pb-8 w-[24rem] h-full" in:slide={{ duration: 200, axis: 'x' }}>
|
||||
<div
|
||||
class="w-full h-full px-5 py-4 bg-white dark:shadow-lg dark:bg-gray-850 border border-gray-50 dark:border-gray-800 rounded-xl z-50 pointer-events-auto overflow-y-auto scrollbar-hidden"
|
||||
>
|
||||
{#if $showCallOverlay}
|
||||
<SvelteFlowProvider>
|
||||
{#if !largeScreen}
|
||||
{#if $showCallOverlay}
|
||||
<div class=" absolute w-full h-screen max-h-[100dvh] flex z-[999] overflow-hidden">
|
||||
<div
|
||||
class="absolute w-full h-screen max-h-[100dvh] bg-white text-gray-700 dark:bg-black dark:text-gray-300 flex justify-center"
|
||||
>
|
||||
<CallOverlay
|
||||
bind:files
|
||||
{submitPrompt}
|
||||
@ -92,20 +62,68 @@
|
||||
{chatId}
|
||||
{eventTarget}
|
||||
on:close={() => {
|
||||
show = false;
|
||||
showControls.set(false);
|
||||
}}
|
||||
/>
|
||||
{:else}
|
||||
</div>
|
||||
</div>
|
||||
{:else if $showControls}
|
||||
<Drawer
|
||||
on:close={() => {
|
||||
showControls.set(false);
|
||||
}}
|
||||
>
|
||||
<div class=" px-6 py-4 h-full">
|
||||
<Controls
|
||||
on:close={() => {
|
||||
show = false;
|
||||
showControls.set(false);
|
||||
}}
|
||||
{models}
|
||||
bind:chatFiles
|
||||
bind:params
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</Drawer>
|
||||
{/if}
|
||||
{:else if $showControls}
|
||||
<div class=" absolute bottom-0 right-0 z-20 h-full pointer-events-none">
|
||||
<div class="pr-4 pt-14 pb-8 w-[24rem] h-full" in:slide={{ duration: 200, axis: 'x' }}>
|
||||
<div
|
||||
class="w-full h-full {$showOverview
|
||||
? ' '
|
||||
: 'px-5 py-4 bg-white dark:shadow-lg dark:bg-gray-850 border border-gray-50 dark:border-gray-800'} rounded-lg z-50 pointer-events-auto overflow-y-auto scrollbar-hidden"
|
||||
>
|
||||
{#if $showCallOverlay}
|
||||
<CallOverlay
|
||||
bind:files
|
||||
{submitPrompt}
|
||||
{stopResponse}
|
||||
{modelId}
|
||||
{chatId}
|
||||
{eventTarget}
|
||||
on:close={() => {
|
||||
showControls.set(false);
|
||||
}}
|
||||
/>
|
||||
{:else if $showOverview}
|
||||
<Overview
|
||||
bind:history
|
||||
on:close={() => {
|
||||
showControls.set(false);
|
||||
}}
|
||||
/>
|
||||
{:else}
|
||||
<Controls
|
||||
on:close={() => {
|
||||
showControls.set(false);
|
||||
}}
|
||||
{models}
|
||||
bind:chatFiles
|
||||
bind:params
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</SvelteFlowProvider>
|
||||
|
@ -1,23 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { settings } from '$lib/stores';
|
||||
import { WEBUI_BASE_URL } from '$lib/constants';
|
||||
import ProfileImageBase from './ProfileImageBase.svelte';
|
||||
|
||||
export let className = 'size-8';
|
||||
|
||||
export let src = '/user.png';
|
||||
export let src = '';
|
||||
</script>
|
||||
|
||||
<div class={`flex-shrink-0 ${($settings?.chatDirection ?? 'LTR') === 'LTR' ? 'mr-3' : 'ml-3'}`}>
|
||||
<img
|
||||
crossorigin="anonymous"
|
||||
src={src.startsWith(WEBUI_BASE_URL) ||
|
||||
src.startsWith('https://www.gravatar.com/avatar/') ||
|
||||
src.startsWith('data:') ||
|
||||
src.startsWith('/')
|
||||
? src
|
||||
: `/user.png`}
|
||||
class=" {className} object-cover rounded-full -translate-y-[1px]"
|
||||
alt="profile"
|
||||
draggable="false"
|
||||
/>
|
||||
<ProfileImageBase {src} {className} />
|
||||
</div>
|
||||
|
21
src/lib/components/chat/Messages/ProfileImageBase.svelte
Normal file
21
src/lib/components/chat/Messages/ProfileImageBase.svelte
Normal file
@ -0,0 +1,21 @@
|
||||
<script lang="ts">
|
||||
import { WEBUI_BASE_URL } from '$lib/constants';
|
||||
|
||||
export let className = 'size-8';
|
||||
export let src = `${WEBUI_BASE_URL}/static/favicon.png`;
|
||||
</script>
|
||||
|
||||
<img
|
||||
crossorigin="anonymous"
|
||||
src={src === ''
|
||||
? `${WEBUI_BASE_URL}/static/favicon.png`
|
||||
: src.startsWith(WEBUI_BASE_URL) ||
|
||||
src.startsWith('https://www.gravatar.com/avatar/') ||
|
||||
src.startsWith('data:') ||
|
||||
src.startsWith('/')
|
||||
? src
|
||||
: `/user.png`}
|
||||
class=" {className} object-cover rounded-full -translate-y-[1px]"
|
||||
alt="profile"
|
||||
draggable="false"
|
||||
/>
|
143
src/lib/components/chat/Overview.svelte
Normal file
143
src/lib/components/chat/Overview.svelte
Normal file
@ -0,0 +1,143 @@
|
||||
<script>
|
||||
import { getContext, createEventDispatcher } from 'svelte';
|
||||
import { useSvelteFlow, useNodesInitialized, useStore } from '@xyflow/svelte';
|
||||
import { SvelteFlow, Controls, Background, BackgroundVariant } from '@xyflow/svelte';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
import { onMount, tick } from 'svelte';
|
||||
|
||||
import { writable } from 'svelte/store';
|
||||
import { models, showOverview, theme, user } from '$lib/stores';
|
||||
|
||||
import '@xyflow/svelte/dist/style.css';
|
||||
|
||||
import CustomNode from './Overview/Node.svelte';
|
||||
import Flow from './Overview/Flow.svelte';
|
||||
import XMark from '../icons/XMark.svelte';
|
||||
|
||||
const { width, height } = useStore();
|
||||
|
||||
const { fitView, getViewport } = useSvelteFlow();
|
||||
const nodesInitialized = useNodesInitialized();
|
||||
|
||||
export let history;
|
||||
|
||||
const nodes = writable([]);
|
||||
const edges = writable([]);
|
||||
|
||||
const nodeTypes = {
|
||||
custom: CustomNode
|
||||
};
|
||||
|
||||
$: if (history) {
|
||||
drawFlow();
|
||||
}
|
||||
|
||||
const drawFlow = async () => {
|
||||
const nodeList = [];
|
||||
const edgeList = [];
|
||||
const levelOffset = 150; // Vertical spacing between layers
|
||||
const siblingOffset = 250; // Horizontal spacing between nodes at the same layer
|
||||
|
||||
// Map to keep track of node positions at each level
|
||||
let positionMap = new Map();
|
||||
|
||||
// Helper function to truncate labels
|
||||
function createLabel(content) {
|
||||
const maxLength = 100;
|
||||
return content.length > maxLength ? content.substr(0, maxLength) + '...' : content;
|
||||
}
|
||||
|
||||
// Create nodes and map children to ensure alignment in width
|
||||
let layerWidths = {}; // Track widths of each layer
|
||||
|
||||
Object.keys(history.messages).forEach((id) => {
|
||||
const message = history.messages[id];
|
||||
const level = message.parentId ? positionMap.get(message.parentId).level + 1 : 0;
|
||||
if (!layerWidths[level]) layerWidths[level] = 0;
|
||||
|
||||
positionMap.set(id, {
|
||||
id: message.id,
|
||||
level,
|
||||
position: layerWidths[level]++
|
||||
});
|
||||
});
|
||||
|
||||
// Adjust positions based on siblings count to centralize vertical spacing
|
||||
Object.keys(history.messages).forEach((id) => {
|
||||
const pos = positionMap.get(id);
|
||||
const xOffset = pos.position * siblingOffset;
|
||||
const y = pos.level * levelOffset;
|
||||
const x = xOffset;
|
||||
|
||||
nodeList.push({
|
||||
id: pos.id,
|
||||
type: 'custom',
|
||||
data: {
|
||||
user: $user,
|
||||
message: history.messages[id],
|
||||
model: $models.find((model) => model.id === history.messages[id].model),
|
||||
label: createLabel(history.messages[id].content)
|
||||
},
|
||||
position: { x, y }
|
||||
});
|
||||
|
||||
// Create edges
|
||||
const parentId = history.messages[id].parentId;
|
||||
if (parentId) {
|
||||
edgeList.push({
|
||||
id: parentId + '-' + pos.id,
|
||||
source: parentId,
|
||||
target: pos.id,
|
||||
type: 'smoothstep',
|
||||
animated: true
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
await edges.set([...edgeList]);
|
||||
await nodes.set([...nodeList]);
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
nodesInitialized.subscribe(async (initialized) => {
|
||||
if (initialized) {
|
||||
await tick();
|
||||
const res = await fitView();
|
||||
}
|
||||
});
|
||||
|
||||
width.subscribe((value) => {
|
||||
if (value) {
|
||||
fitView();
|
||||
}
|
||||
});
|
||||
|
||||
height.subscribe((value) => {
|
||||
if (value) {
|
||||
fitView();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="w-full h-full relative">
|
||||
<div class=" absolute z-50 w-full flex justify-between dark:text-gray-100 px-5 py-4">
|
||||
<div class=" text-lg font-medium self-center font-primary">{$i18n.t('Chat Overview')}</div>
|
||||
<button
|
||||
class="self-center"
|
||||
on:click={() => {
|
||||
dispatch('close');
|
||||
showOverview.set(false);
|
||||
}}
|
||||
>
|
||||
<XMark className="size-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if $nodes.length > 0}
|
||||
<Flow {nodes} {nodeTypes} {edges} />
|
||||
{/if}
|
||||
</div>
|
25
src/lib/components/chat/Overview/Flow.svelte
Normal file
25
src/lib/components/chat/Overview/Flow.svelte
Normal file
@ -0,0 +1,25 @@
|
||||
<script>
|
||||
import { theme } from '$lib/stores';
|
||||
import { Background, Controls, SvelteFlow, BackgroundVariant } from '@xyflow/svelte';
|
||||
|
||||
export let nodes;
|
||||
export let nodeTypes;
|
||||
export let edges;
|
||||
</script>
|
||||
|
||||
<SvelteFlow
|
||||
{nodes}
|
||||
{nodeTypes}
|
||||
{edges}
|
||||
fitView
|
||||
minZoom={0.001}
|
||||
colorMode={$theme.includes('dark') ? 'dark' : 'light'}
|
||||
nodesDraggable={false}
|
||||
on:nodeclick={(event) => console.log('on node click', event.detail.node)}
|
||||
oninit={() => {
|
||||
console.log('Flow initialized');
|
||||
}}
|
||||
>
|
||||
<Controls showLock={false} />
|
||||
<Background variant={BackgroundVariant.Dots} />
|
||||
</SvelteFlow>
|
40
src/lib/components/chat/Overview/Node.svelte
Normal file
40
src/lib/components/chat/Overview/Node.svelte
Normal file
@ -0,0 +1,40 @@
|
||||
<script lang="ts">
|
||||
import { WEBUI_BASE_URL } from '$lib/constants';
|
||||
import { Handle, Position, type NodeProps } from '@xyflow/svelte';
|
||||
|
||||
import ProfileImageBase from '../Messages/ProfileImageBase.svelte';
|
||||
|
||||
type $$Props = NodeProps;
|
||||
export let data: $$Props['data'];
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="px-4 py-3 shadow-md rounded-xl dark:bg-black bg-white border dark:border-gray-900 w-60 h-20"
|
||||
>
|
||||
{#if data.message.role === 'user'}
|
||||
<div class="flex w-full">
|
||||
<ProfileImageBase
|
||||
src={data.user?.profile_image_url ?? '/user.png'}
|
||||
className={'size-5 -translate-y-[1px]'}
|
||||
/>
|
||||
<div class="ml-2">
|
||||
<div class="text-xs font-medium">{data.user.name}</div>
|
||||
<div class="text-gray-500 line-clamp-2 text-xs mt-0.5">{data.message.content}</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex w-full">
|
||||
<ProfileImageBase
|
||||
src={data?.model?.info?.meta?.profile_image_url ?? ''}
|
||||
className={'size-5 -translate-y-[1px]'}
|
||||
/>
|
||||
|
||||
<div class="ml-2">
|
||||
<div class="text-xs font-medium">{data.model.name}</div>
|
||||
<div class="text-gray-500 line-clamp-2 text-xs mt-0.5">{data.message.content}</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<Handle type="target" position={Position.Top} class="w-2 rounded-full dark:bg-gray-900" />
|
||||
<Handle type="source" position={Position.Bottom} class="w-2 rounded-full dark:bg-gray-900" />
|
||||
</div>
|
@ -3,6 +3,8 @@
|
||||
import { flyAndScale } from '$lib/utils/transitions';
|
||||
import { fade, fly, slide } from 'svelte/transition';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let show = false;
|
||||
export let size = 'md';
|
||||
|
||||
@ -47,6 +49,10 @@
|
||||
document.body.style.overflow = 'unset';
|
||||
}
|
||||
|
||||
$: if (!show) {
|
||||
dispatch('close');
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
show = false;
|
||||
if (modalElement) {
|
||||
|
19
src/lib/components/icons/Clipboard.svelte
Normal file
19
src/lib/components/icons/Clipboard.svelte
Normal file
@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
export let className = 'size-4';
|
||||
export let strokeWidth = '2';
|
||||
</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.666 3.888A2.25 2.25 0 0 0 13.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 0 1-.75.75H9a.75.75 0 0 1-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 0 1-2.25 2.25H6.75A2.25 2.25 0 0 1 4.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 0 1 1.927-.184"
|
||||
/>
|
||||
</svg>
|
19
src/lib/components/icons/Map.svelte
Normal file
19
src/lib/components/icons/Map.svelte
Normal file
@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
export let className = 'size-4';
|
||||
export let strokeWidth = '2';
|
||||
</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 6.75V15m6-6v8.25m.503 3.498 4.875-2.437c.381-.19.622-.58.622-1.006V4.82c0-.836-.88-1.38-1.628-1.006l-3.869 1.934c-.317.159-.69.159-1.006 0L9.503 3.252a1.125 1.125 0 0 0-1.006 0L3.622 5.689C3.24 5.88 3 6.27 3 6.695V19.18c0 .836.88 1.38 1.628 1.006l3.869-1.934c.317-.159.69-.159 1.006 0l4.994 2.497c.317.158.69.158 1.006 0Z"
|
||||
/>
|
||||
</svg>
|
@ -8,7 +8,7 @@
|
||||
mobile,
|
||||
settings,
|
||||
showArchivedChats,
|
||||
showSettings,
|
||||
showControls,
|
||||
showSidebar,
|
||||
user
|
||||
} from '$lib/stores';
|
||||
@ -22,6 +22,7 @@
|
||||
import UserMenu from './Sidebar/UserMenu.svelte';
|
||||
import MenuLines from '../icons/MenuLines.svelte';
|
||||
import AdjustmentsHorizontal from '../icons/AdjustmentsHorizontal.svelte';
|
||||
import Map from '../icons/Map.svelte';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
@ -31,9 +32,7 @@
|
||||
|
||||
export let chat;
|
||||
export let selectedModels;
|
||||
|
||||
export let showModelSelector = true;
|
||||
export let showControls = false;
|
||||
|
||||
let showShareChatModal = false;
|
||||
let showDownloadChatModal = false;
|
||||
@ -110,7 +109,7 @@
|
||||
<button
|
||||
class=" flex cursor-pointer px-2 py-2 rounded-xl hover:bg-gray-50 dark:hover:bg-gray-850 transition"
|
||||
on:click={() => {
|
||||
showControls = !showControls;
|
||||
showControls.set(!$showControls);
|
||||
}}
|
||||
aria-label="Controls"
|
||||
>
|
||||
|
@ -8,7 +8,7 @@
|
||||
import { downloadChatAsPDF } from '$lib/apis/utils';
|
||||
import { copyToClipboard } from '$lib/utils';
|
||||
|
||||
import { showSettings } from '$lib/stores';
|
||||
import { showOverview, showControls } from '$lib/stores';
|
||||
import { flyAndScale } from '$lib/utils/transitions';
|
||||
|
||||
import Dropdown from '$lib/components/common/Dropdown.svelte';
|
||||
@ -128,8 +128,9 @@
|
||||
<DropdownMenu.Item
|
||||
class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
|
||||
id="chat-overview-button"
|
||||
on:click={() => {
|
||||
shareHandler();
|
||||
on:click={async () => {
|
||||
await showControls.set(true);
|
||||
await showOverview.set(true);
|
||||
}}
|
||||
>
|
||||
<Map className=" size-4" strokeWidth="1.5" />
|
||||
|
@ -40,6 +40,9 @@ export const showSidebar = writable(false);
|
||||
export const showSettings = writable(false);
|
||||
export const showArchivedChats = writable(false);
|
||||
export const showChangelog = writable(false);
|
||||
|
||||
export const showControls = writable(false);
|
||||
export const showOverview = writable(false);
|
||||
export const showCallOverlay = writable(false);
|
||||
|
||||
export const temporaryChatEnabled = writable(false);
|
||||
|
Loading…
Reference in New Issue
Block a user