Skip to content

Commit 79d3e34

Browse files
committed
refac
1 parent 6772b1c commit 79d3e34

4 files changed

Lines changed: 91 additions & 23 deletions

File tree

src/lib/components/chat/MessageInput.svelte

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@
8484
import GlobeAlt from '../icons/GlobeAlt.svelte';
8585
import Photo from '../icons/Photo.svelte';
8686
import Wrench from '../icons/Wrench.svelte';
87-
import Keyframes from '../icons/Keyframes.svelte';
87+
import Cube from '../icons/Cube.svelte';
8888
import Sparkles from '../icons/Sparkles.svelte';
8989
9090
import InputVariablesModal from './MessageInput/InputVariablesModal.svelte';
@@ -1233,7 +1233,7 @@
12331233
/>
12341234

12351235
{#if loaded}
1236-
<div class="w-full ">
1236+
<div class="w-full">
12371237
<div class=" mx-auto inset-x-0 bg-transparent flex justify-center">
12381238
<div
12391239
class="flex flex-col px-3 {($settings?.widescreenMode ?? null)
@@ -1852,7 +1852,7 @@
18521852
showSkills = !showSkills;
18531853
}}
18541854
>
1855-
<Keyframes className="size-4" strokeWidth="1.75" />
1855+
<Cube className="size-4" strokeWidth="1.75" />
18561856

18571857
<span class="text-sm">
18581858
{(selectedSkillIds ?? []).length}

src/lib/components/chat/MessageInput/Commands/Skills.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import { getContext, onDestroy } from 'svelte';
33
import { getSkillItems } from '$lib/apis/skills';
44
import Tooltip from '$lib/components/common/Tooltip.svelte';
5-
import Keyframes from '$lib/components/icons/Keyframes.svelte';
5+
import Cube from '$lib/components/icons/Cube.svelte';
66
77
const i18n = getContext('i18n');
88
@@ -96,7 +96,7 @@
9696
>
9797
<div class="flex w-full min-w-0 items-center text-black dark:text-gray-100">
9898
<div class="flex items-center justify-center size-5 mr-2 shrink-0">
99-
<Keyframes className="size-4" />
99+
<Cube className="size-4" />
100100
</div>
101101
<div class="truncate min-w-0 flex-1">
102102
{skill.name}

src/lib/components/chat/MessageInput/IntegrationsMenu.svelte

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
import Switch from '$lib/components/common/Switch.svelte';
2828
import Spinner from '$lib/components/common/Spinner.svelte';
2929
import Wrench from '$lib/components/icons/Wrench.svelte';
30-
import Keyframes from '$lib/components/icons/Keyframes.svelte';
30+
import Cube from '$lib/components/icons/Cube.svelte';
3131
import Sparkles from '$lib/components/icons/Sparkles.svelte';
3232
import GlobeAlt from '$lib/components/icons/GlobeAlt.svelte';
3333
import Photo from '$lib/components/icons/Photo.svelte';
@@ -176,7 +176,7 @@
176176
tab = 'skills';
177177
}}
178178
>
179-
<Keyframes className="size-3.5" strokeWidth="1.75" />
179+
<Cube className="size-3.5" strokeWidth="1.75" />
180180

181181
<div class="flex items-center w-full justify-between">
182182
<div class=" line-clamp-1">
@@ -534,7 +534,7 @@
534534
<div class="flex flex-1 gap-2 items-center">
535535
<Tooltip content={skills[skillId]?.name ?? ''} placement="top">
536536
<div class="shrink-0">
537-
<Keyframes className="size-3.5" strokeWidth="1.75" />
537+
<Cube className="size-3.5" strokeWidth="1.75" />
538538
</div>
539539
</Tooltip>
540540
<Tooltip content={skills[skillId]?.description ?? ''} placement="top-start">

src/lib/components/chat/ModelSelector/Selector.svelte

Lines changed: 83 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,10 @@
7474
let show = false;
7575
let triggerElement: HTMLElement | null = null;
7676
let contentElement: HTMLElement | null = null;
77-
let dropdownPosition = { top: 0, left: 0 };
77+
let dropdownPosition = { top: 0, left: 0, maxHeight: undefined as number | undefined };
78+
let isSmallViewport = false;
79+
let positionFrame: number | undefined;
80+
let settleTimers: number[] = [];
7881
7982
const portal = (node: HTMLElement) => {
8083
document.body.appendChild(node);
@@ -85,31 +88,79 @@
8588
};
8689
};
8790
91+
const visualViewportRect = () => {
92+
const viewport = window.visualViewport;
93+
return {
94+
left: viewport?.offsetLeft ?? 0,
95+
top: viewport?.offsetTop ?? 0,
96+
width: viewport?.width ?? window.innerWidth,
97+
height: viewport?.height ?? window.innerHeight
98+
};
99+
};
100+
101+
const updateViewportSize = () => {
102+
isSmallViewport = (window.visualViewport?.width ?? window.innerWidth) < 640;
103+
};
104+
88105
const updatePosition = () => {
89106
if (!show || !triggerElement) return;
90107
const rect = triggerElement.getBoundingClientRect();
91108
const contentRect = contentElement?.getBoundingClientRect();
92109
const contentWidth = contentRect?.width ?? 0;
93110
const contentHeight = contentRect?.height ?? 0;
94-
const spaceBelow = window.innerHeight - rect.bottom - 8;
95-
const spaceAbove = rect.top - 8;
111+
const viewport = visualViewportRect();
112+
const viewportRight = viewport.left + viewport.width;
113+
const viewportBottom = viewport.top + viewport.height;
114+
const pad = 8;
115+
const gap = 2;
116+
const spaceBelow = viewportBottom - rect.bottom - gap - pad;
117+
const spaceAbove = rect.top - viewport.top - gap - pad;
96118
const preferredLeft = align === 'end' && contentWidth ? rect.right - contentWidth : rect.left;
97-
const maxLeft = contentWidth ? window.innerWidth - contentWidth - 8 : preferredLeft;
119+
const maxLeft = contentWidth ? viewportRight - contentWidth - pad : preferredLeft;
98120
const resolvedPlacement =
99121
placement === 'auto'
100122
? contentHeight && spaceBelow < contentHeight && spaceAbove > spaceBelow
101123
? 'top'
102124
: 'bottom'
103125
: placement;
126+
const availableHeight = resolvedPlacement === 'top' ? spaceAbove : spaceBelow;
127+
const constrainedHeight =
128+
contentHeight && availableHeight >= 0
129+
? Math.min(contentHeight, availableHeight)
130+
: contentHeight;
131+
const top =
132+
resolvedPlacement === 'top' && contentHeight
133+
? rect.top - constrainedHeight - gap
134+
: rect.bottom + gap;
135+
104136
dropdownPosition = {
105-
top:
106-
resolvedPlacement === 'top' && contentHeight
107-
? rect.top - contentHeight - 2
108-
: rect.bottom + 2,
109-
left: Math.max(8, Math.min(preferredLeft, maxLeft))
137+
top: Math.max(viewport.top + pad, Math.min(top, viewportBottom - pad - constrainedHeight)),
138+
left: Math.max(viewport.left + pad, Math.min(preferredLeft, maxLeft)),
139+
maxHeight:
140+
contentHeight && availableHeight >= 0 && contentHeight > availableHeight
141+
? Math.max(0, availableHeight)
142+
: undefined
110143
};
111144
};
112145
146+
const schedulePositionUpdate = () => {
147+
if (positionFrame != null) cancelAnimationFrame(positionFrame);
148+
positionFrame = requestAnimationFrame(() => {
149+
positionFrame = undefined;
150+
updatePosition();
151+
});
152+
};
153+
154+
const scheduleSettledPositionUpdates = () => {
155+
updateViewportSize();
156+
for (const timer of settleTimers) window.clearTimeout(timer);
157+
settleTimers = [];
158+
schedulePositionUpdate();
159+
for (const delay of [50, 150, 300]) {
160+
settleTimers.push(window.setTimeout(schedulePositionUpdate, delay));
161+
}
162+
};
163+
113164
const toggleOpen = async () => {
114165
show = !show;
115166
if (show) {
@@ -495,7 +546,7 @@
495546
ollamaVersion = await getOllamaVersion(localStorage.token).catch((error) => false);
496547
};
497548
498-
onMount(async () => {
549+
onMount(() => {
499550
if (items) {
500551
tags = items
501552
.filter((item) => includeHidden || !(item.model?.info?.meta?.hidden ?? false))
@@ -504,6 +555,19 @@
504555
// Remove duplicates and sort
505556
tags = Array.from(new Set(tags)).sort((a, b) => a.localeCompare(b));
506557
}
558+
559+
updateViewportSize();
560+
window.addEventListener('scroll', schedulePositionUpdate, true);
561+
window.visualViewport?.addEventListener('resize', scheduleSettledPositionUpdates);
562+
window.visualViewport?.addEventListener('scroll', schedulePositionUpdate);
563+
564+
return () => {
565+
if (positionFrame != null) cancelAnimationFrame(positionFrame);
566+
for (const timer of settleTimers) window.clearTimeout(timer);
567+
window.removeEventListener('scroll', schedulePositionUpdate, true);
568+
window.visualViewport?.removeEventListener('resize', scheduleSettledPositionUpdates);
569+
window.visualViewport?.removeEventListener('scroll', schedulePositionUpdate);
570+
};
507571
});
508572
509573
$: if (show && !selectionOnly) {
@@ -585,11 +649,12 @@
585649
586650
let listScrollTop = 0;
587651
let listContainer;
652+
$: listViewportHeight = isSmallViewport ? 120 : 288;
588653
589654
$: visibleStart = Math.max(0, Math.floor(listScrollTop / ITEM_HEIGHT) - OVERSCAN);
590655
$: visibleEnd = Math.min(
591656
filteredItems.length,
592-
Math.ceil((listScrollTop + 288) / ITEM_HEIGHT) + OVERSCAN
657+
Math.ceil((listScrollTop + listViewportHeight) / ITEM_HEIGHT) + OVERSCAN
593658
);
594659
</script>
595660

@@ -607,7 +672,7 @@
607672
<svelte:window
608673
on:pointerdown={handlePointerDown}
609674
on:keydown={handleKeydown}
610-
on:resize={updatePosition}
675+
on:resize={scheduleSettledPositionUpdates}
611676
/>
612677

613678
<div class="relative w-full">
@@ -649,7 +714,9 @@
649714
<div
650715
use:portal
651716
bind:this={contentElement}
652-
style="position: fixed; z-index: 9999; top: {dropdownPosition.top}px; left: {dropdownPosition.left}px;"
717+
style="position: fixed; z-index: 9999; top: {dropdownPosition.top}px; left: {dropdownPosition.left}px; {dropdownPosition.maxHeight
718+
? `max-height: ${dropdownPosition.maxHeight}px;`
719+
: ''}"
653720
>
654721
<div
655722
class="z-40 {className} max-w-[calc(100vw-1rem)] justify-start rounded-xl border border-gray-100 bg-white p-0.5 shadow-lg outline-hidden dark:border-gray-800 dark:bg-gray-850 dark:text-white"
@@ -698,7 +765,7 @@
698765
<button
699766
type="button"
700767
class="flex size-[1.375rem] shrink-0 items-center justify-center rounded-lg transition-colors duration-100 {compareEnabled
701-
? 'bg-gray-100/60 text-gray-700 hover:bg-gray-100/60 dark:bg-gray-800/40 dark:text-gray-300 dark:hover:bg-gray-800/40'
768+
? 'bg-gray-50 text-gray-700 hover:bg-gray-50 dark:bg-gray-800/60 dark:text-gray-200 dark:hover:bg-gray-800/60'
702769
: 'text-gray-500 hover:bg-gray-50/40 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-800/40 dark:hover:text-gray-200'}"
703770
aria-label={$i18n.t('Compare')}
704771
aria-pressed={compareEnabled}
@@ -758,7 +825,8 @@
758825
{:else}
759826
<!-- svelte-ignore a11y-no-static-element-interactions -->
760827
<div
761-
class="max-h-72 overflow-y-auto"
828+
class="overflow-y-auto"
829+
style="max-height: {listViewportHeight}px;"
762830
role="listbox"
763831
aria-label={$i18n.t('Available models')}
764832
bind:this={listContainer}

0 commit comments

Comments
 (0)