UserMenu.svelte 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. <script lang="ts">
  2. import { DropdownMenu } from 'bits-ui';
  3. import { createEventDispatcher, getContext, onMount } from 'svelte';
  4. import { flyAndScale } from '$lib/utils/transitions';
  5. import { goto } from '$app/navigation';
  6. import { fade, slide } from 'svelte/transition';
  7. import { getUsage } from '$lib/apis';
  8. import { userSignOut } from '$lib/apis/auths';
  9. import { showSettings, mobile, showSidebar, showShortcuts, user } from '$lib/stores';
  10. import Tooltip from '$lib/components/common/Tooltip.svelte';
  11. import ArchiveBox from '$lib/components/icons/ArchiveBox.svelte';
  12. import QuestionMarkCircle from '$lib/components/icons/QuestionMarkCircle.svelte';
  13. import Map from '$lib/components/icons/Map.svelte';
  14. import Keyboard from '$lib/components/icons/Keyboard.svelte';
  15. import ShortcutsModal from '$lib/components/chat/ShortcutsModal.svelte';
  16. import Settings from '$lib/components/icons/Settings.svelte';
  17. import Code from '$lib/components/icons/Code.svelte';
  18. import UserGroup from '$lib/components/icons/UserGroup.svelte';
  19. import SignOut from '$lib/components/icons/SignOut.svelte';
  20. const i18n = getContext('i18n');
  21. export let show = false;
  22. export let role = '';
  23. export let help = false;
  24. export let className = 'max-w-[240px]';
  25. const dispatch = createEventDispatcher();
  26. let usage = null;
  27. const getUsageInfo = async () => {
  28. const res = await getUsage(localStorage.token).catch((error) => {
  29. console.error('Error fetching usage info:', error);
  30. });
  31. if (res) {
  32. usage = res;
  33. } else {
  34. usage = null;
  35. }
  36. };
  37. $: if (show) {
  38. getUsageInfo();
  39. }
  40. </script>
  41. <ShortcutsModal bind:show={$showShortcuts} />
  42. <!-- svelte-ignore a11y-no-static-element-interactions -->
  43. <DropdownMenu.Root
  44. bind:open={show}
  45. onOpenChange={(state) => {
  46. dispatch('change', state);
  47. }}
  48. >
  49. <DropdownMenu.Trigger>
  50. <slot />
  51. </DropdownMenu.Trigger>
  52. <slot name="content">
  53. <DropdownMenu.Content
  54. class="w-full {className} text-sm rounded-xl px-1 py-1.5 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-lg font-primary"
  55. sideOffset={4}
  56. side="bottom"
  57. align="start"
  58. transition={(e) => fade(e, { duration: 100 })}
  59. >
  60. <DropdownMenu.Item
  61. class="flex rounded-md py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer"
  62. on:click={async () => {
  63. await showSettings.set(true);
  64. show = false;
  65. if ($mobile) {
  66. showSidebar.set(false);
  67. }
  68. }}
  69. >
  70. <div class=" self-center mr-3">
  71. <Settings className="w-5 h-5" strokeWidth="1.5" />
  72. </div>
  73. <div class=" self-center truncate">{$i18n.t('Settings')}</div>
  74. </DropdownMenu.Item>
  75. <DropdownMenu.Item
  76. class="flex rounded-md py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer"
  77. on:click={() => {
  78. dispatch('show', 'archived-chat');
  79. show = false;
  80. if ($mobile) {
  81. showSidebar.set(false);
  82. }
  83. }}
  84. >
  85. <div class=" self-center mr-3">
  86. <ArchiveBox className="size-5" strokeWidth="1.5" />
  87. </div>
  88. <div class=" self-center truncate">{$i18n.t('Archived Chats')}</div>
  89. </DropdownMenu.Item>
  90. {#if role === 'admin'}
  91. <DropdownMenu.Item
  92. as="a"
  93. href="/playground"
  94. class="flex rounded-md py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition select-none"
  95. on:click={() => {
  96. show = false;
  97. if ($mobile) {
  98. showSidebar.set(false);
  99. }
  100. }}
  101. >
  102. <div class=" self-center mr-3">
  103. <Code className="size-5" strokeWidth="1.5" />
  104. </div>
  105. <div class=" self-center truncate">{$i18n.t('Playground')}</div>
  106. </DropdownMenu.Item>
  107. <DropdownMenu.Item
  108. as="a"
  109. href="/admin"
  110. class="flex rounded-md py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition select-none"
  111. on:click={() => {
  112. show = false;
  113. if ($mobile) {
  114. showSidebar.set(false);
  115. }
  116. }}
  117. >
  118. <div class=" self-center mr-3">
  119. <UserGroup className="w-5 h-5" strokeWidth="1.5" />
  120. </div>
  121. <div class=" self-center truncate">{$i18n.t('Admin Panel')}</div>
  122. </DropdownMenu.Item>
  123. {/if}
  124. {#if help}
  125. <hr class=" border-gray-100 dark:border-gray-800 my-1 p-0" />
  126. <!-- {$i18n.t('Help')} -->
  127. <DropdownMenu.Item
  128. as="a"
  129. class="flex gap-2 items-center py-1.5 px-3 text-sm select-none w-full cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md transition"
  130. id="chat-share-button"
  131. on:click={() => {
  132. show = false;
  133. }}
  134. href="https://docs.openwebui.com"
  135. >
  136. <QuestionMarkCircle className="size-5" />
  137. <div class="flex items-center">{$i18n.t('Documentation')}</div>
  138. </DropdownMenu.Item>
  139. <!-- Releases -->
  140. <DropdownMenu.Item
  141. as="a"
  142. class="flex gap-2 items-center py-1.5 px-3 text-sm select-none w-full cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md transition"
  143. id="chat-share-button"
  144. on:click={() => {
  145. show = false;
  146. }}
  147. href="https://github.com/open-webui/"
  148. >
  149. <Map className="size-5" />
  150. <div class="flex items-center">{$i18n.t('Releases')}</div>
  151. </DropdownMenu.Item>
  152. <DropdownMenu.Item
  153. class="flex gap-2 items-center py-1.5 px-3 text-sm select-none w-full cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md transition cursor-pointer"
  154. id="chat-share-button"
  155. on:click={() => {
  156. showShortcuts.set(!$showShortcuts);
  157. show = false;
  158. }}
  159. >
  160. <Keyboard className="size-5" />
  161. <div class="flex items-center">{$i18n.t('Keyboard shortcuts')}</div>
  162. </DropdownMenu.Item>
  163. {/if}
  164. <hr class=" border-gray-100 dark:border-gray-800 my-1 p-0" />
  165. <DropdownMenu.Item
  166. class="flex rounded-md py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition"
  167. on:click={async () => {
  168. const res = await userSignOut();
  169. user.set(null);
  170. localStorage.removeItem('token');
  171. location.href = res?.redirect_url ?? '/auth';
  172. show = false;
  173. }}
  174. >
  175. <div class=" self-center mr-3">
  176. <SignOut className="w-5 h-5" strokeWidth="1.5" />
  177. </div>
  178. <div class=" self-center truncate">{$i18n.t('Sign Out')}</div>
  179. </DropdownMenu.Item>
  180. {#if usage}
  181. {#if usage?.user_ids?.length > 0}
  182. <hr class=" border-gray-100 dark:border-gray-800 my-1 p-0" />
  183. <Tooltip
  184. content={usage?.model_ids && usage?.model_ids.length > 0
  185. ? `${$i18n.t('Running')}: ${usage.model_ids.join(', ')} ✨`
  186. : ''}
  187. >
  188. <div
  189. class="flex rounded-md py-1 px-3 text-xs gap-2.5 items-center"
  190. on:mouseenter={() => {
  191. getUsageInfo();
  192. }}
  193. >
  194. <div class=" flex items-center">
  195. <span class="relative flex size-2">
  196. <span
  197. class="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"
  198. />
  199. <span class="relative inline-flex rounded-full size-2 bg-green-500" />
  200. </span>
  201. </div>
  202. <div class=" ">
  203. <span class="">
  204. {$i18n.t('Active Users')}:
  205. </span>
  206. <span class=" font-semibold">
  207. {usage?.user_ids?.length}
  208. </span>
  209. </div>
  210. </div>
  211. </Tooltip>
  212. {/if}
  213. {/if}
  214. <!-- <DropdownMenu.Item class="flex items-center py-1.5 px-3 text-sm ">
  215. <div class="flex items-center">Profile</div>
  216. </DropdownMenu.Item> -->
  217. </DropdownMenu.Content>
  218. </slot>
  219. </DropdownMenu.Root>