File size: 4,071 Bytes
9e93b10
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
<script lang="ts">
	import { getContext } from "svelte";

	import { models } from "$lib/stores";
	import { REXPRO_API_BASE_URL } from "$lib/constants";

	import Dropdown from "$lib/components/common/Dropdown.svelte";
	import Search from "$lib/components/icons/Search.svelte";

	const i18n = getContext("i18n");

	export let model_id = "";

	export let side: "top" | "bottom" = "top";
	export let align: "start" | "end" = "start";

	/** Optional callback when selection changes */
	export let onChange: () => void = () => {};

	let showDropdown = false;
	let modelSearch = "";

	$: modelLabel = model_id
		? $models.find((m) => m.id === model_id)?.name || model_id
		: $i18n.t("Select model");

	$: filteredModels = (
		modelSearch
			? $models.filter(
					(m) =>
						m.name
							.toLowerCase()
							.includes(modelSearch.toLowerCase()) ||
						m.id.toLowerCase().includes(modelSearch.toLowerCase()),
				)
			: $models
	).filter((m) => !(m?.info?.meta?.hidden ?? false));
</script>

<Dropdown bind:show={showDropdown} {side} {align}>
	<button
		type="button"
		class="flex items-center gap-1.5 px-2.5 py-1.5 rounded-2xl text-xs transition
			text-gray-600 dark:text-gray-400 hover:bg-black/5 dark:hover:bg-white/5"
	>
		<svg
			xmlns="http://www.w3.org/2000/svg"
			fill="none"
			viewBox="0 0 24 24"
			stroke-width="1.5"
			stroke="currentColor"
			class="size-3.5 shrink-0"
		>
			<path
				stroke-linecap="round"
				stroke-linejoin="round"
				d="M9.813 15.904 9 18.75l-.813-2.846a4.5 4.5 0 0 0-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 0 0 3.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 0 0 3.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 0 0-3.09 3.09ZM18.259 8.715 18 9.75l-.259-1.035a3.375 3.375 0 0 0-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 0 0 2.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 0 0 2.455 2.456L21.75 6l-1.036.259a3.375 3.375 0 0 0-2.455 2.456ZM16.894 20.567 16.5 21.75l-.394-1.183a2.25 2.25 0 0 0-1.423-1.423L13.5 18.75l1.183-.394a2.25 2.25 0 0 0 1.423-1.423l.394-1.183.394 1.183a2.25 2.25 0 0 0 1.423 1.423l1.183.394-1.183.394a2.25 2.25 0 0 0-1.423 1.423Z"
			/>
		</svg>
		<span class="whitespace-nowrap max-w-32 truncate">{modelLabel}</span>
		<svg
			xmlns="http://www.w3.org/2000/svg"
			fill="none"
			viewBox="0 0 24 24"
			stroke-width="2"
			stroke="currentColor"
			class="size-2.5"
		>
			<path
				stroke-linecap="round"
				stroke-linejoin="round"
				d="m19.5 8.25-7.5 7.5-7.5-7.5"
			/>
		</svg>
	</button>

	<div
		slot="content"
		class="rounded-2xl shadow-lg border border-gray-200 dark:border-gray-800 flex flex-col bg-white dark:bg-gray-850 w-72 p-1"
	>
		<div class="flex items-center gap-2 px-2.5 py-1.5">
			<Search className="size-3.5" strokeWidth="2.5" />
			<input
				bind:value={modelSearch}
				class="w-full text-sm bg-transparent outline-hidden"
				placeholder={$i18n.t("Search a model")}
				autocomplete="off"
				on:click={(e) => e.stopPropagation()}
			/>
		</div>

		<div class="overflow-y-auto scrollbar-thin max-h-60">
			<div class="px-2 text-xs text-gray-500 py-1">
				{$i18n.t("Models")}
			</div>

			{#each filteredModels as model (model.id)}
				<button
					class="px-2.5 py-1.5 rounded-xl w-full text-left text-sm {model_id ===
					model.id
						? 'bg-gray-50 dark:bg-gray-800'
						: ''}"
					type="button"
					on:click={() => {
						model_id = model.id;
						showDropdown = false;
						modelSearch = "";
						onChange();
					}}
				>
					<div
						class="flex text-black dark:text-gray-100 line-clamp-1"
					>
						<img
							src={`${REXPRO_API_BASE_URL}/models/model/profile/image?id=${encodeURIComponent(model.id)}`}
							alt={model?.name ?? model.id}
							class="rounded-md size-5 items-center mr-2"
							loading="lazy"
							on:error={(e) => {
								e.currentTarget.src = "/favicon.png";
							}}
						/>

						<div class="truncate">
							{model.name}
						</div>
					</div>
				</button>
			{:else}
				<div
					class="block px-3 py-2 text-sm text-gray-700 dark:text-gray-100"
				>
					{$i18n.t("No results found")}
				</div>
			{/each}
		</div>
	</div>
</Dropdown>