瀏覽代碼

Merge branch 'dev' into fix/share-chat-reactive-loop

Timothy Jaeryang Baek 11 月之前
父節點
當前提交
85dbdd23fc
共有 75 個文件被更改,包括 4069 次插入4209 次删除
  1. 28 28
      Dockerfile
  2. 5 0
      backend/main.py
  3. 2 6
      cypress/e2e/settings.cy.ts
  4. 13 4
      src/app.css
  5. 1 1
      src/lib/components/ChangelogModal.svelte
  6. 11 11
      src/lib/components/chat/MessageInput.svelte
  7. 1 1
      src/lib/components/chat/Messages/CitationsModal.svelte
  8. 1 1
      src/lib/components/chat/Messages/Name.svelte
  9. 2 2
      src/lib/components/chat/Messages/ProfileImage.svelte
  10. 38 28
      src/lib/components/chat/Messages/ResponseMessage.svelte
  11. 77 95
      src/lib/components/chat/Messages/UserMessage.svelte
  12. 3 3
      src/lib/components/chat/ModelSelector.svelte
  13. 6 6
      src/lib/components/chat/ModelSelector/Selector.svelte
  14. 19 0
      src/lib/components/icons/MenuLines.svelte
  15. 14 49
      src/lib/components/layout/Navbar.svelte
  16. 61 88
      src/lib/components/layout/Navbar/Menu.svelte
  17. 120 167
      src/lib/components/layout/Sidebar.svelte
  18. 13 1
      src/lib/components/layout/Sidebar/ChatMenu.svelte
  19. 45 70
      src/lib/components/layout/Sidebar/UserMenu.svelte
  20. 612 0
      src/lib/components/workspace/Documents.svelte
  21. 409 0
      src/lib/components/workspace/Modelfiles.svelte
  22. 114 116
      src/lib/components/workspace/Playground.svelte
  23. 331 0
      src/lib/components/workspace/Prompts.svelte
  24. 3 6
      src/lib/i18n/locales/ar-BH/translation.json
  25. 3 6
      src/lib/i18n/locales/bg-BG/translation.json
  26. 3 6
      src/lib/i18n/locales/bn-BD/translation.json
  27. 3 6
      src/lib/i18n/locales/ca-ES/translation.json
  28. 3 6
      src/lib/i18n/locales/de-DE/translation.json
  29. 3 6
      src/lib/i18n/locales/dg-DG/translation.json
  30. 3 6
      src/lib/i18n/locales/en-GB/translation.json
  31. 3 6
      src/lib/i18n/locales/en-US/translation.json
  32. 3 6
      src/lib/i18n/locales/es-ES/translation.json
  33. 3 6
      src/lib/i18n/locales/fa-IR/translation.json
  34. 3 6
      src/lib/i18n/locales/fi-FI/translation.json
  35. 3 6
      src/lib/i18n/locales/fr-CA/translation.json
  36. 3 6
      src/lib/i18n/locales/fr-FR/translation.json
  37. 3 6
      src/lib/i18n/locales/he-IL/translation.json
  38. 3 6
      src/lib/i18n/locales/hi-IN/translation.json
  39. 3 6
      src/lib/i18n/locales/it-IT/translation.json
  40. 3 6
      src/lib/i18n/locales/ja-JP/translation.json
  41. 3 6
      src/lib/i18n/locales/ka-GE/translation.json
  42. 3 6
      src/lib/i18n/locales/ko-KR/translation.json
  43. 3 6
      src/lib/i18n/locales/nl-NL/translation.json
  44. 3 6
      src/lib/i18n/locales/pl-PL/translation.json
  45. 3 6
      src/lib/i18n/locales/pt-BR/translation.json
  46. 3 6
      src/lib/i18n/locales/pt-PT/translation.json
  47. 3 6
      src/lib/i18n/locales/ru-RU/translation.json
  48. 3 6
      src/lib/i18n/locales/sv-SE/translation.json
  49. 3 6
      src/lib/i18n/locales/tr-TR/translation.json
  50. 3 6
      src/lib/i18n/locales/uk-UA/translation.json
  51. 3 6
      src/lib/i18n/locales/vi-VN/translation.json
  52. 3 6
      src/lib/i18n/locales/zh-CN/translation.json
  53. 3 6
      src/lib/i18n/locales/zh-TW/translation.json
  54. 1 0
      src/lib/stores/index.ts
  55. 1 1
      src/routes/(app)/+page.svelte
  56. 242 232
      src/routes/(app)/admin/+page.svelte
  57. 1 1
      src/routes/(app)/c/[id]/+page.svelte
  58. 0 617
      src/routes/(app)/documents/+page.svelte
  59. 0 425
      src/routes/(app)/modelfiles/+page.svelte
  60. 0 727
      src/routes/(app)/modelfiles/create/+page.svelte
  61. 0 515
      src/routes/(app)/modelfiles/edit/+page.svelte
  62. 0 342
      src/routes/(app)/prompts/+page.svelte
  63. 0 254
      src/routes/(app)/prompts/create/+page.svelte
  64. 0 237
      src/routes/(app)/prompts/edit/+page.svelte
  65. 78 0
      src/routes/(app)/workspace/+layout.svelte
  66. 8 0
      src/routes/(app)/workspace/+page.svelte
  67. 5 0
      src/routes/(app)/workspace/documents/+page.svelte
  68. 5 0
      src/routes/(app)/workspace/modelfiles/+page.svelte
  69. 721 0
      src/routes/(app)/workspace/modelfiles/create/+page.svelte
  70. 507 0
      src/routes/(app)/workspace/modelfiles/edit/+page.svelte
  71. 5 0
      src/routes/(app)/workspace/playground/+page.svelte
  72. 5 0
      src/routes/(app)/workspace/prompts/+page.svelte
  73. 245 0
      src/routes/(app)/workspace/prompts/create/+page.svelte
  74. 228 0
      src/routes/(app)/workspace/prompts/edit/+page.svelte
  75. 1 1
      src/routes/+layout.svelte

+ 28 - 28
Dockerfile

@@ -80,25 +80,25 @@ RUN mkdir -p $HOME/.cache/chroma
 RUN echo -n 00000000-0000-0000-0000-000000000000 > $HOME/.cache/chroma/telemetry_user_id
 RUN echo -n 00000000-0000-0000-0000-000000000000 > $HOME/.cache/chroma/telemetry_user_id
 
 
 RUN if [ "$USE_OLLAMA" = "true" ]; then \
 RUN if [ "$USE_OLLAMA" = "true" ]; then \
-        apt-get update && \
-        # Install pandoc and netcat
-        apt-get install -y --no-install-recommends pandoc netcat-openbsd curl && \
-        # for RAG OCR
-        apt-get install -y --no-install-recommends ffmpeg libsm6 libxext6 && \
-        # install helper tools
-        apt-get install -y --no-install-recommends curl && \
-        # install ollama
-        curl -fsSL https://ollama.com/install.sh | sh && \
-        # cleanup
-        rm -rf /var/lib/apt/lists/*; \
+    apt-get update && \
+    # Install pandoc and netcat
+    apt-get install -y --no-install-recommends pandoc netcat-openbsd curl && \
+    # for RAG OCR
+    apt-get install -y --no-install-recommends ffmpeg libsm6 libxext6 && \
+    # install helper tools
+    apt-get install -y --no-install-recommends curl && \
+    # install ollama
+    curl -fsSL https://ollama.com/install.sh | sh && \
+    # cleanup
+    rm -rf /var/lib/apt/lists/*; \
     else \
     else \
-        apt-get update && \
-        # Install pandoc and netcat
-        apt-get install -y --no-install-recommends pandoc netcat-openbsd curl && \
-        # for RAG OCR
-        apt-get install -y --no-install-recommends ffmpeg libsm6 libxext6 && \
-        # cleanup
-        rm -rf /var/lib/apt/lists/*; \
+    apt-get update && \
+    # Install pandoc and netcat
+    apt-get install -y --no-install-recommends pandoc netcat-openbsd curl && \
+    # for RAG OCR
+    apt-get install -y --no-install-recommends ffmpeg libsm6 libxext6 && \
+    # cleanup
+    rm -rf /var/lib/apt/lists/*; \
     fi
     fi
 
 
 # install python dependencies
 # install python dependencies
@@ -106,16 +106,16 @@ COPY ./backend/requirements.txt ./requirements.txt
 
 
 RUN pip3 install uv && \
 RUN pip3 install uv && \
     if [ "$USE_CUDA" = "true" ]; then \
     if [ "$USE_CUDA" = "true" ]; then \
-        # If you use CUDA the whisper and embedding model will be downloaded on first use
-        pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/$USE_CUDA_DOCKER_VER --no-cache-dir && \
-        uv pip install --system -r requirements.txt --no-cache-dir && \
-        python -c "import os; from sentence_transformers import SentenceTransformer; SentenceTransformer(os.environ['RAG_EMBEDDING_MODEL'], device='cpu')" && \
-        python -c "import os; from faster_whisper import WhisperModel; WhisperModel(os.environ['WHISPER_MODEL'], device='cpu', compute_type='int8', download_root=os.environ['WHISPER_MODEL_DIR'])"; \
+    # If you use CUDA the whisper and embedding model will be downloaded on first use
+    pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/$USE_CUDA_DOCKER_VER --no-cache-dir && \
+    uv pip install --system -r requirements.txt --no-cache-dir && \
+    python -c "import os; from sentence_transformers import SentenceTransformer; SentenceTransformer(os.environ['RAG_EMBEDDING_MODEL'], device='cpu')" && \
+    python -c "import os; from faster_whisper import WhisperModel; WhisperModel(os.environ['WHISPER_MODEL'], device='cpu', compute_type='int8', download_root=os.environ['WHISPER_MODEL_DIR'])"; \
     else \
     else \
-        pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu --no-cache-dir && \
-        uv pip install --system -r requirements.txt --no-cache-dir && \
-        python -c "import os; from sentence_transformers import SentenceTransformer; SentenceTransformer(os.environ['RAG_EMBEDDING_MODEL'], device='cpu')" && \
-        python -c "import os; from faster_whisper import WhisperModel; WhisperModel(os.environ['WHISPER_MODEL'], device='cpu', compute_type='int8', download_root=os.environ['WHISPER_MODEL_DIR'])"; \
+    pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu --no-cache-dir && \
+    uv pip install --system -r requirements.txt --no-cache-dir && \
+    python -c "import os; from sentence_transformers import SentenceTransformer; SentenceTransformer(os.environ['RAG_EMBEDDING_MODEL'], device='cpu')" && \
+    python -c "import os; from faster_whisper import WhisperModel; WhisperModel(os.environ['WHISPER_MODEL'], device='cpu', compute_type='int8', download_root=os.environ['WHISPER_MODEL_DIR'])"; \
     fi
     fi
 
 
 
 
@@ -134,6 +134,6 @@ COPY ./backend .
 
 
 EXPOSE 8080
 EXPOSE 8080
 
 
-HEALTHCHECK CMD curl --fail http://localhost:8080 || exit 1 
+HEALTHCHECK CMD curl --fail http://localhost:8080/health || exit 1 
 
 
 CMD [ "bash", "start.sh"]
 CMD [ "bash", "start.sh"]

+ 5 - 0
backend/main.py

@@ -377,6 +377,11 @@ async def get_opensearch_xml():
     return Response(content=xml_content, media_type="application/xml")
     return Response(content=xml_content, media_type="application/xml")
 
 
 
 
+@app.get("/health")
+async def healthcheck():
+    return {"status": True}
+
+
 app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
 app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
 app.mount("/cache", StaticFiles(directory=CACHE_DIR), name="cache")
 app.mount("/cache", StaticFiles(directory=CACHE_DIR), name="cache")
 
 

+ 2 - 6
cypress/e2e/settings.cy.ts

@@ -15,12 +15,8 @@ describe('Settings', () => {
 		cy.loginAdmin();
 		cy.loginAdmin();
 		// Visit the home page
 		// Visit the home page
 		cy.visit('/');
 		cy.visit('/');
-		// Open the sidebar if it is not already open
-		cy.get('[aria-label="Open sidebar"]').then(() => {
-			cy.get('button[id="sidebar-toggle-button"]').click();
-		});
-		// Click on the profile link
-		cy.get('button').contains(adminUser.name).click();
+		// Click on the user menu
+		cy.get('button[aria-label="User Menu"]').click();
 		// Click on the settings link
 		// Click on the settings link
 		cy.get('button').contains('Settings').click();
 		cy.get('button').contains('Settings').click();
 	});
 	});

+ 13 - 4
src/app.css

@@ -83,11 +83,20 @@ select {
 	display: none;
 	display: none;
 }
 }
 
 
-.scrollbar-none:active::-webkit-scrollbar-thumb,
-.scrollbar-none:focus::-webkit-scrollbar-thumb,
-.scrollbar-none:hover::-webkit-scrollbar-thumb {
+.scrollbar-hidden:active::-webkit-scrollbar-thumb,
+.scrollbar-hidden:focus::-webkit-scrollbar-thumb,
+.scrollbar-hidden:hover::-webkit-scrollbar-thumb {
 	visibility: visible;
 	visibility: visible;
 }
 }
-.scrollbar-none::-webkit-scrollbar-thumb {
+.scrollbar-hidden::-webkit-scrollbar-thumb {
 	visibility: hidden;
 	visibility: hidden;
 }
 }
+
+.scrollbar-none::-webkit-scrollbar {
+	display: none; /* for Chrome, Safari and Opera */
+}
+
+.scrollbar-none {
+	-ms-overflow-style: none; /* IE and Edge */
+	scrollbar-width: none; /* Firefox */
+}

+ 1 - 1
src/lib/components/ChangelogModal.svelte

@@ -58,7 +58,7 @@
 	</div>
 	</div>
 
 
 	<div class=" w-full p-4 px-5 text-gray-700 dark:text-gray-100">
 	<div class=" w-full p-4 px-5 text-gray-700 dark:text-gray-100">
-		<div class=" overflow-y-scroll max-h-80 scrollbar-none">
+		<div class=" overflow-y-scroll max-h-80 scrollbar-hidden">
 			<div class="mb-3">
 			<div class="mb-3">
 				{#if changelog}
 				{#if changelog}
 					{#each Object.keys(changelog) as version}
 					{#each Object.keys(changelog) as version}

+ 11 - 11
src/lib/components/chat/MessageInput.svelte

@@ -1,7 +1,7 @@
 <script lang="ts">
 <script lang="ts">
 	import { toast } from 'svelte-sonner';
 	import { toast } from 'svelte-sonner';
 	import { onMount, tick, getContext } from 'svelte';
 	import { onMount, tick, getContext } from 'svelte';
-	import { modelfiles, settings, showSidebar } from '$lib/stores';
+	import { mobile, modelfiles, settings, showSidebar } from '$lib/stores';
 	import { blobToFile, calculateSHA256, findWordIndices } from '$lib/utils';
 	import { blobToFile, calculateSHA256, findWordIndices } from '$lib/utils';
 
 
 	import {
 	import {
@@ -412,7 +412,7 @@
 {#if dragged}
 {#if dragged}
 	<div
 	<div
 		class="fixed {$showSidebar
 		class="fixed {$showSidebar
-			? 'left-0 lg:left-[260px] lg:w-[calc(100%-260px)]'
+			? 'left-0 md:left-[260px] md:w-[calc(100%-260px)]'
 			: 'left-0'}  w-full h-full flex z-50 touch-none pointer-events-none"
 			: 'left-0'}  w-full h-full flex z-50 touch-none pointer-events-none"
 		id="dropzone"
 		id="dropzone"
 		role="region"
 		role="region"
@@ -428,9 +428,9 @@
 	</div>
 	</div>
 {/if}
 {/if}
 
 
-<div class="fixed bottom-0 {$showSidebar ? 'left-0 lg:left-[260px]' : 'left-0'} right-0">
+<div class="fixed bottom-0 {$showSidebar ? 'left-0 md:left-[260px]' : 'left-0'} right-0">
 	<div class="w-full">
 	<div class="w-full">
-		<div class="px-2.5 lg:px-16 -mb-0.5 mx-auto inset-x-0 bg-transparent flex justify-center">
+		<div class="px-2.5 md:px-16 -mb-0.5 mx-auto inset-x-0 bg-transparent flex justify-center">
 			<div class="flex flex-col max-w-5xl w-full">
 			<div class="flex flex-col max-w-5xl w-full">
 				<div class="relative">
 				<div class="relative">
 					{#if autoScroll === false && messages.length > 0}
 					{#if autoScroll === false && messages.length > 0}
@@ -535,7 +535,7 @@
 		</div>
 		</div>
 
 
 		<div class="bg-white dark:bg-gray-900">
 		<div class="bg-white dark:bg-gray-900">
-			<div class="max-w-6xl px-2.5 lg:px-16 mx-auto inset-x-0">
+			<div class="max-w-6xl px-2.5 md:px-16 mx-auto inset-x-0">
 				<div class=" pb-2">
 				<div class=" pb-2">
 					<input
 					<input
 						bind:this={filesInputElement}
 						bind:this={filesInputElement}
@@ -754,7 +754,7 @@
 							<textarea
 							<textarea
 								id="chat-textarea"
 								id="chat-textarea"
 								bind:this={chatTextAreaElement}
 								bind:this={chatTextAreaElement}
-								class="scrollbar-none dark:bg-gray-900 dark:text-gray-100 outline-none w-full py-3 px-3 {fileUploadEnabled
+								class="scrollbar-hidden dark:bg-gray-900 dark:text-gray-100 outline-none w-full py-3 px-3 {fileUploadEnabled
 									? ''
 									? ''
 									: ' pl-4'} rounded-xl resize-none h-[48px]"
 									: ' pl-4'} rounded-xl resize-none h-[48px]"
 								placeholder={chatInputPlaceholder !== ''
 								placeholder={chatInputPlaceholder !== ''
@@ -765,7 +765,7 @@
 								bind:value={prompt}
 								bind:value={prompt}
 								on:keypress={(e) => {
 								on:keypress={(e) => {
 									if (
 									if (
-										window.innerWidth > 1024 ||
+										!$mobile ||
 										!(
 										!(
 											'ontouchstart' in window ||
 											'ontouchstart' in window ||
 											navigator.maxTouchPoints > 0 ||
 											navigator.maxTouchPoints > 0 ||
@@ -1046,12 +1046,12 @@
 </div>
 </div>
 
 
 <style>
 <style>
-	.scrollbar-none:active::-webkit-scrollbar-thumb,
-	.scrollbar-none:focus::-webkit-scrollbar-thumb,
-	.scrollbar-none:hover::-webkit-scrollbar-thumb {
+	.scrollbar-hidden:active::-webkit-scrollbar-thumb,
+	.scrollbar-hidden:focus::-webkit-scrollbar-thumb,
+	.scrollbar-hidden:hover::-webkit-scrollbar-thumb {
 		visibility: visible;
 		visibility: visible;
 	}
 	}
-	.scrollbar-none::-webkit-scrollbar-thumb {
+	.scrollbar-hidden::-webkit-scrollbar-thumb {
 		visibility: hidden;
 		visibility: hidden;
 	}
 	}
 </style>
 </style>

+ 1 - 1
src/lib/components/chat/Messages/CitationsModal.svelte

@@ -47,7 +47,7 @@
 
 
 		<div class="flex flex-col md:flex-row w-full px-6 pb-5 md:space-x-4">
 		<div class="flex flex-col md:flex-row w-full px-6 pb-5 md:space-x-4">
 			<div
 			<div
-				class="flex flex-col w-full dark:text-gray-200 overflow-y-scroll max-h-[22rem] scrollbar-none"
+				class="flex flex-col w-full dark:text-gray-200 overflow-y-scroll max-h-[22rem] scrollbar-hidden"
 			>
 			>
 				{#each mergedDocuments as document, documentIdx}
 				{#each mergedDocuments as document, documentIdx}
 					<div class="flex flex-col w-full">
 					<div class="flex flex-col w-full">

+ 1 - 1
src/lib/components/chat/Messages/Name.svelte

@@ -1,3 +1,3 @@
-<div class=" self-center font-bold mb-0.5 capitalize line-clamp-1">
+<div class=" self-center font-bold mb-0.5 line-clamp-1">
 	<slot />
 	<slot />
 </div>
 </div>

+ 2 - 2
src/lib/components/chat/Messages/ProfileImage.svelte

@@ -2,6 +2,6 @@
 	export let src = '/user.png';
 	export let src = '/user.png';
 </script>
 </script>
 
 
-<div class=" mr-4">
-	<img {src} class=" max-w-[28px] object-cover rounded-full" alt="profile" draggable="false" />
+<div class=" mr-3">
+	<img {src} class=" w-8 object-cover rounded-full" alt="profile" draggable="false" />
 </div>
 </div>

+ 38 - 28
src/lib/components/chat/Messages/ResponseMessage.svelte

@@ -338,7 +338,7 @@
 				($i18n.language === 'dg-DG' ? `/doge.png` : `${WEBUI_BASE_URL}/static/favicon.png`)}
 				($i18n.language === 'dg-DG' ? `/doge.png` : `${WEBUI_BASE_URL}/static/favicon.png`)}
 		/>
 		/>
 
 
-		<div class="w-full overflow-hidden">
+		<div class="w-full overflow-hidden pl-1">
 			<Name>
 			<Name>
 				{#if message.model in modelfiles}
 				{#if message.model in modelfiles}
 					{modelfiles[message.model]?.title}
 					{modelfiles[message.model]?.title}
@@ -347,8 +347,10 @@
 				{/if}
 				{/if}
 
 
 				{#if message.timestamp}
 				{#if message.timestamp}
-					<span class=" invisible group-hover:visible text-gray-400 text-xs font-medium">
-						{dayjs(message.timestamp * 1000).format($i18n.t('DD/MM/YYYY HH:mm'))}
+					<span
+						class=" self-center invisible group-hover:visible text-gray-400 text-xs font-medium uppercase"
+					>
+						{dayjs(message.timestamp * 1000).format($i18n.t('h:mm a'))}
 					</span>
 					</span>
 				{/if}
 				{/if}
 			</Name>
 			</Name>
@@ -370,7 +372,7 @@
 			>
 			>
 				<div>
 				<div>
 					{#if edit === true}
 					{#if edit === true}
-						<div class=" w-full">
+						<div class="w-full bg-gray-50 dark:bg-gray-800 rounded-3xl px-5 py-3 my-2">
 							<textarea
 							<textarea
 								id="message-edit-{message.id}"
 								id="message-edit-{message.id}"
 								bind:this={editTextAreaElement}
 								bind:this={editTextAreaElement}
@@ -382,23 +384,25 @@
 								}}
 								}}
 							/>
 							/>
 
 
-							<div class=" mt-2 mb-1 flex justify-center space-x-2 text-sm font-medium">
+							<div class=" mt-2 mb-1 flex justify-end space-x-1.5 text-sm font-medium">
 								<button
 								<button
-									class="px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
+									id="close-edit-message-button"
+									class=" px-4 py-2 bg-gray-900 hover:bg-gray-850 text-gray-100 transition rounded-3xl"
 									on:click={() => {
 									on:click={() => {
-										editMessageConfirmHandler();
+										cancelEditMessage();
 									}}
 									}}
 								>
 								>
-									{$i18n.t('Save')}
+									{$i18n.t('Cancel')}
 								</button>
 								</button>
 
 
 								<button
 								<button
-									class=" px-4 py-2 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-100 transition outline outline-1 outline-gray-200 dark:outline-gray-600 rounded-lg"
+									id="save-edit-message-button"
+									class="px-4 py-2 bg-white hover:bg-gray-100 text-gray-800 transition rounded-3xl"
 									on:click={() => {
 									on:click={() => {
-										cancelEditMessage();
+										editMessageConfirmHandler();
 									}}
 									}}
 								>
 								>
-									{$i18n.t('Cancel')}
+									{$i18n.t('Save')}
 								</button>
 								</button>
 							</div>
 							</div>
 						</div>
 						</div>
@@ -490,47 +494,53 @@
 									class=" flex justify-start space-x-1 overflow-x-auto buttons text-gray-700 dark:text-gray-500"
 									class=" flex justify-start space-x-1 overflow-x-auto buttons text-gray-700 dark:text-gray-500"
 								>
 								>
 									{#if siblings.length > 1}
 									{#if siblings.length > 1}
-										<div class="flex self-center min-w-fit">
+										<div class="flex self-center">
 											<button
 											<button
-												class="self-center dark:hover:text-white hover:text-black transition"
+												class="self-center p-1 hover:bg-black/5 dark:hover:bg-white/5 dark:hover:text-white hover:text-black rounded-md transition"
 												on:click={() => {
 												on:click={() => {
 													showPreviousMessage(message);
 													showPreviousMessage(message);
 												}}
 												}}
 											>
 											>
 												<svg
 												<svg
 													xmlns="http://www.w3.org/2000/svg"
 													xmlns="http://www.w3.org/2000/svg"
-													viewBox="0 0 20 20"
-													fill="currentColor"
-													class="w-4 h-4"
+													fill="none"
+													viewBox="0 0 24 24"
+													stroke="currentColor"
+													stroke-width="2.5"
+													class="size-3.5"
 												>
 												>
 													<path
 													<path
-														fill-rule="evenodd"
-														d="M12.79 5.23a.75.75 0 01-.02 1.06L8.832 10l3.938 3.71a.75.75 0 11-1.04 1.08l-4.5-4.25a.75.75 0 010-1.08l4.5-4.25a.75.75 0 011.06.02z"
-														clip-rule="evenodd"
+														stroke-linecap="round"
+														stroke-linejoin="round"
+														d="M15.75 19.5 8.25 12l7.5-7.5"
 													/>
 													/>
 												</svg>
 												</svg>
 											</button>
 											</button>
 
 
-											<div class="text-xs font-bold self-center min-w-fit dark:text-gray-100">
-												{siblings.indexOf(message.id) + 1} / {siblings.length}
+											<div
+												class="text-sm tracking-widest font-semibold self-center dark:text-gray-100"
+											>
+												{siblings.indexOf(message.id) + 1}/{siblings.length}
 											</div>
 											</div>
 
 
 											<button
 											<button
-												class="self-center dark:hover:text-white hover:text-black transition"
+												class="self-center p-1 hover:bg-black/5 dark:hover:bg-white/5 dark:hover:text-white hover:text-black rounded-md transition"
 												on:click={() => {
 												on:click={() => {
 													showNextMessage(message);
 													showNextMessage(message);
 												}}
 												}}
 											>
 											>
 												<svg
 												<svg
 													xmlns="http://www.w3.org/2000/svg"
 													xmlns="http://www.w3.org/2000/svg"
-													viewBox="0 0 20 20"
-													fill="currentColor"
-													class="w-4 h-4"
+													fill="none"
+													viewBox="0 0 24 24"
+													stroke="currentColor"
+													stroke-width="2.5"
+													class="size-3.5"
 												>
 												>
 													<path
 													<path
-														fill-rule="evenodd"
-														d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z"
-														clip-rule="evenodd"
+														stroke-linecap="round"
+														stroke-linejoin="round"
+														d="m8.25 4.5 7.5 7.5-7.5 7.5"
 													/>
 													/>
 												</svg>
 												</svg>
 											</button>
 											</button>

+ 77 - 95
src/lib/components/chat/Messages/UserMessage.svelte

@@ -54,49 +54,20 @@
 	};
 	};
 </script>
 </script>
 
 
-<div class=" flex w-full">
-	<ProfileImage
-		src={message.user
-			? $modelfiles.find((modelfile) => modelfile.tagName === message.user)?.imageUrl ?? '/user.png'
-			: user?.profile_image_url ?? '/user.png'}
-	/>
-
+<div class=" flex w-full user-message">
 	<div class="w-full overflow-hidden">
 	<div class="w-full overflow-hidden">
-		<div class="user-message">
-			<Name>
-				{#if message.user}
-					{#if $modelfiles.map((modelfile) => modelfile.tagName).includes(message.user)}
-						{$modelfiles.find((modelfile) => modelfile.tagName === message.user)?.title}
-					{:else}
-						{$i18n.t('You')}
-						<span class=" text-gray-500 text-sm font-medium">{message?.user ?? ''}</span>
-					{/if}
-				{:else if $settings.showUsername}
-					{user.name}
-				{:else}
-					{$i18n.t('You')}
-				{/if}
-
-				{#if message.timestamp}
-					<span class=" invisible group-hover:visible text-gray-400 text-xs font-medium">
-						{dayjs(message.timestamp * 1000).format($i18n.t('DD/MM/YYYY HH:mm'))}
-					</span>
-				{/if}
-			</Name>
-		</div>
-
 		<div
 		<div
-			class="prose chat-{message.role} w-full max-w-full dark:prose-invert prose-headings:my-0 prose-p:my-0 prose-p:-mb-4 prose-pre:my-0 prose-table:my-0 prose-blockquote:my-0 prose-img:my-0 prose-ul:-my-4 prose-ol:-my-4 prose-li:-my-3 prose-ul:-mb-6 prose-ol:-mb-6 prose-li:-mb-4 whitespace-pre-line"
+			class="prose chat-{message.role} w-full max-w-full flex flex-col justify-end dark:prose-invert prose-headings:my-0 prose-p:my-0 prose-p:-mb-4 prose-pre:my-0 prose-table:my-0 prose-blockquote:my-0 prose-img:my-0 prose-ul:-my-4 prose-ol:-my-4 prose-li:-my-3 prose-ul:-mb-6 prose-ol:-mb-6 prose-li:-mb-4 whitespace-pre-line"
 		>
 		>
 			{#if message.files}
 			{#if message.files}
-				<div class="my-2.5 w-full flex overflow-x-auto gap-2 flex-wrap">
+				<div class="mt-2.5 mb-1 w-full flex flex-col justify-end overflow-x-auto gap-1 flex-wrap">
 					{#each message.files as file}
 					{#each message.files as file}
-						<div>
+						<div class="self-end">
 							{#if file.type === 'image'}
 							{#if file.type === 'image'}
 								<img src={file.url} alt="input" class=" max-h-96 rounded-lg" draggable="false" />
 								<img src={file.url} alt="input" class=" max-h-96 rounded-lg" draggable="false" />
 							{:else if file.type === 'doc'}
 							{:else if file.type === 'doc'}
 								<button
 								<button
-									class="h-16 w-[15rem] flex items-center space-x-3 px-2.5 dark:bg-gray-600 rounded-xl border border-gray-200 dark:border-none text-left"
+									class="h-16 w-72 flex items-center space-x-3 px-2.5 dark:bg-gray-850 rounded-xl border border-gray-200 dark:border-none text-left"
 									type="button"
 									type="button"
 									on:click={() => {
 									on:click={() => {
 										if (file?.url) {
 										if (file?.url) {
@@ -132,7 +103,7 @@
 								</button>
 								</button>
 							{:else if file.type === 'collection'}
 							{:else if file.type === 'collection'}
 								<button
 								<button
-									class="h-16 w-[15rem] flex items-center space-x-3 px-2.5 dark:bg-gray-600 rounded-xl border border-gray-200 dark:border-none text-left"
+									class="h-16 w-72 flex items-center space-x-3 px-2.5 dark:bg-gray-600 rounded-xl border border-gray-200 dark:border-none text-left"
 									type="button"
 									type="button"
 								>
 								>
 									<div class="p-2.5 bg-red-400 text-white rounded-lg">
 									<div class="p-2.5 bg-red-400 text-white rounded-lg">
@@ -166,7 +137,7 @@
 			{/if}
 			{/if}
 
 
 			{#if edit === true}
 			{#if edit === true}
-				<div class=" w-full">
+				<div class=" w-full bg-gray-50 dark:bg-gray-800 rounded-3xl px-5 py-3 mb-2">
 					<textarea
 					<textarea
 						id="message-edit-{message.id}"
 						id="message-edit-{message.id}"
 						bind:this={messageEditTextAreaElement}
 						bind:this={messageEditTextAreaElement}
@@ -190,81 +161,41 @@
 						}}
 						}}
 					/>
 					/>
 
 
-					<div class=" mt-2 mb-1 flex justify-center space-x-2 text-sm font-medium">
+					<div class=" mt-2 mb-1 flex justify-end space-x-1.5 text-sm font-medium">
 						<button
 						<button
-							id="save-edit-message-button"
-							class="px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
+							id="close-edit-message-button"
+							class=" px-4 py-2 bg-gray-900 hover:bg-gray-850 text-gray-100 transition rounded-3xl"
 							on:click={() => {
 							on:click={() => {
-								editMessageConfirmHandler();
+								cancelEditMessage();
 							}}
 							}}
 						>
 						>
-							{$i18n.t('Save & Submit')}
+							{$i18n.t('Cancel')}
 						</button>
 						</button>
 
 
 						<button
 						<button
-							id="close-edit-message-button"
-							class=" px-4 py-2 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-100 transition outline outline-1 outline-gray-200 dark:outline-gray-600 rounded-lg"
+							id="save-edit-message-button"
+							class="px-4 py-2 bg-white hover:bg-gray-100 text-gray-800 transition rounded-3xl"
 							on:click={() => {
 							on:click={() => {
-								cancelEditMessage();
+								editMessageConfirmHandler();
 							}}
 							}}
 						>
 						>
-							{$i18n.t('Cancel')}
+							{$i18n.t('Send')}
 						</button>
 						</button>
 					</div>
 					</div>
 				</div>
 				</div>
 			{:else}
 			{:else}
 				<div class="w-full">
 				<div class="w-full">
-					<pre id="user-message">{message.content}</pre>
-
-					<div class=" flex justify-start space-x-1 text-gray-700 dark:text-gray-500">
-						{#if siblings.length > 1}
-							<div class="flex self-center">
-								<button
-									class="self-center dark:hover:text-white hover:text-black transition"
-									on:click={() => {
-										showPreviousMessage(message);
-									}}
-								>
-									<svg
-										xmlns="http://www.w3.org/2000/svg"
-										viewBox="0 0 20 20"
-										fill="currentColor"
-										class="w-4 h-4"
-									>
-										<path
-											fill-rule="evenodd"
-											d="M12.79 5.23a.75.75 0 01-.02 1.06L8.832 10l3.938 3.71a.75.75 0 11-1.04 1.08l-4.5-4.25a.75.75 0 010-1.08l4.5-4.25a.75.75 0 011.06.02z"
-											clip-rule="evenodd"
-										/>
-									</svg>
-								</button>
-
-								<div class="text-xs font-bold self-center dark:text-gray-100">
-									{siblings.indexOf(message.id) + 1} / {siblings.length}
-								</div>
-
-								<button
-									class="self-center dark:hover:text-white hover:text-black transition"
-									on:click={() => {
-										showNextMessage(message);
-									}}
-								>
-									<svg
-										xmlns="http://www.w3.org/2000/svg"
-										viewBox="0 0 20 20"
-										fill="currentColor"
-										class="w-4 h-4"
-									>
-										<path
-											fill-rule="evenodd"
-											d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z"
-											clip-rule="evenodd"
-										/>
-									</svg>
-								</button>
-							</div>
-						{/if}
+					<div class="flex justify-end mb-2">
+						<div
+							class="rounded-3xl px-5 py-2 max-w-[90%] bg-gray-50 dark:bg-gray-850 {message.files
+								? 'rounded-tr-lg'
+								: ''}"
+						>
+							<pre id="user-message">{message.content}</pre>
+						</div>
+					</div>
 
 
+					<div class=" flex justify-end space-x-1 text-gray-700 dark:text-gray-500">
 						{#if !readOnly}
 						{#if !readOnly}
 							<Tooltip content={$i18n.t('Edit')} placement="bottom">
 							<Tooltip content={$i18n.t('Edit')} placement="bottom">
 								<button
 								<button
@@ -340,6 +271,57 @@
 								</button>
 								</button>
 							</Tooltip>
 							</Tooltip>
 						{/if}
 						{/if}
+						{#if siblings.length > 1}
+							<div class="flex self-center">
+								<button
+									class="self-center p-1 hover:bg-black/5 dark:hover:bg-white/5 dark:hover:text-white hover:text-black rounded-md transition"
+									on:click={() => {
+										showPreviousMessage(message);
+									}}
+								>
+									<svg
+										xmlns="http://www.w3.org/2000/svg"
+										fill="none"
+										viewBox="0 0 24 24"
+										stroke="currentColor"
+										stroke-width="2.5"
+										class="size-3.5"
+									>
+										<path
+											stroke-linecap="round"
+											stroke-linejoin="round"
+											d="M15.75 19.5 8.25 12l7.5-7.5"
+										/>
+									</svg>
+								</button>
+
+								<div class="text-sm tracking-widest font-semibold self-center dark:text-gray-100">
+									{siblings.indexOf(message.id) + 1}/{siblings.length}
+								</div>
+
+								<button
+									class="self-center p-1 hover:bg-black/5 dark:hover:bg-white/5 dark:hover:text-white hover:text-black rounded-md transition"
+									on:click={() => {
+										showNextMessage(message);
+									}}
+								>
+									<svg
+										xmlns="http://www.w3.org/2000/svg"
+										fill="none"
+										viewBox="0 0 24 24"
+										stroke="currentColor"
+										stroke-width="2.5"
+										class="size-3.5"
+									>
+										<path
+											stroke-linecap="round"
+											stroke-linejoin="round"
+											d="m8.25 4.5 7.5 7.5-7.5 7.5"
+										/>
+									</svg>
+								</button>
+							</div>
+						{/if}
 					</div>
 					</div>
 				</div>
 				</div>
 			{/if}
 			{/if}

+ 3 - 3
src/lib/components/chat/ModelSelector.svelte

@@ -2,7 +2,7 @@
 	import { Collapsible } from 'bits-ui';
 	import { Collapsible } from 'bits-ui';
 
 
 	import { setDefaultModels } from '$lib/apis/configs';
 	import { setDefaultModels } from '$lib/apis/configs';
-	import { models, showSettings, settings, user } from '$lib/stores';
+	import { models, showSettings, settings, user, mobile } from '$lib/stores';
 	import { onMount, tick, getContext } from 'svelte';
 	import { onMount, tick, getContext } from 'svelte';
 	import { toast } from 'svelte-sonner';
 	import { toast } from 'svelte-sonner';
 	import Selector from './ModelSelector/Selector.svelte';
 	import Selector from './ModelSelector/Selector.svelte';
@@ -108,8 +108,8 @@
 	{/each}
 	{/each}
 </div>
 </div>
 
 
-{#if showSetDefault}
-	<div class="hidden md:absolute text-left mt-0.5 ml-1 text-[0.7rem] text-gray-500">
+{#if showSetDefault && !$mobile}
+	<div class="text-left mt-0.5 ml-1 text-[0.7rem] text-gray-500">
 		<button on:click={saveDefaultModel}> {$i18n.t('Set as default')}</button>
 		<button on:click={saveDefaultModel}> {$i18n.t('Set as default')}</button>
 	</div>
 	</div>
 {/if}
 {/if}

+ 6 - 6
src/lib/components/chat/ModelSelector/Selector.svelte

@@ -25,7 +25,7 @@
 
 
 	export let items = [{ value: 'mango', label: 'Mango' }];
 	export let items = [{ value: 'mango', label: 'Mango' }];
 
 
-	export let className = ' w-[32rem]';
+	export let className = ' w-[30rem]';
 
 
 	let show = false;
 	let show = false;
 
 
@@ -225,7 +225,7 @@
 				<hr class="border-gray-100 dark:border-gray-800" />
 				<hr class="border-gray-100 dark:border-gray-800" />
 			{/if}
 			{/if}
 
 
-			<div class="px-3 my-2 max-h-72 overflow-y-auto scrollbar-none">
+			<div class="px-3 my-2 max-h-64 overflow-y-auto scrollbar-hidden">
 				{#each filteredItems as item}
 				{#each filteredItems as item}
 					<button
 					<button
 						aria-label="model-item"
 						aria-label="model-item"
@@ -407,12 +407,12 @@
 </DropdownMenu.Root>
 </DropdownMenu.Root>
 
 
 <style>
 <style>
-	.scrollbar-none:active::-webkit-scrollbar-thumb,
-	.scrollbar-none:focus::-webkit-scrollbar-thumb,
-	.scrollbar-none:hover::-webkit-scrollbar-thumb {
+	.scrollbar-hidden:active::-webkit-scrollbar-thumb,
+	.scrollbar-hidden:focus::-webkit-scrollbar-thumb,
+	.scrollbar-hidden:hover::-webkit-scrollbar-thumb {
 		visibility: visible;
 		visibility: visible;
 	}
 	}
-	.scrollbar-none::-webkit-scrollbar-thumb {
+	.scrollbar-hidden::-webkit-scrollbar-thumb {
 		visibility: hidden;
 		visibility: hidden;
 	}
 	}
 </style>
 </style>

+ 19 - 0
src/lib/components/icons/MenuLines.svelte

@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'size-5';
+	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="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25H12"
+	/>
+</svg>

+ 14 - 49
src/lib/components/layout/Navbar.svelte

@@ -8,6 +8,7 @@
 		mobile,
 		mobile,
 		modelfiles,
 		modelfiles,
 		settings,
 		settings,
+		showArchivedChats,
 		showSettings,
 		showSettings,
 		showSidebar,
 		showSidebar,
 		user
 		user
@@ -20,6 +21,7 @@
 	import Menu from './Navbar/Menu.svelte';
 	import Menu from './Navbar/Menu.svelte';
 	import { page } from '$app/stores';
 	import { page } from '$app/stores';
 	import UserMenu from './Sidebar/UserMenu.svelte';
 	import UserMenu from './Sidebar/UserMenu.svelte';
+	import MenuLines from '../icons/MenuLines.svelte';
 
 
 	const i18n = getContext('i18n');
 	const i18n = getContext('i18n');
 
 
@@ -40,7 +42,11 @@
 <nav id="nav" class=" sticky py-2.5 top-0 flex flex-row justify-center z-30">
 <nav id="nav" class=" sticky py-2.5 top-0 flex flex-row justify-center z-30">
 	<div class=" flex max-w-full w-full mx-auto px-5 pt-0.5 md:px-[1rem]">
 	<div class=" flex max-w-full w-full mx-auto px-5 pt-0.5 md:px-[1rem]">
 		<div class="flex items-center w-full max-w-full">
 		<div class="flex items-center w-full max-w-full">
-			<div class="{$showSidebar ? 'md:hidden' : ''} mr-3 self-start flex flex-none items-center">
+			<div
+				class="{$showSidebar
+					? 'md:hidden'
+					: ''} mr-3 self-start flex flex-none items-center text-gray-600 dark:text-gray-400"
+			>
 				<button
 				<button
 					id="sidebar-toggle-button"
 					id="sidebar-toggle-button"
 					class="cursor-pointer px-2 py-2 flex rounded-xl hover:bg-gray-100 dark:hover:bg-gray-850 transition"
 					class="cursor-pointer px-2 py-2 flex rounded-xl hover:bg-gray-100 dark:hover:bg-gray-850 transition"
@@ -49,20 +55,7 @@
 					}}
 					}}
 				>
 				>
 					<div class=" m-auto self-center">
 					<div class=" m-auto self-center">
-						<svg
-							xmlns="http://www.w3.org/2000/svg"
-							fill="none"
-							viewBox="0 0 24 24"
-							stroke-width="2"
-							stroke="currentColor"
-							class="size-5"
-						>
-							<path
-								stroke-linecap="round"
-								stroke-linejoin="round"
-								d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25H12"
-							/>
-						</svg>
+						<MenuLines />
 					</div>
 					</div>
 				</button>
 				</button>
 			</div>
 			</div>
@@ -72,40 +65,10 @@
 				{/if}
 				{/if}
 			</div>
 			</div>
 
 
-			<div class="self-start flex flex-none items-center">
+			<div class="self-start flex flex-none items-center text-gray-600 dark:text-gray-400">
 				<!-- <div class="md:hidden flex self-center w-[1px] h-5 mx-2 bg-gray-300 dark:bg-stone-700" /> -->
 				<!-- <div class="md:hidden flex self-center w-[1px] h-5 mx-2 bg-gray-300 dark:bg-stone-700" /> -->
 
 
-				{#if !shareEnabled}
-					<Tooltip content={$i18n.t('Settings')}>
-						<button
-							class="hidden md:flex cursor-pointer px-2 py-2 rounded-xl hover:bg-gray-100 dark:hover:bg-gray-850 transition"
-							id="open-settings-button"
-							on:click={async () => {
-								await showSettings.set(!$showSettings);
-							}}
-						>
-							<svg
-								xmlns="http://www.w3.org/2000/svg"
-								fill="none"
-								viewBox="0 0 24 24"
-								stroke-width="1.5"
-								stroke="currentColor"
-								class="w-5 h-5"
-							>
-								<path
-									stroke-linecap="round"
-									stroke-linejoin="round"
-									d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z"
-								/>
-								<path
-									stroke-linecap="round"
-									stroke-linejoin="round"
-									d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"
-								/>
-							</svg>
-						</button>
-					</Tooltip>
-				{:else}
+				{#if shareEnabled}
 					<Menu
 					<Menu
 						{chat}
 						{chat}
 						{shareEnabled}
 						{shareEnabled}
@@ -167,17 +130,19 @@
 					</button>
 					</button>
 				</Tooltip>
 				</Tooltip>
 
 
-				{#if !$mobile && $user !== undefined}
+				{#if $user !== undefined}
 					<UserMenu
 					<UserMenu
+						className="max-w-[200px]"
 						role={$user.role}
 						role={$user.role}
 						on:show={(e) => {
 						on:show={(e) => {
 							if (e.detail === 'archived-chat') {
 							if (e.detail === 'archived-chat') {
-								// showArchivedChatsModal = true;
+								showArchivedChats.set(true);
 							}
 							}
 						}}
 						}}
 					>
 					>
 						<button
 						<button
 							class=" flex rounded-xl p-1.5 w-full hover:bg-gray-100 dark:hover:bg-gray-850 transition"
 							class=" flex rounded-xl p-1.5 w-full hover:bg-gray-100 dark:hover:bg-gray-850 transition"
+							aria-label="User Menu"
 						>
 						>
 							<div class=" self-center">
 							<div class=" self-center">
 								<img
 								<img

+ 61 - 88
src/lib/components/layout/Navbar/Menu.svelte

@@ -82,7 +82,7 @@
 			align="end"
 			align="end"
 			transition={flyAndScale}
 			transition={flyAndScale}
 		>
 		>
-			<DropdownMenu.Item
+			<!-- <DropdownMenu.Item
 				class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer dark:hover:bg-gray-800 rounded-md"
 				class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer dark:hover:bg-gray-800 rounded-md"
 				on:click={async () => {
 				on:click={async () => {
 					await showSettings.set(!$showSettings);
 					await showSettings.set(!$showSettings);
@@ -108,114 +108,87 @@
 					/>
 					/>
 				</svg>
 				</svg>
 				<div class="flex items-center">{$i18n.t('Settings')}</div>
 				<div class="flex items-center">{$i18n.t('Settings')}</div>
-			</DropdownMenu.Item>
+			</DropdownMenu.Item> -->
 
 
-			{#if shareEnabled}
-				<DropdownMenu.Item
-					class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer dark:hover:bg-gray-800 rounded-md"
-					id="chat-share-button"
-					on:click={() => {
-						shareHandler();
-					}}
+			<DropdownMenu.Item
+				class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer dark:hover:bg-gray-800 rounded-md"
+        id="chat-share-button"
+				on:click={() => {
+					shareHandler();
+				}}
+			>
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 24 24"
+					fill="currentColor"
+					class="size-4"
 				>
 				>
-					<svg
-						xmlns="http://www.w3.org/2000/svg"
-						viewBox="0 0 24 24"
-						fill="currentColor"
-						class="size-4"
-					>
-						<path
-							fill-rule="evenodd"
-							d="M15.75 4.5a3 3 0 1 1 .825 2.066l-8.421 4.679a3.002 3.002 0 0 1 0 1.51l8.421 4.679a3 3 0 1 1-.729 1.31l-8.421-4.678a3 3 0 1 1 0-4.132l8.421-4.679a3 3 0 0 1-.096-.755Z"
-							clip-rule="evenodd"
-						/>
-					</svg>
-					<div class="flex items-center">{$i18n.t('Share')}</div>
-				</DropdownMenu.Item>
+					<path
+						fill-rule="evenodd"
+						d="M15.75 4.5a3 3 0 1 1 .825 2.066l-8.421 4.679a3.002 3.002 0 0 1 0 1.51l8.421 4.679a3 3 0 1 1-.729 1.31l-8.421-4.678a3 3 0 1 1 0-4.132l8.421-4.679a3 3 0 0 1-.096-.755Z"
+						clip-rule="evenodd"
+					/>
+				</svg>
+				<div class="flex items-center">{$i18n.t('Share')}</div>
+			</DropdownMenu.Item>
 
 
-				<!-- <DropdownMenu.Item
+			<DropdownMenu.Item
 					class="flex gap-2 items-center px-3 py-2 text-sm  font-medium cursor-pointer"
 					class="flex gap-2 items-center px-3 py-2 text-sm  font-medium cursor-pointer"
 					on:click={() => {
 					on:click={() => {
 						downloadHandler();
 						downloadHandler();
 					}}
 					}}
-				/> -->
-				<DropdownMenu.Sub>
-					<DropdownMenu.SubTrigger
-						class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer dark:hover:bg-gray-800 rounded-md"
-					>
-						<svg
-							xmlns="http://www.w3.org/2000/svg"
-							fill="none"
-							viewBox="0 0 24 24"
-							stroke-width="1.5"
-							stroke="currentColor"
-							class="size-4"
-						>
-							<path
-								stroke-linecap="round"
-								stroke-linejoin="round"
-								d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5M16.5 12 12 16.5m0 0L7.5 12m4.5 4.5V3"
-							/>
-						</svg>
-
-						<div class="flex items-center">{$i18n.t('Download')}</div>
-					</DropdownMenu.SubTrigger>
-					<DropdownMenu.SubContent
-						class="w-full rounded-lg px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-lg"
-						transition={flyAndScale}
-						sideOffset={8}
-					>
-						<DropdownMenu.Item
-							class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer dark:hover:bg-gray-800 rounded-md"
-							on:click={() => {
-								downloadTxt();
-							}}
-						>
-							<div class="flex items-center line-clamp-1">{$i18n.t('Plain text (.txt)')}</div>
-						</DropdownMenu.Item>
-
-						<DropdownMenu.Item
-							class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer dark:hover:bg-gray-800 rounded-md"
-							on:click={() => {
-								downloadPdf();
-							}}
-						>
-							<div class="flex items-center line-clamp-1">{$i18n.t('PDF document (.pdf)')}</div>
-						</DropdownMenu.Item>
-					</DropdownMenu.SubContent>
-				</DropdownMenu.Sub>
-
-				<hr class="border-gray-100 dark:border-gray-800 mt-2.5 mb-1.5" />
-
-				<div class="flex p-1">
-					<Tags chatId={chat.id} />
-				</div>
-
-				<!-- <DropdownMenu.Item
-					class="flex  gap-2  items-center px-3 py-2 text-sm  font-medium cursor-pointer"
-					on:click={() => {
-						tagHandler();
-					}}
+				/>
+			<DropdownMenu.Sub>
+				<DropdownMenu.SubTrigger
+					class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer dark:hover:bg-gray-800 rounded-md"
 				>
 				>
 					<svg
 					<svg
 						xmlns="http://www.w3.org/2000/svg"
 						xmlns="http://www.w3.org/2000/svg"
 						fill="none"
 						fill="none"
 						viewBox="0 0 24 24"
 						viewBox="0 0 24 24"
-						stroke-width="2"
+						stroke-width="1.5"
 						stroke="currentColor"
 						stroke="currentColor"
 						class="size-4"
 						class="size-4"
 					>
 					>
 						<path
 						<path
 							stroke-linecap="round"
 							stroke-linecap="round"
 							stroke-linejoin="round"
 							stroke-linejoin="round"
-							d="M9.568 3H5.25A2.25 2.25 0 0 0 3 5.25v4.318c0 .597.237 1.17.659 1.591l9.581 9.581c.699.699 1.78.872 2.607.33a18.095 18.095 0 0 0 5.223-5.223c.542-.827.369-1.908-.33-2.607L11.16 3.66A2.25 2.25 0 0 0 9.568 3Z"
+							d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5M16.5 12 12 16.5m0 0L7.5 12m4.5 4.5V3"
 						/>
 						/>
-						<path stroke-linecap="round" stroke-linejoin="round" d="M6 6h.008v.008H6V6Z" />
 					</svg>
 					</svg>
 
 
-					<div class="flex items-center">Tag</div>
-				</DropdownMenu.Item> -->
-			{/if}
+					<div class="flex items-center">{$i18n.t('Download')}</div>
+				</DropdownMenu.SubTrigger>
+				<DropdownMenu.SubContent
+					class="w-full rounded-lg px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-lg"
+					transition={flyAndScale}
+					sideOffset={8}
+				>
+					<DropdownMenu.Item
+						class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer dark:hover:bg-gray-800 rounded-md"
+						on:click={() => {
+							downloadTxt();
+						}}
+					>
+						<div class="flex items-center line-clamp-1">{$i18n.t('Plain text (.txt)')}</div>
+					</DropdownMenu.Item>
+
+					<DropdownMenu.Item
+						class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer dark:hover:bg-gray-800 rounded-md"
+						on:click={() => {
+							downloadPdf();
+						}}
+					>
+						<div class="flex items-center line-clamp-1">{$i18n.t('PDF document (.pdf)')}</div>
+					</DropdownMenu.Item>
+				</DropdownMenu.SubContent>
+			</DropdownMenu.Sub>
+
+			<hr class="border-gray-100 dark:border-gray-800 mt-2.5 mb-1.5" />
+
+			<div class="flex p-1">
+				<Tags chatId={chat.id} />
+			</div>
 		</DropdownMenu.Content>
 		</DropdownMenu.Content>
 	</div>
 	</div>
 </Dropdown>
 </Dropdown>

+ 120 - 167
src/lib/components/layout/Sidebar.svelte

@@ -8,7 +8,8 @@
 		chatId,
 		chatId,
 		tags,
 		tags,
 		showSidebar,
 		showSidebar,
-		mobile
+		mobile,
+		showArchivedChats
 	} from '$lib/stores';
 	} from '$lib/stores';
 	import { onMount, getContext } from 'svelte';
 	import { onMount, getContext } from 'svelte';
 
 
@@ -33,7 +34,7 @@
 	import ArchivedChatsModal from './Sidebar/ArchivedChatsModal.svelte';
 	import ArchivedChatsModal from './Sidebar/ArchivedChatsModal.svelte';
 	import UserMenu from './Sidebar/UserMenu.svelte';
 	import UserMenu from './Sidebar/UserMenu.svelte';
 
 
-	const BREAKPOINT = 1024;
+	const BREAKPOINT = 768;
 
 
 	let show = false;
 	let show = false;
 	let navElement;
 	let navElement;
@@ -49,7 +50,6 @@
 	let chatTitleEditId = null;
 	let chatTitleEditId = null;
 	let chatTitle = '';
 	let chatTitle = '';
 
 
-	let showArchivedChatsModal = false;
 	let showShareChatModal = false;
 	let showShareChatModal = false;
 	let showDropdown = false;
 	let showDropdown = false;
 	let isEditing = false;
 	let isEditing = false;
@@ -76,7 +76,26 @@
 		}
 		}
 	});
 	});
 
 
+	mobile;
+	const onResize = () => {
+		if ($showSidebar && window.innerWidth < BREAKPOINT) {
+			showSidebar.set(false);
+		}
+	};
+
 	onMount(async () => {
 	onMount(async () => {
+		mobile.subscribe((e) => {
+			console.log(e);
+
+			if ($showSidebar && e) {
+				showSidebar.set(false);
+			}
+
+			if (!$showSidebar && !e) {
+				showSidebar.set(true);
+			}
+		});
+
 		showSidebar.set(window.innerWidth > BREAKPOINT);
 		showSidebar.set(window.innerWidth > BREAKPOINT);
 		await chats.set(await getChatList(localStorage.token));
 		await chats.set(await getChatList(localStorage.token));
 
 
@@ -106,20 +125,12 @@
 			checkDirection();
 			checkDirection();
 		};
 		};
 
 
-		const onResize = () => {
-			if ($showSidebar && window.innerWidth < BREAKPOINT) {
-				showSidebar.set(false);
-			}
-		};
-
 		window.addEventListener('touchstart', onTouchStart);
 		window.addEventListener('touchstart', onTouchStart);
 		window.addEventListener('touchend', onTouchEnd);
 		window.addEventListener('touchend', onTouchEnd);
-		window.addEventListener('resize', onResize);
 
 
 		return () => {
 		return () => {
 			window.removeEventListener('touchstart', onTouchStart);
 			window.removeEventListener('touchstart', onTouchStart);
 			window.removeEventListener('touchend', onTouchEnd);
 			window.removeEventListener('touchend', onTouchEnd);
-			window.removeEventListener('resize', onResize);
 		};
 		};
 	});
 	});
 
 
@@ -186,7 +197,7 @@
 
 
 <ShareChatModal bind:show={showShareChatModal} chatId={shareChatId} />
 <ShareChatModal bind:show={showShareChatModal} chatId={shareChatId} />
 <ArchivedChatsModal
 <ArchivedChatsModal
-	bind:show={showArchivedChatsModal}
+	bind:show={$showArchivedChats}
 	on:change={async () => {
 	on:change={async () => {
 		await chats.set(await getChatList(localStorage.token));
 		await chats.set(await getChatList(localStorage.token));
 	}}
 	}}
@@ -206,8 +217,8 @@
 <div
 <div
 	bind:this={navElement}
 	bind:this={navElement}
 	id="sidebar"
 	id="sidebar"
-	class="h-screen max-h-[100dvh] min-h-screen {$showSidebar
-		? 'lg:relative w-[260px]'
+	class="h-screen max-h-[100dvh] min-h-screen select-none {$showSidebar
+		? 'md:relative w-[260px]'
 		: '-translate-x-[260px] w-[0px]'} bg-gray-50 text-gray-900 dark:bg-gray-950 dark:text-gray-200 text-sm transition fixed z-50 top-0 left-0 rounded-r-2xl
 		: '-translate-x-[260px] w-[0px]'} bg-gray-50 text-gray-900 dark:bg-gray-950 dark:text-gray-200 text-sm transition fixed z-50 top-0 left-0 rounded-r-2xl
         "
         "
 	data-state={$showSidebar}
 	data-state={$showSidebar}
@@ -217,34 +228,12 @@
 			? ''
 			? ''
 			: 'invisible'}"
 			: 'invisible'}"
 	>
 	>
-		<div class="px-2 flex justify-between space-x-2">
-			<button
-				class=" cursor-pointer px-2 py-2 flex rounded-xl hover:bg-gray-100 dark:hover:bg-gray-850 transition"
-				on:click={() => {
-					showSidebar.set(!$showSidebar);
-				}}
-			>
-				<div class=" m-auto self-center">
-					<svg
-						xmlns="http://www.w3.org/2000/svg"
-						fill="none"
-						viewBox="0 0 24 24"
-						stroke-width="2"
-						stroke="currentColor"
-						class="size-5"
-					>
-						<path
-							stroke-linecap="round"
-							stroke-linejoin="round"
-							d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25H12"
-						/>
-					</svg>
-				</div>
-			</button>
+		<div class="px-2.5 flex justify-between space-x-1 text-gray-600 dark:text-gray-400">
 			<a
 			<a
 				id="sidebar-new-chat-button"
 				id="sidebar-new-chat-button"
-				class="flex justify-between rounded-xl px-2 py-2 hover:bg-gray-100 dark:hover:bg-gray-850 transition"
+				class="flex flex-1 justify-between rounded-xl px-2 py-2 hover:bg-gray-100 dark:hover:bg-gray-850 transition"
 				href="/"
 				href="/"
+				draggable="false"
 				on:click={async () => {
 				on:click={async () => {
 					selectedChatId = null;
 					selectedChatId = null;
 
 
@@ -252,10 +241,24 @@
 					const newChatButton = document.getElementById('new-chat-button');
 					const newChatButton = document.getElementById('new-chat-button');
 					setTimeout(() => {
 					setTimeout(() => {
 						newChatButton?.click();
 						newChatButton?.click();
+
+						if ($mobile) {
+							showSidebar.set(false);
+						}
 					}, 0);
 					}, 0);
 				}}
 				}}
 			>
 			>
-				<div class="self-center">
+				<div class="self-center mx-1.5">
+					<img
+						src="{WEBUI_BASE_URL}/static/favicon.png"
+						class=" size-6 -translate-x-1.5 rounded-full"
+						alt="logo"
+					/>
+				</div>
+				<div class=" self-center font-medium text-sm text-gray-850 dark:text-white">
+					{$i18n.t('New Chat')}
+				</div>
+				<div class="self-center ml-auto">
 					<svg
 					<svg
 						xmlns="http://www.w3.org/2000/svg"
 						xmlns="http://www.w3.org/2000/svg"
 						viewBox="0 0 20 20"
 						viewBox="0 0 20 20"
@@ -271,17 +274,42 @@
 					</svg>
 					</svg>
 				</div>
 				</div>
 			</a>
 			</a>
+
+			<button
+				class=" cursor-pointer px-2 py-2 flex rounded-xl hover:bg-gray-100 dark:hover:bg-gray-850 transition"
+				on:click={() => {
+					showSidebar.set(!$showSidebar);
+				}}
+			>
+				<div class=" m-auto self-center">
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						fill="none"
+						viewBox="0 0 24 24"
+						stroke-width="2"
+						stroke="currentColor"
+						class="size-5"
+					>
+						<path
+							stroke-linecap="round"
+							stroke-linejoin="round"
+							d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25H12"
+						/>
+					</svg>
+				</div>
+			</button>
 		</div>
 		</div>
 
 
 		{#if $user?.role === 'admin'}
 		{#if $user?.role === 'admin'}
-			<div class="px-2 flex justify-center mt-0.5">
+			<div class="px-2.5 flex justify-center text-gray-800 dark:text-gray-200">
 				<a
 				<a
-					class="flex-grow flex space-x-3 rounded-xl px-3.5 py-2 hover:bg-gray-100 dark:hover:bg-gray-900 transition"
-					href="/modelfiles"
+					class="flex-grow flex space-x-3 rounded-xl px-2.5 py-2 hover:bg-gray-100 dark:hover:bg-gray-900 transition"
+					href="/workspace"
 					on:click={() => {
 					on:click={() => {
 						selectedChatId = null;
 						selectedChatId = null;
 						chatId.set('');
 						chatId.set('');
 					}}
 					}}
+					draggable="false"
 				>
 				>
 					<div class="self-center">
 					<div class="self-center">
 						<svg
 						<svg
@@ -290,7 +318,7 @@
 							viewBox="0 0 24 24"
 							viewBox="0 0 24 24"
 							stroke-width="2"
 							stroke-width="2"
 							stroke="currentColor"
 							stroke="currentColor"
-							class="w-4 h-4"
+							class="size-[1.1rem]"
 						>
 						>
 							<path
 							<path
 								stroke-linecap="round"
 								stroke-linecap="round"
@@ -301,71 +329,7 @@
 					</div>
 					</div>
 
 
 					<div class="flex self-center">
 					<div class="flex self-center">
-						<div class=" self-center font-medium text-sm">{$i18n.t('Modelfiles')}</div>
-					</div>
-				</a>
-			</div>
-
-			<div class="px-2 flex justify-center">
-				<a
-					class="flex-grow flex space-x-3 rounded-xl px-3.5 py-2 hover:bg-gray-100 dark:hover:bg-gray-900 transition"
-					href="/prompts"
-					on:click={() => {
-						selectedChatId = null;
-						chatId.set('');
-					}}
-				>
-					<div class="self-center">
-						<svg
-							xmlns="http://www.w3.org/2000/svg"
-							fill="none"
-							viewBox="0 0 24 24"
-							stroke-width="2"
-							stroke="currentColor"
-							class="w-4 h-4"
-						>
-							<path
-								stroke-linecap="round"
-								stroke-linejoin="round"
-								d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L6.832 19.82a4.5 4.5 0 0 1-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 0 1 1.13-1.897L16.863 4.487Zm0 0L19.5 7.125"
-							/>
-						</svg>
-					</div>
-
-					<div class="flex self-center">
-						<div class=" self-center font-medium text-sm">{$i18n.t('Prompts')}</div>
-					</div>
-				</a>
-			</div>
-
-			<div class="px-2 flex justify-center mb-1">
-				<a
-					class="flex-grow flex space-x-3 rounded-xl px-3.5 py-2 hover:bg-gray-100 dark:hover:bg-gray-900 transition"
-					href="/documents"
-					on:click={() => {
-						selectedChatId = null;
-						chatId.set('');
-					}}
-				>
-					<div class="self-center">
-						<svg
-							xmlns="http://www.w3.org/2000/svg"
-							fill="none"
-							viewBox="0 0 24 24"
-							stroke-width="2"
-							stroke="currentColor"
-							class="w-4 h-4"
-						>
-							<path
-								stroke-linecap="round"
-								stroke-linejoin="round"
-								d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 0 1-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 0 1 1.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 0 0-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 0 1-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 0 0-3.375-3.375h-1.5a1.125 1.125 0 0 1-1.125-1.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H9.75"
-							/>
-						</svg>
-					</div>
-
-					<div class="flex self-center">
-						<div class=" self-center font-medium text-sm">{$i18n.t('Documents')}</div>
+						<div class=" self-center font-medium text-sm">{$i18n.t('Workspace')}</div>
 					</div>
 					</div>
 				</a>
 				</a>
 			</div>
 			</div>
@@ -415,9 +379,9 @@
 				</div>
 				</div>
 			{/if}
 			{/if}
 
 
-			<div class="px-2 mt-1 mb-2 flex justify-center space-x-2">
-				<div class="flex w-full" id="chat-search">
-					<div class="self-center pl-3 py-2 rounded-l-xl bg-white dark:bg-gray-950">
+			<div class="px-2 mt-0.5 mb-2 flex justify-center space-x-2">
+				<div class="flex w-full rounded-xl" id="chat-search">
+					<div class="self-center pl-3 py-2 rounded-l-xl bg-transparent">
 						<svg
 						<svg
 							xmlns="http://www.w3.org/2000/svg"
 							xmlns="http://www.w3.org/2000/svg"
 							viewBox="0 0 20 20"
 							viewBox="0 0 20 20"
@@ -433,7 +397,7 @@
 					</div>
 					</div>
 
 
 					<input
 					<input
-						class="w-full rounded-r-xl py-1.5 pl-2.5 pr-4 text-sm dark:text-gray-300 dark:bg-gray-950 outline-none"
+						class="w-full rounded-r-xl py-1.5 pl-2.5 pr-4 text-sm bg-transparent dark:text-gray-300 outline-none"
 						placeholder={$i18n.t('Search')}
 						placeholder={$i18n.t('Search')}
 						bind:value={search}
 						bind:value={search}
 						on:focus={() => {
 						on:focus={() => {
@@ -444,9 +408,9 @@
 			</div>
 			</div>
 
 
 			{#if $tags.length > 0}
 			{#if $tags.length > 0}
-				<div class="px-2.5 mt-0.5 mb-2 flex gap-1 flex-wrap">
+				<div class="px-2.5 mb-2 flex gap-1 flex-wrap">
 					<button
 					<button
-						class="px-2.5 text-xs font-medium bg-gray-100 dark:bg-gray-900 dark:hover:bg-gray-800 transition rounded-full"
+						class="px-2.5 text-xs font-medium bg-gray-50 dark:bg-gray-900 dark:hover:bg-gray-800 transition rounded-full"
 						on:click={async () => {
 						on:click={async () => {
 							await chats.set(await getChatList(localStorage.token));
 							await chats.set(await getChatList(localStorage.token));
 						}}
 						}}
@@ -455,7 +419,7 @@
 					</button>
 					</button>
 					{#each $tags as tag}
 					{#each $tags as tag}
 						<button
 						<button
-							class="px-2.5 text-xs font-medium bg-gray-100 dark:bg-gray-900 dark:hover:bg-gray-800 transition rounded-full"
+							class="px-2.5 text-xs font-medium bg-gray-50 dark:bg-gray-900 dark:hover:bg-gray-800 transition rounded-full"
 							on:click={async () => {
 							on:click={async () => {
 								let chatIds = await getChatListByTagName(localStorage.token, tag.name);
 								let chatIds = await getChatListByTagName(localStorage.token, tag.name);
 								if (chatIds.length === 0) {
 								if (chatIds.length === 0) {
@@ -471,7 +435,7 @@
 				</div>
 				</div>
 			{/if}
 			{/if}
 
 
-			<div class="pl-2 my-2 flex-1 flex flex-col space-y-1 overflow-y-auto scrollbar-none">
+			<div class="pl-2 my-2 flex-1 flex flex-col space-y-1 overflow-y-auto scrollbar-hidden">
 				{#each filteredChatList as chat, idx}
 				{#each filteredChatList as chat, idx}
 					{#if idx === 0 || (idx > 0 && chat.time_range !== filteredChatList[idx - 1].time_range)}
 					{#if idx === 0 || (idx > 0 && chat.time_range !== filteredChatList[idx - 1].time_range)}
 						<div
 						<div
@@ -526,7 +490,7 @@
 								href="/c/{chat.id}"
 								href="/c/{chat.id}"
 								on:click={() => {
 								on:click={() => {
 									selectedChatId = chat.id;
 									selectedChatId = chat.id;
-									if (window.innerWidth < 1024) {
+									if ($mobile) {
 										showSidebar.set(false);
 										showSidebar.set(false);
 									}
 									}
 								}}
 								}}
@@ -641,6 +605,9 @@
 											shareChatId = selectedChatId;
 											shareChatId = selectedChatId;
 											showShareChatModal = true;
 											showShareChatModal = true;
 										}}
 										}}
+										archiveChatHandler={() => {
+											archiveChatHandler(chat.id);
+										}}
 										renameHandler={() => {
 										renameHandler={() => {
 											chatTitle = chat.title;
 											chatTitle = chat.title;
 											chatTitleEditId = chat.id;
 											chatTitleEditId = chat.id;
@@ -672,18 +639,6 @@
 										</button>
 										</button>
 									</ChatMenu>
 									</ChatMenu>
 
 
-									<Tooltip content={$i18n.t('Archive')}>
-										<button
-											aria-label="Archive"
-											class=" self-center dark:hover:text-white transition"
-											on:click={() => {
-												archiveChatHandler(chat.id);
-											}}
-										>
-											<ArchiveBox />
-										</button>
-									</Tooltip>
-
 									{#if chat.id === $chatId}
 									{#if chat.id === $chatId}
 										<button
 										<button
 											id="delete-chat-button"
 											id="delete-chat-button"
@@ -712,43 +667,41 @@
 			</div>
 			</div>
 		</div>
 		</div>
 
 
-		{#if $mobile}
-			<div class="px-2.5">
-				<!-- <hr class=" border-gray-900 mb-1 w-full" /> -->
-
-				<div class="flex flex-col">
-					{#if $user !== undefined}
-						<UserMenu
-							role={$user.role}
-							on:show={(e) => {
-								if (e.detail === 'archived-chat') {
-									showArchivedChatsModal = true;
-								}
+		<div class="px-2.5">
+			<!-- <hr class=" border-gray-900 mb-1 w-full" /> -->
+
+			<div class="flex flex-col">
+				{#if $user !== undefined}
+					<UserMenu
+						role={$user.role}
+						on:show={(e) => {
+							if (e.detail === 'archived-chat') {
+								showArchivedChats.set(true);
+							}
+						}}
+					>
+						<button
+							class=" flex rounded-xl py-3 px-3.5 w-full hover:bg-gray-100 dark:hover:bg-gray-900 transition"
+							on:click={() => {
+								showDropdown = !showDropdown;
 							}}
 							}}
 						>
 						>
-							<button
-								class=" flex rounded-xl py-3 px-3.5 w-full hover:bg-gray-100 dark:hover:bg-gray-900 transition"
-								on:click={() => {
-									showDropdown = !showDropdown;
-								}}
-							>
-								<div class=" self-center mr-3">
-									<img
-										src={$user.profile_image_url}
-										class=" max-w-[30px] object-cover rounded-full"
-										alt="User profile"
-									/>
-								</div>
-								<div class=" self-center font-semibold">{$user.name}</div>
-							</button>
-						</UserMenu>
-					{/if}
-				</div>
+							<div class=" self-center mr-3">
+								<img
+									src={$user.profile_image_url}
+									class=" max-w-[30px] object-cover rounded-full"
+									alt="User profile"
+								/>
+							</div>
+							<div class=" self-center font-semibold">{$user.name}</div>
+						</button>
+					</UserMenu>
+				{/if}
 			</div>
 			</div>
-		{/if}
+		</div>
 	</div>
 	</div>
 
 
-	<div
+	<!-- <div
 		id="sidebar-handle"
 		id="sidebar-handle"
 		class=" hidden md:fixed left-0 top-[50dvh] -translate-y-1/2 transition-transform translate-x-[255px] md:translate-x-[260px] rotate-0"
 		class=" hidden md:fixed left-0 top-[50dvh] -translate-y-1/2 transition-transform translate-x-[255px] md:translate-x-[260px] rotate-0"
 	>
 	>
@@ -783,16 +736,16 @@
 				</span>
 				</span>
 			</button>
 			</button>
 		</Tooltip>
 		</Tooltip>
-	</div>
+	</div> -->
 </div>
 </div>
 
 
 <style>
 <style>
-	.scrollbar-none:active::-webkit-scrollbar-thumb,
-	.scrollbar-none:focus::-webkit-scrollbar-thumb,
-	.scrollbar-none:hover::-webkit-scrollbar-thumb {
+	.scrollbar-hidden:active::-webkit-scrollbar-thumb,
+	.scrollbar-hidden:focus::-webkit-scrollbar-thumb,
+	.scrollbar-hidden:hover::-webkit-scrollbar-thumb {
 		visibility: visible;
 		visibility: visible;
 	}
 	}
-	.scrollbar-none::-webkit-scrollbar-thumb {
+	.scrollbar-hidden::-webkit-scrollbar-thumb {
 		visibility: hidden;
 		visibility: hidden;
 	}
 	}
 </style>
 </style>

+ 13 - 1
src/lib/components/layout/Sidebar/ChatMenu.svelte

@@ -9,10 +9,12 @@
 	import Tooltip from '$lib/components/common/Tooltip.svelte';
 	import Tooltip from '$lib/components/common/Tooltip.svelte';
 	import Tags from '$lib/components/chat/Tags.svelte';
 	import Tags from '$lib/components/chat/Tags.svelte';
 	import Share from '$lib/components/icons/Share.svelte';
 	import Share from '$lib/components/icons/Share.svelte';
+	import ArchiveBox from '$lib/components/icons/ArchiveBox.svelte';
 
 
 	const i18n = getContext('i18n');
 	const i18n = getContext('i18n');
 
 
 	export let shareHandler: Function;
 	export let shareHandler: Function;
+	export let archiveChatHandler: Function;
 	export let renameHandler: Function;
 	export let renameHandler: Function;
 	export let deleteHandler: Function;
 	export let deleteHandler: Function;
 	export let onClose: Function;
 	export let onClose: Function;
@@ -36,7 +38,7 @@
 
 
 	<div slot="content">
 	<div slot="content">
 		<DropdownMenu.Content
 		<DropdownMenu.Content
-			class="w-full max-w-[180px] rounded-xl px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-850 dark:text-white shadow"
+			class="w-full max-w-[160px] rounded-xl px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-850 dark:text-white shadow"
 			sideOffset={-2}
 			sideOffset={-2}
 			side="bottom"
 			side="bottom"
 			align="start"
 			align="start"
@@ -62,6 +64,16 @@
 				<div class="flex items-center">{$i18n.t('Rename')}</div>
 				<div class="flex items-center">{$i18n.t('Rename')}</div>
 			</DropdownMenu.Item>
 			</DropdownMenu.Item>
 
 
+			<DropdownMenu.Item
+				class="flex gap-2 items-center px-3 py-2 text-sm  font-medium cursor-pointer dark:hover:bg-gray-800 rounded-md"
+				on:click={() => {
+					archiveChatHandler();
+				}}
+			>
+				<ArchiveBox strokeWidth="2" />
+				<div class="flex items-center">{$i18n.t('Archive')}</div>
+			</DropdownMenu.Item>
+
 			<DropdownMenu.Item
 			<DropdownMenu.Item
 				class="flex  gap-2  items-center px-3 py-2 text-sm  font-medium cursor-pointer dark:hover:bg-gray-800 rounded-md"
 				class="flex  gap-2  items-center px-3 py-2 text-sm  font-medium cursor-pointer dark:hover:bg-gray-800 rounded-md"
 				on:click={() => {
 				on:click={() => {

+ 45 - 70
src/lib/components/layout/Sidebar/UserMenu.svelte

@@ -12,6 +12,7 @@
 
 
 	export let show = false;
 	export let show = false;
 	export let role = '';
 	export let role = '';
+	export let className = 'max-w-[240px]';
 
 
 	const dispatch = createEventDispatcher();
 	const dispatch = createEventDispatcher();
 </script>
 </script>
@@ -28,81 +29,14 @@
 
 
 	<slot name="content">
 	<slot name="content">
 		<DropdownMenu.Content
 		<DropdownMenu.Content
-			class="w-full max-w-[240px] rounded-lg p-1 py-1 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-gray-850 text-white text-sm"
+			class="w-full {className} text-sm rounded-xl px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-850 dark:text-white shadow"
 			sideOffset={8}
 			sideOffset={8}
 			side="bottom"
 			side="bottom"
 			align="start"
 			align="start"
 			transition={(e) => fade(e, { duration: 100 })}
 			transition={(e) => fade(e, { duration: 100 })}
 		>
 		>
-			{#if role === 'admin'}
-				<button
-					class="flex rounded-md py-2.5 px-3.5 w-full hover:bg-gray-100 dark:hover:bg-gray-800 transition"
-					on:click={() => {
-						goto('/admin');
-						show = false;
-					}}
-				>
-					<div class=" self-center mr-3">
-						<svg
-							xmlns="http://www.w3.org/2000/svg"
-							fill="none"
-							viewBox="0 0 24 24"
-							stroke-width="1.5"
-							stroke="currentColor"
-							class="w-5 h-5"
-						>
-							<path
-								stroke-linecap="round"
-								stroke-linejoin="round"
-								d="M17.982 18.725A7.488 7.488 0 0012 15.75a7.488 7.488 0 00-5.982 2.975m11.963 0a9 9 0 10-11.963 0m11.963 0A8.966 8.966 0 0112 21a8.966 8.966 0 01-5.982-2.275M15 9.75a3 3 0 11-6 0 3 3 0 016 0z"
-							/>
-						</svg>
-					</div>
-					<div class=" self-center font-medium">{$i18n.t('Admin Panel')}</div>
-				</button>
-
-				<button
-					class="flex rounded-md py-2.5 px-3.5 w-full hover:bg-gray-100 dark:hover:bg-gray-800 transition"
-					on:click={() => {
-						goto('/playground');
-						show = false;
-					}}
-				>
-					<div class=" self-center mr-3">
-						<svg
-							xmlns="http://www.w3.org/2000/svg"
-							fill="none"
-							viewBox="0 0 24 24"
-							stroke-width="1.5"
-							stroke="currentColor"
-							class="w-5 h-5"
-						>
-							<path
-								stroke-linecap="round"
-								stroke-linejoin="round"
-								d="m6.75 7.5 3 2.25-3 2.25m4.5 0h3m-9 8.25h13.5A2.25 2.25 0 0 0 21 18V6a2.25 2.25 0 0 0-2.25-2.25H5.25A2.25 2.25 0 0 0 3 6v12a2.25 2.25 0 0 0 2.25 2.25Z"
-							/>
-						</svg>
-					</div>
-					<div class=" self-center font-medium">{$i18n.t('Playground')}</div>
-				</button>
-			{/if}
-
-			<button
-				class="flex rounded-md py-2.5 px-3.5 w-full hover:bg-gray-100 dark:hover:bg-gray-800 transition"
-				on:click={() => {
-					dispatch('show', 'archived-chat');
-					show = false;
-				}}
-			>
-				<div class=" self-center mr-3">
-					<ArchiveBox className="size-5" strokeWidth="1.5" />
-				</div>
-				<div class=" self-center font-medium">{$i18n.t('Archived Chats')}</div>
-			</button>
-
 			<button
 			<button
-				class="flex rounded-md py-2.5 px-3.5 w-full hover:bg-gray-100 dark:hover:bg-gray-800 transition"
+				class="flex rounded-md py-2 px-3 w-full hover:bg-gray-100 dark:hover:bg-gray-800 transition"
 				on:click={async () => {
 				on:click={async () => {
 					await showSettings.set(true);
 					await showSettings.set(true);
 					show = false;
 					show = false;
@@ -132,10 +66,51 @@
 				<div class=" self-center font-medium">{$i18n.t('Settings')}</div>
 				<div class=" self-center font-medium">{$i18n.t('Settings')}</div>
 			</button>
 			</button>
 
 
+			<button
+				class="flex rounded-md py-2 px-3 w-full hover:bg-gray-100 dark:hover:bg-gray-800 transition"
+				on:click={() => {
+					dispatch('show', 'archived-chat');
+					show = false;
+				}}
+			>
+				<div class=" self-center mr-3">
+					<ArchiveBox className="size-5" strokeWidth="1.5" />
+				</div>
+				<div class=" self-center font-medium">{$i18n.t('Archived Chats')}</div>
+			</button>
+
+			{#if role === 'admin'}
+				<button
+					class="flex rounded-md py-2 px-3 w-full hover:bg-gray-100 dark:hover:bg-gray-800 transition"
+					on:click={() => {
+						goto('/admin');
+						show = false;
+					}}
+				>
+					<div class=" self-center mr-3">
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							fill="none"
+							viewBox="0 0 24 24"
+							stroke-width="1.5"
+							stroke="currentColor"
+							class="w-5 h-5"
+						>
+							<path
+								stroke-linecap="round"
+								stroke-linejoin="round"
+								d="M17.982 18.725A7.488 7.488 0 0012 15.75a7.488 7.488 0 00-5.982 2.975m11.963 0a9 9 0 10-11.963 0m11.963 0A8.966 8.966 0 0112 21a8.966 8.966 0 01-5.982-2.275M15 9.75a3 3 0 11-6 0 3 3 0 016 0z"
+							/>
+						</svg>
+					</div>
+					<div class=" self-center font-medium">{$i18n.t('Admin Panel')}</div>
+				</button>
+			{/if}
+
 			<hr class=" dark:border-gray-800 my-2 p-0" />
 			<hr class=" dark:border-gray-800 my-2 p-0" />
 
 
 			<button
 			<button
-				class="flex rounded-md py-2.5 px-3.5 w-full hover:bg-gray-100 dark:hover:bg-gray-800 transition"
+				class="flex rounded-md py-2 px-3 w-full hover:bg-gray-100 dark:hover:bg-gray-800 transition"
 				on:click={() => {
 				on:click={() => {
 					localStorage.removeItem('token');
 					localStorage.removeItem('token');
 					location.href = '/auth';
 					location.href = '/auth';

+ 612 - 0
src/lib/components/workspace/Documents.svelte

@@ -0,0 +1,612 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+	import fileSaver from 'file-saver';
+	const { saveAs } = fileSaver;
+
+	import { onMount, getContext } from 'svelte';
+	import { WEBUI_NAME, documents } from '$lib/stores';
+	import { createNewDoc, deleteDocByName, getDocs } from '$lib/apis/documents';
+
+	import { SUPPORTED_FILE_TYPE, SUPPORTED_FILE_EXTENSIONS } from '$lib/constants';
+	import { uploadDocToVectorDB } from '$lib/apis/rag';
+	import { transformFileName } from '$lib/utils';
+
+	import Checkbox from '$lib/components/common/Checkbox.svelte';
+
+	import EditDocModal from '$lib/components/documents/EditDocModal.svelte';
+	import AddFilesPlaceholder from '$lib/components/AddFilesPlaceholder.svelte';
+	import SettingsModal from '$lib/components/documents/SettingsModal.svelte';
+	import AddDocModal from '$lib/components/documents/AddDocModal.svelte';
+
+	const i18n = getContext('i18n');
+
+	let importFiles = '';
+
+	let inputFiles = '';
+	let query = '';
+	let documentsImportInputElement: HTMLInputElement;
+	let tags = [];
+
+	let showSettingsModal = false;
+	let showAddDocModal = false;
+	let showEditDocModal = false;
+	let selectedDoc;
+	let selectedTag = '';
+
+	let dragged = false;
+
+	const deleteDoc = async (name) => {
+		await deleteDocByName(localStorage.token, name);
+		await documents.set(await getDocs(localStorage.token));
+	};
+
+	const deleteDocs = async (docs) => {
+		const res = await Promise.all(
+			docs.map(async (doc) => {
+				return await deleteDocByName(localStorage.token, doc.name);
+			})
+		);
+
+		await documents.set(await getDocs(localStorage.token));
+	};
+
+	const uploadDoc = async (file) => {
+		const res = await uploadDocToVectorDB(localStorage.token, '', file).catch((error) => {
+			toast.error(error);
+			return null;
+		});
+
+		if (res) {
+			await createNewDoc(
+				localStorage.token,
+				res.collection_name,
+				res.filename,
+				transformFileName(res.filename),
+				res.filename
+			).catch((error) => {
+				toast.error(error);
+				return null;
+			});
+			await documents.set(await getDocs(localStorage.token));
+		}
+	};
+
+	onMount(() => {
+		documents.subscribe((docs) => {
+			tags = docs.reduce((a, e, i, arr) => {
+				return [...new Set([...a, ...(e?.content?.tags ?? []).map((tag) => tag.name)])];
+			}, []);
+		});
+		const dropZone = document.querySelector('body');
+
+		const onDragOver = (e) => {
+			e.preventDefault();
+			dragged = true;
+		};
+
+		const onDragLeave = () => {
+			dragged = false;
+		};
+
+		const onDrop = async (e) => {
+			e.preventDefault();
+			console.log(e);
+
+			if (e.dataTransfer?.files) {
+				let reader = new FileReader();
+
+				reader.onload = (event) => {
+					files = [
+						...files,
+						{
+							type: 'image',
+							url: `${event.target.result}`
+						}
+					];
+				};
+
+				const inputFiles = e.dataTransfer?.files;
+
+				if (inputFiles && inputFiles.length > 0) {
+					for (const file of inputFiles) {
+						console.log(file, file.name.split('.').at(-1));
+						if (
+							SUPPORTED_FILE_TYPE.includes(file['type']) ||
+							SUPPORTED_FILE_EXTENSIONS.includes(file.name.split('.').at(-1))
+						) {
+							uploadDoc(file);
+						} else {
+							toast.error(
+								`Unknown File Type '${file['type']}', but accepting and treating as plain text`
+							);
+							uploadDoc(file);
+						}
+					}
+				} else {
+					toast.error($i18n.t(`File not found.`));
+				}
+			}
+
+			dragged = false;
+		};
+
+		dropZone?.addEventListener('dragover', onDragOver);
+		dropZone?.addEventListener('drop', onDrop);
+		dropZone?.addEventListener('dragleave', onDragLeave);
+
+		return () => {
+			dropZone?.removeEventListener('dragover', onDragOver);
+			dropZone?.removeEventListener('drop', onDrop);
+			dropZone?.removeEventListener('dragleave', onDragLeave);
+		};
+	});
+
+	let filteredDocs;
+
+	$: filteredDocs = $documents.filter(
+		(doc) =>
+			(selectedTag === '' ||
+				(doc?.content?.tags ?? []).map((tag) => tag.name).includes(selectedTag)) &&
+			(query === '' || doc.name.includes(query))
+	);
+</script>
+
+<svelte:head>
+	<title>
+		{$i18n.t('Documents')} | {$WEBUI_NAME}
+	</title>
+</svelte:head>
+
+{#if dragged}
+	<div
+		class="fixed w-full h-full flex z-50 touch-none pointer-events-none"
+		id="dropzone"
+		role="region"
+		aria-label="Drag and Drop Container"
+	>
+		<div class="absolute rounded-xl w-full h-full backdrop-blur bg-gray-800/40 flex justify-center">
+			<div class="m-auto pt-64 flex flex-col justify-center">
+				<div class="max-w-md">
+					<AddFilesPlaceholder>
+						<div class=" mt-2 text-center text-sm dark:text-gray-200 w-full">
+							Drop any files here to add to my documents
+						</div>
+					</AddFilesPlaceholder>
+				</div>
+			</div>
+		</div>
+	</div>
+{/if}
+
+{#key selectedDoc}
+	<EditDocModal bind:show={showEditDocModal} {selectedDoc} />
+{/key}
+
+<AddDocModal bind:show={showAddDocModal} />
+
+<SettingsModal bind:show={showSettingsModal} />
+
+<div class="mb-3">
+	<div class="flex justify-between items-center">
+		<div class=" text-lg font-semibold self-center">{$i18n.t('Documents')}</div>
+
+		<div>
+			<button
+				class="flex items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 transition"
+				type="button"
+				on:click={() => {
+					showSettingsModal = !showSettingsModal;
+				}}
+			>
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 16 16"
+					fill="currentColor"
+					class="w-4 h-4"
+				>
+					<path
+						fill-rule="evenodd"
+						d="M6.955 1.45A.5.5 0 0 1 7.452 1h1.096a.5.5 0 0 1 .497.45l.17 1.699c.484.12.94.312 1.356.562l1.321-1.081a.5.5 0 0 1 .67.033l.774.775a.5.5 0 0 1 .034.67l-1.08 1.32c.25.417.44.873.561 1.357l1.699.17a.5.5 0 0 1 .45.497v1.096a.5.5 0 0 1-.45.497l-1.699.17c-.12.484-.312.94-.562 1.356l1.082 1.322a.5.5 0 0 1-.034.67l-.774.774a.5.5 0 0 1-.67.033l-1.322-1.08c-.416.25-.872.44-1.356.561l-.17 1.699a.5.5 0 0 1-.497.45H7.452a.5.5 0 0 1-.497-.45l-.17-1.699a4.973 4.973 0 0 1-1.356-.562L4.108 13.37a.5.5 0 0 1-.67-.033l-.774-.775a.5.5 0 0 1-.034-.67l1.08-1.32a4.971 4.971 0 0 1-.561-1.357l-1.699-.17A.5.5 0 0 1 1 8.548V7.452a.5.5 0 0 1 .45-.497l1.699-.17c.12-.484.312-.94.562-1.356L2.629 4.107a.5.5 0 0 1 .034-.67l.774-.774a.5.5 0 0 1 .67-.033L5.43 3.71a4.97 4.97 0 0 1 1.356-.561l.17-1.699ZM6 8c0 .538.212 1.026.558 1.385l.057.057a2 2 0 0 0 2.828-2.828l-.058-.056A2 2 0 0 0 6 8Z"
+						clip-rule="evenodd"
+					/>
+				</svg>
+
+				<div class=" text-xs">{$i18n.t('Document Settings')}</div>
+			</button>
+		</div>
+	</div>
+	<div class=" text-gray-500 text-xs mt-1">
+		ⓘ {$i18n.t("Use '#' in the prompt input to load and select your documents.")}
+	</div>
+</div>
+
+<div class=" flex w-full space-x-2">
+	<div class="flex flex-1">
+		<div class=" self-center ml-1 mr-3">
+			<svg
+				xmlns="http://www.w3.org/2000/svg"
+				viewBox="0 0 20 20"
+				fill="currentColor"
+				class="w-4 h-4"
+			>
+				<path
+					fill-rule="evenodd"
+					d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z"
+					clip-rule="evenodd"
+				/>
+			</svg>
+		</div>
+		<input
+			class=" w-full text-sm pr-4 py-1 rounded-r-xl outline-none bg-transparent"
+			bind:value={query}
+			placeholder={$i18n.t('Search Documents')}
+		/>
+	</div>
+
+	<div>
+		<button
+			class=" px-2 py-2 rounded-xl border border-gray-200 dark:border-gray-600 dark:border-0 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 transition font-medium text-sm flex items-center space-x-1"
+			on:click={() => {
+				showAddDocModal = true;
+			}}
+		>
+			<svg
+				xmlns="http://www.w3.org/2000/svg"
+				viewBox="0 0 16 16"
+				fill="currentColor"
+				class="w-4 h-4"
+			>
+				<path
+					d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
+				/>
+			</svg>
+		</button>
+	</div>
+</div>
+
+<!-- <div>
+    <div
+        class="my-3 py-16 rounded-lg border-2 border-dashed dark:border-gray-600 {dragged &&
+            ' dark:bg-gray-700'} "
+        role="region"
+        on:drop={onDrop}
+        on:dragover={onDragOver}
+        on:dragleave={onDragLeave}
+    >
+        <div class="  pointer-events-none">
+            <div class="text-center dark:text-white text-2xl font-semibold z-50">{$i18n.t('Add Files')}</div>
+
+            <div class=" mt-2 text-center text-sm dark:text-gray-200 w-full">
+                Drop any files here to add to my documents
+            </div>
+        </div>
+    </div>
+</div> -->
+
+<hr class=" dark:border-gray-850 my-2.5" />
+
+{#if tags.length > 0}
+	<div class="px-2.5 pt-1 flex gap-1 flex-wrap">
+		<div class="ml-0.5 pr-3 my-auto flex items-center">
+			<Checkbox
+				state={filteredDocs.filter((doc) => doc?.selected === 'checked').length ===
+				filteredDocs.length
+					? 'checked'
+					: 'unchecked'}
+				indeterminate={filteredDocs.filter((doc) => doc?.selected === 'checked').length > 0 &&
+					filteredDocs.filter((doc) => doc?.selected === 'checked').length !== filteredDocs.length}
+				on:change={(e) => {
+					if (e.detail === 'checked') {
+						filteredDocs = filteredDocs.map((doc) => ({ ...doc, selected: 'checked' }));
+					} else if (e.detail === 'unchecked') {
+						filteredDocs = filteredDocs.map((doc) => ({ ...doc, selected: 'unchecked' }));
+					}
+				}}
+			/>
+		</div>
+
+		{#if filteredDocs.filter((doc) => doc?.selected === 'checked').length === 0}
+			<button
+				class="px-2 py-0.5 space-x-1 flex h-fit items-center rounded-full transition bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:text-white"
+				on:click={async () => {
+					selectedTag = '';
+					// await chats.set(await getChatListByTagName(localStorage.token, tag.name));
+				}}
+			>
+				<div class=" text-xs font-medium self-center line-clamp-1">{$i18n.t('all')}</div>
+			</button>
+
+			{#each tags as tag}
+				<button
+					class="px-2 py-0.5 space-x-1 flex h-fit items-center rounded-full transition bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:text-white"
+					on:click={async () => {
+						selectedTag = tag;
+						// await chats.set(await getChatListByTagName(localStorage.token, tag.name));
+					}}
+				>
+					<div class=" text-xs font-medium self-center line-clamp-1">
+						#{tag}
+					</div>
+				</button>
+			{/each}
+		{:else}
+			<div class="flex-1 flex w-full justify-between items-center">
+				<div class="text-xs font-medium py-0.5 self-center mr-1">
+					{filteredDocs.filter((doc) => doc?.selected === 'checked').length} Selected
+				</div>
+
+				<div class="flex gap-1">
+					<!-- <button
+                        class="px-2 py-0.5 space-x-1 flex h-fit items-center rounded-full transition bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:text-white"
+                        on:click={async () => {
+                            selectedTag = '';
+                            // await chats.set(await getChatListByTagName(localStorage.token, tag.name));
+                        }}
+                    >
+                        <div class=" text-xs font-medium self-center line-clamp-1">add tags</div>
+                    </button> -->
+
+					<button
+						class="px-2 py-0.5 space-x-1 flex h-fit items-center rounded-full transition bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:text-white"
+						on:click={async () => {
+							deleteDocs(filteredDocs.filter((doc) => doc.selected === 'checked'));
+							// await chats.set(await getChatListByTagName(localStorage.token, tag.name));
+						}}
+					>
+						<div class=" text-xs font-medium self-center line-clamp-1">
+							{$i18n.t('delete')}
+						</div>
+					</button>
+				</div>
+			</div>
+		{/if}
+	</div>
+{/if}
+
+<div class="my-3 mb-5">
+	{#each filteredDocs as doc}
+		<button
+			class=" flex space-x-4 cursor-pointer text-left w-full px-3 py-2 dark:hover:bg-white/5 hover:bg-black/5 rounded-xl"
+			on:click={() => {
+				if (doc?.selected === 'checked') {
+					doc.selected = 'unchecked';
+				} else {
+					doc.selected = 'checked';
+				}
+			}}
+		>
+			<div class="my-auto flex items-center">
+				<Checkbox state={doc?.selected ?? 'unchecked'} />
+			</div>
+			<div class=" flex flex-1 space-x-4 cursor-pointer w-full">
+				<div class=" flex items-center space-x-3">
+					<div class="p-2.5 bg-red-400 text-white rounded-lg">
+						{#if doc}
+							<svg
+								xmlns="http://www.w3.org/2000/svg"
+								viewBox="0 0 24 24"
+								fill="currentColor"
+								class="w-6 h-6"
+							>
+								<path
+									fill-rule="evenodd"
+									d="M5.625 1.5c-1.036 0-1.875.84-1.875 1.875v17.25c0 1.035.84 1.875 1.875 1.875h12.75c1.035 0 1.875-.84 1.875-1.875V12.75A3.75 3.75 0 0 0 16.5 9h-1.875a1.875 1.875 0 0 1-1.875-1.875V5.25A3.75 3.75 0 0 0 9 1.5H5.625ZM7.5 15a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 7.5 15Zm.75 2.25a.75.75 0 0 0 0 1.5H12a.75.75 0 0 0 0-1.5H8.25Z"
+									clip-rule="evenodd"
+								/>
+								<path
+									d="M12.971 1.816A5.23 5.23 0 0 1 14.25 5.25v1.875c0 .207.168.375.375.375H16.5a5.23 5.23 0 0 1 3.434 1.279 9.768 9.768 0 0 0-6.963-6.963Z"
+								/>
+							</svg>
+						{:else}
+							<svg
+								class=" w-6 h-6 translate-y-[0.5px]"
+								fill="currentColor"
+								viewBox="0 0 24 24"
+								xmlns="http://www.w3.org/2000/svg"
+								><style>
+									.spinner_qM83 {
+										animation: spinner_8HQG 1.05s infinite;
+									}
+									.spinner_oXPr {
+										animation-delay: 0.1s;
+									}
+									.spinner_ZTLf {
+										animation-delay: 0.2s;
+									}
+									@keyframes spinner_8HQG {
+										0%,
+										57.14% {
+											animation-timing-function: cubic-bezier(0.33, 0.66, 0.66, 1);
+											transform: translate(0);
+										}
+										28.57% {
+											animation-timing-function: cubic-bezier(0.33, 0, 0.66, 0.33);
+											transform: translateY(-6px);
+										}
+										100% {
+											transform: translate(0);
+										}
+									}
+								</style><circle class="spinner_qM83" cx="4" cy="12" r="2.5" /><circle
+									class="spinner_qM83 spinner_oXPr"
+									cx="12"
+									cy="12"
+									r="2.5"
+								/><circle class="spinner_qM83 spinner_ZTLf" cx="20" cy="12" r="2.5" /></svg
+							>
+						{/if}
+					</div>
+					<div class=" self-center flex-1">
+						<div class=" font-bold line-clamp-1">#{doc.name} ({doc.filename})</div>
+						<div class=" text-xs overflow-hidden text-ellipsis line-clamp-1">
+							{doc.title}
+						</div>
+					</div>
+				</div>
+			</div>
+			<div class="flex flex-row space-x-1 self-center">
+				<button
+					class="self-center w-fit text-sm z-20 px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+					type="button"
+					on:click={async (e) => {
+						e.stopPropagation();
+						showEditDocModal = !showEditDocModal;
+						selectedDoc = doc;
+					}}
+				>
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						fill="none"
+						viewBox="0 0 24 24"
+						stroke-width="1.5"
+						stroke="currentColor"
+						class="w-4 h-4"
+					>
+						<path
+							stroke-linecap="round"
+							stroke-linejoin="round"
+							d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125"
+						/>
+					</svg>
+				</button>
+
+				<!-- <button
+            class="self-center w-fit text-sm px-2 py-2 border dark:border-gray-600 rounded-xl"
+            type="button"
+            on:click={() => {
+                console.log('download file');
+            }}
+        >
+            <svg
+                xmlns="http://www.w3.org/2000/svg"
+                viewBox="0 0 16 16"
+                fill="currentColor"
+                class="w-4 h-4"
+            >
+                <path
+                    d="M8.75 2.75a.75.75 0 0 0-1.5 0v5.69L5.03 6.22a.75.75 0 0 0-1.06 1.06l3.5 3.5a.75.75 0 0 0 1.06 0l3.5-3.5a.75.75 0 0 0-1.06-1.06L8.75 8.44V2.75Z"
+                />
+                <path
+                    d="M3.5 9.75a.75.75 0 0 0-1.5 0v1.5A2.75 2.75 0 0 0 4.75 14h6.5A2.75 2.75 0 0 0 14 11.25v-1.5a.75.75 0 0 0-1.5 0v1.5c0 .69-.56 1.25-1.25 1.25h-6.5c-.69 0-1.25-.56-1.25-1.25v-1.5Z"
+                />
+            </svg>
+        </button> -->
+
+				<button
+					class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+					type="button"
+					on:click={(e) => {
+						e.stopPropagation();
+
+						deleteDoc(doc.name);
+					}}
+				>
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						fill="none"
+						viewBox="0 0 24 24"
+						stroke-width="1.5"
+						stroke="currentColor"
+						class="w-4 h-4"
+					>
+						<path
+							stroke-linecap="round"
+							stroke-linejoin="round"
+							d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
+						/>
+					</svg>
+				</button>
+			</div>
+		</button>
+	{/each}
+</div>
+
+<div class=" flex justify-end w-full mb-2">
+	<div class="flex space-x-2">
+		<input
+			id="documents-import-input"
+			bind:this={documentsImportInputElement}
+			bind:files={importFiles}
+			type="file"
+			accept=".json"
+			hidden
+			on:change={() => {
+				console.log(importFiles);
+
+				const reader = new FileReader();
+				reader.onload = async (event) => {
+					const savedDocs = JSON.parse(event.target.result);
+					console.log(savedDocs);
+
+					for (const doc of savedDocs) {
+						await createNewDoc(
+							localStorage.token,
+							doc.collection_name,
+							doc.filename,
+							doc.name,
+							doc.title
+						).catch((error) => {
+							toast.error(error);
+							return null;
+						});
+					}
+
+					await documents.set(await getDocs(localStorage.token));
+				};
+
+				reader.readAsText(importFiles[0]);
+			}}
+		/>
+
+		<button
+			class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
+			on:click={() => {
+				documentsImportInputElement.click();
+			}}
+		>
+			<div class=" self-center mr-2 font-medium">{$i18n.t('Import Documents Mapping')}</div>
+
+			<div class=" self-center">
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 16 16"
+					fill="currentColor"
+					class="w-4 h-4"
+				>
+					<path
+						fill-rule="evenodd"
+						d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 9.5a.75.75 0 0 1-.75-.75V8.06l-.72.72a.75.75 0 0 1-1.06-1.06l2-2a.75.75 0 0 1 1.06 0l2 2a.75.75 0 1 1-1.06 1.06l-.72-.72v2.69a.75.75 0 0 1-.75.75Z"
+						clip-rule="evenodd"
+					/>
+				</svg>
+			</div>
+		</button>
+
+		<button
+			class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
+			on:click={async () => {
+				let blob = new Blob([JSON.stringify($documents)], {
+					type: 'application/json'
+				});
+				saveAs(blob, `documents-mapping-export-${Date.now()}.json`);
+			}}
+		>
+			<div class=" self-center mr-2 font-medium">{$i18n.t('Export Documents Mapping')}</div>
+
+			<div class=" self-center">
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 16 16"
+					fill="currentColor"
+					class="w-4 h-4"
+				>
+					<path
+						fill-rule="evenodd"
+						d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 3.5a.75.75 0 0 1 .75.75v2.69l.72-.72a.75.75 0 1 1 1.06 1.06l-2 2a.75.75 0 0 1-1.06 0l-2-2a.75.75 0 0 1 1.06-1.06l.72.72V6.25A.75.75 0 0 1 8 5.5Z"
+						clip-rule="evenodd"
+					/>
+				</svg>
+			</div>
+		</button>
+	</div>
+</div>

+ 409 - 0
src/lib/components/workspace/Modelfiles.svelte

@@ -0,0 +1,409 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+	import fileSaver from 'file-saver';
+	const { saveAs } = fileSaver;
+
+	import { onMount, getContext } from 'svelte';
+
+	import { WEBUI_NAME, modelfiles, settings, user } from '$lib/stores';
+	import { createModel, deleteModel } from '$lib/apis/ollama';
+	import {
+		createNewModelfile,
+		deleteModelfileByTagName,
+		getModelfiles
+	} from '$lib/apis/modelfiles';
+	import { goto } from '$app/navigation';
+
+	const i18n = getContext('i18n');
+
+	let localModelfiles = [];
+	let importFiles;
+	let modelfilesImportInputElement: HTMLInputElement;
+	const deleteModelHandler = async (tagName) => {
+		let success = null;
+
+		success = await deleteModel(localStorage.token, tagName).catch((err) => {
+			toast.error(err);
+			return null;
+		});
+
+		if (success) {
+			toast.success($i18n.t(`Deleted {{tagName}}`, { tagName }));
+		}
+
+		return success;
+	};
+
+	const deleteModelfile = async (tagName) => {
+		await deleteModelHandler(tagName);
+		await deleteModelfileByTagName(localStorage.token, tagName);
+		await modelfiles.set(await getModelfiles(localStorage.token));
+	};
+
+	const shareModelfile = async (modelfile) => {
+		toast.success($i18n.t('Redirecting you to OpenWebUI Community'));
+
+		const url = 'https://openwebui.com';
+
+		const tab = await window.open(`${url}/modelfiles/create`, '_blank');
+		window.addEventListener(
+			'message',
+			(event) => {
+				if (event.origin !== url) return;
+				if (event.data === 'loaded') {
+					tab.postMessage(JSON.stringify(modelfile), '*');
+				}
+			},
+			false
+		);
+	};
+
+	const saveModelfiles = async (modelfiles) => {
+		let blob = new Blob([JSON.stringify(modelfiles)], {
+			type: 'application/json'
+		});
+		saveAs(blob, `modelfiles-export-${Date.now()}.json`);
+	};
+
+	onMount(() => {
+		localModelfiles = JSON.parse(localStorage.getItem('modelfiles') ?? '[]');
+
+		if (localModelfiles) {
+			console.log(localModelfiles);
+		}
+	});
+</script>
+
+<svelte:head>
+	<title>
+		{$i18n.t('Modelfiles')} | {$WEBUI_NAME}
+	</title>
+</svelte:head>
+
+<div class=" text-lg font-semibold mb-3">{$i18n.t('Modelfiles')}</div>
+
+<a class=" flex space-x-4 cursor-pointer w-full mb-2 px-3 py-2" href="/workspace/modelfiles/create">
+	<div class=" self-center w-10">
+		<div
+			class="w-full h-10 flex justify-center rounded-full bg-transparent dark:bg-gray-700 border border-dashed border-gray-200"
+		>
+			<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-6">
+				<path
+					fill-rule="evenodd"
+					d="M12 3.75a.75.75 0 01.75.75v6.75h6.75a.75.75 0 010 1.5h-6.75v6.75a.75.75 0 01-1.5 0v-6.75H4.5a.75.75 0 010-1.5h6.75V4.5a.75.75 0 01.75-.75z"
+					clip-rule="evenodd"
+				/>
+			</svg>
+		</div>
+	</div>
+
+	<div class=" self-center">
+		<div class=" font-bold">{$i18n.t('Create a modelfile')}</div>
+		<div class=" text-sm">{$i18n.t('Customize Ollama models for a specific purpose')}</div>
+	</div>
+</a>
+
+<hr class=" dark:border-gray-850" />
+
+<div class=" my-2 mb-5">
+	{#each $modelfiles as modelfile}
+		<div
+			class=" flex space-x-4 cursor-pointer w-full px-3 py-2 dark:hover:bg-white/5 hover:bg-black/5 rounded-xl"
+		>
+			<a
+				class=" flex flex-1 space-x-4 cursor-pointer w-full"
+				href={`/?models=${encodeURIComponent(modelfile.tagName)}`}
+			>
+				<div class=" self-center w-10">
+					<div class=" rounded-full bg-stone-700">
+						<img
+							src={modelfile.imageUrl ?? '/user.png'}
+							alt="modelfile profile"
+							class=" rounded-full w-full h-auto object-cover"
+						/>
+					</div>
+				</div>
+
+				<div class=" flex-1 self-center">
+					<div class=" font-bold capitalize">{modelfile.title}</div>
+					<div class=" text-sm overflow-hidden text-ellipsis line-clamp-1">
+						{modelfile.desc}
+					</div>
+				</div>
+			</a>
+			<div class="flex flex-row space-x-1 self-center">
+				<a
+					class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+					type="button"
+					href={`/workspace/modelfiles/edit?tag=${encodeURIComponent(modelfile.tagName)}`}
+				>
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						fill="none"
+						viewBox="0 0 24 24"
+						stroke-width="1.5"
+						stroke="currentColor"
+						class="w-4 h-4"
+					>
+						<path
+							stroke-linecap="round"
+							stroke-linejoin="round"
+							d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L6.832 19.82a4.5 4.5 0 0 1-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 0 1 1.13-1.897L16.863 4.487Zm0 0L19.5 7.125"
+						/>
+					</svg>
+				</a>
+
+				<button
+					class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+					type="button"
+					on:click={() => {
+						// console.log(modelfile);
+						sessionStorage.modelfile = JSON.stringify(modelfile);
+						goto('/workspace/modelfiles/create');
+					}}
+				>
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						fill="none"
+						viewBox="0 0 24 24"
+						stroke-width="1.5"
+						stroke="currentColor"
+						class="w-4 h-4"
+					>
+						<path
+							stroke-linecap="round"
+							stroke-linejoin="round"
+							d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 0 1-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 0 1 1.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 0 0-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 0 1-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 0 0-3.375-3.375h-1.5a1.125 1.125 0 0 1-1.125-1.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H9.75"
+						/>
+					</svg>
+				</button>
+
+				<button
+					class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+					type="button"
+					on:click={() => {
+						shareModelfile(modelfile);
+					}}
+				>
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						fill="none"
+						viewBox="0 0 24 24"
+						stroke-width="1.5"
+						stroke="currentColor"
+						class="w-4 h-4"
+					>
+						<path
+							stroke-linecap="round"
+							stroke-linejoin="round"
+							d="M7.217 10.907a2.25 2.25 0 1 0 0 2.186m0-2.186c.18.324.283.696.283 1.093s-.103.77-.283 1.093m0-2.186 9.566-5.314m-9.566 7.5 9.566 5.314m0 0a2.25 2.25 0 1 0 3.935 2.186 2.25 2.25 0 0 0-3.935-2.186Zm0-12.814a2.25 2.25 0 1 0 3.933-2.185 2.25 2.25 0 0 0-3.933 2.185Z"
+						/>
+					</svg>
+				</button>
+
+				<button
+					class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+					type="button"
+					on:click={() => {
+						deleteModelfile(modelfile.tagName);
+					}}
+				>
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						fill="none"
+						viewBox="0 0 24 24"
+						stroke-width="1.5"
+						stroke="currentColor"
+						class="w-4 h-4"
+					>
+						<path
+							stroke-linecap="round"
+							stroke-linejoin="round"
+							d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
+						/>
+					</svg>
+				</button>
+			</div>
+		</div>
+	{/each}
+</div>
+
+<div class=" flex justify-end w-full mb-3">
+	<div class="flex space-x-1">
+		<input
+			id="modelfiles-import-input"
+			bind:this={modelfilesImportInputElement}
+			bind:files={importFiles}
+			type="file"
+			accept=".json"
+			hidden
+			on:change={() => {
+				console.log(importFiles);
+
+				let reader = new FileReader();
+				reader.onload = async (event) => {
+					let savedModelfiles = JSON.parse(event.target.result);
+					console.log(savedModelfiles);
+
+					for (const modelfile of savedModelfiles) {
+						await createNewModelfile(localStorage.token, modelfile).catch((error) => {
+							return null;
+						});
+					}
+
+					await modelfiles.set(await getModelfiles(localStorage.token));
+				};
+
+				reader.readAsText(importFiles[0]);
+			}}
+		/>
+
+		<button
+			class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
+			on:click={() => {
+				modelfilesImportInputElement.click();
+			}}
+		>
+			<div class=" self-center mr-2 font-medium">{$i18n.t('Import Modelfiles')}</div>
+
+			<div class=" self-center">
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 16 16"
+					fill="currentColor"
+					class="w-3.5 h-3.5"
+				>
+					<path
+						fill-rule="evenodd"
+						d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 9.5a.75.75 0 0 1-.75-.75V8.06l-.72.72a.75.75 0 0 1-1.06-1.06l2-2a.75.75 0 0 1 1.06 0l2 2a.75.75 0 1 1-1.06 1.06l-.72-.72v2.69a.75.75 0 0 1-.75.75Z"
+						clip-rule="evenodd"
+					/>
+				</svg>
+			</div>
+		</button>
+
+		<button
+			class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
+			on:click={async () => {
+				saveModelfiles($modelfiles);
+			}}
+		>
+			<div class=" self-center mr-2 font-medium">{$i18n.t('Export Modelfiles')}</div>
+
+			<div class=" self-center">
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 16 16"
+					fill="currentColor"
+					class="w-3.5 h-3.5"
+				>
+					<path
+						fill-rule="evenodd"
+						d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 3.5a.75.75 0 0 1 .75.75v2.69l.72-.72a.75.75 0 1 1 1.06 1.06l-2 2a.75.75 0 0 1-1.06 0l-2-2a.75.75 0 0 1 1.06-1.06l.72.72V6.25A.75.75 0 0 1 8 5.5Z"
+						clip-rule="evenodd"
+					/>
+				</svg>
+			</div>
+		</button>
+	</div>
+
+	{#if localModelfiles.length > 0}
+		<div class="flex">
+			<div class=" self-center text-sm font-medium mr-4">
+				{localModelfiles.length} Local Modelfiles Detected
+			</div>
+
+			<div class="flex space-x-1">
+				<button
+					class="self-center w-fit text-sm px-3 py-1 border dark:border-gray-600 rounded-xl flex"
+					on:click={async () => {
+						for (const modelfile of localModelfiles) {
+							await createNewModelfile(localStorage.token, modelfile).catch((error) => {
+								return null;
+							});
+						}
+
+						saveModelfiles(localModelfiles);
+						localStorage.removeItem('modelfiles');
+						localModelfiles = JSON.parse(localStorage.getItem('modelfiles') ?? '[]');
+						await modelfiles.set(await getModelfiles(localStorage.token));
+					}}
+				>
+					<div class=" self-center mr-2 font-medium">{$i18n.t('Sync All')}</div>
+
+					<div class=" self-center">
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							viewBox="0 0 16 16"
+							fill="currentColor"
+							class="w-3.5 h-3.5"
+						>
+							<path
+								fill-rule="evenodd"
+								d="M13.836 2.477a.75.75 0 0 1 .75.75v3.182a.75.75 0 0 1-.75.75h-3.182a.75.75 0 0 1 0-1.5h1.37l-.84-.841a4.5 4.5 0 0 0-7.08.932.75.75 0 0 1-1.3-.75 6 6 0 0 1 9.44-1.242l.842.84V3.227a.75.75 0 0 1 .75-.75Zm-.911 7.5A.75.75 0 0 1 13.199 11a6 6 0 0 1-9.44 1.241l-.84-.84v1.371a.75.75 0 0 1-1.5 0V9.591a.75.75 0 0 1 .75-.75H5.35a.75.75 0 0 1 0 1.5H3.98l.841.841a4.5 4.5 0 0 0 7.08-.932.75.75 0 0 1 1.025-.273Z"
+								clip-rule="evenodd"
+							/>
+						</svg>
+					</div>
+				</button>
+
+				<button
+					class="self-center w-fit text-sm p-1.5 border dark:border-gray-600 rounded-xl flex"
+					on:click={async () => {
+						saveModelfiles(localModelfiles);
+
+						localStorage.removeItem('modelfiles');
+						localModelfiles = JSON.parse(localStorage.getItem('modelfiles') ?? '[]');
+						await modelfiles.set(await getModelfiles(localStorage.token));
+					}}
+				>
+					<div class=" self-center">
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							fill="none"
+							viewBox="0 0 24 24"
+							stroke-width="1.5"
+							stroke="currentColor"
+							class="w-4 h-4"
+						>
+							<path
+								stroke-linecap="round"
+								stroke-linejoin="round"
+								d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
+							/>
+						</svg>
+					</div>
+				</button>
+			</div>
+		</div>
+	{/if}
+</div>
+
+<div class=" my-16">
+	<div class=" text-lg font-semibold mb-3">{$i18n.t('Made by OpenWebUI Community')}</div>
+
+	<a
+		class=" flex space-x-4 cursor-pointer w-full mb-2 px-3 py-2"
+		href="https://openwebui.com/"
+		target="_blank"
+	>
+		<div class=" self-center w-10">
+			<div
+				class="w-full h-10 flex justify-center rounded-full bg-transparent dark:bg-gray-700 border border-dashed border-gray-200"
+			>
+				<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-6">
+					<path
+						fill-rule="evenodd"
+						d="M12 3.75a.75.75 0 01.75.75v6.75h6.75a.75.75 0 010 1.5h-6.75v6.75a.75.75 0 01-1.5 0v-6.75H4.5a.75.75 0 010-1.5h6.75V4.5a.75.75 0 01.75-.75z"
+						clip-rule="evenodd"
+					/>
+				</svg>
+			</div>
+		</div>
+
+		<div class=" self-center">
+			<div class=" font-bold">{$i18n.t('Discover a modelfile')}</div>
+			<div class=" text-sm">{$i18n.t('Discover, download, and explore model presets')}</div>
+		</div>
+	</a>
+</div>

+ 114 - 116
src/routes/(app)/playground/+page.svelte → src/lib/components/workspace/Playground.svelte

@@ -268,75 +268,74 @@
 	</title>
 	</title>
 </svelte:head>
 </svelte:head>
 
 
-<div class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white">
-	<div class=" flex flex-col justify-between w-full overflow-y-auto h-[100dvh]">
-		<div class="max-w-2xl mx-auto w-full px-3 md:px-0 my-10 h-full">
-			<div class=" flex flex-col h-full">
-				<div class="flex flex-col justify-between mb-2.5 gap-1">
-					<div class="flex justify-between items-center gap-2">
-						<div class=" text-2xl font-semibold self-center flex">
-							{$i18n.t('Playground')}
-							<span class=" text-xs text-gray-500 self-center ml-1">{$i18n.t('(Beta)')}</span>
-						</div>
-
-						<div>
-							<button
-								class=" flex items-center gap-0.5 text-xs px-2.5 py-0.5 rounded-lg {mode ===
-									'chat' && 'text-sky-600 dark:text-sky-200 bg-sky-200/30'} {mode === 'complete' &&
-									'text-green-600 dark:text-green-200 bg-green-200/30'} "
-								on:click={() => {
-									if (mode === 'complete') {
-										mode = 'chat';
-									} else {
-										mode = 'complete';
-									}
-								}}
-							>
-								{#if mode === 'complete'}
-									{$i18n.t('Text Completion')}
-								{:else if mode === 'chat'}
-									{$i18n.t('Chat')}
-								{/if}
-
-								<div>
-									<svg
-										xmlns="http://www.w3.org/2000/svg"
-										viewBox="0 0 16 16"
-										fill="currentColor"
-										class="w-3 h-3"
-									>
-										<path
-											fill-rule="evenodd"
-											d="M5.22 10.22a.75.75 0 0 1 1.06 0L8 11.94l1.72-1.72a.75.75 0 1 1 1.06 1.06l-2.25 2.25a.75.75 0 0 1-1.06 0l-2.25-2.25a.75.75 0 0 1 0-1.06ZM10.78 5.78a.75.75 0 0 1-1.06 0L8 4.06 6.28 5.78a.75.75 0 0 1-1.06-1.06l2.25-2.25a.75.75 0 0 1 1.06 0l2.25 2.25a.75.75 0 0 1 0 1.06Z"
-											clip-rule="evenodd"
-										/>
-									</svg>
-								</div>
-							</button>
-						</div>
+<div class=" flex flex-col justify-between w-full overflow-y-auto h-full">
+	<div class="mx-auto w-full md:px-0 h-full">
+		<div class=" flex flex-col h-full">
+			<div class="flex flex-col justify-between mb-2.5 gap-1">
+				<div class="flex justify-between items-center gap-2">
+					<div class=" text-lg font-semibold self-center flex">
+						{$i18n.t('Playground')}
+						<span class=" text-xs text-gray-500 self-center ml-1">{$i18n.t('(Beta)')}</span>
 					</div>
 					</div>
 
 
-					<div class="flex flex-col gap-1 w-full">
-						<div class="flex w-full">
-							<div class="overflow-hidden w-full">
-								<div class="max-w-full">
-									<Selector
-										placeholder={$i18n.t('Select a model')}
-										items={$models
-											.filter((model) => model.name !== 'hr')
-											.map((model) => ({
-												value: model.id,
-												label: model.name,
-												info: model
-											}))}
-										bind:value={selectedModelId}
-										className="w-[42rem]"
+					<div>
+						<button
+							class=" flex items-center gap-0.5 text-xs px-2.5 py-0.5 rounded-lg {mode === 'chat' &&
+								'text-sky-600 dark:text-sky-200 bg-sky-200/30'} {mode === 'complete' &&
+								'text-green-600 dark:text-green-200 bg-green-200/30'} "
+							on:click={() => {
+								if (mode === 'complete') {
+									mode = 'chat';
+								} else {
+									mode = 'complete';
+								}
+							}}
+						>
+							{#if mode === 'complete'}
+								{$i18n.t('Text Completion')}
+							{:else if mode === 'chat'}
+								{$i18n.t('Chat')}
+							{/if}
+
+							<div>
+								<svg
+									xmlns="http://www.w3.org/2000/svg"
+									viewBox="0 0 16 16"
+									fill="currentColor"
+									class="w-3 h-3"
+								>
+									<path
+										fill-rule="evenodd"
+										d="M5.22 10.22a.75.75 0 0 1 1.06 0L8 11.94l1.72-1.72a.75.75 0 1 1 1.06 1.06l-2.25 2.25a.75.75 0 0 1-1.06 0l-2.25-2.25a.75.75 0 0 1 0-1.06ZM10.78 5.78a.75.75 0 0 1-1.06 0L8 4.06 6.28 5.78a.75.75 0 0 1-1.06-1.06l2.25-2.25a.75.75 0 0 1 1.06 0l2.25 2.25a.75.75 0 0 1 0 1.06Z"
+										clip-rule="evenodd"
 									/>
 									/>
-								</div>
+								</svg>
+							</div>
+						</button>
+					</div>
+				</div>
+
+				<div class="flex flex-col gap-1 w-full">
+					<div class="flex w-full">
+						<div class="overflow-hidden w-full">
+							<div class="max-w-full">
+								<Selector
+									placeholder={$i18n.t('Select a model')}
+									items={$models
+										.filter((model) => model.name !== 'hr')
+										.map((model) => ({
+											value: model.id,
+											label: model.name,
+											info: model
+										}))}
+									bind:value={selectedModelId}
+									className="w-[42rem]"
+								/>
 							</div>
 							</div>
 						</div>
 						</div>
+					</div>
 
 
-						<!-- <button
+					<!-- <button
 							class=" self-center dark:hover:text-gray-300"
 							class=" self-center dark:hover:text-gray-300"
 							id="open-settings-button"
 							id="open-settings-button"
 							on:click={async () => {}}
 							on:click={async () => {}}
@@ -361,67 +360,66 @@
 								/>
 								/>
 							</svg>
 							</svg>
 						</button> -->
 						</button> -->
-					</div>
 				</div>
 				</div>
+			</div>
 
 
-				{#if mode === 'chat'}
-					<div class="p-1">
-						<div class="p-3 outline outline-1 outline-gray-200 dark:outline-gray-800 rounded-lg">
-							<div class=" text-sm font-medium">{$i18n.t('System')}</div>
+			{#if mode === 'chat'}
+				<div class="p-1">
+					<div class="p-3 outline outline-1 outline-gray-200 dark:outline-gray-800 rounded-lg">
+						<div class=" text-sm font-medium">{$i18n.t('System')}</div>
+						<textarea
+							id="system-textarea"
+							class="w-full h-full bg-transparent resize-none outline-none text-sm"
+							bind:value={system}
+							placeholder={$i18n.t("You're a helpful assistant.")}
+							rows="4"
+						/>
+					</div>
+				</div>
+			{/if}
+
+			<div
+				class=" pb-2.5 flex flex-col justify-between w-full flex-auto overflow-auto h-0"
+				id="messages-container"
+				bind:this={messagesContainerElement}
+			>
+				<div class=" h-full w-full flex flex-col">
+					<div class="flex-1 p-1">
+						{#if mode === 'complete'}
 							<textarea
 							<textarea
-								id="system-textarea"
-								class="w-full h-full bg-transparent resize-none outline-none text-sm"
-								bind:value={system}
+								id="text-completion-textarea"
+								bind:this={textCompletionAreaElement}
+								class="w-full h-full p-3 bg-transparent outline outline-1 outline-gray-200 dark:outline-gray-800 resize-none rounded-lg text-sm"
+								bind:value={text}
 								placeholder={$i18n.t("You're a helpful assistant.")}
 								placeholder={$i18n.t("You're a helpful assistant.")}
-								rows="4"
 							/>
 							/>
-						</div>
-					</div>
-				{/if}
-
-				<div
-					class=" pb-2.5 flex flex-col justify-between w-full flex-auto overflow-auto h-0"
-					id="messages-container"
-					bind:this={messagesContainerElement}
-				>
-					<div class=" h-full w-full flex flex-col">
-						<div class="flex-1 p-1">
-							{#if mode === 'complete'}
-								<textarea
-									id="text-completion-textarea"
-									bind:this={textCompletionAreaElement}
-									class="w-full h-full p-3 bg-transparent outline outline-1 outline-gray-200 dark:outline-gray-800 resize-none rounded-lg text-sm"
-									bind:value={text}
-									placeholder={$i18n.t("You're a helpful assistant.")}
-								/>
-							{:else if mode === 'chat'}
-								<ChatCompletion bind:messages />
-							{/if}
-						</div>
+						{:else if mode === 'chat'}
+							<ChatCompletion bind:messages />
+						{/if}
 					</div>
 					</div>
 				</div>
 				</div>
+			</div>
 
 
-				<div class="pb-2">
-					{#if !loading}
-						<button
-							class="px-3 py-1.5 text-sm font-medium bg-emerald-600 hover:bg-emerald-700 text-gray-50 transition rounded-lg"
-							on:click={() => {
-								submitHandler();
-							}}
-						>
-							{$i18n.t('Submit')}
-						</button>
-					{:else}
-						<button
-							class="px-3 py-1.5 text-sm font-medium bg-gray-100 hover:bg-gray-200 text-gray-900 transition rounded-lg"
-							on:click={() => {
-								stopResponse();
-							}}
-						>
-							{$i18n.t('Cancel')}
-						</button>
-					{/if}
-				</div>
+			<div class="pb-3">
+				{#if !loading}
+					<button
+						class="px-3 py-1.5 text-sm font-medium bg-emerald-600 hover:bg-emerald-700 text-gray-50 transition rounded-lg"
+						on:click={() => {
+							submitHandler();
+						}}
+					>
+						{$i18n.t('Submit')}
+					</button>
+				{:else}
+					<button
+						class="px-3 py-1.5 text-sm font-medium bg-gray-100 hover:bg-gray-200 text-gray-900 transition rounded-lg"
+						on:click={() => {
+							stopResponse();
+						}}
+					>
+						{$i18n.t('Cancel')}
+					</button>
+				{/if}
 			</div>
 			</div>
 		</div>
 		</div>
 	</div>
 	</div>

+ 331 - 0
src/lib/components/workspace/Prompts.svelte

@@ -0,0 +1,331 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+	import fileSaver from 'file-saver';
+	const { saveAs } = fileSaver;
+
+	import { onMount, getContext } from 'svelte';
+	import { WEBUI_NAME, prompts } from '$lib/stores';
+	import { createNewPrompt, deletePromptByCommand, getPrompts } from '$lib/apis/prompts';
+	import { error } from '@sveltejs/kit';
+	import { goto } from '$app/navigation';
+
+	const i18n = getContext('i18n');
+
+	let importFiles = '';
+	let query = '';
+	let promptsImportInputElement: HTMLInputElement;
+	const sharePrompt = async (prompt) => {
+		toast.success($i18n.t('Redirecting you to OpenWebUI Community'));
+
+		const url = 'https://openwebui.com';
+
+		const tab = await window.open(`${url}/prompts/create`, '_blank');
+		window.addEventListener(
+			'message',
+			(event) => {
+				if (event.origin !== url) return;
+				if (event.data === 'loaded') {
+					tab.postMessage(JSON.stringify(prompt), '*');
+				}
+			},
+			false
+		);
+	};
+
+	const deletePrompt = async (command) => {
+		await deletePromptByCommand(localStorage.token, command);
+		await prompts.set(await getPrompts(localStorage.token));
+	};
+</script>
+
+<svelte:head>
+	<title>
+		{$i18n.t('Prompts')} | {$WEBUI_NAME}
+	</title>
+</svelte:head>
+
+<div class="mb-3 flex justify-between items-center">
+	<div class=" text-lg font-semibold self-center">{$i18n.t('Prompts')}</div>
+</div>
+
+<div class=" flex w-full space-x-2">
+	<div class="flex flex-1">
+		<div class=" self-center ml-1 mr-3">
+			<svg
+				xmlns="http://www.w3.org/2000/svg"
+				viewBox="0 0 20 20"
+				fill="currentColor"
+				class="w-4 h-4"
+			>
+				<path
+					fill-rule="evenodd"
+					d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z"
+					clip-rule="evenodd"
+				/>
+			</svg>
+		</div>
+		<input
+			class=" w-full text-sm pr-4 py-1 rounded-r-xl outline-none bg-transparent"
+			bind:value={query}
+			placeholder={$i18n.t('Search Prompts')}
+		/>
+	</div>
+
+	<div>
+		<a
+			class=" px-2 py-2 rounded-xl border border-gray-200 dark:border-gray-600 dark:border-0 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 transition font-medium text-sm flex items-center space-x-1"
+			href="/workspace/prompts/create"
+		>
+			<svg
+				xmlns="http://www.w3.org/2000/svg"
+				viewBox="0 0 16 16"
+				fill="currentColor"
+				class="w-4 h-4"
+			>
+				<path
+					d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
+				/>
+			</svg>
+		</a>
+	</div>
+</div>
+<hr class=" dark:border-gray-850 my-2.5" />
+
+<div class="my-3 mb-5">
+	{#each $prompts.filter((p) => query === '' || p.command.includes(query)) as prompt}
+		<div
+			class=" flex space-x-4 cursor-pointer w-full px-3 py-2 dark:hover:bg-white/5 hover:bg-black/5 rounded-xl"
+		>
+			<div class=" flex flex-1 space-x-4 cursor-pointer w-full">
+				<a href={`/workspace/prompts/edit?command=${encodeURIComponent(prompt.command)}`}>
+					<div class=" flex-1 self-center pl-5">
+						<div class=" font-bold">{prompt.command}</div>
+						<div class=" text-xs overflow-hidden text-ellipsis line-clamp-1">
+							{prompt.title}
+						</div>
+					</div>
+				</a>
+			</div>
+			<div class="flex flex-row space-x-1 self-center">
+				<a
+					class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+					type="button"
+					href={`/workspace/prompts/edit?command=${encodeURIComponent(prompt.command)}`}
+				>
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						fill="none"
+						viewBox="0 0 24 24"
+						stroke-width="1.5"
+						stroke="currentColor"
+						class="w-4 h-4"
+					>
+						<path
+							stroke-linecap="round"
+							stroke-linejoin="round"
+							d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125"
+						/>
+					</svg>
+				</a>
+
+				<button
+					class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+					type="button"
+					on:click={() => {
+						// console.log(modelfile);
+						sessionStorage.prompt = JSON.stringify(prompt);
+						goto('/workspace/prompts/create');
+					}}
+				>
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						fill="none"
+						viewBox="0 0 24 24"
+						stroke-width="1.5"
+						stroke="currentColor"
+						class="w-4 h-4"
+					>
+						<path
+							stroke-linecap="round"
+							stroke-linejoin="round"
+							d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 0 1-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 0 1 1.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 0 0-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 0 1-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 0 0-3.375-3.375h-1.5a1.125 1.125 0 0 1-1.125-1.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H9.75"
+						/>
+					</svg>
+				</button>
+
+				<button
+					class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+					type="button"
+					on:click={() => {
+						sharePrompt(prompt);
+					}}
+				>
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						fill="none"
+						viewBox="0 0 24 24"
+						stroke-width="1.5"
+						stroke="currentColor"
+						class="w-4 h-4"
+					>
+						<path
+							stroke-linecap="round"
+							stroke-linejoin="round"
+							d="M7.217 10.907a2.25 2.25 0 100 2.186m0-2.186c.18.324.283.696.283 1.093s-.103.77-.283 1.093m0-2.186l9.566-5.314m-9.566 7.5l9.566 5.314m0 0a2.25 2.25 0 103.935 2.186 2.25 2.25 0 00-3.935-2.186zm0-12.814a2.25 2.25 0 103.933-2.185 2.25 2.25 0 00-3.933 2.185z"
+						/>
+					</svg>
+				</button>
+
+				<button
+					class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+					type="button"
+					on:click={() => {
+						deletePrompt(prompt.command);
+					}}
+				>
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						fill="none"
+						viewBox="0 0 24 24"
+						stroke-width="1.5"
+						stroke="currentColor"
+						class="w-4 h-4"
+					>
+						<path
+							stroke-linecap="round"
+							stroke-linejoin="round"
+							d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
+						/>
+					</svg>
+				</button>
+			</div>
+		</div>
+	{/each}
+</div>
+
+<div class=" flex justify-end w-full mb-3">
+	<div class="flex space-x-2">
+		<input
+			id="prompts-import-input"
+			bind:this={promptsImportInputElement}
+			bind:files={importFiles}
+			type="file"
+			accept=".json"
+			hidden
+			on:change={() => {
+				console.log(importFiles);
+
+				const reader = new FileReader();
+				reader.onload = async (event) => {
+					const savedPrompts = JSON.parse(event.target.result);
+					console.log(savedPrompts);
+
+					for (const prompt of savedPrompts) {
+						await createNewPrompt(
+							localStorage.token,
+							prompt.command.charAt(0) === '/' ? prompt.command.slice(1) : prompt.command,
+							prompt.title,
+							prompt.content
+						).catch((error) => {
+							toast.error(error);
+							return null;
+						});
+					}
+
+					await prompts.set(await getPrompts(localStorage.token));
+				};
+
+				reader.readAsText(importFiles[0]);
+			}}
+		/>
+
+		<button
+			class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
+			on:click={() => {
+				promptsImportInputElement.click();
+			}}
+		>
+			<div class=" self-center mr-2 font-medium">{$i18n.t('Import Prompts')}</div>
+
+			<div class=" self-center">
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 16 16"
+					fill="currentColor"
+					class="w-4 h-4"
+				>
+					<path
+						fill-rule="evenodd"
+						d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 9.5a.75.75 0 0 1-.75-.75V8.06l-.72.72a.75.75 0 0 1-1.06-1.06l2-2a.75.75 0 0 1 1.06 0l2 2a.75.75 0 1 1-1.06 1.06l-.72-.72v2.69a.75.75 0 0 1-.75.75Z"
+						clip-rule="evenodd"
+					/>
+				</svg>
+			</div>
+		</button>
+
+		<button
+			class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
+			on:click={async () => {
+				// promptsImportInputElement.click();
+				let blob = new Blob([JSON.stringify($prompts)], {
+					type: 'application/json'
+				});
+				saveAs(blob, `prompts-export-${Date.now()}.json`);
+			}}
+		>
+			<div class=" self-center mr-2 font-medium">{$i18n.t('Export Prompts')}</div>
+
+			<div class=" self-center">
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 16 16"
+					fill="currentColor"
+					class="w-4 h-4"
+				>
+					<path
+						fill-rule="evenodd"
+						d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 3.5a.75.75 0 0 1 .75.75v2.69l.72-.72a.75.75 0 1 1 1.06 1.06l-2 2a.75.75 0 0 1-1.06 0l-2-2a.75.75 0 0 1 1.06-1.06l.72.72V6.25A.75.75 0 0 1 8 5.5Z"
+						clip-rule="evenodd"
+					/>
+				</svg>
+			</div>
+		</button>
+
+		<!-- <button
+						on:click={() => {
+							loadDefaultPrompts();
+						}}
+					>
+						dd
+					</button> -->
+	</div>
+</div>
+
+<div class=" my-16">
+	<div class=" text-lg font-semibold mb-3">{$i18n.t('Made by OpenWebUI Community')}</div>
+
+	<a
+		class=" flex space-x-4 cursor-pointer w-full mb-3 px-3 py-2"
+		href="https://openwebui.com/?type=prompts"
+		target="_blank"
+	>
+		<div class=" self-center w-10">
+			<div
+				class="w-full h-10 flex justify-center rounded-full bg-transparent dark:bg-gray-700 border border-dashed border-gray-200"
+			>
+				<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-6">
+					<path
+						fill-rule="evenodd"
+						d="M12 3.75a.75.75 0 01.75.75v6.75h6.75a.75.75 0 010 1.5h-6.75v6.75a.75.75 0 01-1.5 0v-6.75H4.5a.75.75 0 010-1.5h6.75V4.5a.75.75 0 01.75-.75z"
+						clip-rule="evenodd"
+					/>
+				</svg>
+			</div>
+		</div>
+
+		<div class=" self-center">
+			<div class=" font-bold">{$i18n.t('Discover a prompt')}</div>
+			<div class=" text-sm">{$i18n.t('Discover, download, and explore custom prompts')}</div>
+		</div>
+	</a>
+</div>

+ 3 - 6
src/lib/i18n/locales/ar-BH/translation.json

@@ -117,7 +117,6 @@
 	"Dark": "مظلم",
 	"Dark": "مظلم",
 	"Dashboard": "لوحة التحكم",
 	"Dashboard": "لوحة التحكم",
 	"Database": "قاعدة البيانات",
 	"Database": "قاعدة البيانات",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
 	"December": "ديسمبر",
 	"December": "ديسمبر",
 	"Default": "الإفتراضي",
 	"Default": "الإفتراضي",
 	"Default (Automatic1111)": "(Automatic1111) الإفتراضي",
 	"Default (Automatic1111)": "(Automatic1111) الإفتراضي",
@@ -211,6 +210,7 @@
 	"General Settings": "الاعدادات العامة",
 	"General Settings": "الاعدادات العامة",
 	"Generation Info": "معلومات الجيل",
 	"Generation Info": "معلومات الجيل",
 	"Good Response": "استجابة جيدة",
 	"Good Response": "استجابة جيدة",
+	"h:mm a": "",
 	"has no conversations.": "ليس لديه محادثات.",
 	"has no conversations.": "ليس لديه محادثات.",
 	"Hello, {{name}}": " {{name}} مرحبا",
 	"Hello, {{name}}": " {{name}} مرحبا",
 	"Help": "مساعدة",
 	"Help": "مساعدة",
@@ -276,9 +276,6 @@
 	"Modelfiles": "ملفات الموديل",
 	"Modelfiles": "ملفات الموديل",
 	"Models": "الموديلات",
 	"Models": "الموديلات",
 	"More": "المزيد",
 	"More": "المزيد",
-	"My Documents": "مستنداتي",
-	"My Modelfiles": "ملفاتي النموذجية",
-	"My Prompts": "مطالباتي",
 	"Name": "الأسم",
 	"Name": "الأسم",
 	"Name Tag": "أسم التاق",
 	"Name Tag": "أسم التاق",
 	"Name your modelfile": "قم بتسمية ملف النموذج الخاص بك",
 	"Name your modelfile": "قم بتسمية ملف النموذج الخاص بك",
@@ -362,7 +359,6 @@
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Save": "حفظ",
 	"Save": "حفظ",
 	"Save & Create": "حفظ وإنشاء",
 	"Save & Create": "حفظ وإنشاء",
-	"Save & Submit": "حفظ وإرسال",
 	"Save & Update": "حفظ وتحديث",
 	"Save & Update": "حفظ وتحديث",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "لم يعد حفظ سجلات الدردشة مباشرة في مساحة تخزين متصفحك مدعومًا. يرجى تخصيص بعض الوقت لتنزيل وحذف سجلات الدردشة الخاصة بك عن طريق النقر على الزر أدناه. لا تقلق، يمكنك بسهولة إعادة استيراد سجلات الدردشة الخاصة بك إلى الواجهة الخلفية من خلاله",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "لم يعد حفظ سجلات الدردشة مباشرة في مساحة تخزين متصفحك مدعومًا. يرجى تخصيص بعض الوقت لتنزيل وحذف سجلات الدردشة الخاصة بك عن طريق النقر على الزر أدناه. لا تقلق، يمكنك بسهولة إعادة استيراد سجلات الدردشة الخاصة بك إلى الواجهة الخلفية من خلاله",
 	"Scan": "مسح",
 	"Scan": "مسح",
@@ -379,6 +375,7 @@
 	"Select a model": "أختار الموديل",
 	"Select a model": "أختار الموديل",
 	"Select an Ollama instance": "أختار سيرفر ",
 	"Select an Ollama instance": "أختار سيرفر ",
 	"Select model": " أختار موديل",
 	"Select model": " أختار موديل",
+	"Send": "",
 	"Send a Message": "يُرجى إدخال طلبك هنا",
 	"Send a Message": "يُرجى إدخال طلبك هنا",
 	"Send message": "يُرجى إدخال طلبك هنا.",
 	"Send message": "يُرجى إدخال طلبك هنا.",
 	"September": "سبتمبر",
 	"September": "سبتمبر",
@@ -482,10 +479,10 @@
 	"What’s New in": "ما هو الجديد",
 	"What’s New in": "ما هو الجديد",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "عند إيقاف تشغيل السجل، لن تظهر الدردشات الجديدة على هذا المتصفح في سجلك على أي من أجهزتك.",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "عند إيقاف تشغيل السجل، لن تظهر الدردشات الجديدة على هذا المتصفح في سجلك على أي من أجهزتك.",
 	"Whisper (Local)": "Whisper (Local)",
 	"Whisper (Local)": "Whisper (Local)",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "اكتب اقتراحًا سريعًا (على سبيل المثال، من أنت؟)",
 	"Write a prompt suggestion (e.g. Who are you?)": "اكتب اقتراحًا سريعًا (على سبيل المثال، من أنت؟)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "اكتب ملخصًا في 50 كلمة يلخص [الموضوع أو الكلمة الرئيسية]",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "اكتب ملخصًا في 50 كلمة يلخص [الموضوع أو الكلمة الرئيسية]",
 	"Yesterday": "أمس",
 	"Yesterday": "أمس",
-	"You": "أنت",
 	"You have no archived conversations.": "لا تملك محادثات محفوظه",
 	"You have no archived conversations.": "لا تملك محادثات محفوظه",
 	"You have shared this chat": "تم مشاركة هذه المحادثة",
 	"You have shared this chat": "تم مشاركة هذه المحادثة",
 	"You're a helpful assistant.": "مساعدك المفيد هنا",
 	"You're a helpful assistant.": "مساعدك المفيد هنا",

+ 3 - 6
src/lib/i18n/locales/bg-BG/translation.json

@@ -117,7 +117,6 @@
 	"Dark": "Тъмен",
 	"Dark": "Тъмен",
 	"Dashboard": "",
 	"Dashboard": "",
 	"Database": "База данни",
 	"Database": "База данни",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
 	"December": "",
 	"December": "",
 	"Default": "По подразбиране",
 	"Default": "По подразбиране",
 	"Default (Automatic1111)": "По подразбиране (Automatic1111)",
 	"Default (Automatic1111)": "По подразбиране (Automatic1111)",
@@ -211,6 +210,7 @@
 	"General Settings": "Основни Настройки",
 	"General Settings": "Основни Настройки",
 	"Generation Info": "",
 	"Generation Info": "",
 	"Good Response": "",
 	"Good Response": "",
+	"h:mm a": "",
 	"has no conversations.": "",
 	"has no conversations.": "",
 	"Hello, {{name}}": "Здравей, {{name}}",
 	"Hello, {{name}}": "Здравей, {{name}}",
 	"Help": "",
 	"Help": "",
@@ -276,9 +276,6 @@
 	"Modelfiles": "Модфайлове",
 	"Modelfiles": "Модфайлове",
 	"Models": "Модели",
 	"Models": "Модели",
 	"More": "",
 	"More": "",
-	"My Documents": "Мои документи",
-	"My Modelfiles": "Мои модфайлове",
-	"My Prompts": "Мои промптове",
 	"Name": "Име",
 	"Name": "Име",
 	"Name Tag": "Име Таг",
 	"Name Tag": "Име Таг",
 	"Name your modelfile": "Име на модфайла",
 	"Name your modelfile": "Име на модфайла",
@@ -362,7 +359,6 @@
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Save": "Запис",
 	"Save": "Запис",
 	"Save & Create": "Запис & Създаване",
 	"Save & Create": "Запис & Създаване",
-	"Save & Submit": "Запис & Изпращане",
 	"Save & Update": "Запис & Актуализиране",
 	"Save & Update": "Запис & Актуализиране",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Запазването на чат логове директно в хранилището на вашия браузър вече не се поддържа. Моля, отделете малко време, за да изтеглите и изтриете чат логовете си, като щракнете върху бутона по-долу. Не се притеснявайте, можете лесно да импортирате отново чат логовете си в бекенда чрез",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Запазването на чат логове директно в хранилището на вашия браузър вече не се поддържа. Моля, отделете малко време, за да изтеглите и изтриете чат логовете си, като щракнете върху бутона по-долу. Не се притеснявайте, можете лесно да импортирате отново чат логовете си в бекенда чрез",
 	"Scan": "Сканиране",
 	"Scan": "Сканиране",
@@ -379,6 +375,7 @@
 	"Select a model": "Изберете модел",
 	"Select a model": "Изберете модел",
 	"Select an Ollama instance": "Изберете Ollama инстанция",
 	"Select an Ollama instance": "Изберете Ollama инстанция",
 	"Select model": "",
 	"Select model": "",
+	"Send": "",
 	"Send a Message": "Изпращане на Съобщение",
 	"Send a Message": "Изпращане на Съобщение",
 	"Send message": "Изпращане на съобщение",
 	"Send message": "Изпращане на съобщение",
 	"September": "",
 	"September": "",
@@ -482,10 +479,10 @@
 	"What’s New in": "Какво е новото в",
 	"What’s New in": "Какво е новото в",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Когато историята е изключена, нови чатове в този браузър ще не се показват в историята на никои от вашия профил.",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Когато историята е изключена, нови чатове в този браузър ще не се показват в историята на никои от вашия профил.",
 	"Whisper (Local)": "Whisper (Локален)",
 	"Whisper (Local)": "Whisper (Локален)",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "Напиши предложение за промпт (напр. Кой сте вие?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "Напиши предложение за промпт (напр. Кой сте вие?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Напиши описание в 50 знака, което описва [тема или ключова дума].",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Напиши описание в 50 знака, което описва [тема или ключова дума].",
 	"Yesterday": "",
 	"Yesterday": "",
-	"You": "Вие",
 	"You have no archived conversations.": "",
 	"You have no archived conversations.": "",
 	"You have shared this chat": "",
 	"You have shared this chat": "",
 	"You're a helpful assistant.": "Вие сте полезен асистент.",
 	"You're a helpful assistant.": "Вие сте полезен асистент.",

+ 3 - 6
src/lib/i18n/locales/bn-BD/translation.json

@@ -117,7 +117,6 @@
 	"Dark": "ডার্ক",
 	"Dark": "ডার্ক",
 	"Dashboard": "",
 	"Dashboard": "",
 	"Database": "ডেটাবেজ",
 	"Database": "ডেটাবেজ",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
 	"December": "",
 	"December": "",
 	"Default": "ডিফল্ট",
 	"Default": "ডিফল্ট",
 	"Default (Automatic1111)": "ডিফল্ট (Automatic1111)",
 	"Default (Automatic1111)": "ডিফল্ট (Automatic1111)",
@@ -211,6 +210,7 @@
 	"General Settings": "সাধারণ সেটিংসমূহ",
 	"General Settings": "সাধারণ সেটিংসমূহ",
 	"Generation Info": "",
 	"Generation Info": "",
 	"Good Response": "",
 	"Good Response": "",
+	"h:mm a": "",
 	"has no conversations.": "",
 	"has no conversations.": "",
 	"Hello, {{name}}": "হ্যালো, {{name}}",
 	"Hello, {{name}}": "হ্যালো, {{name}}",
 	"Help": "",
 	"Help": "",
@@ -276,9 +276,6 @@
 	"Modelfiles": "মডেলফাইলসমূহ",
 	"Modelfiles": "মডেলফাইলসমূহ",
 	"Models": "মডেলসমূহ",
 	"Models": "মডেলসমূহ",
 	"More": "",
 	"More": "",
-	"My Documents": "আমার ডকুমেন্টসমূহ",
-	"My Modelfiles": "আমার মডেলফাইলসমূহ",
-	"My Prompts": "আমার প্রম্পটসমূহ",
 	"Name": "নাম",
 	"Name": "নাম",
 	"Name Tag": "নামের ট্যাগ",
 	"Name Tag": "নামের ট্যাগ",
 	"Name your modelfile": "আপনার মডেলফাইলের নাম দিন",
 	"Name your modelfile": "আপনার মডেলফাইলের নাম দিন",
@@ -362,7 +359,6 @@
 	"Rosé Pine Dawn": "ভোরের রোজ পাইন",
 	"Rosé Pine Dawn": "ভোরের রোজ পাইন",
 	"Save": "সংরক্ষণ",
 	"Save": "সংরক্ষণ",
 	"Save & Create": "সংরক্ষণ এবং তৈরি করুন",
 	"Save & Create": "সংরক্ষণ এবং তৈরি করুন",
-	"Save & Submit": "সংরক্ষণ এবং সাবমিট করুন",
 	"Save & Update": "সংরক্ষণ এবং আপডেট করুন",
 	"Save & Update": "সংরক্ষণ এবং আপডেট করুন",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "মাধ্যমে",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "মাধ্যমে",
 	"Scan": "স্ক্যান",
 	"Scan": "স্ক্যান",
@@ -379,6 +375,7 @@
 	"Select a model": "একটি মডেল নির্বাচন করুন",
 	"Select a model": "একটি মডেল নির্বাচন করুন",
 	"Select an Ollama instance": "একটি Ollama ইন্সট্যান্স নির্বাচন করুন",
 	"Select an Ollama instance": "একটি Ollama ইন্সট্যান্স নির্বাচন করুন",
 	"Select model": "",
 	"Select model": "",
+	"Send": "",
 	"Send a Message": "একটি মেসেজ পাঠান",
 	"Send a Message": "একটি মেসেজ পাঠান",
 	"Send message": "মেসেজ পাঠান",
 	"Send message": "মেসেজ পাঠান",
 	"September": "",
 	"September": "",
@@ -482,10 +479,10 @@
 	"What’s New in": "এতে নতুন কী",
 	"What’s New in": "এতে নতুন কী",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "যদি হিস্টোরি বন্ধ থাকে তাহলে এই ব্রাউজারের নতুন চ্যাটগুলো আপনার কোন ডিভাইসের হিস্টোরিতেই দেখা যাবে না।",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "যদি হিস্টোরি বন্ধ থাকে তাহলে এই ব্রাউজারের নতুন চ্যাটগুলো আপনার কোন ডিভাইসের হিস্টোরিতেই দেখা যাবে না।",
 	"Whisper (Local)": "Whisper (লোকাল)",
 	"Whisper (Local)": "Whisper (লোকাল)",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "একটি প্রম্পট সাজেশন লিখুন (যেমন Who are you?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "একটি প্রম্পট সাজেশন লিখুন (যেমন Who are you?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "৫০ শব্দের মধ্যে [topic or keyword] এর একটি সারসংক্ষেপ লিখুন।",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "৫০ শব্দের মধ্যে [topic or keyword] এর একটি সারসংক্ষেপ লিখুন।",
 	"Yesterday": "",
 	"Yesterday": "",
-	"You": "আপনি",
 	"You have no archived conversations.": "",
 	"You have no archived conversations.": "",
 	"You have shared this chat": "",
 	"You have shared this chat": "",
 	"You're a helpful assistant.": "আপনি একজন উপকারী এসিস্ট্যান্ট",
 	"You're a helpful assistant.": "আপনি একজন উপকারী এসিস্ট্যান্ট",

+ 3 - 6
src/lib/i18n/locales/ca-ES/translation.json

@@ -117,7 +117,6 @@
 	"Dark": "Fosc",
 	"Dark": "Fosc",
 	"Dashboard": "",
 	"Dashboard": "",
 	"Database": "Base de Dades",
 	"Database": "Base de Dades",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
 	"December": "",
 	"December": "",
 	"Default": "Per defecte",
 	"Default": "Per defecte",
 	"Default (Automatic1111)": "Per defecte (Automatic1111)",
 	"Default (Automatic1111)": "Per defecte (Automatic1111)",
@@ -211,6 +210,7 @@
 	"General Settings": "Configuració General",
 	"General Settings": "Configuració General",
 	"Generation Info": "",
 	"Generation Info": "",
 	"Good Response": "",
 	"Good Response": "",
+	"h:mm a": "",
 	"has no conversations.": "",
 	"has no conversations.": "",
 	"Hello, {{name}}": "Hola, {{name}}",
 	"Hello, {{name}}": "Hola, {{name}}",
 	"Help": "",
 	"Help": "",
@@ -276,9 +276,6 @@
 	"Modelfiles": "Fitxers de Model",
 	"Modelfiles": "Fitxers de Model",
 	"Models": "Models",
 	"Models": "Models",
 	"More": "",
 	"More": "",
-	"My Documents": "Els Meus Documents",
-	"My Modelfiles": "Els Meus Fitxers de Model",
-	"My Prompts": "Els Meus Prompts",
 	"Name": "Nom",
 	"Name": "Nom",
 	"Name Tag": "Etiqueta de Nom",
 	"Name Tag": "Etiqueta de Nom",
 	"Name your modelfile": "Nomena el teu fitxer de model",
 	"Name your modelfile": "Nomena el teu fitxer de model",
@@ -362,7 +359,6 @@
 	"Rosé Pine Dawn": "Albada Rosé Pine",
 	"Rosé Pine Dawn": "Albada Rosé Pine",
 	"Save": "Guarda",
 	"Save": "Guarda",
 	"Save & Create": "Guarda i Crea",
 	"Save & Create": "Guarda i Crea",
-	"Save & Submit": "Guarda i Envia",
 	"Save & Update": "Guarda i Actualitza",
 	"Save & Update": "Guarda i Actualitza",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Guardar registres de xat directament a l'emmagatzematge del teu navegador ja no és suportat. Si us plau, pren un moment per descarregar i eliminar els teus registres de xat fent clic al botó de sota. No et preocupis, pots reimportar fàcilment els teus registres de xat al backend a través de",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Guardar registres de xat directament a l'emmagatzematge del teu navegador ja no és suportat. Si us plau, pren un moment per descarregar i eliminar els teus registres de xat fent clic al botó de sota. No et preocupis, pots reimportar fàcilment els teus registres de xat al backend a través de",
 	"Scan": "Escaneja",
 	"Scan": "Escaneja",
@@ -379,6 +375,7 @@
 	"Select a model": "Selecciona un model",
 	"Select a model": "Selecciona un model",
 	"Select an Ollama instance": "Selecciona una instància d'Ollama",
 	"Select an Ollama instance": "Selecciona una instància d'Ollama",
 	"Select model": "",
 	"Select model": "",
+	"Send": "",
 	"Send a Message": "Envia un Missatge",
 	"Send a Message": "Envia un Missatge",
 	"Send message": "Envia missatge",
 	"Send message": "Envia missatge",
 	"September": "",
 	"September": "",
@@ -482,10 +479,10 @@
 	"What’s New in": "Què hi ha de Nou en",
 	"What’s New in": "Què hi ha de Nou en",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Quan l'historial està desactivat, els nous xats en aquest navegador no apareixeran en el teu historial en cap dels teus dispositius.",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Quan l'historial està desactivat, els nous xats en aquest navegador no apareixeran en el teu historial en cap dels teus dispositius.",
 	"Whisper (Local)": "Whisper (Local)",
 	"Whisper (Local)": "Whisper (Local)",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "Escriu una suggerència de prompt (p. ex. Qui ets tu?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "Escriu una suggerència de prompt (p. ex. Qui ets tu?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Escriu un resum en 50 paraules que resumeixi [tema o paraula clau].",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Escriu un resum en 50 paraules que resumeixi [tema o paraula clau].",
 	"Yesterday": "",
 	"Yesterday": "",
-	"You": "Tu",
 	"You have no archived conversations.": "",
 	"You have no archived conversations.": "",
 	"You have shared this chat": "",
 	"You have shared this chat": "",
 	"You're a helpful assistant.": "Ets un assistent útil.",
 	"You're a helpful assistant.": "Ets un assistent útil.",

+ 3 - 6
src/lib/i18n/locales/de-DE/translation.json

@@ -117,7 +117,6 @@
 	"Dark": "Dunkel",
 	"Dark": "Dunkel",
 	"Dashboard": "Dashboard",
 	"Dashboard": "Dashboard",
 	"Database": "Datenbank",
 	"Database": "Datenbank",
-	"DD/MM/YYYY HH:mm": "DD.MM.YYYY HH:mm",
 	"December": "Dezember",
 	"December": "Dezember",
 	"Default": "Standard",
 	"Default": "Standard",
 	"Default (Automatic1111)": "Standard (Automatic1111)",
 	"Default (Automatic1111)": "Standard (Automatic1111)",
@@ -211,6 +210,7 @@
 	"General Settings": "Allgemeine Einstellungen",
 	"General Settings": "Allgemeine Einstellungen",
 	"Generation Info": "Generierungsinformationen",
 	"Generation Info": "Generierungsinformationen",
 	"Good Response": "Gute Antwort",
 	"Good Response": "Gute Antwort",
+	"h:mm a": "",
 	"has no conversations.": "hat keine Unterhaltungen.",
 	"has no conversations.": "hat keine Unterhaltungen.",
 	"Hello, {{name}}": "Hallo, {{name}}",
 	"Hello, {{name}}": "Hallo, {{name}}",
 	"Help": "Hilfe",
 	"Help": "Hilfe",
@@ -276,9 +276,6 @@
 	"Modelfiles": "Modelfiles",
 	"Modelfiles": "Modelfiles",
 	"Models": "Modelle",
 	"Models": "Modelle",
 	"More": "Mehr",
 	"More": "Mehr",
-	"My Documents": "Meine Dokumente",
-	"My Modelfiles": "Meine Modelfiles",
-	"My Prompts": "Meine Prompts",
 	"Name": "Name",
 	"Name": "Name",
 	"Name Tag": "Namens-Tag",
 	"Name Tag": "Namens-Tag",
 	"Name your modelfile": "Benenne dein modelfile",
 	"Name your modelfile": "Benenne dein modelfile",
@@ -362,7 +359,6 @@
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Save": "Speichern",
 	"Save": "Speichern",
 	"Save & Create": "Speichern und erstellen",
 	"Save & Create": "Speichern und erstellen",
-	"Save & Submit": "Speichern und senden",
 	"Save & Update": "Speichern und aktualisieren",
 	"Save & Update": "Speichern und aktualisieren",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Das direkte Speichern von Chat-Protokollen im Browser-Speicher wird nicht mehr unterstützt. Bitte nimm dir einen Moment Zeit, um deine Chat-Protokolle herunterzuladen und zu löschen, indem du auf die Schaltfläche unten klickst. Keine Sorge, du kannst deine Chat-Protokolle problemlos über das Backend wieder importieren.",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Das direkte Speichern von Chat-Protokollen im Browser-Speicher wird nicht mehr unterstützt. Bitte nimm dir einen Moment Zeit, um deine Chat-Protokolle herunterzuladen und zu löschen, indem du auf die Schaltfläche unten klickst. Keine Sorge, du kannst deine Chat-Protokolle problemlos über das Backend wieder importieren.",
 	"Scan": "Scannen",
 	"Scan": "Scannen",
@@ -379,6 +375,7 @@
 	"Select a model": "Ein Modell auswählen",
 	"Select a model": "Ein Modell auswählen",
 	"Select an Ollama instance": "Eine Ollama Instanz auswählen",
 	"Select an Ollama instance": "Eine Ollama Instanz auswählen",
 	"Select model": "",
 	"Select model": "",
+	"Send": "",
 	"Send a Message": "Eine Nachricht senden",
 	"Send a Message": "Eine Nachricht senden",
 	"Send message": "Nachricht senden",
 	"Send message": "Nachricht senden",
 	"September": "September",
 	"September": "September",
@@ -482,10 +479,10 @@
 	"What’s New in": "Was gibt's Neues in",
 	"What’s New in": "Was gibt's Neues in",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Wenn die Historie ausgeschaltet ist, werden neue Chats nicht in deiner Historie auf deine Geräte angezeigt.",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Wenn die Historie ausgeschaltet ist, werden neue Chats nicht in deiner Historie auf deine Geräte angezeigt.",
 	"Whisper (Local)": "Whisper (Lokal)",
 	"Whisper (Local)": "Whisper (Lokal)",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "Gebe einen Prompt-Vorschlag ein (z.B. Wer bist du?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "Gebe einen Prompt-Vorschlag ein (z.B. Wer bist du?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Schreibe eine kurze Zusammenfassung in 50 Wörtern, die [Thema oder Schlüsselwort] zusammenfasst.",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Schreibe eine kurze Zusammenfassung in 50 Wörtern, die [Thema oder Schlüsselwort] zusammenfasst.",
 	"Yesterday": "Gestern",
 	"Yesterday": "Gestern",
-	"You": "Du",
 	"You have no archived conversations.": "Du hast keine archivierten Unterhaltungen.",
 	"You have no archived conversations.": "Du hast keine archivierten Unterhaltungen.",
 	"You have shared this chat": "Du hast diesen Chat",
 	"You have shared this chat": "Du hast diesen Chat",
 	"You're a helpful assistant.": "Du bist ein hilfreicher Assistent.",
 	"You're a helpful assistant.": "Du bist ein hilfreicher Assistent.",

+ 3 - 6
src/lib/i18n/locales/dg-DG/translation.json

@@ -117,7 +117,6 @@
 	"Dark": "Dark",
 	"Dark": "Dark",
 	"Dashboard": "",
 	"Dashboard": "",
 	"Database": "Database",
 	"Database": "Database",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
 	"December": "",
 	"December": "",
 	"Default": "Default",
 	"Default": "Default",
 	"Default (Automatic1111)": "Default (Automatic1111)",
 	"Default (Automatic1111)": "Default (Automatic1111)",
@@ -211,6 +210,7 @@
 	"General Settings": "General Doge Settings",
 	"General Settings": "General Doge Settings",
 	"Generation Info": "",
 	"Generation Info": "",
 	"Good Response": "",
 	"Good Response": "",
+	"h:mm a": "",
 	"has no conversations.": "",
 	"has no conversations.": "",
 	"Hello, {{name}}": "Much helo, {{name}}",
 	"Hello, {{name}}": "Much helo, {{name}}",
 	"Help": "",
 	"Help": "",
@@ -276,9 +276,6 @@
 	"Modelfiles": "Modelfiles",
 	"Modelfiles": "Modelfiles",
 	"Models": "Wowdels",
 	"Models": "Wowdels",
 	"More": "",
 	"More": "",
-	"My Documents": "My Doguments",
-	"My Modelfiles": "My Modelfiles",
-	"My Prompts": "My Promptos",
 	"Name": "Name",
 	"Name": "Name",
 	"Name Tag": "Name Tag",
 	"Name Tag": "Name Tag",
 	"Name your modelfile": "Name your modelfile",
 	"Name your modelfile": "Name your modelfile",
@@ -362,7 +359,6 @@
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Save": "Save much wow",
 	"Save": "Save much wow",
 	"Save & Create": "Save & Create much create",
 	"Save & Create": "Save & Create much create",
-	"Save & Submit": "Save & Submit very submit",
 	"Save & Update": "Save & Update much update",
 	"Save & Update": "Save & Update much update",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Saving chat logs in browser storage not support anymore. Pls download and delete your chat logs by click button below. Much easy re-import to backend through",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Saving chat logs in browser storage not support anymore. Pls download and delete your chat logs by click button below. Much easy re-import to backend through",
 	"Scan": "Scan much scan",
 	"Scan": "Scan much scan",
@@ -379,6 +375,7 @@
 	"Select a model": "Select a model much choice",
 	"Select a model": "Select a model much choice",
 	"Select an Ollama instance": "Select an Ollama instance very choose",
 	"Select an Ollama instance": "Select an Ollama instance very choose",
 	"Select model": "",
 	"Select model": "",
+	"Send": "",
 	"Send a Message": "Send a Message much message",
 	"Send a Message": "Send a Message much message",
 	"Send message": "Send message very send",
 	"Send message": "Send message very send",
 	"September": "",
 	"September": "",
@@ -482,10 +479,10 @@
 	"What’s New in": "What’s New in much new",
 	"What’s New in": "What’s New in much new",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "When history is turned off, new chats on this browser won't appear in your history on any of your devices. Much history.",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "When history is turned off, new chats on this browser won't appear in your history on any of your devices. Much history.",
 	"Whisper (Local)": "Whisper (Local) much whisper",
 	"Whisper (Local)": "Whisper (Local) much whisper",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "Write a prompt suggestion (e.g. Who are you?) much suggest",
 	"Write a prompt suggestion (e.g. Who are you?)": "Write a prompt suggestion (e.g. Who are you?) much suggest",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Write a summary in 50 words that summarizes [topic or keyword]. Much summarize.",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Write a summary in 50 words that summarizes [topic or keyword]. Much summarize.",
 	"Yesterday": "",
 	"Yesterday": "",
-	"You": "You very you",
 	"You have no archived conversations.": "",
 	"You have no archived conversations.": "",
 	"You have shared this chat": "",
 	"You have shared this chat": "",
 	"You're a helpful assistant.": "You're a helpful assistant. Much helpful.",
 	"You're a helpful assistant.": "You're a helpful assistant. Much helpful.",

+ 3 - 6
src/lib/i18n/locales/en-GB/translation.json

@@ -117,7 +117,6 @@
 	"Dark": "",
 	"Dark": "",
 	"Dashboard": "",
 	"Dashboard": "",
 	"Database": "",
 	"Database": "",
-	"DD/MM/YYYY HH:mm": "",
 	"December": "",
 	"December": "",
 	"Default": "",
 	"Default": "",
 	"Default (Automatic1111)": "",
 	"Default (Automatic1111)": "",
@@ -211,6 +210,7 @@
 	"General Settings": "",
 	"General Settings": "",
 	"Generation Info": "",
 	"Generation Info": "",
 	"Good Response": "",
 	"Good Response": "",
+	"h:mm a": "",
 	"has no conversations.": "",
 	"has no conversations.": "",
 	"Hello, {{name}}": "",
 	"Hello, {{name}}": "",
 	"Help": "",
 	"Help": "",
@@ -276,9 +276,6 @@
 	"Modelfiles": "",
 	"Modelfiles": "",
 	"Models": "",
 	"Models": "",
 	"More": "",
 	"More": "",
-	"My Documents": "",
-	"My Modelfiles": "",
-	"My Prompts": "",
 	"Name": "",
 	"Name": "",
 	"Name Tag": "",
 	"Name Tag": "",
 	"Name your modelfile": "",
 	"Name your modelfile": "",
@@ -362,7 +359,6 @@
 	"Rosé Pine Dawn": "",
 	"Rosé Pine Dawn": "",
 	"Save": "",
 	"Save": "",
 	"Save & Create": "",
 	"Save & Create": "",
-	"Save & Submit": "",
 	"Save & Update": "",
 	"Save & Update": "",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "",
 	"Scan": "",
 	"Scan": "",
@@ -379,6 +375,7 @@
 	"Select a model": "",
 	"Select a model": "",
 	"Select an Ollama instance": "",
 	"Select an Ollama instance": "",
 	"Select model": "",
 	"Select model": "",
+	"Send": "",
 	"Send a Message": "",
 	"Send a Message": "",
 	"Send message": "",
 	"Send message": "",
 	"September": "",
 	"September": "",
@@ -482,10 +479,10 @@
 	"What’s New in": "",
 	"What’s New in": "",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "",
 	"Whisper (Local)": "",
 	"Whisper (Local)": "",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "",
 	"Yesterday": "",
 	"Yesterday": "",
-	"You": "",
 	"You have no archived conversations.": "",
 	"You have no archived conversations.": "",
 	"You have shared this chat": "",
 	"You have shared this chat": "",
 	"You're a helpful assistant.": "",
 	"You're a helpful assistant.": "",

+ 3 - 6
src/lib/i18n/locales/en-US/translation.json

@@ -117,7 +117,6 @@
 	"Dark": "",
 	"Dark": "",
 	"Dashboard": "",
 	"Dashboard": "",
 	"Database": "",
 	"Database": "",
-	"DD/MM/YYYY HH:mm": "",
 	"December": "",
 	"December": "",
 	"Default": "",
 	"Default": "",
 	"Default (Automatic1111)": "",
 	"Default (Automatic1111)": "",
@@ -211,6 +210,7 @@
 	"General Settings": "",
 	"General Settings": "",
 	"Generation Info": "",
 	"Generation Info": "",
 	"Good Response": "",
 	"Good Response": "",
+	"h:mm a": "",
 	"has no conversations.": "",
 	"has no conversations.": "",
 	"Hello, {{name}}": "",
 	"Hello, {{name}}": "",
 	"Help": "",
 	"Help": "",
@@ -276,9 +276,6 @@
 	"Modelfiles": "",
 	"Modelfiles": "",
 	"Models": "",
 	"Models": "",
 	"More": "",
 	"More": "",
-	"My Documents": "",
-	"My Modelfiles": "",
-	"My Prompts": "",
 	"Name": "",
 	"Name": "",
 	"Name Tag": "",
 	"Name Tag": "",
 	"Name your modelfile": "",
 	"Name your modelfile": "",
@@ -362,7 +359,6 @@
 	"Rosé Pine Dawn": "",
 	"Rosé Pine Dawn": "",
 	"Save": "",
 	"Save": "",
 	"Save & Create": "",
 	"Save & Create": "",
-	"Save & Submit": "",
 	"Save & Update": "",
 	"Save & Update": "",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "",
 	"Scan": "",
 	"Scan": "",
@@ -379,6 +375,7 @@
 	"Select a model": "",
 	"Select a model": "",
 	"Select an Ollama instance": "",
 	"Select an Ollama instance": "",
 	"Select model": "",
 	"Select model": "",
+	"Send": "",
 	"Send a Message": "",
 	"Send a Message": "",
 	"Send message": "",
 	"Send message": "",
 	"September": "",
 	"September": "",
@@ -482,10 +479,10 @@
 	"What’s New in": "",
 	"What’s New in": "",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "",
 	"Whisper (Local)": "",
 	"Whisper (Local)": "",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "",
 	"Yesterday": "",
 	"Yesterday": "",
-	"You": "",
 	"You have no archived conversations.": "",
 	"You have no archived conversations.": "",
 	"You have shared this chat": "",
 	"You have shared this chat": "",
 	"You're a helpful assistant.": "",
 	"You're a helpful assistant.": "",

+ 3 - 6
src/lib/i18n/locales/es-ES/translation.json

@@ -117,7 +117,6 @@
 	"Dark": "Oscuro",
 	"Dark": "Oscuro",
 	"Dashboard": "",
 	"Dashboard": "",
 	"Database": "Base de datos",
 	"Database": "Base de datos",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
 	"December": "",
 	"December": "",
 	"Default": "Por defecto",
 	"Default": "Por defecto",
 	"Default (Automatic1111)": "Por defecto (Automatic1111)",
 	"Default (Automatic1111)": "Por defecto (Automatic1111)",
@@ -211,6 +210,7 @@
 	"General Settings": "Opciones Generales",
 	"General Settings": "Opciones Generales",
 	"Generation Info": "",
 	"Generation Info": "",
 	"Good Response": "",
 	"Good Response": "",
+	"h:mm a": "",
 	"has no conversations.": "",
 	"has no conversations.": "",
 	"Hello, {{name}}": "Hola, {{name}}",
 	"Hello, {{name}}": "Hola, {{name}}",
 	"Help": "",
 	"Help": "",
@@ -276,9 +276,6 @@
 	"Modelfiles": "Modelfiles",
 	"Modelfiles": "Modelfiles",
 	"Models": "Modelos",
 	"Models": "Modelos",
 	"More": "",
 	"More": "",
-	"My Documents": "Mis Documentos",
-	"My Modelfiles": "Mis Modelfiles",
-	"My Prompts": "Mis Prompts",
 	"Name": "Nombre",
 	"Name": "Nombre",
 	"Name Tag": "Nombre de etiqueta",
 	"Name Tag": "Nombre de etiqueta",
 	"Name your modelfile": "Nombra tu modelfile",
 	"Name your modelfile": "Nombra tu modelfile",
@@ -362,7 +359,6 @@
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Save": "Guardar",
 	"Save": "Guardar",
 	"Save & Create": "Guardar y Crear",
 	"Save & Create": "Guardar y Crear",
-	"Save & Submit": "Guardar y Enviar",
 	"Save & Update": "Guardar y Actualizar",
 	"Save & Update": "Guardar y Actualizar",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Ya no se admite guardar registros de chat directamente en el almacenamiento de su navegador. Tómese un momento para descargar y eliminar sus registros de chat haciendo clic en el botón a continuación. No te preocupes, puedes volver a importar fácilmente tus registros de chat al backend a través de",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Ya no se admite guardar registros de chat directamente en el almacenamiento de su navegador. Tómese un momento para descargar y eliminar sus registros de chat haciendo clic en el botón a continuación. No te preocupes, puedes volver a importar fácilmente tus registros de chat al backend a través de",
 	"Scan": "Escanear",
 	"Scan": "Escanear",
@@ -379,6 +375,7 @@
 	"Select a model": "Selecciona un modelo",
 	"Select a model": "Selecciona un modelo",
 	"Select an Ollama instance": "Seleccione una instancia de Ollama",
 	"Select an Ollama instance": "Seleccione una instancia de Ollama",
 	"Select model": "",
 	"Select model": "",
+	"Send": "",
 	"Send a Message": "Enviar un Mensaje",
 	"Send a Message": "Enviar un Mensaje",
 	"Send message": "Enviar Mensaje",
 	"Send message": "Enviar Mensaje",
 	"September": "",
 	"September": "",
@@ -482,10 +479,10 @@
 	"What’s New in": "Novedades en",
 	"What’s New in": "Novedades en",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Cuando el historial está desactivado, los nuevos chats en este navegador no aparecerán en el historial de ninguno de sus dispositivos..",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Cuando el historial está desactivado, los nuevos chats en este navegador no aparecerán en el historial de ninguno de sus dispositivos..",
 	"Whisper (Local)": "Whisper (Local)",
 	"Whisper (Local)": "Whisper (Local)",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "Escribe una sugerencia para un prompt (por ejemplo, ¿quién eres?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "Escribe una sugerencia para un prompt (por ejemplo, ¿quién eres?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Escribe un resumen en 50 palabras que resuma [tema o palabra clave].",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Escribe un resumen en 50 palabras que resuma [tema o palabra clave].",
 	"Yesterday": "",
 	"Yesterday": "",
-	"You": "Usted",
 	"You have no archived conversations.": "",
 	"You have no archived conversations.": "",
 	"You have shared this chat": "",
 	"You have shared this chat": "",
 	"You're a helpful assistant.": "Eres un asistente útil.",
 	"You're a helpful assistant.": "Eres un asistente útil.",

+ 3 - 6
src/lib/i18n/locales/fa-IR/translation.json

@@ -117,7 +117,6 @@
 	"Dark": "تیره",
 	"Dark": "تیره",
 	"Dashboard": "",
 	"Dashboard": "",
 	"Database": "پایگاه داده",
 	"Database": "پایگاه داده",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
 	"December": "",
 	"December": "",
 	"Default": "پیشفرض",
 	"Default": "پیشفرض",
 	"Default (Automatic1111)": "پیشفرض (Automatic1111)",
 	"Default (Automatic1111)": "پیشفرض (Automatic1111)",
@@ -211,6 +210,7 @@
 	"General Settings": "تنظیمات عمومی",
 	"General Settings": "تنظیمات عمومی",
 	"Generation Info": "",
 	"Generation Info": "",
 	"Good Response": "",
 	"Good Response": "",
+	"h:mm a": "",
 	"has no conversations.": "",
 	"has no conversations.": "",
 	"Hello, {{name}}": "سلام، {{name}}",
 	"Hello, {{name}}": "سلام، {{name}}",
 	"Help": "",
 	"Help": "",
@@ -276,9 +276,6 @@
 	"Modelfiles": "فایل\u200cهای مدل",
 	"Modelfiles": "فایل\u200cهای مدل",
 	"Models": "مدل\u200cها",
 	"Models": "مدل\u200cها",
 	"More": "",
 	"More": "",
-	"My Documents": "اسناد من",
-	"My Modelfiles": "فایل\u200cهای مدل من",
-	"My Prompts": "پرامپت\u200cهای من",
 	"Name": "نام",
 	"Name": "نام",
 	"Name Tag": "نام تگ",
 	"Name Tag": "نام تگ",
 	"Name your modelfile": "فایل مدل را نام\u200cگذاری کنید",
 	"Name your modelfile": "فایل مدل را نام\u200cگذاری کنید",
@@ -362,7 +359,6 @@
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Save": "ذخیره",
 	"Save": "ذخیره",
 	"Save & Create": "ذخیره و ایجاد",
 	"Save & Create": "ذخیره و ایجاد",
-	"Save & Submit": "ذخیره و ارسال",
 	"Save & Update": "ذخیره و به\u200cروزرسانی",
 	"Save & Update": "ذخیره و به\u200cروزرسانی",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "ذخیره گزارش\u200cهای چت مستقیماً در حافظه مرورگر شما دیگر پشتیبانی نمی\u200cشود. لطفاً با کلیک بر روی دکمه زیر، چند لحظه برای دانلود و حذف گزارش های چت خود وقت بگذارید. نگران نباشید، شما به راحتی می توانید گزارش های چت خود را از طریق بکند دوباره وارد کنید",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "ذخیره گزارش\u200cهای چت مستقیماً در حافظه مرورگر شما دیگر پشتیبانی نمی\u200cشود. لطفاً با کلیک بر روی دکمه زیر، چند لحظه برای دانلود و حذف گزارش های چت خود وقت بگذارید. نگران نباشید، شما به راحتی می توانید گزارش های چت خود را از طریق بکند دوباره وارد کنید",
 	"Scan": "اسکن",
 	"Scan": "اسکن",
@@ -379,6 +375,7 @@
 	"Select a model": "انتخاب یک مدل",
 	"Select a model": "انتخاب یک مدل",
 	"Select an Ollama instance": "انتخاب یک نمونه از اولاما",
 	"Select an Ollama instance": "انتخاب یک نمونه از اولاما",
 	"Select model": "",
 	"Select model": "",
+	"Send": "",
 	"Send a Message": "ارسال یک پیام",
 	"Send a Message": "ارسال یک پیام",
 	"Send message": "ارسال پیام",
 	"Send message": "ارسال پیام",
 	"September": "",
 	"September": "",
@@ -482,10 +479,10 @@
 	"What’s New in": "موارد جدید در",
 	"What’s New in": "موارد جدید در",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "وقتی سابقه خاموش است، چت\u200cهای جدید در این مرورگر در سابقه شما در هیچ یک از دستگاه\u200cهایتان ظاهر نمی\u200cشوند.",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "وقتی سابقه خاموش است، چت\u200cهای جدید در این مرورگر در سابقه شما در هیچ یک از دستگاه\u200cهایتان ظاهر نمی\u200cشوند.",
 	"Whisper (Local)": "ویسپر (محلی)",
 	"Whisper (Local)": "ویسپر (محلی)",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "یک پیشنهاد پرامپت بنویسید (مثلاً شما کی هستید؟)",
 	"Write a prompt suggestion (e.g. Who are you?)": "یک پیشنهاد پرامپت بنویسید (مثلاً شما کی هستید؟)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "خلاصه ای در 50 کلمه بنویسید که [موضوع یا کلمه کلیدی] را خلاصه کند.",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "خلاصه ای در 50 کلمه بنویسید که [موضوع یا کلمه کلیدی] را خلاصه کند.",
 	"Yesterday": "",
 	"Yesterday": "",
-	"You": "شما",
 	"You have no archived conversations.": "",
 	"You have no archived conversations.": "",
 	"You have shared this chat": "",
 	"You have shared this chat": "",
 	"You're a helpful assistant.": "تو یک دستیار سودمند هستی.",
 	"You're a helpful assistant.": "تو یک دستیار سودمند هستی.",

+ 3 - 6
src/lib/i18n/locales/fi-FI/translation.json

@@ -117,7 +117,6 @@
 	"Dark": "Tumma",
 	"Dark": "Tumma",
 	"Dashboard": "Kojelauta",
 	"Dashboard": "Kojelauta",
 	"Database": "Tietokanta",
 	"Database": "Tietokanta",
-	"DD/MM/YYYY HH:mm": "DD.MM.YYYY HH:mm",
 	"December": "joulukuu",
 	"December": "joulukuu",
 	"Default": "Oletus",
 	"Default": "Oletus",
 	"Default (Automatic1111)": "Oletus (AUTOMATIC1111)",
 	"Default (Automatic1111)": "Oletus (AUTOMATIC1111)",
@@ -211,6 +210,7 @@
 	"General Settings": "Yleisasetukset",
 	"General Settings": "Yleisasetukset",
 	"Generation Info": "Generointitiedot",
 	"Generation Info": "Generointitiedot",
 	"Good Response": "Hyvä vastaus",
 	"Good Response": "Hyvä vastaus",
+	"h:mm a": "",
 	"has no conversations.": "ei ole keskusteluja.",
 	"has no conversations.": "ei ole keskusteluja.",
 	"Hello, {{name}}": "Terve, {{name}}",
 	"Hello, {{name}}": "Terve, {{name}}",
 	"Help": "Apua",
 	"Help": "Apua",
@@ -276,9 +276,6 @@
 	"Modelfiles": "Mallitiedostot",
 	"Modelfiles": "Mallitiedostot",
 	"Models": "Mallit",
 	"Models": "Mallit",
 	"More": "Lisää",
 	"More": "Lisää",
-	"My Documents": "Omat asiakirjat",
-	"My Modelfiles": "Omat mallitiedostot",
-	"My Prompts": "Omat kehotteet",
 	"Name": "Nimi",
 	"Name": "Nimi",
 	"Name Tag": "Nimitagi",
 	"Name Tag": "Nimitagi",
 	"Name your modelfile": "Nimeä mallitiedostosi",
 	"Name your modelfile": "Nimeä mallitiedostosi",
@@ -362,7 +359,6 @@
 	"Rosé Pine Dawn": "Aamuinen Rosee-mänty",
 	"Rosé Pine Dawn": "Aamuinen Rosee-mänty",
 	"Save": "Tallenna",
 	"Save": "Tallenna",
 	"Save & Create": "Tallenna ja luo",
 	"Save & Create": "Tallenna ja luo",
-	"Save & Submit": "Tallenna ja lähetä",
 	"Save & Update": "Tallenna ja päivitä",
 	"Save & Update": "Tallenna ja päivitä",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Keskustelulokien tallentaminen suoraan selaimen tallennustilaan ei ole enää tuettua. Lataa ja poista keskustelulokit napsauttamalla alla olevaa painiketta. Älä huoli, voit helposti tuoda keskustelulokit takaisin backendiin",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Keskustelulokien tallentaminen suoraan selaimen tallennustilaan ei ole enää tuettua. Lataa ja poista keskustelulokit napsauttamalla alla olevaa painiketta. Älä huoli, voit helposti tuoda keskustelulokit takaisin backendiin",
 	"Scan": "Skannaa",
 	"Scan": "Skannaa",
@@ -379,6 +375,7 @@
 	"Select a model": "Valitse malli",
 	"Select a model": "Valitse malli",
 	"Select an Ollama instance": "Valitse Ollama-instanssi",
 	"Select an Ollama instance": "Valitse Ollama-instanssi",
 	"Select model": "Valitse malli",
 	"Select model": "Valitse malli",
+	"Send": "",
 	"Send a Message": "Lähetä viesti",
 	"Send a Message": "Lähetä viesti",
 	"Send message": "Lähetä viesti",
 	"Send message": "Lähetä viesti",
 	"September": "syyskuu",
 	"September": "syyskuu",
@@ -482,10 +479,10 @@
 	"What’s New in": "Mitä uutta",
 	"What’s New in": "Mitä uutta",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Kun historia on pois päältä, uudet keskustelut tässä selaimessa eivät näy historiassasi millään laitteellasi.",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Kun historia on pois päältä, uudet keskustelut tässä selaimessa eivät näy historiassasi millään laitteellasi.",
 	"Whisper (Local)": "Whisper (paikallinen)",
 	"Whisper (Local)": "Whisper (paikallinen)",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "Kirjoita ehdotettu kehote (esim. Kuka olet?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "Kirjoita ehdotettu kehote (esim. Kuka olet?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Kirjoita 50 sanan yhteenveto, joka tiivistää [aihe tai avainsana].",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Kirjoita 50 sanan yhteenveto, joka tiivistää [aihe tai avainsana].",
 	"Yesterday": "Eilen",
 	"Yesterday": "Eilen",
-	"You": "Sinä",
 	"You have no archived conversations.": "Sinulla ei ole arkistoituja keskusteluja.",
 	"You have no archived conversations.": "Sinulla ei ole arkistoituja keskusteluja.",
 	"You have shared this chat": "Olet jakanut tämän keskustelun",
 	"You have shared this chat": "Olet jakanut tämän keskustelun",
 	"You're a helpful assistant.": "Olet avulias apulainen.",
 	"You're a helpful assistant.": "Olet avulias apulainen.",

+ 3 - 6
src/lib/i18n/locales/fr-CA/translation.json

@@ -117,7 +117,6 @@
 	"Dark": "Sombre",
 	"Dark": "Sombre",
 	"Dashboard": "",
 	"Dashboard": "",
 	"Database": "Base de données",
 	"Database": "Base de données",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
 	"December": "",
 	"December": "",
 	"Default": "Par défaut",
 	"Default": "Par défaut",
 	"Default (Automatic1111)": "Par défaut (Automatic1111)",
 	"Default (Automatic1111)": "Par défaut (Automatic1111)",
@@ -211,6 +210,7 @@
 	"General Settings": "Paramètres généraux",
 	"General Settings": "Paramètres généraux",
 	"Generation Info": "",
 	"Generation Info": "",
 	"Good Response": "",
 	"Good Response": "",
+	"h:mm a": "",
 	"has no conversations.": "",
 	"has no conversations.": "",
 	"Hello, {{name}}": "Bonjour, {{name}}",
 	"Hello, {{name}}": "Bonjour, {{name}}",
 	"Help": "",
 	"Help": "",
@@ -276,9 +276,6 @@
 	"Modelfiles": "Fichiers de modèle",
 	"Modelfiles": "Fichiers de modèle",
 	"Models": "Modèles",
 	"Models": "Modèles",
 	"More": "",
 	"More": "",
-	"My Documents": "Mes documents",
-	"My Modelfiles": "Mes fichiers de modèle",
-	"My Prompts": "Mes prompts",
 	"Name": "Nom",
 	"Name": "Nom",
 	"Name Tag": "Tag de nom",
 	"Name Tag": "Tag de nom",
 	"Name your modelfile": "Nommez votre fichier de modèle",
 	"Name your modelfile": "Nommez votre fichier de modèle",
@@ -362,7 +359,6 @@
 	"Rosé Pine Dawn": "Aube Pin Rosé",
 	"Rosé Pine Dawn": "Aube Pin Rosé",
 	"Save": "Enregistrer",
 	"Save": "Enregistrer",
 	"Save & Create": "Enregistrer & Créer",
 	"Save & Create": "Enregistrer & Créer",
-	"Save & Submit": "Enregistrer & Soumettre",
 	"Save & Update": "Enregistrer & Mettre à jour",
 	"Save & Update": "Enregistrer & Mettre à jour",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "La sauvegarde des journaux de discussion directement dans le stockage de votre navigateur n'est plus prise en charge. Veuillez prendre un moment pour télécharger et supprimer vos journaux de discussion en cliquant sur le bouton ci-dessous. Ne vous inquiétez pas, vous pouvez facilement réimporter vos journaux de discussion dans le backend via",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "La sauvegarde des journaux de discussion directement dans le stockage de votre navigateur n'est plus prise en charge. Veuillez prendre un moment pour télécharger et supprimer vos journaux de discussion en cliquant sur le bouton ci-dessous. Ne vous inquiétez pas, vous pouvez facilement réimporter vos journaux de discussion dans le backend via",
 	"Scan": "Scanner",
 	"Scan": "Scanner",
@@ -379,6 +375,7 @@
 	"Select a model": "Sélectionnez un modèle",
 	"Select a model": "Sélectionnez un modèle",
 	"Select an Ollama instance": "Sélectionner une instance Ollama",
 	"Select an Ollama instance": "Sélectionner une instance Ollama",
 	"Select model": "",
 	"Select model": "",
+	"Send": "",
 	"Send a Message": "Envoyer un message",
 	"Send a Message": "Envoyer un message",
 	"Send message": "Envoyer un message",
 	"Send message": "Envoyer un message",
 	"September": "",
 	"September": "",
@@ -482,10 +479,10 @@
 	"What’s New in": "Quoi de neuf dans",
 	"What’s New in": "Quoi de neuf dans",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Lorsque l'historique est désactivé, les nouvelles discussions sur ce navigateur n'apparaîtront pas dans votre historique sur aucun de vos appareils.",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Lorsque l'historique est désactivé, les nouvelles discussions sur ce navigateur n'apparaîtront pas dans votre historique sur aucun de vos appareils.",
 	"Whisper (Local)": "Whisper (Local)",
 	"Whisper (Local)": "Whisper (Local)",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "Rédigez une suggestion de prompt (p. ex. Qui êtes-vous ?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "Rédigez une suggestion de prompt (p. ex. Qui êtes-vous ?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Rédigez un résumé en 50 mots qui résume [sujet ou mot-clé].",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Rédigez un résumé en 50 mots qui résume [sujet ou mot-clé].",
 	"Yesterday": "",
 	"Yesterday": "",
-	"You": "You",
 	"You have no archived conversations.": "",
 	"You have no archived conversations.": "",
 	"You have shared this chat": "",
 	"You have shared this chat": "",
 	"You're a helpful assistant.": "Vous êtes un assistant utile",
 	"You're a helpful assistant.": "Vous êtes un assistant utile",

+ 3 - 6
src/lib/i18n/locales/fr-FR/translation.json

@@ -117,7 +117,6 @@
 	"Dark": "Sombre",
 	"Dark": "Sombre",
 	"Dashboard": "",
 	"Dashboard": "",
 	"Database": "Base de données",
 	"Database": "Base de données",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
 	"December": "",
 	"December": "",
 	"Default": "Par défaut",
 	"Default": "Par défaut",
 	"Default (Automatic1111)": "Par défaut (Automatic1111)",
 	"Default (Automatic1111)": "Par défaut (Automatic1111)",
@@ -211,6 +210,7 @@
 	"General Settings": "Paramètres généraux",
 	"General Settings": "Paramètres généraux",
 	"Generation Info": "",
 	"Generation Info": "",
 	"Good Response": "",
 	"Good Response": "",
+	"h:mm a": "",
 	"has no conversations.": "",
 	"has no conversations.": "",
 	"Hello, {{name}}": "Bonjour, {{name}}",
 	"Hello, {{name}}": "Bonjour, {{name}}",
 	"Help": "",
 	"Help": "",
@@ -276,9 +276,6 @@
 	"Modelfiles": "Fichiers de modèle",
 	"Modelfiles": "Fichiers de modèle",
 	"Models": "Modèles",
 	"Models": "Modèles",
 	"More": "",
 	"More": "",
-	"My Documents": "Mes documents",
-	"My Modelfiles": "Mes fichiers de modèle",
-	"My Prompts": "Mes prompts",
 	"Name": "Nom",
 	"Name": "Nom",
 	"Name Tag": "Tag de nom",
 	"Name Tag": "Tag de nom",
 	"Name your modelfile": "Nommez votre fichier de modèle",
 	"Name your modelfile": "Nommez votre fichier de modèle",
@@ -362,7 +359,6 @@
 	"Rosé Pine Dawn": "Aube Pin Rosé",
 	"Rosé Pine Dawn": "Aube Pin Rosé",
 	"Save": "Enregistrer",
 	"Save": "Enregistrer",
 	"Save & Create": "Enregistrer & Créer",
 	"Save & Create": "Enregistrer & Créer",
-	"Save & Submit": "Enregistrer & Soumettre",
 	"Save & Update": "Enregistrer & Mettre à jour",
 	"Save & Update": "Enregistrer & Mettre à jour",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "La sauvegarde des chat directement dans le stockage de votre navigateur n'est plus prise en charge. Veuillez prendre un moment pour télécharger et supprimer vos journaux de chat en cliquant sur le bouton ci-dessous. Ne vous inquiétez pas, vous pouvez facilement importer vos sauvegardes de chat via",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "La sauvegarde des chat directement dans le stockage de votre navigateur n'est plus prise en charge. Veuillez prendre un moment pour télécharger et supprimer vos journaux de chat en cliquant sur le bouton ci-dessous. Ne vous inquiétez pas, vous pouvez facilement importer vos sauvegardes de chat via",
 	"Scan": "Scanner",
 	"Scan": "Scanner",
@@ -379,6 +375,7 @@
 	"Select a model": "Sélectionner un modèle",
 	"Select a model": "Sélectionner un modèle",
 	"Select an Ollama instance": "Sélectionner une instance Ollama",
 	"Select an Ollama instance": "Sélectionner une instance Ollama",
 	"Select model": "",
 	"Select model": "",
+	"Send": "",
 	"Send a Message": "Envoyer un message",
 	"Send a Message": "Envoyer un message",
 	"Send message": "Envoyer un message",
 	"Send message": "Envoyer un message",
 	"September": "",
 	"September": "",
@@ -482,10 +479,10 @@
 	"What’s New in": "Quoi de neuf dans",
 	"What’s New in": "Quoi de neuf dans",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Lorsque l'historique est désactivé, les nouveaux chats sur ce navigateur n'apparaîtront pas dans votre historique sur aucun de vos appareils.",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Lorsque l'historique est désactivé, les nouveaux chats sur ce navigateur n'apparaîtront pas dans votre historique sur aucun de vos appareils.",
 	"Whisper (Local)": "Whisper (Local)",
 	"Whisper (Local)": "Whisper (Local)",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "Écrivez un prompt (e.x. Qui est-tu ?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "Écrivez un prompt (e.x. Qui est-tu ?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Ecrivez un résumé en 50 mots [sujet ou mot-clé]",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Ecrivez un résumé en 50 mots [sujet ou mot-clé]",
 	"Yesterday": "",
 	"Yesterday": "",
-	"You": "You",
 	"You have no archived conversations.": "",
 	"You have no archived conversations.": "",
 	"You have shared this chat": "",
 	"You have shared this chat": "",
 	"You're a helpful assistant.": "Vous êtes un assistant utile",
 	"You're a helpful assistant.": "Vous êtes un assistant utile",

+ 3 - 6
src/lib/i18n/locales/he-IL/translation.json

@@ -117,7 +117,6 @@
 	"Dark": "כהה",
 	"Dark": "כהה",
 	"Dashboard": "לוח בקרה",
 	"Dashboard": "לוח בקרה",
 	"Database": "מסד נתונים",
 	"Database": "מסד נתונים",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
 	"December": "דצמבר",
 	"December": "דצמבר",
 	"Default": "ברירת מחדל",
 	"Default": "ברירת מחדל",
 	"Default (Automatic1111)": "ברירת מחדל (Automatic1111)",
 	"Default (Automatic1111)": "ברירת מחדל (Automatic1111)",
@@ -211,6 +210,7 @@
 	"General Settings": "הגדרות כלליות",
 	"General Settings": "הגדרות כלליות",
 	"Generation Info": "מידע על היצירה",
 	"Generation Info": "מידע על היצירה",
 	"Good Response": "תגובה טובה",
 	"Good Response": "תגובה טובה",
+	"h:mm a": "",
 	"has no conversations.": "אין שיחות.",
 	"has no conversations.": "אין שיחות.",
 	"Hello, {{name}}": "שלום, {{name}}",
 	"Hello, {{name}}": "שלום, {{name}}",
 	"Help": "עזרה",
 	"Help": "עזרה",
@@ -276,9 +276,6 @@
 	"Modelfiles": "קבצי מודל",
 	"Modelfiles": "קבצי מודל",
 	"Models": "מודלים",
 	"Models": "מודלים",
 	"More": "עוד",
 	"More": "עוד",
-	"My Documents": "המסמכים שלי",
-	"My Modelfiles": "קבצי המודל שלי",
-	"My Prompts": "הפקודות שלי",
 	"Name": "שם",
 	"Name": "שם",
 	"Name Tag": "תג שם",
 	"Name Tag": "תג שם",
 	"Name your modelfile": "תן שם לקובץ המודל שלך",
 	"Name your modelfile": "תן שם לקובץ המודל שלך",
@@ -362,7 +359,6 @@
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Save": "שמור",
 	"Save": "שמור",
 	"Save & Create": "שמור וצור",
 	"Save & Create": "שמור וצור",
-	"Save & Submit": "שמור ושלח",
 	"Save & Update": "שמור ועדכן",
 	"Save & Update": "שמור ועדכן",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "שמירת יומני צ'אט ישירות באחסון הדפדפן שלך אינה נתמכת יותר. אנא הקדש רגע להוריד ולמחוק את יומני הצ'אט שלך על ידי לחיצה על הכפתור למטה. אל דאגה, באפשרותך לייבא מחדש בקלות את יומני הצ'אט שלך לשרת האחורי דרך",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "שמירת יומני צ'אט ישירות באחסון הדפדפן שלך אינה נתמכת יותר. אנא הקדש רגע להוריד ולמחוק את יומני הצ'אט שלך על ידי לחיצה על הכפתור למטה. אל דאגה, באפשרותך לייבא מחדש בקלות את יומני הצ'אט שלך לשרת האחורי דרך",
 	"Scan": "סרוק",
 	"Scan": "סרוק",
@@ -379,6 +375,7 @@
 	"Select a model": "בחר מודל",
 	"Select a model": "בחר מודל",
 	"Select an Ollama instance": "בחר מופע של Ollama",
 	"Select an Ollama instance": "בחר מופע של Ollama",
 	"Select model": "בחר מודל",
 	"Select model": "בחר מודל",
+	"Send": "",
 	"Send a Message": "שלח הודעה",
 	"Send a Message": "שלח הודעה",
 	"Send message": "שלח הודעה",
 	"Send message": "שלח הודעה",
 	"September": "ספטמבר",
 	"September": "ספטמבר",
@@ -482,10 +479,10 @@
 	"What’s New in": "",
 	"What’s New in": "",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "",
 	"Whisper (Local)": "",
 	"Whisper (Local)": "",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "",
 	"Yesterday": "אתמול",
 	"Yesterday": "אתמול",
-	"You": "אתה",
 	"You have no archived conversations.": "",
 	"You have no archived conversations.": "",
 	"You have shared this chat": "",
 	"You have shared this chat": "",
 	"You're a helpful assistant.": "",
 	"You're a helpful assistant.": "",

+ 3 - 6
src/lib/i18n/locales/hi-IN/translation.json

@@ -117,7 +117,6 @@
 	"Dark": "",
 	"Dark": "",
 	"Dashboard": "",
 	"Dashboard": "",
 	"Database": "डेटाबेस",
 	"Database": "डेटाबेस",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
 	"December": "",
 	"December": "",
 	"Default": "डिफ़ॉल्ट",
 	"Default": "डिफ़ॉल्ट",
 	"Default (Automatic1111)": "डिफ़ॉल्ट (Automatic1111)",
 	"Default (Automatic1111)": "डिफ़ॉल्ट (Automatic1111)",
@@ -211,6 +210,7 @@
 	"General Settings": "सामान्य सेटिंग्स",
 	"General Settings": "सामान्य सेटिंग्स",
 	"Generation Info": "जनरेशन की जानकारी",
 	"Generation Info": "जनरेशन की जानकारी",
 	"Good Response": "अच्छी प्रतिक्रिया",
 	"Good Response": "अच्छी प्रतिक्रिया",
+	"h:mm a": "",
 	"has no conversations.": "कोई बातचीत नहीं है",
 	"has no conversations.": "कोई बातचीत नहीं है",
 	"Hello, {{name}}": "नमस्ते, {{name}}",
 	"Hello, {{name}}": "नमस्ते, {{name}}",
 	"Help": "",
 	"Help": "",
@@ -276,9 +276,6 @@
 	"Modelfiles": "मॉडल फ़ाइलें",
 	"Modelfiles": "मॉडल फ़ाइलें",
 	"Models": "सभी मॉडल",
 	"Models": "सभी मॉडल",
 	"More": "और..",
 	"More": "और..",
-	"My Documents": "मेरे दस्तावेज़",
-	"My Modelfiles": "मेरी मॉडल फ़ाइलें",
-	"My Prompts": "मेरे प्रॉम्प्ट",
 	"Name": "नाम",
 	"Name": "नाम",
 	"Name Tag": "नाम टैग",
 	"Name Tag": "नाम टैग",
 	"Name your modelfile": "अपनी मॉडलफ़ाइल को नाम दें",
 	"Name your modelfile": "अपनी मॉडलफ़ाइल को नाम दें",
@@ -362,7 +359,6 @@
 	"Rosé Pine Dawn": "",
 	"Rosé Pine Dawn": "",
 	"Save": "सहेजें",
 	"Save": "सहेजें",
 	"Save & Create": "सहेजें और बनाएं",
 	"Save & Create": "सहेजें और बनाएं",
-	"Save & Submit": "सहेजें और सबमिट करें",
 	"Save & Update": "सहेजें और अपडेट करें",
 	"Save & Update": "सहेजें और अपडेट करें",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "चैट लॉग को सीधे आपके ब्राउज़र के स्टोरेज में सहेजना अब समर्थित नहीं है। कृपया नीचे दिए गए बटन पर क्लिक करके डाउनलोड करने और अपने चैट लॉग को हटाने के लिए कुछ समय दें। चिंता न करें, आप आसानी से अपने चैट लॉग को बैकएंड पर पुनः आयात कर सकते हैं",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "चैट लॉग को सीधे आपके ब्राउज़र के स्टोरेज में सहेजना अब समर्थित नहीं है। कृपया नीचे दिए गए बटन पर क्लिक करके डाउनलोड करने और अपने चैट लॉग को हटाने के लिए कुछ समय दें। चिंता न करें, आप आसानी से अपने चैट लॉग को बैकएंड पर पुनः आयात कर सकते हैं",
 	"Scan": "स्कैन",
 	"Scan": "स्कैन",
@@ -379,6 +375,7 @@
 	"Select a model": "एक मॉडल चुनें",
 	"Select a model": "एक मॉडल चुनें",
 	"Select an Ollama instance": "एक Ollama Instance चुनें",
 	"Select an Ollama instance": "एक Ollama Instance चुनें",
 	"Select model": "",
 	"Select model": "",
+	"Send": "",
 	"Send a Message": "एक संदेश भेजो",
 	"Send a Message": "एक संदेश भेजो",
 	"Send message": "मेसेज भेजें",
 	"Send message": "मेसेज भेजें",
 	"September": "",
 	"September": "",
@@ -482,10 +479,10 @@
 	"What’s New in": "इसमें नया क्या है",
 	"What’s New in": "इसमें नया क्या है",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "जब इतिहास बंद हो जाता है, तो इस ब्राउज़र पर नई चैट आपके किसी भी डिवाइस पर इतिहास में दिखाई नहीं देंगी।",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "जब इतिहास बंद हो जाता है, तो इस ब्राउज़र पर नई चैट आपके किसी भी डिवाइस पर इतिहास में दिखाई नहीं देंगी।",
 	"Whisper (Local)": "Whisper (स्थानीय)",
 	"Whisper (Local)": "Whisper (स्थानीय)",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "एक त्वरित सुझाव लिखें (जैसे कि आप कौन हैं?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "एक त्वरित सुझाव लिखें (जैसे कि आप कौन हैं?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "50 शब्दों में एक सारांश लिखें जो [विषय या कीवर्ड] का सारांश प्रस्तुत करता हो।",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "50 शब्दों में एक सारांश लिखें जो [विषय या कीवर्ड] का सारांश प्रस्तुत करता हो।",
 	"Yesterday": "",
 	"Yesterday": "",
-	"You": "आप",
 	"You have no archived conversations.": "",
 	"You have no archived conversations.": "",
 	"You have shared this chat": "",
 	"You have shared this chat": "",
 	"You're a helpful assistant.": "आप एक सहायक सहायक हैं",
 	"You're a helpful assistant.": "आप एक सहायक सहायक हैं",

+ 3 - 6
src/lib/i18n/locales/it-IT/translation.json

@@ -117,7 +117,6 @@
 	"Dark": "Scuro",
 	"Dark": "Scuro",
 	"Dashboard": "Pannello di controllo",
 	"Dashboard": "Pannello di controllo",
 	"Database": "Database",
 	"Database": "Database",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
 	"December": "Dicembre",
 	"December": "Dicembre",
 	"Default": "Predefinito",
 	"Default": "Predefinito",
 	"Default (Automatic1111)": "Predefinito (Automatic1111)",
 	"Default (Automatic1111)": "Predefinito (Automatic1111)",
@@ -211,6 +210,7 @@
 	"General Settings": "Impostazioni generali",
 	"General Settings": "Impostazioni generali",
 	"Generation Info": "Informazioni generazione",
 	"Generation Info": "Informazioni generazione",
 	"Good Response": "Buona risposta",
 	"Good Response": "Buona risposta",
+	"h:mm a": "",
 	"has no conversations.": "non ha conversazioni.",
 	"has no conversations.": "non ha conversazioni.",
 	"Hello, {{name}}": "Ciao, {{name}}",
 	"Hello, {{name}}": "Ciao, {{name}}",
 	"Help": "Aiuto",
 	"Help": "Aiuto",
@@ -276,9 +276,6 @@
 	"Modelfiles": "File modello",
 	"Modelfiles": "File modello",
 	"Models": "Modelli",
 	"Models": "Modelli",
 	"More": "Altro",
 	"More": "Altro",
-	"My Documents": "I miei documenti",
-	"My Modelfiles": "I miei file modello",
-	"My Prompts": "I miei prompt",
 	"Name": "Nome",
 	"Name": "Nome",
 	"Name Tag": "Nome tag",
 	"Name Tag": "Nome tag",
 	"Name your modelfile": "Assegna un nome al tuo file modello",
 	"Name your modelfile": "Assegna un nome al tuo file modello",
@@ -362,7 +359,6 @@
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Save": "Salva",
 	"Save": "Salva",
 	"Save & Create": "Salva e crea",
 	"Save & Create": "Salva e crea",
-	"Save & Submit": "Salva e invia",
 	"Save & Update": "Salva e aggiorna",
 	"Save & Update": "Salva e aggiorna",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Il salvataggio dei registri della chat direttamente nell'archivio del browser non è più supportato. Si prega di dedicare un momento per scaricare ed eliminare i registri della chat facendo clic sul pulsante in basso. Non preoccuparti, puoi facilmente reimportare i registri della chat nel backend tramite",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Il salvataggio dei registri della chat direttamente nell'archivio del browser non è più supportato. Si prega di dedicare un momento per scaricare ed eliminare i registri della chat facendo clic sul pulsante in basso. Non preoccuparti, puoi facilmente reimportare i registri della chat nel backend tramite",
 	"Scan": "Scansione",
 	"Scan": "Scansione",
@@ -379,6 +375,7 @@
 	"Select a model": "Seleziona un modello",
 	"Select a model": "Seleziona un modello",
 	"Select an Ollama instance": "Seleziona un'istanza Ollama",
 	"Select an Ollama instance": "Seleziona un'istanza Ollama",
 	"Select model": "Seleziona modello",
 	"Select model": "Seleziona modello",
+	"Send": "",
 	"Send a Message": "Invia un messaggio",
 	"Send a Message": "Invia un messaggio",
 	"Send message": "Invia messaggio",
 	"Send message": "Invia messaggio",
 	"September": "Settembre",
 	"September": "Settembre",
@@ -482,10 +479,10 @@
 	"What’s New in": "Novità in",
 	"What’s New in": "Novità in",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Quando la cronologia è disattivata, le nuove chat su questo browser non verranno visualizzate nella cronologia su nessuno dei tuoi dispositivi.",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Quando la cronologia è disattivata, le nuove chat su questo browser non verranno visualizzate nella cronologia su nessuno dei tuoi dispositivi.",
 	"Whisper (Local)": "Whisper (locale)",
 	"Whisper (Local)": "Whisper (locale)",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "Scrivi un suggerimento per il prompt (ad esempio Chi sei?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "Scrivi un suggerimento per il prompt (ad esempio Chi sei?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Scrivi un riassunto in 50 parole che riassume [argomento o parola chiave].",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Scrivi un riassunto in 50 parole che riassume [argomento o parola chiave].",
 	"Yesterday": "Ieri",
 	"Yesterday": "Ieri",
-	"You": "Tu",
 	"You have no archived conversations.": "Non hai conversazioni archiviate.",
 	"You have no archived conversations.": "Non hai conversazioni archiviate.",
 	"You have shared this chat": "Hai condiviso questa chat",
 	"You have shared this chat": "Hai condiviso questa chat",
 	"You're a helpful assistant.": "Sei un assistente utile.",
 	"You're a helpful assistant.": "Sei un assistente utile.",

+ 3 - 6
src/lib/i18n/locales/ja-JP/translation.json

@@ -117,7 +117,6 @@
 	"Dark": "ダーク",
 	"Dark": "ダーク",
 	"Dashboard": "",
 	"Dashboard": "",
 	"Database": "データベース",
 	"Database": "データベース",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
 	"December": "",
 	"December": "",
 	"Default": "デフォルト",
 	"Default": "デフォルト",
 	"Default (Automatic1111)": "デフォルト (Automatic1111)",
 	"Default (Automatic1111)": "デフォルト (Automatic1111)",
@@ -211,6 +210,7 @@
 	"General Settings": "一般設定",
 	"General Settings": "一般設定",
 	"Generation Info": "",
 	"Generation Info": "",
 	"Good Response": "",
 	"Good Response": "",
+	"h:mm a": "",
 	"has no conversations.": "",
 	"has no conversations.": "",
 	"Hello, {{name}}": "こんにちは、{{name}} さん",
 	"Hello, {{name}}": "こんにちは、{{name}} さん",
 	"Help": "",
 	"Help": "",
@@ -276,9 +276,6 @@
 	"Modelfiles": "モデルファイル",
 	"Modelfiles": "モデルファイル",
 	"Models": "モデル",
 	"Models": "モデル",
 	"More": "",
 	"More": "",
-	"My Documents": "マイ ドキュメント",
-	"My Modelfiles": "マイ モデルファイル",
-	"My Prompts": "マイ プロンプト",
 	"Name": "名前",
 	"Name": "名前",
 	"Name Tag": "名前タグ",
 	"Name Tag": "名前タグ",
 	"Name your modelfile": "モデルファイルに名前を付ける",
 	"Name your modelfile": "モデルファイルに名前を付ける",
@@ -362,7 +359,6 @@
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Save": "保存",
 	"Save": "保存",
 	"Save & Create": "保存して作成",
 	"Save & Create": "保存して作成",
-	"Save & Submit": "保存して送信",
 	"Save & Update": "保存して更新",
 	"Save & Update": "保存して更新",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "チャットログをブラウザのストレージに直接保存する機能はサポートされなくなりました。下のボタンをクリックして、チャットログをダウンロードして削除してください。ご心配なく。チャットログは、次の方法でバックエンドに簡単に再インポートできます。",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "チャットログをブラウザのストレージに直接保存する機能はサポートされなくなりました。下のボタンをクリックして、チャットログをダウンロードして削除してください。ご心配なく。チャットログは、次の方法でバックエンドに簡単に再インポートできます。",
 	"Scan": "スキャン",
 	"Scan": "スキャン",
@@ -379,6 +375,7 @@
 	"Select a model": "モデルを選択",
 	"Select a model": "モデルを選択",
 	"Select an Ollama instance": "Ollama インスタンスを選択",
 	"Select an Ollama instance": "Ollama インスタンスを選択",
 	"Select model": "",
 	"Select model": "",
+	"Send": "",
 	"Send a Message": "メッセージを送信",
 	"Send a Message": "メッセージを送信",
 	"Send message": "メッセージを送信",
 	"Send message": "メッセージを送信",
 	"September": "",
 	"September": "",
@@ -482,10 +479,10 @@
 	"What’s New in": "新機能",
 	"What’s New in": "新機能",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "履歴が無効になっている場合、このブラウザでの新しいチャットは、どのデバイスの履歴にも表示されません。",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "履歴が無効になっている場合、このブラウザでの新しいチャットは、どのデバイスの履歴にも表示されません。",
 	"Whisper (Local)": "Whisper (ローカル)",
 	"Whisper (Local)": "Whisper (ローカル)",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "プロンプトの提案を書いてください (例: あなたは誰ですか?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "プロンプトの提案を書いてください (例: あなたは誰ですか?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "[トピックまたはキーワード] を要約する 50 語の概要を書いてください。",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "[トピックまたはキーワード] を要約する 50 語の概要を書いてください。",
 	"Yesterday": "",
 	"Yesterday": "",
-	"You": "あなた",
 	"You have no archived conversations.": "",
 	"You have no archived conversations.": "",
 	"You have shared this chat": "",
 	"You have shared this chat": "",
 	"You're a helpful assistant.": "あなたは役に立つアシスタントです。",
 	"You're a helpful assistant.": "あなたは役に立つアシスタントです。",

+ 3 - 6
src/lib/i18n/locales/ka-GE/translation.json

@@ -117,7 +117,6 @@
 	"Dark": "მუქი",
 	"Dark": "მუქი",
 	"Dashboard": "",
 	"Dashboard": "",
 	"Database": "მონაცემთა ბაზა",
 	"Database": "მონაცემთა ბაზა",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
 	"December": "",
 	"December": "",
 	"Default": "დეფოლტი",
 	"Default": "დეფოლტი",
 	"Default (Automatic1111)": "დეფოლტ (Automatic1111)",
 	"Default (Automatic1111)": "დეფოლტ (Automatic1111)",
@@ -211,6 +210,7 @@
 	"General Settings": "ზოგადი პარამეტრები",
 	"General Settings": "ზოგადი პარამეტრები",
 	"Generation Info": "",
 	"Generation Info": "",
 	"Good Response": "",
 	"Good Response": "",
+	"h:mm a": "",
 	"has no conversations.": "",
 	"has no conversations.": "",
 	"Hello, {{name}}": "გამარჯობა, {{name}}",
 	"Hello, {{name}}": "გამარჯობა, {{name}}",
 	"Help": "",
 	"Help": "",
@@ -276,9 +276,6 @@
 	"Modelfiles": "მოდელური ფაილები",
 	"Modelfiles": "მოდელური ფაილები",
 	"Models": "მოდელები",
 	"Models": "მოდელები",
 	"More": "",
 	"More": "",
-	"My Documents": "ჩემი დოკუმენტები",
-	"My Modelfiles": "ჩემი მოდელური ფაილები",
-	"My Prompts": "ჩემი მოთხოვნები",
 	"Name": "სახელი",
 	"Name": "სახელი",
 	"Name Tag": "სახელის ტეგი",
 	"Name Tag": "სახელის ტეგი",
 	"Name your modelfile": "თქვენი მოდელური ფაილის სახელი",
 	"Name your modelfile": "თქვენი მოდელური ფაილის სახელი",
@@ -362,7 +359,6 @@
 	"Rosé Pine Dawn": "ვარდისფერი ფიჭვის გარიჟრაჟი",
 	"Rosé Pine Dawn": "ვარდისფერი ფიჭვის გარიჟრაჟი",
 	"Save": "შენახვა",
 	"Save": "შენახვა",
 	"Save & Create": "დამახსოვრება და შექმნა",
 	"Save & Create": "დამახსოვრება და შექმნა",
-	"Save & Submit": "დამახსოვრება და გაგზავნა",
 	"Save & Update": "დამახსოვრება და განახლება",
 	"Save & Update": "დამახსოვრება და განახლება",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "ჩეთის ისტორიის შენახვა პირდაპირ თქვენი ბრაუზერის საცავში აღარ არის მხარდაჭერილი. გთხოვთ, დაუთმოთ და წაშალოთ თქვენი ჩატის ჟურნალები ქვემოთ მოცემულ ღილაკზე დაწკაპუნებით. არ ინერვიულოთ, თქვენ შეგიძლიათ მარტივად ხელახლა შემოიტანოთ თქვენი ჩეთის ისტორია ბექენდში",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "ჩეთის ისტორიის შენახვა პირდაპირ თქვენი ბრაუზერის საცავში აღარ არის მხარდაჭერილი. გთხოვთ, დაუთმოთ და წაშალოთ თქვენი ჩატის ჟურნალები ქვემოთ მოცემულ ღილაკზე დაწკაპუნებით. არ ინერვიულოთ, თქვენ შეგიძლიათ მარტივად ხელახლა შემოიტანოთ თქვენი ჩეთის ისტორია ბექენდში",
 	"Scan": "სკანირება",
 	"Scan": "სკანირება",
@@ -379,6 +375,7 @@
 	"Select a model": "მოდელის არჩევა",
 	"Select a model": "მოდელის არჩევა",
 	"Select an Ollama instance": "",
 	"Select an Ollama instance": "",
 	"Select model": "",
 	"Select model": "",
+	"Send": "",
 	"Send a Message": "შეტყობინების გაგზავნა",
 	"Send a Message": "შეტყობინების გაგზავნა",
 	"Send message": "შეტყობინების გაგზავნა",
 	"Send message": "შეტყობინების გაგზავნა",
 	"September": "",
 	"September": "",
@@ -482,10 +479,10 @@
 	"What’s New in": "რა არის ახალი",
 	"What’s New in": "რა არის ახალი",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "როდესაც ისტორია გამორთულია, ახალი ჩეთები ამ ბრაუზერში არ გამოჩნდება თქვენს ისტორიაში არცერთ მოწყობილობაზე.",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "როდესაც ისტორია გამორთულია, ახალი ჩეთები ამ ბრაუზერში არ გამოჩნდება თქვენს ისტორიაში არცერთ მოწყობილობაზე.",
 	"Whisper (Local)": "ჩურჩული (ადგილობრივი)",
 	"Whisper (Local)": "ჩურჩული (ადგილობრივი)",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "დაწერეთ მოკლე წინადადება (მაგ. ვინ ხარ?",
 	"Write a prompt suggestion (e.g. Who are you?)": "დაწერეთ მოკლე წინადადება (მაგ. ვინ ხარ?",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "დაწერეთ რეზიუმე 50 სიტყვით, რომელიც აჯამებს [თემას ან საკვანძო სიტყვას].",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "დაწერეთ რეზიუმე 50 სიტყვით, რომელიც აჯამებს [თემას ან საკვანძო სიტყვას].",
 	"Yesterday": "",
 	"Yesterday": "",
-	"You": "თქვენ",
 	"You have no archived conversations.": "",
 	"You have no archived conversations.": "",
 	"You have shared this chat": "",
 	"You have shared this chat": "",
 	"You're a helpful assistant.": "თქვენ სასარგებლო ასისტენტი ხართ.",
 	"You're a helpful assistant.": "თქვენ სასარგებლო ასისტენტი ხართ.",

+ 3 - 6
src/lib/i18n/locales/ko-KR/translation.json

@@ -117,7 +117,6 @@
 	"Dark": "어두운",
 	"Dark": "어두운",
 	"Dashboard": "",
 	"Dashboard": "",
 	"Database": "데이터베이스",
 	"Database": "데이터베이스",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
 	"December": "",
 	"December": "",
 	"Default": "기본값",
 	"Default": "기본값",
 	"Default (Automatic1111)": "기본값 (Automatic1111)",
 	"Default (Automatic1111)": "기본값 (Automatic1111)",
@@ -211,6 +210,7 @@
 	"General Settings": "일반 설정",
 	"General Settings": "일반 설정",
 	"Generation Info": "",
 	"Generation Info": "",
 	"Good Response": "",
 	"Good Response": "",
+	"h:mm a": "",
 	"has no conversations.": "",
 	"has no conversations.": "",
 	"Hello, {{name}}": "안녕하세요, {{name}}",
 	"Hello, {{name}}": "안녕하세요, {{name}}",
 	"Help": "",
 	"Help": "",
@@ -276,9 +276,6 @@
 	"Modelfiles": "모델파일",
 	"Modelfiles": "모델파일",
 	"Models": "모델",
 	"Models": "모델",
 	"More": "",
 	"More": "",
-	"My Documents": "내 문서",
-	"My Modelfiles": "내 모델파일",
-	"My Prompts": "내 프롬프트",
 	"Name": "이름",
 	"Name": "이름",
 	"Name Tag": "이름 태그",
 	"Name Tag": "이름 태그",
 	"Name your modelfile": "모델파일 이름 지정",
 	"Name your modelfile": "모델파일 이름 지정",
@@ -362,7 +359,6 @@
 	"Rosé Pine Dawn": "로제 파인 던",
 	"Rosé Pine Dawn": "로제 파인 던",
 	"Save": "저장",
 	"Save": "저장",
 	"Save & Create": "저장 및 생성",
 	"Save & Create": "저장 및 생성",
-	"Save & Submit": "저장 및 제출",
 	"Save & Update": "저장 및 업데이트",
 	"Save & Update": "저장 및 업데이트",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "브라우저의 저장소에 채팅 로그를 직접 저장하는 것은 더 이상 지원되지 않습니다. 아래 버튼을 클릭하여 채팅 로그를 다운로드하고 삭제하세요. 걱정 마세요. 백엔드를 통해 채팅 로그를 쉽게 다시 가져올 수 있습니다.",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "브라우저의 저장소에 채팅 로그를 직접 저장하는 것은 더 이상 지원되지 않습니다. 아래 버튼을 클릭하여 채팅 로그를 다운로드하고 삭제하세요. 걱정 마세요. 백엔드를 통해 채팅 로그를 쉽게 다시 가져올 수 있습니다.",
 	"Scan": "스캔",
 	"Scan": "스캔",
@@ -379,6 +375,7 @@
 	"Select a model": "모델 선택",
 	"Select a model": "모델 선택",
 	"Select an Ollama instance": "Ollama 인스턴스 선택",
 	"Select an Ollama instance": "Ollama 인스턴스 선택",
 	"Select model": "",
 	"Select model": "",
+	"Send": "",
 	"Send a Message": "메시지 보내기",
 	"Send a Message": "메시지 보내기",
 	"Send message": "메시지 보내기",
 	"Send message": "메시지 보내기",
 	"September": "",
 	"September": "",
@@ -482,10 +479,10 @@
 	"What’s New in": "",
 	"What’s New in": "",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "기록 기능이 꺼져 있으면 이 브라우저의 새 채팅이 다른 장치의 채팅 기록에 나타나지 않습니다.",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "기록 기능이 꺼져 있으면 이 브라우저의 새 채팅이 다른 장치의 채팅 기록에 나타나지 않습니다.",
 	"Whisper (Local)": "위스퍼 (Local)",
 	"Whisper (Local)": "위스퍼 (Local)",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "프롬프트 제안 작성 (예: 당신은 누구인가요?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "프롬프트 제안 작성 (예: 당신은 누구인가요?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "[주제 또는 키워드]에 대한 50단어 요약문 작성.",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "[주제 또는 키워드]에 대한 50단어 요약문 작성.",
 	"Yesterday": "",
 	"Yesterday": "",
-	"You": "당신",
 	"You have no archived conversations.": "",
 	"You have no archived conversations.": "",
 	"You have shared this chat": "",
 	"You have shared this chat": "",
 	"You're a helpful assistant.": "당신은 유용한 어시스턴트입니다.",
 	"You're a helpful assistant.": "당신은 유용한 어시스턴트입니다.",

+ 3 - 6
src/lib/i18n/locales/nl-NL/translation.json

@@ -117,7 +117,6 @@
 	"Dark": "Donker",
 	"Dark": "Donker",
 	"Dashboard": "",
 	"Dashboard": "",
 	"Database": "Database",
 	"Database": "Database",
-	"DD/MM/YYYY HH:mm": "YYYY/MM/DD HH:mm",
 	"December": "",
 	"December": "",
 	"Default": "Standaard",
 	"Default": "Standaard",
 	"Default (Automatic1111)": "Standaard (Automatic1111)",
 	"Default (Automatic1111)": "Standaard (Automatic1111)",
@@ -211,6 +210,7 @@
 	"General Settings": "Algemene Instellingen",
 	"General Settings": "Algemene Instellingen",
 	"Generation Info": "",
 	"Generation Info": "",
 	"Good Response": "",
 	"Good Response": "",
+	"h:mm a": "",
 	"has no conversations.": "",
 	"has no conversations.": "",
 	"Hello, {{name}}": "Hallo, {{name}}",
 	"Hello, {{name}}": "Hallo, {{name}}",
 	"Help": "",
 	"Help": "",
@@ -276,9 +276,6 @@
 	"Modelfiles": "Modelfiles",
 	"Modelfiles": "Modelfiles",
 	"Models": "Modellen",
 	"Models": "Modellen",
 	"More": "",
 	"More": "",
-	"My Documents": "Mijn Documenten",
-	"My Modelfiles": "Mijn Modelfiles",
-	"My Prompts": "Mijn Prompts",
 	"Name": "Naam",
 	"Name": "Naam",
 	"Name Tag": "Naam Tag",
 	"Name Tag": "Naam Tag",
 	"Name your modelfile": "Benoem je modelfile",
 	"Name your modelfile": "Benoem je modelfile",
@@ -362,7 +359,6 @@
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Save": "Opslaan",
 	"Save": "Opslaan",
 	"Save & Create": "Opslaan & Creëren",
 	"Save & Create": "Opslaan & Creëren",
-	"Save & Submit": "Opslaan & Verzenden",
 	"Save & Update": "Opslaan & Bijwerken",
 	"Save & Update": "Opslaan & Bijwerken",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Chat logs direct opslaan in de opslag van je browser wordt niet langer ondersteund. Neem even de tijd om je chat logs te downloaden en te verwijderen door op de knop hieronder te klikken. Maak je geen zorgen, je kunt je chat logs eenvoudig opnieuw importeren naar de backend via",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Chat logs direct opslaan in de opslag van je browser wordt niet langer ondersteund. Neem even de tijd om je chat logs te downloaden en te verwijderen door op de knop hieronder te klikken. Maak je geen zorgen, je kunt je chat logs eenvoudig opnieuw importeren naar de backend via",
 	"Scan": "Scan",
 	"Scan": "Scan",
@@ -379,6 +375,7 @@
 	"Select a model": "Selecteer een model",
 	"Select a model": "Selecteer een model",
 	"Select an Ollama instance": "Selecteer een Ollama instantie",
 	"Select an Ollama instance": "Selecteer een Ollama instantie",
 	"Select model": "",
 	"Select model": "",
+	"Send": "",
 	"Send a Message": "Stuur een Bericht",
 	"Send a Message": "Stuur een Bericht",
 	"Send message": "Stuur bericht",
 	"Send message": "Stuur bericht",
 	"September": "",
 	"September": "",
@@ -482,10 +479,10 @@
 	"What’s New in": "Wat is nieuw in",
 	"What’s New in": "Wat is nieuw in",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Wanneer geschiedenis is uitgeschakeld, zullen nieuwe chats op deze browser niet verschijnen in je geschiedenis op een van je apparaten.",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Wanneer geschiedenis is uitgeschakeld, zullen nieuwe chats op deze browser niet verschijnen in je geschiedenis op een van je apparaten.",
 	"Whisper (Local)": "Fluister (Lokaal)",
 	"Whisper (Local)": "Fluister (Lokaal)",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "Schrijf een prompt suggestie (bijv. Wie ben je?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "Schrijf een prompt suggestie (bijv. Wie ben je?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Schrijf een samenvatting in 50 woorden die [onderwerp of trefwoord] samenvat.",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Schrijf een samenvatting in 50 woorden die [onderwerp of trefwoord] samenvat.",
 	"Yesterday": "",
 	"Yesterday": "",
-	"You": "Jij",
 	"You have no archived conversations.": "",
 	"You have no archived conversations.": "",
 	"You have shared this chat": "",
 	"You have shared this chat": "",
 	"You're a helpful assistant.": "Jij bent een behulpzame assistent.",
 	"You're a helpful assistant.": "Jij bent een behulpzame assistent.",

+ 3 - 6
src/lib/i18n/locales/pl-PL/translation.json

@@ -117,7 +117,6 @@
 	"Dark": "Ciemny",
 	"Dark": "Ciemny",
 	"Dashboard": "Dashboard",
 	"Dashboard": "Dashboard",
 	"Database": "Baza danych",
 	"Database": "Baza danych",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
 	"December": "Grudzień",
 	"December": "Grudzień",
 	"Default": "Domyślny",
 	"Default": "Domyślny",
 	"Default (Automatic1111)": "Domyślny (Automatic1111)",
 	"Default (Automatic1111)": "Domyślny (Automatic1111)",
@@ -211,6 +210,7 @@
 	"General Settings": "Ogólne ustawienia",
 	"General Settings": "Ogólne ustawienia",
 	"Generation Info": "Informacja o generacji",
 	"Generation Info": "Informacja o generacji",
 	"Good Response": "Dobra odpowiedź",
 	"Good Response": "Dobra odpowiedź",
+	"h:mm a": "",
 	"has no conversations.": "nie ma rozmów.",
 	"has no conversations.": "nie ma rozmów.",
 	"Hello, {{name}}": "Witaj, {{name}}",
 	"Hello, {{name}}": "Witaj, {{name}}",
 	"Help": "Pomoc",
 	"Help": "Pomoc",
@@ -276,9 +276,6 @@
 	"Modelfiles": "Pliki modeli",
 	"Modelfiles": "Pliki modeli",
 	"Models": "Modele",
 	"Models": "Modele",
 	"More": "Więcej",
 	"More": "Więcej",
-	"My Documents": "Moje dokumenty",
-	"My Modelfiles": "Moje pliki modeli",
-	"My Prompts": "Moje prompty",
 	"Name": "Nazwa",
 	"Name": "Nazwa",
 	"Name Tag": "Etykieta nazwy",
 	"Name Tag": "Etykieta nazwy",
 	"Name your modelfile": "Nadaj nazwę swojemu plikowi modelu",
 	"Name your modelfile": "Nadaj nazwę swojemu plikowi modelu",
@@ -362,7 +359,6 @@
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Save": "Zapisz",
 	"Save": "Zapisz",
 	"Save & Create": "Zapisz i utwórz",
 	"Save & Create": "Zapisz i utwórz",
-	"Save & Submit": "Zapisz i wyślij",
 	"Save & Update": "Zapisz i zaktualizuj",
 	"Save & Update": "Zapisz i zaktualizuj",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Bezpośrednie zapisywanie dzienników czatu w pamięci przeglądarki nie jest już obsługiwane. Prosimy o pobranie i usunięcie dzienników czatu, klikając poniższy przycisk. Nie martw się, możesz łatwo ponownie zaimportować dzienniki czatu do backendu za pomocą",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Bezpośrednie zapisywanie dzienników czatu w pamięci przeglądarki nie jest już obsługiwane. Prosimy o pobranie i usunięcie dzienników czatu, klikając poniższy przycisk. Nie martw się, możesz łatwo ponownie zaimportować dzienniki czatu do backendu za pomocą",
 	"Scan": "Skanuj",
 	"Scan": "Skanuj",
@@ -379,6 +375,7 @@
 	"Select a model": "Wybierz model",
 	"Select a model": "Wybierz model",
 	"Select an Ollama instance": "Wybierz instancję Ollama",
 	"Select an Ollama instance": "Wybierz instancję Ollama",
 	"Select model": "Wybierz model",
 	"Select model": "Wybierz model",
+	"Send": "",
 	"Send a Message": "Wyślij Wiadomość",
 	"Send a Message": "Wyślij Wiadomość",
 	"Send message": "Wyślij wiadomość",
 	"Send message": "Wyślij wiadomość",
 	"September": "Wrzesień",
 	"September": "Wrzesień",
@@ -482,10 +479,10 @@
 	"What’s New in": "Co nowego w",
 	"What’s New in": "Co nowego w",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Kiedy historia jest wyłączona, nowe czaty na tej przeglądarce nie będą widoczne w historii na żadnym z twoich urządzeń.",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Kiedy historia jest wyłączona, nowe czaty na tej przeglądarce nie będą widoczne w historii na żadnym z twoich urządzeń.",
 	"Whisper (Local)": "Whisper (Lokalnie)",
 	"Whisper (Local)": "Whisper (Lokalnie)",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "Napisz sugestię do polecenia (np. Kim jesteś?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "Napisz sugestię do polecenia (np. Kim jesteś?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Napisz podsumowanie w 50 słowach, które podsumowuje [temat lub słowo kluczowe].",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Napisz podsumowanie w 50 słowach, które podsumowuje [temat lub słowo kluczowe].",
 	"Yesterday": "Wczoraj",
 	"Yesterday": "Wczoraj",
-	"You": "Ty",
 	"You have no archived conversations.": "Nie masz zarchiwizowanych rozmów.",
 	"You have no archived conversations.": "Nie masz zarchiwizowanych rozmów.",
 	"You have shared this chat": "Udostępniłeś ten czat",
 	"You have shared this chat": "Udostępniłeś ten czat",
 	"You're a helpful assistant.": "Jesteś pomocnym asystentem.",
 	"You're a helpful assistant.": "Jesteś pomocnym asystentem.",

+ 3 - 6
src/lib/i18n/locales/pt-BR/translation.json

@@ -117,7 +117,6 @@
 	"Dark": "Escuro",
 	"Dark": "Escuro",
 	"Dashboard": "",
 	"Dashboard": "",
 	"Database": "Banco de dados",
 	"Database": "Banco de dados",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
 	"December": "",
 	"December": "",
 	"Default": "Padrão",
 	"Default": "Padrão",
 	"Default (Automatic1111)": "Padrão (Automatic1111)",
 	"Default (Automatic1111)": "Padrão (Automatic1111)",
@@ -211,6 +210,7 @@
 	"General Settings": "Configurações Gerais",
 	"General Settings": "Configurações Gerais",
 	"Generation Info": "",
 	"Generation Info": "",
 	"Good Response": "",
 	"Good Response": "",
+	"h:mm a": "",
 	"has no conversations.": "",
 	"has no conversations.": "",
 	"Hello, {{name}}": "Olá, {{name}}",
 	"Hello, {{name}}": "Olá, {{name}}",
 	"Help": "",
 	"Help": "",
@@ -276,9 +276,6 @@
 	"Modelfiles": "Arquivos de Modelo",
 	"Modelfiles": "Arquivos de Modelo",
 	"Models": "Modelos",
 	"Models": "Modelos",
 	"More": "",
 	"More": "",
-	"My Documents": "Meus Documentos",
-	"My Modelfiles": "Meus Arquivos de Modelo",
-	"My Prompts": "Meus Prompts",
 	"Name": "Nome",
 	"Name": "Nome",
 	"Name Tag": "Nome da Tag",
 	"Name Tag": "Nome da Tag",
 	"Name your modelfile": "Nomeie seu arquivo de modelo",
 	"Name your modelfile": "Nomeie seu arquivo de modelo",
@@ -362,7 +359,6 @@
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Save": "Salvar",
 	"Save": "Salvar",
 	"Save & Create": "Salvar e Criar",
 	"Save & Create": "Salvar e Criar",
-	"Save & Submit": "Salvar e Enviar",
 	"Save & Update": "Salvar e Atualizar",
 	"Save & Update": "Salvar e Atualizar",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Salvar logs de bate-papo diretamente no armazenamento do seu navegador não é mais suportado. Reserve um momento para baixar e excluir seus logs de bate-papo clicando no botão abaixo. Não se preocupe, você pode facilmente reimportar seus logs de bate-papo para o backend através de",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Salvar logs de bate-papo diretamente no armazenamento do seu navegador não é mais suportado. Reserve um momento para baixar e excluir seus logs de bate-papo clicando no botão abaixo. Não se preocupe, você pode facilmente reimportar seus logs de bate-papo para o backend através de",
 	"Scan": "Digitalizar",
 	"Scan": "Digitalizar",
@@ -379,6 +375,7 @@
 	"Select a model": "Selecione um modelo",
 	"Select a model": "Selecione um modelo",
 	"Select an Ollama instance": "Selecione uma instância Ollama",
 	"Select an Ollama instance": "Selecione uma instância Ollama",
 	"Select model": "",
 	"Select model": "",
+	"Send": "",
 	"Send a Message": "Enviar uma Mensagem",
 	"Send a Message": "Enviar uma Mensagem",
 	"Send message": "Enviar mensagem",
 	"Send message": "Enviar mensagem",
 	"September": "",
 	"September": "",
@@ -482,10 +479,10 @@
 	"What’s New in": "O que há de novo em",
 	"What’s New in": "O que há de novo em",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Quando o histórico está desativado, novos bate-papos neste navegador não aparecerão em seu histórico em nenhum dos seus dispositivos.",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Quando o histórico está desativado, novos bate-papos neste navegador não aparecerão em seu histórico em nenhum dos seus dispositivos.",
 	"Whisper (Local)": "Whisper (Local)",
 	"Whisper (Local)": "Whisper (Local)",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "Escreva uma sugestão de prompt (por exemplo, Quem é você?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "Escreva uma sugestão de prompt (por exemplo, Quem é você?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Escreva um resumo em 50 palavras que resuma [tópico ou palavra-chave].",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Escreva um resumo em 50 palavras que resuma [tópico ou palavra-chave].",
 	"Yesterday": "",
 	"Yesterday": "",
-	"You": "Você",
 	"You have no archived conversations.": "",
 	"You have no archived conversations.": "",
 	"You have shared this chat": "",
 	"You have shared this chat": "",
 	"You're a helpful assistant.": "Você é um assistente útil.",
 	"You're a helpful assistant.": "Você é um assistente útil.",

+ 3 - 6
src/lib/i18n/locales/pt-PT/translation.json

@@ -117,7 +117,6 @@
 	"Dark": "Escuro",
 	"Dark": "Escuro",
 	"Dashboard": "",
 	"Dashboard": "",
 	"Database": "Banco de dados",
 	"Database": "Banco de dados",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
 	"December": "",
 	"December": "",
 	"Default": "Padrão",
 	"Default": "Padrão",
 	"Default (Automatic1111)": "Padrão (Automatic1111)",
 	"Default (Automatic1111)": "Padrão (Automatic1111)",
@@ -211,6 +210,7 @@
 	"General Settings": "Configurações Gerais",
 	"General Settings": "Configurações Gerais",
 	"Generation Info": "",
 	"Generation Info": "",
 	"Good Response": "",
 	"Good Response": "",
+	"h:mm a": "",
 	"has no conversations.": "",
 	"has no conversations.": "",
 	"Hello, {{name}}": "Olá, {{name}}",
 	"Hello, {{name}}": "Olá, {{name}}",
 	"Help": "",
 	"Help": "",
@@ -276,9 +276,6 @@
 	"Modelfiles": "Arquivos de Modelo",
 	"Modelfiles": "Arquivos de Modelo",
 	"Models": "Modelos",
 	"Models": "Modelos",
 	"More": "",
 	"More": "",
-	"My Documents": "Meus Documentos",
-	"My Modelfiles": "Meus Arquivos de Modelo",
-	"My Prompts": "Meus Prompts",
 	"Name": "Nome",
 	"Name": "Nome",
 	"Name Tag": "Tag de Nome",
 	"Name Tag": "Tag de Nome",
 	"Name your modelfile": "Nomeie seu arquivo de modelo",
 	"Name your modelfile": "Nomeie seu arquivo de modelo",
@@ -362,7 +359,6 @@
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Save": "Salvar",
 	"Save": "Salvar",
 	"Save & Create": "Salvar e Criar",
 	"Save & Create": "Salvar e Criar",
-	"Save & Submit": "Salvar e Enviar",
 	"Save & Update": "Salvar e Atualizar",
 	"Save & Update": "Salvar e Atualizar",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Salvar logs de bate-papo diretamente no armazenamento do seu navegador não é mais suportado. Reserve um momento para baixar e excluir seus logs de bate-papo clicando no botão abaixo. Não se preocupe, você pode facilmente reimportar seus logs de bate-papo para o backend através de",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Salvar logs de bate-papo diretamente no armazenamento do seu navegador não é mais suportado. Reserve um momento para baixar e excluir seus logs de bate-papo clicando no botão abaixo. Não se preocupe, você pode facilmente reimportar seus logs de bate-papo para o backend através de",
 	"Scan": "Digitalizar",
 	"Scan": "Digitalizar",
@@ -379,6 +375,7 @@
 	"Select a model": "Selecione um modelo",
 	"Select a model": "Selecione um modelo",
 	"Select an Ollama instance": "Selecione uma instância Ollama",
 	"Select an Ollama instance": "Selecione uma instância Ollama",
 	"Select model": "",
 	"Select model": "",
+	"Send": "",
 	"Send a Message": "Enviar uma Mensagem",
 	"Send a Message": "Enviar uma Mensagem",
 	"Send message": "Enviar mensagem",
 	"Send message": "Enviar mensagem",
 	"September": "",
 	"September": "",
@@ -482,10 +479,10 @@
 	"What’s New in": "O que há de novo em",
 	"What’s New in": "O que há de novo em",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Quando o histórico está desativado, novos bate-papos neste navegador não aparecerão em seu histórico em nenhum dos seus dispositivos.",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Quando o histórico está desativado, novos bate-papos neste navegador não aparecerão em seu histórico em nenhum dos seus dispositivos.",
 	"Whisper (Local)": "Whisper (Local)",
 	"Whisper (Local)": "Whisper (Local)",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "Escreva uma sugestão de prompt (por exemplo, Quem é você?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "Escreva uma sugestão de prompt (por exemplo, Quem é você?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Escreva um resumo em 50 palavras que resuma [tópico ou palavra-chave].",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Escreva um resumo em 50 palavras que resuma [tópico ou palavra-chave].",
 	"Yesterday": "",
 	"Yesterday": "",
-	"You": "Você",
 	"You have no archived conversations.": "",
 	"You have no archived conversations.": "",
 	"You have shared this chat": "",
 	"You have shared this chat": "",
 	"You're a helpful assistant.": "Você é um assistente útil.",
 	"You're a helpful assistant.": "Você é um assistente útil.",

+ 3 - 6
src/lib/i18n/locales/ru-RU/translation.json

@@ -117,7 +117,6 @@
 	"Dark": "Тёмный",
 	"Dark": "Тёмный",
 	"Dashboard": "",
 	"Dashboard": "",
 	"Database": "База данных",
 	"Database": "База данных",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
 	"December": "",
 	"December": "",
 	"Default": "По умолчанию",
 	"Default": "По умолчанию",
 	"Default (Automatic1111)": "По умолчанию (Automatic1111)",
 	"Default (Automatic1111)": "По умолчанию (Automatic1111)",
@@ -211,6 +210,7 @@
 	"General Settings": "Общие настройки",
 	"General Settings": "Общие настройки",
 	"Generation Info": "",
 	"Generation Info": "",
 	"Good Response": "",
 	"Good Response": "",
+	"h:mm a": "",
 	"has no conversations.": "",
 	"has no conversations.": "",
 	"Hello, {{name}}": "Привет, {{name}}",
 	"Hello, {{name}}": "Привет, {{name}}",
 	"Help": "",
 	"Help": "",
@@ -276,9 +276,6 @@
 	"Modelfiles": "Файлы моделей",
 	"Modelfiles": "Файлы моделей",
 	"Models": "Модели",
 	"Models": "Модели",
 	"More": "",
 	"More": "",
-	"My Documents": "Мои документы",
-	"My Modelfiles": "Мои файлы моделей",
-	"My Prompts": "Мои подсказки",
 	"Name": "Имя",
 	"Name": "Имя",
 	"Name Tag": "Имя тега",
 	"Name Tag": "Имя тега",
 	"Name your modelfile": "Назовите свой файл модели",
 	"Name your modelfile": "Назовите свой файл модели",
@@ -362,7 +359,6 @@
 	"Rosé Pine Dawn": "Розовое сосновое дерево рассвет",
 	"Rosé Pine Dawn": "Розовое сосновое дерево рассвет",
 	"Save": "Сохранить",
 	"Save": "Сохранить",
 	"Save & Create": "Сохранить и создать",
 	"Save & Create": "Сохранить и создать",
-	"Save & Submit": "Сохранить и отправить",
 	"Save & Update": "Сохранить и обновить",
 	"Save & Update": "Сохранить и обновить",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Прямое сохранение журналов чата в хранилище вашего браузера больше не поддерживается. Пожалуйста, потратьте минуту, чтобы скачать и удалить ваши журналы чата, нажав на кнопку ниже. Не волнуйтесь, вы легко сможете повторно импортировать свои журналы чата в бэкенд через",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Прямое сохранение журналов чата в хранилище вашего браузера больше не поддерживается. Пожалуйста, потратьте минуту, чтобы скачать и удалить ваши журналы чата, нажав на кнопку ниже. Не волнуйтесь, вы легко сможете повторно импортировать свои журналы чата в бэкенд через",
 	"Scan": "Сканировать",
 	"Scan": "Сканировать",
@@ -379,6 +375,7 @@
 	"Select a model": "Выберите модель",
 	"Select a model": "Выберите модель",
 	"Select an Ollama instance": "Выберите экземпляр Ollama",
 	"Select an Ollama instance": "Выберите экземпляр Ollama",
 	"Select model": "",
 	"Select model": "",
+	"Send": "",
 	"Send a Message": "Отправить сообщение",
 	"Send a Message": "Отправить сообщение",
 	"Send message": "Отправить сообщение",
 	"Send message": "Отправить сообщение",
 	"September": "",
 	"September": "",
@@ -482,10 +479,10 @@
 	"What’s New in": "Что нового в",
 	"What’s New in": "Что нового в",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Когда история отключена, новые чаты в этом браузере не будут отображаться в вашей истории на любом из ваших устройств.",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Когда история отключена, новые чаты в этом браузере не будут отображаться в вашей истории на любом из ваших устройств.",
 	"Whisper (Local)": "Шепот (локальный)",
 	"Whisper (Local)": "Шепот (локальный)",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "Напишите предложение промпта (например, Кто вы?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "Напишите предложение промпта (например, Кто вы?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Напишите резюме в 50 словах, которое кратко описывает [тему или ключевое слово].",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Напишите резюме в 50 словах, которое кратко описывает [тему или ключевое слово].",
 	"Yesterday": "",
 	"Yesterday": "",
-	"You": "Вы",
 	"You have no archived conversations.": "",
 	"You have no archived conversations.": "",
 	"You have shared this chat": "",
 	"You have shared this chat": "",
 	"You're a helpful assistant.": "Вы полезный ассистент.",
 	"You're a helpful assistant.": "Вы полезный ассистент.",

+ 3 - 6
src/lib/i18n/locales/sv-SE/translation.json

@@ -117,7 +117,6 @@
 	"Dark": "Mörk",
 	"Dark": "Mörk",
 	"Dashboard": "",
 	"Dashboard": "",
 	"Database": "Databas",
 	"Database": "Databas",
-	"DD/MM/YYYY HH:mm": "DD/MM/ÅÅÅÅ TT:mm",
 	"December": "",
 	"December": "",
 	"Default": "Standard",
 	"Default": "Standard",
 	"Default (Automatic1111)": "Standard (Automatic1111)",
 	"Default (Automatic1111)": "Standard (Automatic1111)",
@@ -211,6 +210,7 @@
 	"General Settings": "Allmänna inställningar",
 	"General Settings": "Allmänna inställningar",
 	"Generation Info": "",
 	"Generation Info": "",
 	"Good Response": "",
 	"Good Response": "",
+	"h:mm a": "",
 	"has no conversations.": "",
 	"has no conversations.": "",
 	"Hello, {{name}}": "Hej, {{name}}",
 	"Hello, {{name}}": "Hej, {{name}}",
 	"Help": "",
 	"Help": "",
@@ -276,9 +276,6 @@
 	"Modelfiles": "Modelfiler",
 	"Modelfiles": "Modelfiler",
 	"Models": "Modeller",
 	"Models": "Modeller",
 	"More": "",
 	"More": "",
-	"My Documents": "Mina dokument",
-	"My Modelfiles": "Mina modelfiler",
-	"My Prompts": "Mina promptar",
 	"Name": "Namn",
 	"Name": "Namn",
 	"Name Tag": "Namntag",
 	"Name Tag": "Namntag",
 	"Name your modelfile": "Namnge din modelfil",
 	"Name your modelfile": "Namnge din modelfil",
@@ -362,7 +359,6 @@
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Save": "Spara",
 	"Save": "Spara",
 	"Save & Create": "Spara och skapa",
 	"Save & Create": "Spara och skapa",
-	"Save & Submit": "Spara och skicka",
 	"Save & Update": "Spara och uppdatera",
 	"Save & Update": "Spara och uppdatera",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Att spara chatloggar direkt till din webbläsares lagring stöds inte längre. Ta en stund och ladda ner och radera dina chattloggar genom att klicka på knappen nedan. Oroa dig inte, du kan enkelt importera dina chattloggar till backend genom",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Att spara chatloggar direkt till din webbläsares lagring stöds inte längre. Ta en stund och ladda ner och radera dina chattloggar genom att klicka på knappen nedan. Oroa dig inte, du kan enkelt importera dina chattloggar till backend genom",
 	"Scan": "Skanna",
 	"Scan": "Skanna",
@@ -379,6 +375,7 @@
 	"Select a model": "Välj en modell",
 	"Select a model": "Välj en modell",
 	"Select an Ollama instance": "Välj en Ollama-instans",
 	"Select an Ollama instance": "Välj en Ollama-instans",
 	"Select model": "",
 	"Select model": "",
+	"Send": "",
 	"Send a Message": "Skicka ett meddelande",
 	"Send a Message": "Skicka ett meddelande",
 	"Send message": "Skicka meddelande",
 	"Send message": "Skicka meddelande",
 	"September": "",
 	"September": "",
@@ -482,10 +479,10 @@
 	"What’s New in": "Vad är nytt i",
 	"What’s New in": "Vad är nytt i",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "När historiken är avstängd visas inte nya chattar i denna webbläsare i din historik på någon av dina enheter.",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "När historiken är avstängd visas inte nya chattar i denna webbläsare i din historik på någon av dina enheter.",
 	"Whisper (Local)": "Whisper (lokal)",
 	"Whisper (Local)": "Whisper (lokal)",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "Skriv ett förslag (t.ex. Vem är du?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "Skriv ett förslag (t.ex. Vem är du?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Skriv en sammanfattning på 50 ord som sammanfattar [ämne eller nyckelord].",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Skriv en sammanfattning på 50 ord som sammanfattar [ämne eller nyckelord].",
 	"Yesterday": "",
 	"Yesterday": "",
-	"You": "Du",
 	"You have no archived conversations.": "",
 	"You have no archived conversations.": "",
 	"You have shared this chat": "",
 	"You have shared this chat": "",
 	"You're a helpful assistant.": "Du är en hjälpsam assistent.",
 	"You're a helpful assistant.": "Du är en hjälpsam assistent.",

+ 3 - 6
src/lib/i18n/locales/tr-TR/translation.json

@@ -117,7 +117,6 @@
 	"Dark": "Koyu",
 	"Dark": "Koyu",
 	"Dashboard": "Panel",
 	"Dashboard": "Panel",
 	"Database": "Veritabanı",
 	"Database": "Veritabanı",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
 	"December": "Aralık",
 	"December": "Aralık",
 	"Default": "Varsayılan",
 	"Default": "Varsayılan",
 	"Default (Automatic1111)": "Varsayılan (Automatic1111)",
 	"Default (Automatic1111)": "Varsayılan (Automatic1111)",
@@ -211,6 +210,7 @@
 	"General Settings": "Genel Ayarlar",
 	"General Settings": "Genel Ayarlar",
 	"Generation Info": "Üretim Bilgisi",
 	"Generation Info": "Üretim Bilgisi",
 	"Good Response": "İyi Yanıt",
 	"Good Response": "İyi Yanıt",
+	"h:mm a": "",
 	"has no conversations.": "hiç konuşması yok.",
 	"has no conversations.": "hiç konuşması yok.",
 	"Hello, {{name}}": "Merhaba, {{name}}",
 	"Hello, {{name}}": "Merhaba, {{name}}",
 	"Help": "Yardım",
 	"Help": "Yardım",
@@ -276,9 +276,6 @@
 	"Modelfiles": "Model Dosyaları",
 	"Modelfiles": "Model Dosyaları",
 	"Models": "Modeller",
 	"Models": "Modeller",
 	"More": "Daha Fazla",
 	"More": "Daha Fazla",
-	"My Documents": "Belgelerim",
-	"My Modelfiles": "Model Dosyalarım",
-	"My Prompts": "Promptlarım",
 	"Name": "Ad",
 	"Name": "Ad",
 	"Name Tag": "Ad Etiketi",
 	"Name Tag": "Ad Etiketi",
 	"Name your modelfile": "Model dosyanıza ad verin",
 	"Name your modelfile": "Model dosyanıza ad verin",
@@ -362,7 +359,6 @@
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Save": "Kaydet",
 	"Save": "Kaydet",
 	"Save & Create": "Kaydet ve Oluştur",
 	"Save & Create": "Kaydet ve Oluştur",
-	"Save & Submit": "Kaydet ve Gönder",
 	"Save & Update": "Kaydet ve Güncelle",
 	"Save & Update": "Kaydet ve Güncelle",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Sohbet kayıtlarının doğrudan tarayıcınızın depolama alanına kaydedilmesi artık desteklenmemektedir. Lütfen aşağıdaki butona tıklayarak sohbet kayıtlarınızı indirmek ve silmek için bir dakikanızı ayırın. Endişelenmeyin, sohbet günlüklerinizi arkayüze kolayca yeniden aktarabilirsiniz:",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Sohbet kayıtlarının doğrudan tarayıcınızın depolama alanına kaydedilmesi artık desteklenmemektedir. Lütfen aşağıdaki butona tıklayarak sohbet kayıtlarınızı indirmek ve silmek için bir dakikanızı ayırın. Endişelenmeyin, sohbet günlüklerinizi arkayüze kolayca yeniden aktarabilirsiniz:",
 	"Scan": "Tarama",
 	"Scan": "Tarama",
@@ -379,6 +375,7 @@
 	"Select a model": "Bir model seç",
 	"Select a model": "Bir model seç",
 	"Select an Ollama instance": "Bir Ollama örneği seçin",
 	"Select an Ollama instance": "Bir Ollama örneği seçin",
 	"Select model": "Model seç",
 	"Select model": "Model seç",
+	"Send": "",
 	"Send a Message": "Bir Mesaj Gönder",
 	"Send a Message": "Bir Mesaj Gönder",
 	"Send message": "Mesaj gönder",
 	"Send message": "Mesaj gönder",
 	"September": "Eylül",
 	"September": "Eylül",
@@ -482,10 +479,10 @@
 	"What’s New in": "Yenilikler:",
 	"What’s New in": "Yenilikler:",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Geçmiş kapatıldığında, bu tarayıcıdaki yeni sohbetler hiçbir cihazınızdaki geçmişinizde görünmez.",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Geçmiş kapatıldığında, bu tarayıcıdaki yeni sohbetler hiçbir cihazınızdaki geçmişinizde görünmez.",
 	"Whisper (Local)": "Whisper (Yerel)",
 	"Whisper (Local)": "Whisper (Yerel)",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "Bir prompt önerisi yazın (örn. Sen kimsin?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "Bir prompt önerisi yazın (örn. Sen kimsin?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "[Konuyu veya anahtar kelimeyi] özetleyen 50 kelimelik bir özet yazın.",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "[Konuyu veya anahtar kelimeyi] özetleyen 50 kelimelik bir özet yazın.",
 	"Yesterday": "Dün",
 	"Yesterday": "Dün",
-	"You": "Siz",
 	"You have no archived conversations.": "Arşivlenmiş sohbetleriniz yok.",
 	"You have no archived conversations.": "Arşivlenmiş sohbetleriniz yok.",
 	"You have shared this chat": "Bu sohbeti paylaştınız",
 	"You have shared this chat": "Bu sohbeti paylaştınız",
 	"You're a helpful assistant.": "Sen yardımcı bir asistansın.",
 	"You're a helpful assistant.": "Sen yardımcı bir asistansın.",

+ 3 - 6
src/lib/i18n/locales/uk-UA/translation.json

@@ -117,7 +117,6 @@
 	"Dark": "Темна",
 	"Dark": "Темна",
 	"Dashboard": "Панель управління",
 	"Dashboard": "Панель управління",
 	"Database": "База даних",
 	"Database": "База даних",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
 	"December": "Грудень",
 	"December": "Грудень",
 	"Default": "За замовчуванням",
 	"Default": "За замовчуванням",
 	"Default (Automatic1111)": "За замовчуванням (Automatic1111)",
 	"Default (Automatic1111)": "За замовчуванням (Automatic1111)",
@@ -211,6 +210,7 @@
 	"General Settings": "Загальні налаштування",
 	"General Settings": "Загальні налаштування",
 	"Generation Info": "Інформація про генерацію",
 	"Generation Info": "Інформація про генерацію",
 	"Good Response": "Гарна відповідь",
 	"Good Response": "Гарна відповідь",
+	"h:mm a": "",
 	"has no conversations.": "не має розмов.",
 	"has no conversations.": "не має розмов.",
 	"Hello, {{name}}": "Привіт, {{name}}",
 	"Hello, {{name}}": "Привіт, {{name}}",
 	"Help": "Допоможіть",
 	"Help": "Допоможіть",
@@ -276,9 +276,6 @@
 	"Modelfiles": "Файли моделей",
 	"Modelfiles": "Файли моделей",
 	"Models": "Моделі",
 	"Models": "Моделі",
 	"More": "Більше",
 	"More": "Більше",
-	"My Documents": "Мої документи",
-	"My Modelfiles": "Мої файли моделей",
-	"My Prompts": "Мої промти",
 	"Name": "Ім'я",
 	"Name": "Ім'я",
 	"Name Tag": "Назва тегу",
 	"Name Tag": "Назва тегу",
 	"Name your modelfile": "Назвіть свій файл моделі",
 	"Name your modelfile": "Назвіть свій файл моделі",
@@ -362,7 +359,6 @@
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Save": "Зберегти",
 	"Save": "Зберегти",
 	"Save & Create": "Зберегти та створити",
 	"Save & Create": "Зберегти та створити",
-	"Save & Submit": "Зберегти та надіслати",
 	"Save & Update": "Зберегти та оновити",
 	"Save & Update": "Зберегти та оновити",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Збереження журналів чату безпосередньо в сховище вашого браузера більше не підтримується. Будь ласка, завантажте та видаліть журнали чату, натиснувши кнопку нижче. Не хвилюйтеся, ви можете легко повторно імпортувати журнали чату до бекенду через",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Збереження журналів чату безпосередньо в сховище вашого браузера більше не підтримується. Будь ласка, завантажте та видаліть журнали чату, натиснувши кнопку нижче. Не хвилюйтеся, ви можете легко повторно імпортувати журнали чату до бекенду через",
 	"Scan": "Сканування",
 	"Scan": "Сканування",
@@ -379,6 +375,7 @@
 	"Select a model": "Виберіть модель",
 	"Select a model": "Виберіть модель",
 	"Select an Ollama instance": "Виберіть екземпляр Ollama",
 	"Select an Ollama instance": "Виберіть екземпляр Ollama",
 	"Select model": "Вибрати модель",
 	"Select model": "Вибрати модель",
+	"Send": "",
 	"Send a Message": "Надіслати повідомлення",
 	"Send a Message": "Надіслати повідомлення",
 	"Send message": "Надіслати повідомлення",
 	"Send message": "Надіслати повідомлення",
 	"September": "Вересень",
 	"September": "Вересень",
@@ -482,10 +479,10 @@
 	"What’s New in": "Що нового в",
 	"What’s New in": "Що нового в",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Коли історія вимкнена, нові чати в цьому браузері не будуть відображатися в історії на жодному з ваших пристроїв.",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Коли історія вимкнена, нові чати в цьому браузері не будуть відображатися в історії на жодному з ваших пристроїв.",
 	"Whisper (Local)": "Whisper (локально)",
 	"Whisper (Local)": "Whisper (локально)",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "Напишіть промт (напр., Хто ти?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "Напишіть промт (напр., Хто ти?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Напишіть стислий зміст у 50 слів, який узагальнює [тема або ключове слово].",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Напишіть стислий зміст у 50 слів, який узагальнює [тема або ключове слово].",
 	"Yesterday": "Вчора",
 	"Yesterday": "Вчора",
-	"You": "Ви",
 	"You have no archived conversations.": "У вас немає архівованих розмов.",
 	"You have no archived conversations.": "У вас немає архівованих розмов.",
 	"You have shared this chat": "Ви поділилися цим чатом",
 	"You have shared this chat": "Ви поділилися цим чатом",
 	"You're a helpful assistant.": "Ви корисний асистент.",
 	"You're a helpful assistant.": "Ви корисний асистент.",

+ 3 - 6
src/lib/i18n/locales/vi-VN/translation.json

@@ -117,7 +117,6 @@
 	"Dark": "Tối",
 	"Dark": "Tối",
 	"Dashboard": "",
 	"Dashboard": "",
 	"Database": "Cơ sở dữ liệu",
 	"Database": "Cơ sở dữ liệu",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
 	"December": "",
 	"December": "",
 	"Default": "Mặc định",
 	"Default": "Mặc định",
 	"Default (Automatic1111)": "Mặc định (Automatic1111)",
 	"Default (Automatic1111)": "Mặc định (Automatic1111)",
@@ -211,6 +210,7 @@
 	"General Settings": "Cấu hình chung",
 	"General Settings": "Cấu hình chung",
 	"Generation Info": "",
 	"Generation Info": "",
 	"Good Response": "",
 	"Good Response": "",
+	"h:mm a": "",
 	"has no conversations.": "",
 	"has no conversations.": "",
 	"Hello, {{name}}": "Xin chào, {{name}}",
 	"Hello, {{name}}": "Xin chào, {{name}}",
 	"Help": "",
 	"Help": "",
@@ -276,9 +276,6 @@
 	"Modelfiles": "Tệp Mô hình",
 	"Modelfiles": "Tệp Mô hình",
 	"Models": "Mô hình",
 	"Models": "Mô hình",
 	"More": "",
 	"More": "",
-	"My Documents": "Tài liệu của tôi",
-	"My Modelfiles": "Tệp Mô hình của tôi",
-	"My Prompts": "Các prompt của tôi",
 	"Name": "Tên",
 	"Name": "Tên",
 	"Name Tag": "Tên Thẻ",
 	"Name Tag": "Tên Thẻ",
 	"Name your modelfile": "Đặt tên cho tệp mô hình của bạn",
 	"Name your modelfile": "Đặt tên cho tệp mô hình của bạn",
@@ -362,7 +359,6 @@
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Save": "Lưu",
 	"Save": "Lưu",
 	"Save & Create": "Lưu & Tạo",
 	"Save & Create": "Lưu & Tạo",
-	"Save & Submit": "Lưu & Gửi",
 	"Save & Update": "Lưu & Cập nhật",
 	"Save & Update": "Lưu & Cập nhật",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Không còn hỗ trợ lưu trữ lịch sử chat trực tiếp vào bộ nhớ trình duyệt của bạn. Vui lòng dành thời gian để tải xuống và xóa lịch sử chat của bạn bằng cách nhấp vào nút bên dưới. Đừng lo lắng, bạn có thể dễ dàng nhập lại lịch sử chat của mình vào backend thông qua",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Không còn hỗ trợ lưu trữ lịch sử chat trực tiếp vào bộ nhớ trình duyệt của bạn. Vui lòng dành thời gian để tải xuống và xóa lịch sử chat của bạn bằng cách nhấp vào nút bên dưới. Đừng lo lắng, bạn có thể dễ dàng nhập lại lịch sử chat của mình vào backend thông qua",
 	"Scan": "Quét tài liệu",
 	"Scan": "Quét tài liệu",
@@ -379,6 +375,7 @@
 	"Select a model": "Chọn mô hình",
 	"Select a model": "Chọn mô hình",
 	"Select an Ollama instance": "Chọn một thực thể Ollama",
 	"Select an Ollama instance": "Chọn một thực thể Ollama",
 	"Select model": "",
 	"Select model": "",
+	"Send": "",
 	"Send a Message": "Gửi yêu cầu",
 	"Send a Message": "Gửi yêu cầu",
 	"Send message": "Gửi yêu cầu",
 	"Send message": "Gửi yêu cầu",
 	"September": "",
 	"September": "",
@@ -482,10 +479,10 @@
 	"What’s New in": "",
 	"What’s New in": "",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Khi chế độ lịch sử chat đã tắt, các nội dung chat mới trên trình duyệt này sẽ không xuất hiện trên bất kỳ thiết bị nào của bạn.",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Khi chế độ lịch sử chat đã tắt, các nội dung chat mới trên trình duyệt này sẽ không xuất hiện trên bất kỳ thiết bị nào của bạn.",
 	"Whisper (Local)": "Whisper (Local)",
 	"Whisper (Local)": "Whisper (Local)",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "Hãy viết một prompt (vd: Bạn là ai?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "Hãy viết một prompt (vd: Bạn là ai?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Viết một tóm tắt trong vòng 50 từ cho [chủ đề hoặc từ khóa].",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Viết một tóm tắt trong vòng 50 từ cho [chủ đề hoặc từ khóa].",
 	"Yesterday": "",
 	"Yesterday": "",
-	"You": "Bạn",
 	"You have no archived conversations.": "",
 	"You have no archived conversations.": "",
 	"You have shared this chat": "",
 	"You have shared this chat": "",
 	"You're a helpful assistant.": "Bạn là một trợ lý hữu ích.",
 	"You're a helpful assistant.": "Bạn là một trợ lý hữu ích.",

+ 3 - 6
src/lib/i18n/locales/zh-CN/translation.json

@@ -117,7 +117,6 @@
 	"Dark": "暗色",
 	"Dark": "暗色",
 	"Dashboard": "",
 	"Dashboard": "",
 	"Database": "数据库",
 	"Database": "数据库",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
 	"December": "",
 	"December": "",
 	"Default": "默认",
 	"Default": "默认",
 	"Default (Automatic1111)": "默认(Automatic1111)",
 	"Default (Automatic1111)": "默认(Automatic1111)",
@@ -211,6 +210,7 @@
 	"General Settings": "通用设置",
 	"General Settings": "通用设置",
 	"Generation Info": "生成信息",
 	"Generation Info": "生成信息",
 	"Good Response": "",
 	"Good Response": "",
+	"h:mm a": "",
 	"has no conversations.": "",
 	"has no conversations.": "",
 	"Hello, {{name}}": "你好,{{name}}",
 	"Hello, {{name}}": "你好,{{name}}",
 	"Help": "帮助",
 	"Help": "帮助",
@@ -276,9 +276,6 @@
 	"Modelfiles": "模型文件",
 	"Modelfiles": "模型文件",
 	"Models": "模型",
 	"Models": "模型",
 	"More": "",
 	"More": "",
-	"My Documents": "我的文档",
-	"My Modelfiles": "我的模型文件",
-	"My Prompts": "我的提示词",
 	"Name": "名称",
 	"Name": "名称",
 	"Name Tag": "名称标签",
 	"Name Tag": "名称标签",
 	"Name your modelfile": "命名你的模型文件",
 	"Name your modelfile": "命名你的模型文件",
@@ -362,7 +359,6 @@
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"Save": "保存",
 	"Save": "保存",
 	"Save & Create": "保存并创建",
 	"Save & Create": "保存并创建",
-	"Save & Submit": "保存并提交",
 	"Save & Update": "保存并更新",
 	"Save & Update": "保存并更新",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "不再支持直接将聊天记录保存到浏览器存储中。请点击下面的按钮下载并删除您的聊天记录。别担心,您可以通过轻松地将聊天记录重新导入到后端",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "不再支持直接将聊天记录保存到浏览器存储中。请点击下面的按钮下载并删除您的聊天记录。别担心,您可以通过轻松地将聊天记录重新导入到后端",
 	"Scan": "扫描",
 	"Scan": "扫描",
@@ -379,6 +375,7 @@
 	"Select a model": "选择一个模型",
 	"Select a model": "选择一个模型",
 	"Select an Ollama instance": "选择一个 Ollama 实例",
 	"Select an Ollama instance": "选择一个 Ollama 实例",
 	"Select model": "",
 	"Select model": "",
+	"Send": "",
 	"Send a Message": "发送消息",
 	"Send a Message": "发送消息",
 	"Send message": "发送消息",
 	"Send message": "发送消息",
 	"September": "",
 	"September": "",
@@ -482,10 +479,10 @@
 	"What’s New in": "最新变化",
 	"What’s New in": "最新变化",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "当历史记录被关闭时,这个浏览器上的新聊天不会出现在你任何设备的历史记录中。",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "当历史记录被关闭时,这个浏览器上的新聊天不会出现在你任何设备的历史记录中。",
 	"Whisper (Local)": "Whisper(本地)",
 	"Whisper (Local)": "Whisper(本地)",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "写一个提示建议(例如:你是谁?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "写一个提示建议(例如:你是谁?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "用 50 个字写一个总结 [主题或关键词]。",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "用 50 个字写一个总结 [主题或关键词]。",
 	"Yesterday": "",
 	"Yesterday": "",
-	"You": "你",
 	"You have no archived conversations.": "你没有存档的对话。",
 	"You have no archived conversations.": "你没有存档的对话。",
 	"You have shared this chat": "",
 	"You have shared this chat": "",
 	"You're a helpful assistant.": "你是一个有帮助的助手。",
 	"You're a helpful assistant.": "你是一个有帮助的助手。",

+ 3 - 6
src/lib/i18n/locales/zh-TW/translation.json

@@ -117,7 +117,6 @@
 	"Dark": "暗色",
 	"Dark": "暗色",
 	"Dashboard": "",
 	"Dashboard": "",
 	"Database": "資料庫",
 	"Database": "資料庫",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
 	"December": "",
 	"December": "",
 	"Default": "預設",
 	"Default": "預設",
 	"Default (Automatic1111)": "預設(Automatic1111)",
 	"Default (Automatic1111)": "預設(Automatic1111)",
@@ -211,6 +210,7 @@
 	"General Settings": "常用設定",
 	"General Settings": "常用設定",
 	"Generation Info": "",
 	"Generation Info": "",
 	"Good Response": "",
 	"Good Response": "",
+	"h:mm a": "",
 	"has no conversations.": "",
 	"has no conversations.": "",
 	"Hello, {{name}}": "你好,{{name}}",
 	"Hello, {{name}}": "你好,{{name}}",
 	"Help": "",
 	"Help": "",
@@ -276,9 +276,6 @@
 	"Modelfiles": "Modelfiles",
 	"Modelfiles": "Modelfiles",
 	"Models": "模型",
 	"Models": "模型",
 	"More": "",
 	"More": "",
-	"My Documents": "我的文件",
-	"My Modelfiles": "我的 Modelfiles",
-	"My Prompts": "我的提示詞",
 	"Name": "名稱",
 	"Name": "名稱",
 	"Name Tag": "名稱標籤",
 	"Name Tag": "名稱標籤",
 	"Name your modelfile": "命名你的 Modelfile",
 	"Name your modelfile": "命名你的 Modelfile",
@@ -362,7 +359,6 @@
 	"Rosé Pine Dawn": "黎明玫瑰松",
 	"Rosé Pine Dawn": "黎明玫瑰松",
 	"Save": "儲存",
 	"Save": "儲存",
 	"Save & Create": "儲存並建立",
 	"Save & Create": "儲存並建立",
-	"Save & Submit": "儲存並送出",
 	"Save & Update": "儲存並更新",
 	"Save & Update": "儲存並更新",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "現已不支援將聊天紀錄儲存到瀏覽器儲存空間中。請點擊下面的按鈕下載並刪除你的聊天記錄。別擔心,你可以通過以下方式輕鬆地重新匯入你的聊天記錄到後台",
 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "現已不支援將聊天紀錄儲存到瀏覽器儲存空間中。請點擊下面的按鈕下載並刪除你的聊天記錄。別擔心,你可以通過以下方式輕鬆地重新匯入你的聊天記錄到後台",
 	"Scan": "掃描",
 	"Scan": "掃描",
@@ -379,6 +375,7 @@
 	"Select a model": "選擇一個模型",
 	"Select a model": "選擇一個模型",
 	"Select an Ollama instance": "選擇 Ollama 實例",
 	"Select an Ollama instance": "選擇 Ollama 實例",
 	"Select model": "",
 	"Select model": "",
+	"Send": "",
 	"Send a Message": "傳送訊息",
 	"Send a Message": "傳送訊息",
 	"Send message": "傳送訊息",
 	"Send message": "傳送訊息",
 	"September": "九月",
 	"September": "九月",
@@ -482,10 +479,10 @@
 	"What’s New in": "全新內容",
 	"What’s New in": "全新內容",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "當歷史被關閉時,這個瀏覽器上的新聊天將不會出現在任何裝置的歷史記錄中。",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "當歷史被關閉時,這個瀏覽器上的新聊天將不會出現在任何裝置的歷史記錄中。",
 	"Whisper (Local)": "Whisper(本機)",
 	"Whisper (Local)": "Whisper(本機)",
+	"Workspace": "",
 	"Write a prompt suggestion (e.g. Who are you?)": "寫一個提示詞建議(例如:你是誰?)",
 	"Write a prompt suggestion (e.g. Who are you?)": "寫一個提示詞建議(例如:你是誰?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "寫一個 50 字的摘要來概括 [主題或關鍵詞]。",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "寫一個 50 字的摘要來概括 [主題或關鍵詞]。",
 	"Yesterday": "",
 	"Yesterday": "",
-	"You": "你",
 	"You have no archived conversations.": "",
 	"You have no archived conversations.": "",
 	"You have shared this chat": "",
 	"You have shared this chat": "",
 	"You're a helpful assistant.": "你是一位善於協助他人的助手。",
 	"You're a helpful assistant.": "你是一位善於協助他人的助手。",

+ 1 - 0
src/lib/stores/index.ts

@@ -39,6 +39,7 @@ export const settings: Writable<Settings> = writable({});
 
 
 export const showSidebar = writable(false);
 export const showSidebar = writable(false);
 export const showSettings = writable(false);
 export const showSettings = writable(false);
+export const showArchivedChats = writable(false);
 export const showChangelog = writable(false);
 export const showChangelog = writable(false);
 
 
 type Model = OpenAIModel | OllamaModel;
 type Model = OpenAIModel | OllamaModel;

+ 1 - 1
src/routes/(app)/+page.svelte

@@ -883,7 +883,7 @@
 
 
 <div
 <div
 	class="min-h-screen max-h-screen {$showSidebar
 	class="min-h-screen max-h-screen {$showSidebar
-		? 'lg:max-w-[calc(100%-260px)]'
+		? 'md:max-w-[calc(100%-260px)]'
 		: ''} w-full max-w-full flex flex-col"
 		: ''} w-full max-w-full flex flex-col"
 >
 >
 	<Navbar
 	<Navbar

+ 242 - 232
src/routes/(app)/admin/+page.svelte

@@ -1,6 +1,6 @@
 <script>
 <script>
 	import { WEBUI_API_BASE_URL } from '$lib/constants';
 	import { WEBUI_API_BASE_URL } from '$lib/constants';
-	import { WEBUI_NAME, config, user } from '$lib/stores';
+	import { WEBUI_NAME, config, user, showSidebar } from '$lib/stores';
 	import { goto } from '$app/navigation';
 	import { goto } from '$app/navigation';
 	import { onMount, getContext } from 'svelte';
 	import { onMount, getContext } from 'svelte';
 
 
@@ -12,6 +12,9 @@
 
 
 	import { updateUserRole, getUsers, deleteUserById } from '$lib/apis/users';
 	import { updateUserRole, getUsers, deleteUserById } from '$lib/apis/users';
 	import { getSignUpEnabledStatus, toggleSignUpEnabledStatus } from '$lib/apis/auths';
 	import { getSignUpEnabledStatus, toggleSignUpEnabledStatus } from '$lib/apis/auths';
+
+	import MenuLines from '$lib/components/icons/MenuLines.svelte';
+
 	import EditUserModal from '$lib/components/admin/EditUserModal.svelte';
 	import EditUserModal from '$lib/components/admin/EditUserModal.svelte';
 	import SettingsModal from '$lib/components/admin/SettingsModal.svelte';
 	import SettingsModal from '$lib/components/admin/SettingsModal.svelte';
 	import Pagination from '$lib/components/common/Pagination.svelte';
 	import Pagination from '$lib/components/common/Pagination.svelte';
@@ -23,6 +26,7 @@
 	const i18n = getContext('i18n');
 	const i18n = getContext('i18n');
 
 
 	let loaded = false;
 	let loaded = false;
+	let tab = '';
 	let users = [];
 	let users = [];
 
 
 	let search = '';
 	let search = '';
@@ -102,252 +106,258 @@
 <UserChatsModal bind:show={showUserChatsModal} user={selectedUser} />
 <UserChatsModal bind:show={showUserChatsModal} user={selectedUser} />
 <SettingsModal bind:show={showSettingsModal} />
 <SettingsModal bind:show={showSettingsModal} />
 
 
-<div class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white">
+<div class=" flex flex-col w-full min-h-screen">
 	{#if loaded}
 	{#if loaded}
-		<div class=" flex flex-col justify-between w-full overflow-y-auto">
-			<div class=" mx-auto w-full">
-				<div class="w-full">
-					<div class=" flex flex-col justify-center">
-						<div class=" px-6 pt-4">
-							<div class=" flex justify-between items-center">
-								<div class="flex items-center text-2xl font-semibold">{$i18n.t('Dashboard')}</div>
-								<div>
-									<Tooltip content={$i18n.t('Admin Settings')}>
-										<button
-											class="flex items-center space-x-1 p-2 md:px-3 md:py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 transition"
-											type="button"
-											on:click={() => {
-												showSettingsModal = !showSettingsModal;
-											}}
-										>
-											<svg
-												xmlns="http://www.w3.org/2000/svg"
-												viewBox="0 0 16 16"
-												fill="currentColor"
-												class="w-4 h-4"
-											>
-												<path
-													fill-rule="evenodd"
-													d="M6.955 1.45A.5.5 0 0 1 7.452 1h1.096a.5.5 0 0 1 .497.45l.17 1.699c.484.12.94.312 1.356.562l1.321-1.081a.5.5 0 0 1 .67.033l.774.775a.5.5 0 0 1 .034.67l-1.08 1.32c.25.417.44.873.561 1.357l1.699.17a.5.5 0 0 1 .45.497v1.096a.5.5 0 0 1-.45.497l-1.699.17c-.12.484-.312.94-.562 1.356l1.082 1.322a.5.5 0 0 1-.034.67l-.774.774a.5.5 0 0 1-.67.033l-1.322-1.08c-.416.25-.872.44-1.356.561l-.17 1.699a.5.5 0 0 1-.497.45H7.452a.5.5 0 0 1-.497-.45l-.17-1.699a4.973 4.973 0 0 1-1.356-.562L4.108 13.37a.5.5 0 0 1-.67-.033l-.774-.775a.5.5 0 0 1-.034-.67l1.08-1.32a4.971 4.971 0 0 1-.561-1.357l-1.699-.17A.5.5 0 0 1 1 8.548V7.452a.5.5 0 0 1 .45-.497l1.699-.17c.12-.484.312-.94.562-1.356L2.629 4.107a.5.5 0 0 1 .034-.67l.774-.774a.5.5 0 0 1 .67-.033L5.43 3.71a4.97 4.97 0 0 1 1.356-.561l.17-1.699ZM6 8c0 .538.212 1.026.558 1.385l.057.057a2 2 0 0 0 2.828-2.828l-.058-.056A2 2 0 0 0 6 8Z"
-													clip-rule="evenodd"
-												/>
-											</svg>
-
-											<div class="hidden md:inline text-xs">{$i18n.t('Admin Settings')}</div>
-										</button>
-									</Tooltip>
-								</div>
-							</div>
+		<div class="px-4 pt-3 mt-0.5 mb-1">
+			<div class=" flex items-center gap-1">
+				<div class="{$showSidebar ? 'md:hidden' : ''} mr-1 self-start flex flex-none items-center">
+					<button
+						id="sidebar-toggle-button"
+						class="cursor-pointer p-1 flex rounded-xl hover:bg-gray-100 dark:hover:bg-gray-850 transition"
+						on:click={() => {
+							showSidebar.set(!$showSidebar);
+						}}
+					>
+						<div class=" m-auto self-center">
+							<MenuLines />
 						</div>
 						</div>
+					</button>
+				</div>
+				<div class="flex items-center text-xl font-semibold">{$i18n.t('Dashboard')}</div>
+			</div>
+		</div>
 
 
-						<div class="px-6 flex text-sm gap-2.5">
-							<div class="py-3 border-b font-medium text-gray-100 cursor-pointer">
-								{$i18n.t('Overview')}
-							</div>
-							<!-- <div class="py-3 text-gray-300 cursor-pointer">Users</div> -->
-						</div>
+		<!-- <div class="px-4 my-1">
+			<div
+				class="flex scrollbar-none overflow-x-auto w-fit text-center text-sm font-medium rounded-xl bg-transparent/10 p-1"
+			>
+				<button
+					class="min-w-fit rounded-lg p-1.5 px-3 {tab === ''
+						? 'bg-gray-50 dark:bg-gray-850'
+						: ''} transition"
+					type="button"
+					on:click={() => {
+						tab = '';
+					}}>{$i18n.t('Overview')}</button
+				>
+			</div>
+		</div> -->
 
 
-						<hr class=" mb-3 dark:border-gray-800" />
+		<hr class=" my-2 dark:border-gray-850" />
 
 
-						<div class="px-6">
-							<div class="mt-0.5 mb-3 gap-1 flex flex-col md:flex-row justify-between">
-								<div class="flex text-lg font-medium px-0.5">
-									{$i18n.t('All Users')}
-									<div class="flex self-center w-[1px] h-6 mx-2.5 bg-gray-200 dark:bg-gray-700" />
-									<span class="text-lg font-medium text-gray-500 dark:text-gray-300"
-										>{users.length}</span
-									>
-								</div>
+		<div class="px-6">
+			<div class="mt-0.5 mb-3 gap-1 flex flex-col md:flex-row justify-between">
+				<div class="flex self-center text-lg font-medium px-0.5">
+					{$i18n.t('All Users')}
+					<div class="flex self-center w-[1px] h-6 mx-2.5 bg-gray-200 dark:bg-gray-700" />
+					<span class="text-lg font-medium text-gray-500 dark:text-gray-300">{users.length}</span>
+				</div>
 
 
-								<div class="flex gap-1">
-									<input
-										class="w-full md:w-60 rounded-xl py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
-										placeholder={$i18n.t('Search')}
-										bind:value={search}
+				<div class="flex gap-1">
+					<input
+						class="w-full md:w-60 rounded-xl py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
+						placeholder={$i18n.t('Search')}
+						bind:value={search}
+					/>
+
+					<div class="flex gap-0.5">
+						<Tooltip content="Add User">
+							<button
+								class=" px-2 py-2 rounded-xl border border-gray-200 dark:border-gray-600 dark:border-0 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 transition font-medium text-sm flex items-center space-x-1"
+								on:click={() => {
+									showAddUserModal = !showAddUserModal;
+								}}
+							>
+								<svg
+									xmlns="http://www.w3.org/2000/svg"
+									viewBox="0 0 16 16"
+									fill="currentColor"
+									class="w-4 h-4"
+								>
+									<path
+										d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
 									/>
 									/>
+								</svg>
+							</button>
+						</Tooltip>
+
+						<Tooltip content={$i18n.t('Admin Settings')}>
+							<button
+								class=" px-2 py-2 rounded-xl border border-gray-200 dark:border-gray-600 dark:border-0 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 transition font-medium text-sm flex items-center space-x-1"
+								on:click={() => {
+									showSettingsModal = !showSettingsModal;
+								}}
+							>
+								<svg
+									xmlns="http://www.w3.org/2000/svg"
+									viewBox="0 0 16 16"
+									fill="currentColor"
+									class="w-4 h-4"
+								>
+									<path
+										fill-rule="evenodd"
+										d="M6.955 1.45A.5.5 0 0 1 7.452 1h1.096a.5.5 0 0 1 .497.45l.17 1.699c.484.12.94.312 1.356.562l1.321-1.081a.5.5 0 0 1 .67.033l.774.775a.5.5 0 0 1 .034.67l-1.08 1.32c.25.417.44.873.561 1.357l1.699.17a.5.5 0 0 1 .45.497v1.096a.5.5 0 0 1-.45.497l-1.699.17c-.12.484-.312.94-.562 1.356l1.082 1.322a.5.5 0 0 1-.034.67l-.774.774a.5.5 0 0 1-.67.033l-1.322-1.08c-.416.25-.872.44-1.356.561l-.17 1.699a.5.5 0 0 1-.497.45H7.452a.5.5 0 0 1-.497-.45l-.17-1.699a4.973 4.973 0 0 1-1.356-.562L4.108 13.37a.5.5 0 0 1-.67-.033l-.774-.775a.5.5 0 0 1-.034-.67l1.08-1.32a4.971 4.971 0 0 1-.561-1.357l-1.699-.17A.5.5 0 0 1 1 8.548V7.452a.5.5 0 0 1 .45-.497l1.699-.17c.12-.484.312-.94.562-1.356L2.629 4.107a.5.5 0 0 1 .034-.67l.774-.774a.5.5 0 0 1 .67-.033L5.43 3.71a4.97 4.97 0 0 1 1.356-.561l.17-1.699ZM6 8c0 .538.212 1.026.558 1.385l.057.057a2 2 0 0 0 2.828-2.828l-.058-.056A2 2 0 0 0 6 8Z"
+										clip-rule="evenodd"
+									/>
+								</svg>
+							</button>
+						</Tooltip>
+					</div>
+				</div>
+			</div>
 
 
-									<div>
-										<Tooltip content="Add User">
-											<button
-												class=" px-2 py-2 rounded-xl border border-gray-200 dark:border-gray-600 dark:border-0 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 transition font-medium text-sm flex items-center space-x-1"
-												on:click={() => {
-													showAddUserModal = !showAddUserModal;
-												}}
-											>
-												<svg
-													xmlns="http://www.w3.org/2000/svg"
-													viewBox="0 0 16 16"
-													fill="currentColor"
-													class="w-4 h-4"
-												>
-													<path
-														d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
-													/>
-												</svg>
-											</button>
-										</Tooltip>
-									</div>
-								</div>
-							</div>
-
-							<div class="scrollbar-hidden relative overflow-x-auto whitespace-nowrap">
-								<table class="w-full text-sm text-left text-gray-500 dark:text-gray-400 table-auto">
-									<thead
-										class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-850 dark:text-gray-400"
+			<div class="scrollbar-hidden relative overflow-x-auto whitespace-nowrap">
+				<table class="w-full text-sm text-left text-gray-500 dark:text-gray-400 table-auto">
+					<thead
+						class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-850 dark:text-gray-400"
+					>
+						<tr>
+							<th scope="col" class="px-3 py-2"> {$i18n.t('Role')} </th>
+							<th scope="col" class="px-3 py-2"> {$i18n.t('Name')} </th>
+							<th scope="col" class="px-3 py-2"> {$i18n.t('Email')} </th>
+							<th scope="col" class="px-3 py-2"> {$i18n.t('Last Active')} </th>
+
+							<th scope="col" class="px-3 py-2"> {$i18n.t('Created at')} </th>
+
+							<th scope="col" class="px-3 py-2 text-right" />
+						</tr>
+					</thead>
+					<tbody>
+						{#each users
+							.filter((user) => {
+								if (search === '') {
+									return true;
+								} else {
+									let name = user.name.toLowerCase();
+									const query = search.toLowerCase();
+									return name.includes(query);
+								}
+							})
+							.slice((page - 1) * 20, page * 20) as user}
+							<tr class="bg-white border-b dark:bg-gray-900 dark:border-gray-700 text-xs">
+								<td class="px-3 py-2 min-w-[7rem] w-28">
+									<button
+										class=" flex items-center gap-2 text-xs px-3 py-0.5 rounded-lg {user.role ===
+											'admin' && 'text-sky-600 dark:text-sky-200 bg-sky-200/30'} {user.role ===
+											'user' && 'text-green-600 dark:text-green-200 bg-green-200/30'} {user.role ===
+											'pending' && 'text-gray-600 dark:text-gray-200 bg-gray-200/30'}"
+										on:click={() => {
+											if (user.role === 'user') {
+												updateRoleHandler(user.id, 'admin');
+											} else if (user.role === 'pending') {
+												updateRoleHandler(user.id, 'user');
+											} else {
+												updateRoleHandler(user.id, 'pending');
+											}
+										}}
 									>
 									>
-										<tr>
-											<th scope="col" class="px-3 py-2"> {$i18n.t('Role')} </th>
-											<th scope="col" class="px-3 py-2"> {$i18n.t('Name')} </th>
-											<th scope="col" class="px-3 py-2"> {$i18n.t('Email')} </th>
-											<th scope="col" class="px-3 py-2"> {$i18n.t('Last Active')} </th>
-
-											<th scope="col" class="px-3 py-2"> {$i18n.t('Created at')} </th>
-
-											<th scope="col" class="px-3 py-2 text-right" />
-										</tr>
-									</thead>
-									<tbody>
-										{#each users
-											.filter((user) => {
-												if (search === '') {
-													return true;
-												} else {
-													let name = user.name.toLowerCase();
-													const query = search.toLowerCase();
-													return name.includes(query);
-												}
-											})
-											.slice((page - 1) * 20, page * 20) as user}
-											<tr class="bg-white border-b dark:bg-gray-900 dark:border-gray-700 text-xs">
-												<td class="px-3 py-2 min-w-[7rem] w-28">
-													<button
-														class=" flex items-center gap-2 text-xs px-3 py-0.5 rounded-lg {user.role ===
-															'admin' &&
-															'text-sky-600 dark:text-sky-200 bg-sky-200/30'} {user.role ===
-															'user' &&
-															'text-green-600 dark:text-green-200 bg-green-200/30'} {user.role ===
-															'pending' && 'text-gray-600 dark:text-gray-200 bg-gray-200/30'}"
-														on:click={() => {
-															if (user.role === 'user') {
-																updateRoleHandler(user.id, 'admin');
-															} else if (user.role === 'pending') {
-																updateRoleHandler(user.id, 'user');
-															} else {
-																updateRoleHandler(user.id, 'pending');
-															}
-														}}
+										<div
+											class="w-1 h-1 rounded-full {user.role === 'admin' &&
+												'bg-sky-600 dark:bg-sky-300'} {user.role === 'user' &&
+												'bg-green-600 dark:bg-green-300'} {user.role === 'pending' &&
+												'bg-gray-600 dark:bg-gray-300'}"
+										/>
+										{$i18n.t(user.role)}</button
+									>
+								</td>
+								<td class="px-3 py-2 font-medium text-gray-900 dark:text-white w-max">
+									<div class="flex flex-row w-max">
+										<img
+											class=" rounded-full w-6 h-6 object-cover mr-2.5"
+											src={user.profile_image_url}
+											alt="user"
+										/>
+
+										<div class=" font-medium self-center">{user.name}</div>
+									</div>
+								</td>
+								<td class=" px-3 py-2"> {user.email} </td>
+
+								<td class=" px-3 py-2">
+									{dayjs(user.last_active_at * 1000).fromNow()}
+								</td>
+
+								<td class=" px-3 py-2">
+									{dayjs(user.created_at * 1000).format($i18n.t('MMMM DD, YYYY'))}
+								</td>
+
+								<td class="px-3 py-2 text-right">
+									<div class="flex justify-end w-full">
+										{#if user.role !== 'admin'}
+											<Tooltip content={$i18n.t('Chats')}>
+												<button
+													class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+													on:click={async () => {
+														showUserChatsModal = !showUserChatsModal;
+														selectedUser = user;
+													}}
+												>
+													<ChatBubbles />
+												</button>
+											</Tooltip>
+
+											<Tooltip content={$i18n.t('Edit User')}>
+												<button
+													class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+													on:click={async () => {
+														showEditUserModal = !showEditUserModal;
+														selectedUser = user;
+													}}
+												>
+													<svg
+														xmlns="http://www.w3.org/2000/svg"
+														fill="none"
+														viewBox="0 0 24 24"
+														stroke-width="1.5"
+														stroke="currentColor"
+														class="w-4 h-4"
 													>
 													>
-														<div
-															class="w-1 h-1 rounded-full {user.role === 'admin' &&
-																'bg-sky-600 dark:bg-sky-300'} {user.role === 'user' &&
-																'bg-green-600 dark:bg-green-300'} {user.role === 'pending' &&
-																'bg-gray-600 dark:bg-gray-300'}"
+														<path
+															stroke-linecap="round"
+															stroke-linejoin="round"
+															d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L6.832 19.82a4.5 4.5 0 0 1-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 0 1 1.13-1.897L16.863 4.487Zm0 0L19.5 7.125"
 														/>
 														/>
-														{$i18n.t(user.role)}</button
+													</svg>
+												</button>
+											</Tooltip>
+
+											<Tooltip content={$i18n.t('Delete User')}>
+												<button
+													class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+													on:click={async () => {
+														deleteUserHandler(user.id);
+													}}
+												>
+													<svg
+														xmlns="http://www.w3.org/2000/svg"
+														fill="none"
+														viewBox="0 0 24 24"
+														stroke-width="1.5"
+														stroke="currentColor"
+														class="w-4 h-4"
 													>
 													>
-												</td>
-												<td class="px-3 py-2 font-medium text-gray-900 dark:text-white w-max">
-													<div class="flex flex-row w-max">
-														<img
-															class=" rounded-full w-6 h-6 object-cover mr-2.5"
-															src={user.profile_image_url}
-															alt="user"
+														<path
+															stroke-linecap="round"
+															stroke-linejoin="round"
+															d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
 														/>
 														/>
+													</svg>
+												</button>
+											</Tooltip>
+										{/if}
+									</div>
+								</td>
+							</tr>
+						{/each}
+					</tbody>
+				</table>
+			</div>
 
 
-														<div class=" font-medium self-center">{user.name}</div>
-													</div>
-												</td>
-												<td class=" px-3 py-2"> {user.email} </td>
-
-												<td class=" px-3 py-2">
-													{dayjs(user.last_active_at * 1000).fromNow()}
-												</td>
-
-												<td class=" px-3 py-2">
-													{dayjs(user.created_at * 1000).format($i18n.t('MMMM DD, YYYY'))}
-												</td>
-
-												<td class="px-3 py-2 text-right">
-													<div class="flex justify-end w-full">
-														{#if user.role !== 'admin'}
-															<Tooltip content={$i18n.t('Chats')}>
-																<button
-																	class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
-																	on:click={async () => {
-																		showUserChatsModal = !showUserChatsModal;
-																		selectedUser = user;
-																	}}
-																>
-																	<ChatBubbles />
-																</button>
-															</Tooltip>
-
-															<Tooltip content={$i18n.t('Edit User')}>
-																<button
-																	class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
-																	on:click={async () => {
-																		showEditUserModal = !showEditUserModal;
-																		selectedUser = user;
-																	}}
-																>
-																	<svg
-																		xmlns="http://www.w3.org/2000/svg"
-																		fill="none"
-																		viewBox="0 0 24 24"
-																		stroke-width="1.5"
-																		stroke="currentColor"
-																		class="w-4 h-4"
-																	>
-																		<path
-																			stroke-linecap="round"
-																			stroke-linejoin="round"
-																			d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L6.832 19.82a4.5 4.5 0 0 1-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 0 1 1.13-1.897L16.863 4.487Zm0 0L19.5 7.125"
-																		/>
-																	</svg>
-																</button>
-															</Tooltip>
-
-															<Tooltip content={$i18n.t('Delete User')}>
-																<button
-																	class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
-																	on:click={async () => {
-																		deleteUserHandler(user.id);
-																	}}
-																>
-																	<svg
-																		xmlns="http://www.w3.org/2000/svg"
-																		fill="none"
-																		viewBox="0 0 24 24"
-																		stroke-width="1.5"
-																		stroke="currentColor"
-																		class="w-4 h-4"
-																	>
-																		<path
-																			stroke-linecap="round"
-																			stroke-linejoin="round"
-																			d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
-																		/>
-																	</svg>
-																</button>
-															</Tooltip>
-														{/if}
-													</div>
-												</td>
-											</tr>
-										{/each}
-									</tbody>
-								</table>
-							</div>
-
-							<div class=" text-gray-500 text-xs mt-2 text-right">
-								ⓘ {$i18n.t("Click on the user role button to change a user's role.")}
-							</div>
-
-							<Pagination bind:page count={users.length} />
-						</div>
-					</div>
-				</div>
+			<div class=" text-gray-500 text-xs mt-2 text-right">
+				ⓘ {$i18n.t("Click on the user role button to change a user's role.")}
 			</div>
 			</div>
+
+			<Pagination bind:page count={users.length} />
 		</div>
 		</div>
 	{/if}
 	{/if}
 </div>
 </div>

+ 1 - 1
src/routes/(app)/c/[id]/+page.svelte

@@ -893,7 +893,7 @@
 {#if loaded}
 {#if loaded}
 	<div
 	<div
 		class="min-h-screen max-h-screen {$showSidebar
 		class="min-h-screen max-h-screen {$showSidebar
-			? 'lg:max-w-[calc(100%-260px)]'
+			? 'md:max-w-[calc(100%-260px)]'
 			: ''} w-full max-w-full flex flex-col"
 			: ''} w-full max-w-full flex flex-col"
 	>
 	>
 		<Navbar
 		<Navbar

+ 0 - 617
src/routes/(app)/documents/+page.svelte

@@ -1,617 +0,0 @@
-<script lang="ts">
-	import { toast } from 'svelte-sonner';
-	import fileSaver from 'file-saver';
-	const { saveAs } = fileSaver;
-
-	import { onMount, getContext } from 'svelte';
-	import { WEBUI_NAME, documents } from '$lib/stores';
-	import { createNewDoc, deleteDocByName, getDocs } from '$lib/apis/documents';
-
-	import { SUPPORTED_FILE_TYPE, SUPPORTED_FILE_EXTENSIONS } from '$lib/constants';
-	import { uploadDocToVectorDB } from '$lib/apis/rag';
-	import { transformFileName } from '$lib/utils';
-
-	import Checkbox from '$lib/components/common/Checkbox.svelte';
-
-	import EditDocModal from '$lib/components/documents/EditDocModal.svelte';
-	import AddFilesPlaceholder from '$lib/components/AddFilesPlaceholder.svelte';
-	import SettingsModal from '$lib/components/documents/SettingsModal.svelte';
-	import AddDocModal from '$lib/components/documents/AddDocModal.svelte';
-
-	const i18n = getContext('i18n');
-
-	let importFiles = '';
-
-	let inputFiles = '';
-	let query = '';
-	let documentsImportInputElement: HTMLInputElement;
-	let tags = [];
-
-	let showSettingsModal = false;
-	let showAddDocModal = false;
-	let showEditDocModal = false;
-	let selectedDoc;
-	let selectedTag = '';
-
-	let dragged = false;
-
-	const deleteDoc = async (name) => {
-		await deleteDocByName(localStorage.token, name);
-		await documents.set(await getDocs(localStorage.token));
-	};
-
-	const deleteDocs = async (docs) => {
-		const res = await Promise.all(
-			docs.map(async (doc) => {
-				return await deleteDocByName(localStorage.token, doc.name);
-			})
-		);
-
-		await documents.set(await getDocs(localStorage.token));
-	};
-
-	const uploadDoc = async (file) => {
-		const res = await uploadDocToVectorDB(localStorage.token, '', file).catch((error) => {
-			toast.error(error);
-			return null;
-		});
-
-		if (res) {
-			await createNewDoc(
-				localStorage.token,
-				res.collection_name,
-				res.filename,
-				transformFileName(res.filename),
-				res.filename
-			).catch((error) => {
-				toast.error(error);
-				return null;
-			});
-			await documents.set(await getDocs(localStorage.token));
-		}
-	};
-
-	onMount(() => {
-		documents.subscribe((docs) => {
-			tags = docs.reduce((a, e, i, arr) => {
-				return [...new Set([...a, ...(e?.content?.tags ?? []).map((tag) => tag.name)])];
-			}, []);
-		});
-		const dropZone = document.querySelector('body');
-
-		const onDragOver = (e) => {
-			e.preventDefault();
-			dragged = true;
-		};
-
-		const onDragLeave = () => {
-			dragged = false;
-		};
-
-		const onDrop = async (e) => {
-			e.preventDefault();
-			console.log(e);
-
-			if (e.dataTransfer?.files) {
-				let reader = new FileReader();
-
-				reader.onload = (event) => {
-					files = [
-						...files,
-						{
-							type: 'image',
-							url: `${event.target.result}`
-						}
-					];
-				};
-
-				const inputFiles = e.dataTransfer?.files;
-
-				if (inputFiles && inputFiles.length > 0) {
-					for (const file of inputFiles) {
-						console.log(file, file.name.split('.').at(-1));
-						if (
-							SUPPORTED_FILE_TYPE.includes(file['type']) ||
-							SUPPORTED_FILE_EXTENSIONS.includes(file.name.split('.').at(-1))
-						) {
-							uploadDoc(file);
-						} else {
-							toast.error(
-								`Unknown File Type '${file['type']}', but accepting and treating as plain text`
-							);
-							uploadDoc(file);
-						}
-					}
-				} else {
-					toast.error($i18n.t(`File not found.`));
-				}
-			}
-
-			dragged = false;
-		};
-
-		dropZone?.addEventListener('dragover', onDragOver);
-		dropZone?.addEventListener('drop', onDrop);
-		dropZone?.addEventListener('dragleave', onDragLeave);
-
-		return () => {
-			dropZone?.removeEventListener('dragover', onDragOver);
-			dropZone?.removeEventListener('drop', onDrop);
-			dropZone?.removeEventListener('dragleave', onDragLeave);
-		};
-	});
-
-	let filteredDocs;
-
-	$: filteredDocs = $documents.filter(
-		(doc) =>
-			(selectedTag === '' ||
-				(doc?.content?.tags ?? []).map((tag) => tag.name).includes(selectedTag)) &&
-			(query === '' || doc.name.includes(query))
-	);
-</script>
-
-<svelte:head>
-	<title>{$i18n.t('Documents')} | {$WEBUI_NAME}</title>
-</svelte:head>
-
-{#if dragged}
-	<div
-		class="fixed w-full h-full flex z-50 touch-none pointer-events-none"
-		id="dropzone"
-		role="region"
-		aria-label="Drag and Drop Container"
-	>
-		<div class="absolute rounded-xl w-full h-full backdrop-blur bg-gray-800/40 flex justify-center">
-			<div class="m-auto pt-64 flex flex-col justify-center">
-				<div class="max-w-md">
-					<AddFilesPlaceholder>
-						<div class=" mt-2 text-center text-sm dark:text-gray-200 w-full">
-							Drop any files here to add to my documents
-						</div>
-					</AddFilesPlaceholder>
-				</div>
-			</div>
-		</div>
-	</div>
-{/if}
-
-{#key selectedDoc}
-	<EditDocModal bind:show={showEditDocModal} {selectedDoc} />
-{/key}
-
-<AddDocModal bind:show={showAddDocModal} />
-
-<SettingsModal bind:show={showSettingsModal} />
-
-<div class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white">
-	<div class=" flex flex-col justify-between w-full overflow-y-auto">
-		<div class="max-w-2xl mx-auto w-full px-3 md:px-0 my-10">
-			<div class="mb-6">
-				<div class="flex justify-between items-center">
-					<div class=" text-2xl font-semibold self-center">{$i18n.t('My Documents')}</div>
-
-					<div>
-						<button
-							class="flex items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 transition"
-							type="button"
-							on:click={() => {
-								showSettingsModal = !showSettingsModal;
-							}}
-						>
-							<svg
-								xmlns="http://www.w3.org/2000/svg"
-								viewBox="0 0 16 16"
-								fill="currentColor"
-								class="w-4 h-4"
-							>
-								<path
-									fill-rule="evenodd"
-									d="M6.955 1.45A.5.5 0 0 1 7.452 1h1.096a.5.5 0 0 1 .497.45l.17 1.699c.484.12.94.312 1.356.562l1.321-1.081a.5.5 0 0 1 .67.033l.774.775a.5.5 0 0 1 .034.67l-1.08 1.32c.25.417.44.873.561 1.357l1.699.17a.5.5 0 0 1 .45.497v1.096a.5.5 0 0 1-.45.497l-1.699.17c-.12.484-.312.94-.562 1.356l1.082 1.322a.5.5 0 0 1-.034.67l-.774.774a.5.5 0 0 1-.67.033l-1.322-1.08c-.416.25-.872.44-1.356.561l-.17 1.699a.5.5 0 0 1-.497.45H7.452a.5.5 0 0 1-.497-.45l-.17-1.699a4.973 4.973 0 0 1-1.356-.562L4.108 13.37a.5.5 0 0 1-.67-.033l-.774-.775a.5.5 0 0 1-.034-.67l1.08-1.32a4.971 4.971 0 0 1-.561-1.357l-1.699-.17A.5.5 0 0 1 1 8.548V7.452a.5.5 0 0 1 .45-.497l1.699-.17c.12-.484.312-.94.562-1.356L2.629 4.107a.5.5 0 0 1 .034-.67l.774-.774a.5.5 0 0 1 .67-.033L5.43 3.71a4.97 4.97 0 0 1 1.356-.561l.17-1.699ZM6 8c0 .538.212 1.026.558 1.385l.057.057a2 2 0 0 0 2.828-2.828l-.058-.056A2 2 0 0 0 6 8Z"
-									clip-rule="evenodd"
-								/>
-							</svg>
-
-							<div class=" text-xs">{$i18n.t('Document Settings')}</div>
-						</button>
-					</div>
-				</div>
-				<div class=" text-gray-500 text-xs mt-1">
-					ⓘ {$i18n.t("Use '#' in the prompt input to load and select your documents.")}
-				</div>
-			</div>
-
-			<div class=" flex w-full space-x-2">
-				<div class="flex flex-1">
-					<div class=" self-center ml-1 mr-3">
-						<svg
-							xmlns="http://www.w3.org/2000/svg"
-							viewBox="0 0 20 20"
-							fill="currentColor"
-							class="w-4 h-4"
-						>
-							<path
-								fill-rule="evenodd"
-								d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z"
-								clip-rule="evenodd"
-							/>
-						</svg>
-					</div>
-					<input
-						class=" w-full text-sm pr-4 py-1 rounded-r-xl outline-none bg-transparent"
-						bind:value={query}
-						placeholder={$i18n.t('Search Documents')}
-					/>
-				</div>
-
-				<div>
-					<button
-						class=" px-2 py-2 rounded-xl border border-gray-200 dark:border-gray-600 dark:border-0 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 transition font-medium text-sm flex items-center space-x-1"
-						on:click={() => {
-							showAddDocModal = true;
-						}}
-					>
-						<svg
-							xmlns="http://www.w3.org/2000/svg"
-							viewBox="0 0 16 16"
-							fill="currentColor"
-							class="w-4 h-4"
-						>
-							<path
-								d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
-							/>
-						</svg>
-					</button>
-				</div>
-			</div>
-
-			<!-- <div>
-				<div
-					class="my-3 py-16 rounded-lg border-2 border-dashed dark:border-gray-600 {dragged &&
-						' dark:bg-gray-700'} "
-					role="region"
-					on:drop={onDrop}
-					on:dragover={onDragOver}
-					on:dragleave={onDragLeave}
-				>
-					<div class="  pointer-events-none">
-						<div class="text-center dark:text-white text-2xl font-semibold z-50">{$i18n.t('Add Files')}</div>
-
-						<div class=" mt-2 text-center text-sm dark:text-gray-200 w-full">
-							Drop any files here to add to my documents
-						</div>
-					</div>
-				</div>
-			</div> -->
-
-			<hr class=" dark:border-gray-700 my-2.5" />
-
-			{#if tags.length > 0}
-				<div class="px-2.5 pt-1 flex gap-1 flex-wrap">
-					<div class="ml-0.5 pr-3 my-auto flex items-center">
-						<Checkbox
-							state={filteredDocs.filter((doc) => doc?.selected === 'checked').length ===
-							filteredDocs.length
-								? 'checked'
-								: 'unchecked'}
-							indeterminate={filteredDocs.filter((doc) => doc?.selected === 'checked').length > 0 &&
-								filteredDocs.filter((doc) => doc?.selected === 'checked').length !==
-									filteredDocs.length}
-							on:change={(e) => {
-								if (e.detail === 'checked') {
-									filteredDocs = filteredDocs.map((doc) => ({ ...doc, selected: 'checked' }));
-								} else if (e.detail === 'unchecked') {
-									filteredDocs = filteredDocs.map((doc) => ({ ...doc, selected: 'unchecked' }));
-								}
-							}}
-						/>
-					</div>
-
-					{#if filteredDocs.filter((doc) => doc?.selected === 'checked').length === 0}
-						<button
-							class="px-2 py-0.5 space-x-1 flex h-fit items-center rounded-full transition bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:text-white"
-							on:click={async () => {
-								selectedTag = '';
-								// await chats.set(await getChatListByTagName(localStorage.token, tag.name));
-							}}
-						>
-							<div class=" text-xs font-medium self-center line-clamp-1">{$i18n.t('all')}</div>
-						</button>
-
-						{#each tags as tag}
-							<button
-								class="px-2 py-0.5 space-x-1 flex h-fit items-center rounded-full transition bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:text-white"
-								on:click={async () => {
-									selectedTag = tag;
-									// await chats.set(await getChatListByTagName(localStorage.token, tag.name));
-								}}
-							>
-								<div class=" text-xs font-medium self-center line-clamp-1">
-									#{tag}
-								</div>
-							</button>
-						{/each}
-					{:else}
-						<div class="flex-1 flex w-full justify-between items-center">
-							<div class="text-xs font-medium py-0.5 self-center mr-1">
-								{filteredDocs.filter((doc) => doc?.selected === 'checked').length} Selected
-							</div>
-
-							<div class="flex gap-1">
-								<!-- <button
-									class="px-2 py-0.5 space-x-1 flex h-fit items-center rounded-full transition bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:text-white"
-									on:click={async () => {
-										selectedTag = '';
-										// await chats.set(await getChatListByTagName(localStorage.token, tag.name));
-									}}
-								>
-									<div class=" text-xs font-medium self-center line-clamp-1">add tags</div>
-								</button> -->
-
-								<button
-									class="px-2 py-0.5 space-x-1 flex h-fit items-center rounded-full transition bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:text-white"
-									on:click={async () => {
-										deleteDocs(filteredDocs.filter((doc) => doc.selected === 'checked'));
-										// await chats.set(await getChatListByTagName(localStorage.token, tag.name));
-									}}
-								>
-									<div class=" text-xs font-medium self-center line-clamp-1">
-										{$i18n.t('delete')}
-									</div>
-								</button>
-							</div>
-						</div>
-					{/if}
-				</div>
-			{/if}
-
-			<div class="my-3 mb-5">
-				{#each filteredDocs as doc}
-					<button
-						class=" flex space-x-4 cursor-pointer text-left w-full px-3 py-2 dark:hover:bg-white/5 hover:bg-black/5 rounded-xl"
-						on:click={() => {
-							if (doc?.selected === 'checked') {
-								doc.selected = 'unchecked';
-							} else {
-								doc.selected = 'checked';
-							}
-						}}
-					>
-						<div class="my-auto flex items-center">
-							<Checkbox state={doc?.selected ?? 'unchecked'} />
-						</div>
-						<div class=" flex flex-1 space-x-4 cursor-pointer w-full">
-							<div class=" flex items-center space-x-3">
-								<div class="p-2.5 bg-red-400 text-white rounded-lg">
-									{#if doc}
-										<svg
-											xmlns="http://www.w3.org/2000/svg"
-											viewBox="0 0 24 24"
-											fill="currentColor"
-											class="w-6 h-6"
-										>
-											<path
-												fill-rule="evenodd"
-												d="M5.625 1.5c-1.036 0-1.875.84-1.875 1.875v17.25c0 1.035.84 1.875 1.875 1.875h12.75c1.035 0 1.875-.84 1.875-1.875V12.75A3.75 3.75 0 0 0 16.5 9h-1.875a1.875 1.875 0 0 1-1.875-1.875V5.25A3.75 3.75 0 0 0 9 1.5H5.625ZM7.5 15a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 7.5 15Zm.75 2.25a.75.75 0 0 0 0 1.5H12a.75.75 0 0 0 0-1.5H8.25Z"
-												clip-rule="evenodd"
-											/>
-											<path
-												d="M12.971 1.816A5.23 5.23 0 0 1 14.25 5.25v1.875c0 .207.168.375.375.375H16.5a5.23 5.23 0 0 1 3.434 1.279 9.768 9.768 0 0 0-6.963-6.963Z"
-											/>
-										</svg>
-									{:else}
-										<svg
-											class=" w-6 h-6 translate-y-[0.5px]"
-											fill="currentColor"
-											viewBox="0 0 24 24"
-											xmlns="http://www.w3.org/2000/svg"
-											><style>
-												.spinner_qM83 {
-													animation: spinner_8HQG 1.05s infinite;
-												}
-												.spinner_oXPr {
-													animation-delay: 0.1s;
-												}
-												.spinner_ZTLf {
-													animation-delay: 0.2s;
-												}
-												@keyframes spinner_8HQG {
-													0%,
-													57.14% {
-														animation-timing-function: cubic-bezier(0.33, 0.66, 0.66, 1);
-														transform: translate(0);
-													}
-													28.57% {
-														animation-timing-function: cubic-bezier(0.33, 0, 0.66, 0.33);
-														transform: translateY(-6px);
-													}
-													100% {
-														transform: translate(0);
-													}
-												}
-											</style><circle class="spinner_qM83" cx="4" cy="12" r="2.5" /><circle
-												class="spinner_qM83 spinner_oXPr"
-												cx="12"
-												cy="12"
-												r="2.5"
-											/><circle class="spinner_qM83 spinner_ZTLf" cx="20" cy="12" r="2.5" /></svg
-										>
-									{/if}
-								</div>
-								<div class=" self-center flex-1">
-									<div class=" font-bold line-clamp-1">#{doc.name} ({doc.filename})</div>
-									<div class=" text-xs overflow-hidden text-ellipsis line-clamp-1">
-										{doc.title}
-									</div>
-								</div>
-							</div>
-						</div>
-						<div class="flex flex-row space-x-1 self-center">
-							<button
-								class="self-center w-fit text-sm z-20 px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
-								type="button"
-								on:click={async (e) => {
-									e.stopPropagation();
-									showEditDocModal = !showEditDocModal;
-									selectedDoc = doc;
-								}}
-							>
-								<svg
-									xmlns="http://www.w3.org/2000/svg"
-									fill="none"
-									viewBox="0 0 24 24"
-									stroke-width="1.5"
-									stroke="currentColor"
-									class="w-4 h-4"
-								>
-									<path
-										stroke-linecap="round"
-										stroke-linejoin="round"
-										d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125"
-									/>
-								</svg>
-							</button>
-
-							<!-- <button
-						class="self-center w-fit text-sm px-2 py-2 border dark:border-gray-600 rounded-xl"
-						type="button"
-						on:click={() => {
-							console.log('download file');
-						}}
-					>
-						<svg
-							xmlns="http://www.w3.org/2000/svg"
-							viewBox="0 0 16 16"
-							fill="currentColor"
-							class="w-4 h-4"
-						>
-							<path
-								d="M8.75 2.75a.75.75 0 0 0-1.5 0v5.69L5.03 6.22a.75.75 0 0 0-1.06 1.06l3.5 3.5a.75.75 0 0 0 1.06 0l3.5-3.5a.75.75 0 0 0-1.06-1.06L8.75 8.44V2.75Z"
-							/>
-							<path
-								d="M3.5 9.75a.75.75 0 0 0-1.5 0v1.5A2.75 2.75 0 0 0 4.75 14h6.5A2.75 2.75 0 0 0 14 11.25v-1.5a.75.75 0 0 0-1.5 0v1.5c0 .69-.56 1.25-1.25 1.25h-6.5c-.69 0-1.25-.56-1.25-1.25v-1.5Z"
-							/>
-						</svg>
-					</button> -->
-
-							<button
-								class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
-								type="button"
-								on:click={(e) => {
-									e.stopPropagation();
-
-									deleteDoc(doc.name);
-								}}
-							>
-								<svg
-									xmlns="http://www.w3.org/2000/svg"
-									fill="none"
-									viewBox="0 0 24 24"
-									stroke-width="1.5"
-									stroke="currentColor"
-									class="w-4 h-4"
-								>
-									<path
-										stroke-linecap="round"
-										stroke-linejoin="round"
-										d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
-									/>
-								</svg>
-							</button>
-						</div>
-					</button>
-				{/each}
-			</div>
-
-			<div class=" flex justify-end w-full mb-2">
-				<div class="flex space-x-2">
-					<input
-						id="documents-import-input"
-						bind:this={documentsImportInputElement}
-						bind:files={importFiles}
-						type="file"
-						accept=".json"
-						hidden
-						on:change={() => {
-							console.log(importFiles);
-
-							const reader = new FileReader();
-							reader.onload = async (event) => {
-								const savedDocs = JSON.parse(event.target.result);
-								console.log(savedDocs);
-
-								for (const doc of savedDocs) {
-									await createNewDoc(
-										localStorage.token,
-										doc.collection_name,
-										doc.filename,
-										doc.name,
-										doc.title
-									).catch((error) => {
-										toast.error(error);
-										return null;
-									});
-								}
-
-								await documents.set(await getDocs(localStorage.token));
-							};
-
-							reader.readAsText(importFiles[0]);
-						}}
-					/>
-
-					<button
-						class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
-						on:click={() => {
-							documentsImportInputElement.click();
-						}}
-					>
-						<div class=" self-center mr-2 font-medium">{$i18n.t('Import Documents Mapping')}</div>
-
-						<div class=" self-center">
-							<svg
-								xmlns="http://www.w3.org/2000/svg"
-								viewBox="0 0 16 16"
-								fill="currentColor"
-								class="w-4 h-4"
-							>
-								<path
-									fill-rule="evenodd"
-									d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 9.5a.75.75 0 0 1-.75-.75V8.06l-.72.72a.75.75 0 0 1-1.06-1.06l2-2a.75.75 0 0 1 1.06 0l2 2a.75.75 0 1 1-1.06 1.06l-.72-.72v2.69a.75.75 0 0 1-.75.75Z"
-									clip-rule="evenodd"
-								/>
-							</svg>
-						</div>
-					</button>
-
-					<button
-						class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
-						on:click={async () => {
-							let blob = new Blob([JSON.stringify($documents)], {
-								type: 'application/json'
-							});
-							saveAs(blob, `documents-mapping-export-${Date.now()}.json`);
-						}}
-					>
-						<div class=" self-center mr-2 font-medium">{$i18n.t('Export Documents Mapping')}</div>
-
-						<div class=" self-center">
-							<svg
-								xmlns="http://www.w3.org/2000/svg"
-								viewBox="0 0 16 16"
-								fill="currentColor"
-								class="w-4 h-4"
-							>
-								<path
-									fill-rule="evenodd"
-									d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 3.5a.75.75 0 0 1 .75.75v2.69l.72-.72a.75.75 0 1 1 1.06 1.06l-2 2a.75.75 0 0 1-1.06 0l-2-2a.75.75 0 0 1 1.06-1.06l.72.72V6.25A.75.75 0 0 1 8 5.5Z"
-									clip-rule="evenodd"
-								/>
-							</svg>
-						</div>
-					</button>
-				</div>
-			</div>
-		</div>
-	</div>
-</div>

+ 0 - 425
src/routes/(app)/modelfiles/+page.svelte

@@ -1,425 +0,0 @@
-<script lang="ts">
-	import { toast } from 'svelte-sonner';
-	import fileSaver from 'file-saver';
-	const { saveAs } = fileSaver;
-
-	import { onMount, getContext } from 'svelte';
-
-	import { WEBUI_NAME, modelfiles, settings, user } from '$lib/stores';
-	import { createModel, deleteModel } from '$lib/apis/ollama';
-	import {
-		createNewModelfile,
-		deleteModelfileByTagName,
-		getModelfiles
-	} from '$lib/apis/modelfiles';
-	import { goto } from '$app/navigation';
-
-	const i18n = getContext('i18n');
-
-	let localModelfiles = [];
-	let importFiles;
-	let modelfilesImportInputElement: HTMLInputElement;
-	const deleteModelHandler = async (tagName) => {
-		let success = null;
-
-		success = await deleteModel(localStorage.token, tagName).catch((err) => {
-			toast.error(err);
-			return null;
-		});
-
-		if (success) {
-			toast.success($i18n.t(`Deleted {{tagName}}`, { tagName }));
-		}
-
-		return success;
-	};
-
-	const deleteModelfile = async (tagName) => {
-		await deleteModelHandler(tagName);
-		await deleteModelfileByTagName(localStorage.token, tagName);
-		await modelfiles.set(await getModelfiles(localStorage.token));
-	};
-
-	const shareModelfile = async (modelfile) => {
-		toast.success($i18n.t('Redirecting you to OpenWebUI Community'));
-
-		const url = 'https://openwebui.com';
-
-		const tab = await window.open(`${url}/modelfiles/create`, '_blank');
-		window.addEventListener(
-			'message',
-			(event) => {
-				if (event.origin !== url) return;
-				if (event.data === 'loaded') {
-					tab.postMessage(JSON.stringify(modelfile), '*');
-				}
-			},
-			false
-		);
-	};
-
-	const saveModelfiles = async (modelfiles) => {
-		let blob = new Blob([JSON.stringify(modelfiles)], {
-			type: 'application/json'
-		});
-		saveAs(blob, `modelfiles-export-${Date.now()}.json`);
-	};
-
-	onMount(() => {
-		localModelfiles = JSON.parse(localStorage.getItem('modelfiles') ?? '[]');
-
-		if (localModelfiles) {
-			console.log(localModelfiles);
-		}
-	});
-</script>
-
-<svelte:head>
-	<title>
-		{$i18n.t('Modelfiles')} | {$WEBUI_NAME}
-	</title>
-</svelte:head>
-
-<div class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white">
-	<div class="flex flex-col justify-between w-full overflow-y-auto">
-		<div class="max-w-2xl mx-auto w-full px-3 md:px-0 my-10">
-			<div class=" text-2xl font-semibold mb-3">{$i18n.t('My Modelfiles')}</div>
-
-			<a class=" flex space-x-4 cursor-pointer w-full mb-2 px-3 py-2" href="/modelfiles/create">
-				<div class=" self-center w-10">
-					<div
-						class="w-full h-10 flex justify-center rounded-full bg-transparent dark:bg-gray-700 border border-dashed border-gray-200"
-					>
-						<svg
-							xmlns="http://www.w3.org/2000/svg"
-							viewBox="0 0 24 24"
-							fill="currentColor"
-							class="w-6"
-						>
-							<path
-								fill-rule="evenodd"
-								d="M12 3.75a.75.75 0 01.75.75v6.75h6.75a.75.75 0 010 1.5h-6.75v6.75a.75.75 0 01-1.5 0v-6.75H4.5a.75.75 0 010-1.5h6.75V4.5a.75.75 0 01.75-.75z"
-								clip-rule="evenodd"
-							/>
-						</svg>
-					</div>
-				</div>
-
-				<div class=" self-center">
-					<div class=" font-bold">{$i18n.t('Create a modelfile')}</div>
-					<div class=" text-sm">{$i18n.t('Customize Ollama models for a specific purpose')}</div>
-				</div>
-			</a>
-
-			<hr class=" dark:border-gray-700" />
-
-			<div class=" my-2 mb-5">
-				{#each $modelfiles as modelfile}
-					<div
-						class=" flex space-x-4 cursor-pointer w-full px-3 py-2 dark:hover:bg-white/5 hover:bg-black/5 rounded-xl"
-					>
-						<a
-							class=" flex flex-1 space-x-4 cursor-pointer w-full"
-							href={`/?models=${encodeURIComponent(modelfile.tagName)}`}
-						>
-							<div class=" self-center w-10">
-								<div class=" rounded-full bg-stone-700">
-									<img
-										src={modelfile.imageUrl ?? '/user.png'}
-										alt="modelfile profile"
-										class=" rounded-full w-full h-auto object-cover"
-									/>
-								</div>
-							</div>
-
-							<div class=" flex-1 self-center">
-								<div class=" font-bold capitalize">{modelfile.title}</div>
-								<div class=" text-sm overflow-hidden text-ellipsis line-clamp-1">
-									{modelfile.desc}
-								</div>
-							</div>
-						</a>
-						<div class="flex flex-row space-x-1 self-center">
-							<a
-								class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
-								type="button"
-								href={`/modelfiles/edit?tag=${encodeURIComponent(modelfile.tagName)}`}
-							>
-								<svg
-									xmlns="http://www.w3.org/2000/svg"
-									fill="none"
-									viewBox="0 0 24 24"
-									stroke-width="1.5"
-									stroke="currentColor"
-									class="w-4 h-4"
-								>
-									<path
-										stroke-linecap="round"
-										stroke-linejoin="round"
-										d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L6.832 19.82a4.5 4.5 0 0 1-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 0 1 1.13-1.897L16.863 4.487Zm0 0L19.5 7.125"
-									/>
-								</svg>
-							</a>
-
-							<button
-								class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
-								type="button"
-								on:click={() => {
-									// console.log(modelfile);
-									sessionStorage.modelfile = JSON.stringify(modelfile);
-									goto('/modelfiles/create');
-								}}
-							>
-								<svg
-									xmlns="http://www.w3.org/2000/svg"
-									fill="none"
-									viewBox="0 0 24 24"
-									stroke-width="1.5"
-									stroke="currentColor"
-									class="w-4 h-4"
-								>
-									<path
-										stroke-linecap="round"
-										stroke-linejoin="round"
-										d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 0 1-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 0 1 1.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 0 0-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 0 1-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 0 0-3.375-3.375h-1.5a1.125 1.125 0 0 1-1.125-1.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H9.75"
-									/>
-								</svg>
-							</button>
-
-							<button
-								class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
-								type="button"
-								on:click={() => {
-									shareModelfile(modelfile);
-								}}
-							>
-								<svg
-									xmlns="http://www.w3.org/2000/svg"
-									fill="none"
-									viewBox="0 0 24 24"
-									stroke-width="1.5"
-									stroke="currentColor"
-									class="w-4 h-4"
-								>
-									<path
-										stroke-linecap="round"
-										stroke-linejoin="round"
-										d="M7.217 10.907a2.25 2.25 0 1 0 0 2.186m0-2.186c.18.324.283.696.283 1.093s-.103.77-.283 1.093m0-2.186 9.566-5.314m-9.566 7.5 9.566 5.314m0 0a2.25 2.25 0 1 0 3.935 2.186 2.25 2.25 0 0 0-3.935-2.186Zm0-12.814a2.25 2.25 0 1 0 3.933-2.185 2.25 2.25 0 0 0-3.933 2.185Z"
-									/>
-								</svg>
-							</button>
-
-							<button
-								class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
-								type="button"
-								on:click={() => {
-									deleteModelfile(modelfile.tagName);
-								}}
-							>
-								<svg
-									xmlns="http://www.w3.org/2000/svg"
-									fill="none"
-									viewBox="0 0 24 24"
-									stroke-width="1.5"
-									stroke="currentColor"
-									class="w-4 h-4"
-								>
-									<path
-										stroke-linecap="round"
-										stroke-linejoin="round"
-										d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
-									/>
-								</svg>
-							</button>
-						</div>
-					</div>
-				{/each}
-			</div>
-
-			<div class=" flex justify-end w-full mb-3">
-				<div class="flex space-x-1">
-					<input
-						id="modelfiles-import-input"
-						bind:this={modelfilesImportInputElement}
-						bind:files={importFiles}
-						type="file"
-						accept=".json"
-						hidden
-						on:change={() => {
-							console.log(importFiles);
-
-							let reader = new FileReader();
-							reader.onload = async (event) => {
-								let savedModelfiles = JSON.parse(event.target.result);
-								console.log(savedModelfiles);
-
-								for (const modelfile of savedModelfiles) {
-									await createNewModelfile(localStorage.token, modelfile).catch((error) => {
-										return null;
-									});
-								}
-
-								await modelfiles.set(await getModelfiles(localStorage.token));
-							};
-
-							reader.readAsText(importFiles[0]);
-						}}
-					/>
-
-					<button
-						class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
-						on:click={() => {
-							modelfilesImportInputElement.click();
-						}}
-					>
-						<div class=" self-center mr-2 font-medium">{$i18n.t('Import Modelfiles')}</div>
-
-						<div class=" self-center">
-							<svg
-								xmlns="http://www.w3.org/2000/svg"
-								viewBox="0 0 16 16"
-								fill="currentColor"
-								class="w-3.5 h-3.5"
-							>
-								<path
-									fill-rule="evenodd"
-									d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 9.5a.75.75 0 0 1-.75-.75V8.06l-.72.72a.75.75 0 0 1-1.06-1.06l2-2a.75.75 0 0 1 1.06 0l2 2a.75.75 0 1 1-1.06 1.06l-.72-.72v2.69a.75.75 0 0 1-.75.75Z"
-									clip-rule="evenodd"
-								/>
-							</svg>
-						</div>
-					</button>
-
-					<button
-						class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
-						on:click={async () => {
-							saveModelfiles($modelfiles);
-						}}
-					>
-						<div class=" self-center mr-2 font-medium">{$i18n.t('Export Modelfiles')}</div>
-
-						<div class=" self-center">
-							<svg
-								xmlns="http://www.w3.org/2000/svg"
-								viewBox="0 0 16 16"
-								fill="currentColor"
-								class="w-3.5 h-3.5"
-							>
-								<path
-									fill-rule="evenodd"
-									d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 3.5a.75.75 0 0 1 .75.75v2.69l.72-.72a.75.75 0 1 1 1.06 1.06l-2 2a.75.75 0 0 1-1.06 0l-2-2a.75.75 0 0 1 1.06-1.06l.72.72V6.25A.75.75 0 0 1 8 5.5Z"
-									clip-rule="evenodd"
-								/>
-							</svg>
-						</div>
-					</button>
-				</div>
-
-				{#if localModelfiles.length > 0}
-					<div class="flex">
-						<div class=" self-center text-sm font-medium mr-4">
-							{localModelfiles.length} Local Modelfiles Detected
-						</div>
-
-						<div class="flex space-x-1">
-							<button
-								class="self-center w-fit text-sm px-3 py-1 border dark:border-gray-600 rounded-xl flex"
-								on:click={async () => {
-									for (const modelfile of localModelfiles) {
-										await createNewModelfile(localStorage.token, modelfile).catch((error) => {
-											return null;
-										});
-									}
-
-									saveModelfiles(localModelfiles);
-									localStorage.removeItem('modelfiles');
-									localModelfiles = JSON.parse(localStorage.getItem('modelfiles') ?? '[]');
-									await modelfiles.set(await getModelfiles(localStorage.token));
-								}}
-							>
-								<div class=" self-center mr-2 font-medium">{$i18n.t('Sync All')}</div>
-
-								<div class=" self-center">
-									<svg
-										xmlns="http://www.w3.org/2000/svg"
-										viewBox="0 0 16 16"
-										fill="currentColor"
-										class="w-3.5 h-3.5"
-									>
-										<path
-											fill-rule="evenodd"
-											d="M13.836 2.477a.75.75 0 0 1 .75.75v3.182a.75.75 0 0 1-.75.75h-3.182a.75.75 0 0 1 0-1.5h1.37l-.84-.841a4.5 4.5 0 0 0-7.08.932.75.75 0 0 1-1.3-.75 6 6 0 0 1 9.44-1.242l.842.84V3.227a.75.75 0 0 1 .75-.75Zm-.911 7.5A.75.75 0 0 1 13.199 11a6 6 0 0 1-9.44 1.241l-.84-.84v1.371a.75.75 0 0 1-1.5 0V9.591a.75.75 0 0 1 .75-.75H5.35a.75.75 0 0 1 0 1.5H3.98l.841.841a4.5 4.5 0 0 0 7.08-.932.75.75 0 0 1 1.025-.273Z"
-											clip-rule="evenodd"
-										/>
-									</svg>
-								</div>
-							</button>
-
-							<button
-								class="self-center w-fit text-sm p-1.5 border dark:border-gray-600 rounded-xl flex"
-								on:click={async () => {
-									saveModelfiles(localModelfiles);
-
-									localStorage.removeItem('modelfiles');
-									localModelfiles = JSON.parse(localStorage.getItem('modelfiles') ?? '[]');
-									await modelfiles.set(await getModelfiles(localStorage.token));
-								}}
-							>
-								<div class=" self-center">
-									<svg
-										xmlns="http://www.w3.org/2000/svg"
-										fill="none"
-										viewBox="0 0 24 24"
-										stroke-width="1.5"
-										stroke="currentColor"
-										class="w-4 h-4"
-									>
-										<path
-											stroke-linecap="round"
-											stroke-linejoin="round"
-											d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
-										/>
-									</svg>
-								</div>
-							</button>
-						</div>
-					</div>
-				{/if}
-			</div>
-
-			<div class=" my-16">
-				<div class=" text-2xl font-semibold mb-3">{$i18n.t('Made by OpenWebUI Community')}</div>
-
-				<a
-					class=" flex space-x-4 cursor-pointer w-full mb-2 px-3 py-2"
-					href="https://openwebui.com/"
-					target="_blank"
-				>
-					<div class=" self-center w-10">
-						<div
-							class="w-full h-10 flex justify-center rounded-full bg-transparent dark:bg-gray-700 border border-dashed border-gray-200"
-						>
-							<svg
-								xmlns="http://www.w3.org/2000/svg"
-								viewBox="0 0 24 24"
-								fill="currentColor"
-								class="w-6"
-							>
-								<path
-									fill-rule="evenodd"
-									d="M12 3.75a.75.75 0 01.75.75v6.75h6.75a.75.75 0 010 1.5h-6.75v6.75a.75.75 0 01-1.5 0v-6.75H4.5a.75.75 0 010-1.5h6.75V4.5a.75.75 0 01.75-.75z"
-									clip-rule="evenodd"
-								/>
-							</svg>
-						</div>
-					</div>
-
-					<div class=" self-center">
-						<div class=" font-bold">{$i18n.t('Discover a modelfile')}</div>
-						<div class=" text-sm">{$i18n.t('Discover, download, and explore model presets')}</div>
-					</div>
-				</a>
-			</div>
-		</div>
-	</div>
-</div>

+ 0 - 727
src/routes/(app)/modelfiles/create/+page.svelte

@@ -1,727 +0,0 @@
-<script>
-	import { v4 as uuidv4 } from 'uuid';
-	import { toast } from 'svelte-sonner';
-	import { goto } from '$app/navigation';
-	import { settings, user, config, modelfiles, models } from '$lib/stores';
-
-	import AdvancedParams from '$lib/components/chat/Settings/Advanced/AdvancedParams.svelte';
-	import { splitStream } from '$lib/utils';
-	import { onMount, tick, getContext } from 'svelte';
-	import { createModel } from '$lib/apis/ollama';
-	import { createNewModelfile, getModelfileByTagName, getModelfiles } from '$lib/apis/modelfiles';
-
-	const i18n = getContext('i18n');
-
-	let loading = false;
-
-	let filesInputElement;
-	let inputFiles;
-	let imageUrl = null;
-	let digest = '';
-	let pullProgress = null;
-	let success = false;
-
-	// ///////////
-	// Modelfile
-	// ///////////
-
-	let title = '';
-	let tagName = '';
-	let desc = '';
-
-	let raw = true;
-	let advanced = false;
-
-	// Raw Mode
-	let content = '';
-
-	// Builder Mode
-	let model = '';
-	let system = '';
-	let template = '';
-	let options = {
-		// Advanced
-		seed: 0,
-		stop: '',
-		temperature: '',
-		repeat_penalty: '',
-		repeat_last_n: '',
-		mirostat: '',
-		mirostat_eta: '',
-		mirostat_tau: '',
-		top_k: '',
-		top_p: '',
-		tfs_z: '',
-		num_ctx: '',
-		num_predict: ''
-	};
-
-	let modelfileCreator = null;
-
-	$: tagName = title !== '' ? `${title.replace(/\s+/g, '-').toLowerCase()}:latest` : '';
-
-	$: if (!raw) {
-		content = `FROM ${model}
-${template !== '' ? `TEMPLATE """${template}"""` : ''}
-${options.seed !== 0 ? `PARAMETER seed ${options.seed}` : ''}
-${options.stop !== '' ? `PARAMETER stop ${options.stop}` : ''}
-${options.temperature !== '' ? `PARAMETER temperature ${options.temperature}` : ''}
-${options.repeat_penalty !== '' ? `PARAMETER repeat_penalty ${options.repeat_penalty}` : ''}
-${options.repeat_last_n !== '' ? `PARAMETER repeat_last_n ${options.repeat_last_n}` : ''}
-${options.mirostat !== '' ? `PARAMETER mirostat ${options.mirostat}` : ''}
-${options.mirostat_eta !== '' ? `PARAMETER mirostat_eta ${options.mirostat_eta}` : ''}
-${options.mirostat_tau !== '' ? `PARAMETER mirostat_tau ${options.mirostat_tau}` : ''}
-${options.top_k !== '' ? `PARAMETER top_k ${options.top_k}` : ''}
-${options.top_p !== '' ? `PARAMETER top_p ${options.top_p}` : ''}
-${options.tfs_z !== '' ? `PARAMETER tfs_z ${options.tfs_z}` : ''}
-${options.num_ctx !== '' ? `PARAMETER num_ctx ${options.num_ctx}` : ''}
-${options.num_predict !== '' ? `PARAMETER num_predict ${options.num_predict}` : ''}
-SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
-	}
-
-	let suggestions = [
-		{
-			content: ''
-		}
-	];
-
-	let categories = {
-		character: false,
-		assistant: false,
-		writing: false,
-		productivity: false,
-		programming: false,
-		'data analysis': false,
-		lifestyle: false,
-		education: false,
-		business: false
-	};
-
-	const saveModelfile = async (modelfile) => {
-		await createNewModelfile(localStorage.token, modelfile);
-		await modelfiles.set(await getModelfiles(localStorage.token));
-	};
-
-	const submitHandler = async () => {
-		loading = true;
-
-		if (Object.keys(categories).filter((category) => categories[category]).length == 0) {
-			toast.error(
-				'Uh-oh! It looks like you missed selecting a category. Please choose one to complete your modelfile.'
-			);
-			loading = false;
-			success = false;
-			return success;
-		}
-
-		if (
-			$models.map((model) => model.name).includes(tagName) ||
-			(await getModelfileByTagName(localStorage.token, tagName).catch(() => false))
-		) {
-			toast.error(
-				`Uh-oh! It looks like you already have a model named '${tagName}'. Please choose a different name to complete your modelfile.`
-			);
-			loading = false;
-			success = false;
-			return success;
-		}
-
-		if (
-			title !== '' &&
-			desc !== '' &&
-			content !== '' &&
-			Object.keys(categories).filter((category) => categories[category]).length > 0 &&
-			!$models.includes(tagName)
-		) {
-			const res = await createModel(localStorage.token, tagName, content);
-
-			if (res) {
-				const reader = res.body
-					.pipeThrough(new TextDecoderStream())
-					.pipeThrough(splitStream('\n'))
-					.getReader();
-
-				while (true) {
-					const { value, done } = await reader.read();
-					if (done) break;
-
-					try {
-						let lines = value.split('\n');
-
-						for (const line of lines) {
-							if (line !== '') {
-								console.log(line);
-								let data = JSON.parse(line);
-								console.log(data);
-
-								if (data.error) {
-									throw data.error;
-								}
-								if (data.detail) {
-									throw data.detail;
-								}
-
-								if (data.status) {
-									if (
-										!data.digest &&
-										!data.status.includes('writing') &&
-										!data.status.includes('sha256')
-									) {
-										toast.success(data.status);
-
-										if (data.status === 'success') {
-											success = true;
-										}
-									} else {
-										if (data.digest) {
-											digest = data.digest;
-
-											if (data.completed) {
-												pullProgress = Math.round((data.completed / data.total) * 1000) / 10;
-											} else {
-												pullProgress = 100;
-											}
-										}
-									}
-								}
-							}
-						}
-					} catch (error) {
-						console.log(error);
-						toast.error(error);
-					}
-				}
-			}
-
-			if (success) {
-				await saveModelfile({
-					tagName: tagName,
-					imageUrl: imageUrl,
-					title: title,
-					desc: desc,
-					content: content,
-					suggestionPrompts: suggestions.filter((prompt) => prompt.content !== ''),
-					categories: Object.keys(categories).filter((category) => categories[category]),
-					user: modelfileCreator !== null ? modelfileCreator : undefined
-				});
-				await goto('/modelfiles');
-			}
-		}
-		loading = false;
-		success = false;
-	};
-
-	onMount(async () => {
-		window.addEventListener('message', async (event) => {
-			if (
-				![
-					'https://ollamahub.com',
-					'https://www.ollamahub.com',
-					'https://openwebui.com',
-					'https://www.openwebui.com',
-					'http://localhost:5173'
-				].includes(event.origin)
-			)
-				return;
-			const modelfile = JSON.parse(event.data);
-			console.log(modelfile);
-
-			imageUrl = modelfile.imageUrl;
-			title = modelfile.title;
-			await tick();
-			tagName = `${modelfile.user.username === 'hub' ? '' : `hub/`}${modelfile.user.username}/${
-				modelfile.tagName
-			}`;
-			desc = modelfile.desc;
-			content = modelfile.content;
-			suggestions =
-				modelfile.suggestionPrompts.length != 0
-					? modelfile.suggestionPrompts
-					: [
-							{
-								content: ''
-							}
-					  ];
-
-			modelfileCreator = {
-				username: modelfile.user.username,
-				name: modelfile.user.name
-			};
-			for (const category of modelfile.categories) {
-				categories[category.toLowerCase()] = true;
-			}
-		});
-
-		if (window.opener ?? false) {
-			window.opener.postMessage('loaded', '*');
-		}
-
-		if (sessionStorage.modelfile) {
-			const modelfile = JSON.parse(sessionStorage.modelfile);
-			console.log(modelfile);
-			imageUrl = modelfile.imageUrl;
-			title = modelfile.title;
-			await tick();
-			tagName = modelfile.tagName;
-			desc = modelfile.desc;
-			content = modelfile.content;
-			suggestions =
-				modelfile.suggestionPrompts.length != 0
-					? modelfile.suggestionPrompts
-					: [
-							{
-								content: ''
-							}
-					  ];
-
-			for (const category of modelfile.categories) {
-				categories[category.toLowerCase()] = true;
-			}
-
-			sessionStorage.removeItem('modelfile');
-		}
-	});
-</script>
-
-<div class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white">
-	<div class=" flex flex-col justify-between w-full overflow-y-auto">
-		<div class="max-w-2xl mx-auto w-full px-3 md:px-0 my-10">
-			<input
-				bind:this={filesInputElement}
-				bind:files={inputFiles}
-				type="file"
-				hidden
-				accept="image/*"
-				on:change={() => {
-					let reader = new FileReader();
-					reader.onload = (event) => {
-						let originalImageUrl = `${event.target.result}`;
-
-						const img = new Image();
-						img.src = originalImageUrl;
-
-						img.onload = function () {
-							const canvas = document.createElement('canvas');
-							const ctx = canvas.getContext('2d');
-
-							// Calculate the aspect ratio of the image
-							const aspectRatio = img.width / img.height;
-
-							// Calculate the new width and height to fit within 100x100
-							let newWidth, newHeight;
-							if (aspectRatio > 1) {
-								newWidth = 100 * aspectRatio;
-								newHeight = 100;
-							} else {
-								newWidth = 100;
-								newHeight = 100 / aspectRatio;
-							}
-
-							// Set the canvas size
-							canvas.width = 100;
-							canvas.height = 100;
-
-							// Calculate the position to center the image
-							const offsetX = (100 - newWidth) / 2;
-							const offsetY = (100 - newHeight) / 2;
-
-							// Draw the image on the canvas
-							ctx.drawImage(img, offsetX, offsetY, newWidth, newHeight);
-
-							// Get the base64 representation of the compressed image
-							const compressedSrc = canvas.toDataURL('image/jpeg');
-
-							// Display the compressed image
-							imageUrl = compressedSrc;
-
-							inputFiles = null;
-						};
-					};
-
-					if (
-						inputFiles &&
-						inputFiles.length > 0 &&
-						['image/gif', 'image/jpeg', 'image/png'].includes(inputFiles[0]['type'])
-					) {
-						reader.readAsDataURL(inputFiles[0]);
-					} else {
-						console.log(`Unsupported File Type '${inputFiles[0]['type']}'.`);
-						inputFiles = null;
-					}
-				}}
-			/>
-
-			<div class=" text-2xl font-semibold mb-6">{$i18n.t('My Modelfiles')}</div>
-
-			<button
-				class="flex space-x-1"
-				on:click={() => {
-					history.back();
-				}}
-			>
-				<div class=" self-center">
-					<svg
-						xmlns="http://www.w3.org/2000/svg"
-						viewBox="0 0 20 20"
-						fill="currentColor"
-						class="w-4 h-4"
-					>
-						<path
-							fill-rule="evenodd"
-							d="M17 10a.75.75 0 01-.75.75H5.612l4.158 3.96a.75.75 0 11-1.04 1.08l-5.5-5.25a.75.75 0 010-1.08l5.5-5.25a.75.75 0 111.04 1.08L5.612 9.25H16.25A.75.75 0 0117 10z"
-							clip-rule="evenodd"
-						/>
-					</svg>
-				</div>
-				<div class=" self-center font-medium text-sm">{$i18n.t('Back')}</div>
-			</button>
-			<hr class="my-3 dark:border-gray-700" />
-
-			<form
-				class="flex flex-col"
-				on:submit|preventDefault={() => {
-					submitHandler();
-				}}
-			>
-				<div class="flex justify-center my-4">
-					<div class="self-center">
-						<button
-							class=" {imageUrl
-								? ''
-								: 'p-6'} rounded-full dark:bg-gray-700 border border-dashed border-gray-200"
-							type="button"
-							on:click={() => {
-								filesInputElement.click();
-							}}
-						>
-							{#if imageUrl}
-								<img
-									src={imageUrl}
-									alt="modelfile profile"
-									class=" rounded-full w-20 h-20 object-cover"
-								/>
-							{:else}
-								<svg
-									xmlns="http://www.w3.org/2000/svg"
-									viewBox="0 0 24 24"
-									fill="currentColor"
-									class="w-8"
-								>
-									<path
-										fill-rule="evenodd"
-										d="M12 3.75a.75.75 0 01.75.75v6.75h6.75a.75.75 0 010 1.5h-6.75v6.75a.75.75 0 01-1.5 0v-6.75H4.5a.75.75 0 010-1.5h6.75V4.5a.75.75 0 01.75-.75z"
-										clip-rule="evenodd"
-									/>
-								</svg>
-							{/if}
-						</button>
-					</div>
-				</div>
-
-				<div class="my-2 flex space-x-2">
-					<div class="flex-1">
-						<div class=" text-sm font-semibold mb-2">{$i18n.t('Name')}*</div>
-
-						<div>
-							<input
-								class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
-								placeholder={$i18n.t('Name your modelfile')}
-								bind:value={title}
-								required
-							/>
-						</div>
-					</div>
-
-					<div class="flex-1">
-						<div class=" text-sm font-semibold mb-2">{$i18n.t('Model Tag Name')}*</div>
-
-						<div>
-							<input
-								class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
-								placeholder={$i18n.t('Add a model tag name')}
-								bind:value={tagName}
-								required
-							/>
-						</div>
-					</div>
-				</div>
-
-				<div class="my-2">
-					<div class=" text-sm font-semibold mb-2">{$i18n.t('Description')}*</div>
-
-					<div>
-						<input
-							class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
-							placeholder={$i18n.t('Add a short description about what this modelfile does')}
-							bind:value={desc}
-							required
-						/>
-					</div>
-				</div>
-
-				<div class="my-2">
-					<div class="flex w-full justify-between">
-						<div class=" self-center text-sm font-semibold">{$i18n.t('Modelfile')}</div>
-
-						<button
-							class="p-1 px-3 text-xs flex rounded transition"
-							type="button"
-							on:click={() => {
-								raw = !raw;
-							}}
-						>
-							{#if raw}
-								<span class="ml-2 self-center"> {$i18n.t('Raw Format')} </span>
-							{:else}
-								<span class="ml-2 self-center"> {$i18n.t('Builder Mode')} </span>
-							{/if}
-						</button>
-					</div>
-
-					<!-- <div class=" text-sm font-semibold mb-2"></div> -->
-
-					{#if raw}
-						<div class="mt-2">
-							<div class=" text-xs font-semibold mb-2">{$i18n.t('Content')}*</div>
-
-							<div>
-								<textarea
-									class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
-									placeholder={`FROM llama2\nPARAMETER temperature 1\nSYSTEM """\nYou are Mario from Super Mario Bros, acting as an assistant.\n"""`}
-									rows="6"
-									bind:value={content}
-									required
-								/>
-							</div>
-
-							<div class="text-xs text-gray-400 dark:text-gray-500">
-								{$i18n.t('Not sure what to write? Switch to')}
-								<button
-									class="text-gray-500 dark:text-gray-300 font-medium cursor-pointer"
-									type="button"
-									on:click={() => {
-										raw = !raw;
-									}}>{$i18n.t('Builder Mode')}</button
-								>
-								or
-								<a
-									class=" text-gray-500 dark:text-gray-300 font-medium"
-									href="https://openwebui.com"
-									target="_blank"
-								>
-									{$i18n.t('Click here to check other modelfiles.')}
-								</a>
-							</div>
-						</div>
-					{:else}
-						<div class="my-2">
-							<div class=" text-xs font-semibold mb-2">{$i18n.t('From (Base Model)')}*</div>
-
-							<div>
-								<input
-									class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
-									placeholder="Write a modelfile base model name (e.g. llama2, mistral)"
-									bind:value={model}
-									required
-								/>
-							</div>
-
-							<div class="mt-1 text-xs text-gray-400 dark:text-gray-500">
-								{$i18n.t('To access the available model names for downloading,')}
-								<a
-									class=" text-gray-500 dark:text-gray-300 font-medium"
-									href="https://ollama.com/library"
-									target="_blank">{$i18n.t('click here.')}</a
-								>
-							</div>
-						</div>
-
-						<div class="my-1">
-							<div class=" text-xs font-semibold mb-2">{$i18n.t('System Prompt')}</div>
-
-							<div>
-								<textarea
-									class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg -mb-1"
-									placeholder={`Write your modelfile system prompt content here\ne.g.) You are Mario from Super Mario Bros, acting as an assistant.`}
-									rows="4"
-									bind:value={system}
-								/>
-							</div>
-						</div>
-
-						<div class="flex w-full justify-between">
-							<div class=" self-center text-sm font-semibold">
-								{$i18n.t('Modelfile Advanced Settings')}
-							</div>
-
-							<button
-								class="p-1 px-3 text-xs flex rounded transition"
-								type="button"
-								on:click={() => {
-									advanced = !advanced;
-								}}
-							>
-								{#if advanced}
-									<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
-								{:else}
-									<span class="ml-2 self-center">{$i18n.t('Default')}</span>
-								{/if}
-							</button>
-						</div>
-
-						{#if advanced}
-							<div class="my-2">
-								<div class=" text-xs font-semibold mb-2">{$i18n.t('Template')}</div>
-
-								<div>
-									<textarea
-										class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg -mb-1"
-										placeholder="Write your modelfile template content here"
-										rows="4"
-										bind:value={template}
-									/>
-								</div>
-							</div>
-
-							<div class="my-2">
-								<div class=" text-xs font-semibold mb-2">{$i18n.t('Parameters')}</div>
-
-								<div>
-									<AdvancedParams bind:options />
-								</div>
-							</div>
-						{/if}
-					{/if}
-				</div>
-
-				<div class="my-2">
-					<div class="flex w-full justify-between mb-2">
-						<div class=" self-center text-sm font-semibold">{$i18n.t('Prompt suggestions')}</div>
-
-						<button
-							class="p-1 px-3 text-xs flex rounded transition"
-							type="button"
-							on:click={() => {
-								if (suggestions.length === 0 || suggestions.at(-1).content !== '') {
-									suggestions = [...suggestions, { content: '' }];
-								}
-							}}
-						>
-							<svg
-								xmlns="http://www.w3.org/2000/svg"
-								viewBox="0 0 20 20"
-								fill="currentColor"
-								class="w-4 h-4"
-							>
-								<path
-									d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z"
-								/>
-							</svg>
-						</button>
-					</div>
-					<div class="flex flex-col space-y-1">
-						{#each suggestions as prompt, promptIdx}
-							<div class=" flex border dark:border-gray-600 rounded-lg">
-								<input
-									class="px-3 py-1.5 text-sm w-full bg-transparent outline-none border-r dark:border-gray-600"
-									placeholder={$i18n.t('Write a prompt suggestion (e.g. Who are you?)')}
-									bind:value={prompt.content}
-								/>
-
-								<button
-									class="px-2"
-									type="button"
-									on:click={() => {
-										suggestions.splice(promptIdx, 1);
-										suggestions = suggestions;
-									}}
-								>
-									<svg
-										xmlns="http://www.w3.org/2000/svg"
-										viewBox="0 0 20 20"
-										fill="currentColor"
-										class="w-4 h-4"
-									>
-										<path
-											d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
-										/>
-									</svg>
-								</button>
-							</div>
-						{/each}
-					</div>
-				</div>
-
-				<div class="my-2">
-					<div class=" text-sm font-semibold mb-2">{$i18n.t('Categories')}</div>
-
-					<div class="grid grid-cols-4">
-						{#each Object.keys(categories) as category}
-							<div class="flex space-x-2 text-sm">
-								<input type="checkbox" bind:checked={categories[category]} />
-								<div class="capitalize">{category}</div>
-							</div>
-						{/each}
-					</div>
-				</div>
-
-				{#if pullProgress !== null}
-					<div class="my-2">
-						<div class=" text-sm font-semibold mb-2">{$i18n.t('Pull Progress')}</div>
-						<div class="w-full rounded-full dark:bg-gray-800">
-							<div
-								class="dark:bg-gray-600 bg-gray-500 text-xs font-medium text-gray-100 text-center p-0.5 leading-none rounded-full"
-								style="width: {Math.max(15, pullProgress ?? 0)}%"
-							>
-								{pullProgress ?? 0}%
-							</div>
-						</div>
-						<div class="mt-1 text-xs dark:text-gray-500" style="font-size: 0.5rem;">
-							{digest}
-						</div>
-					</div>
-				{/if}
-
-				<div class="my-2 flex justify-end">
-					<button
-						class=" text-sm px-3 py-2 transition rounded-xl {loading
-							? ' cursor-not-allowed bg-gray-100 dark:bg-gray-800'
-							: ' bg-gray-50 hover:bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-800'} flex"
-						type="submit"
-						disabled={loading}
-					>
-						<div class=" self-center font-medium">{$i18n.t('Save & Create')}</div>
-
-						{#if loading}
-							<div class="ml-1.5 self-center">
-								<svg
-									class=" w-4 h-4"
-									viewBox="0 0 24 24"
-									fill="currentColor"
-									xmlns="http://www.w3.org/2000/svg"
-									><style>
-										.spinner_ajPY {
-											transform-origin: center;
-											animation: spinner_AtaB 0.75s infinite linear;
-										}
-										@keyframes spinner_AtaB {
-											100% {
-												transform: rotate(360deg);
-											}
-										}
-									</style><path
-										d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
-										opacity=".25"
-									/><path
-										d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
-										class="spinner_ajPY"
-									/></svg
-								>
-							</div>
-						{/if}
-					</button>
-				</div>
-			</form>
-		</div>
-	</div>
-</div>

+ 0 - 515
src/routes/(app)/modelfiles/edit/+page.svelte

@@ -1,515 +0,0 @@
-<script>
-	import { v4 as uuidv4 } from 'uuid';
-	import { toast } from 'svelte-sonner';
-	import { goto } from '$app/navigation';
-
-	import { onMount, getContext } from 'svelte';
-	import { page } from '$app/stores';
-
-	import { settings, user, config, modelfiles } from '$lib/stores';
-	import { splitStream } from '$lib/utils';
-
-	import { createModel } from '$lib/apis/ollama';
-	import { getModelfiles, updateModelfileByTagName } from '$lib/apis/modelfiles';
-
-	import AdvancedParams from '$lib/components/chat/Settings/Advanced/AdvancedParams.svelte';
-
-	const i18n = getContext('i18n');
-
-	let loading = false;
-
-	let filesInputElement;
-	let inputFiles;
-	let imageUrl = null;
-	let digest = '';
-	let pullProgress = null;
-	let success = false;
-
-	let modelfile = null;
-	// ///////////
-	// Modelfile
-	// ///////////
-
-	let title = '';
-	let tagName = '';
-	let desc = '';
-
-	// Raw Mode
-	let content = '';
-
-	let suggestions = [
-		{
-			content: ''
-		}
-	];
-
-	let categories = {
-		character: false,
-		assistant: false,
-		writing: false,
-		productivity: false,
-		programming: false,
-		'data analysis': false,
-		lifestyle: false,
-		education: false,
-		business: false
-	};
-
-	onMount(() => {
-		tagName = $page.url.searchParams.get('tag');
-
-		if (tagName) {
-			modelfile = $modelfiles.filter((modelfile) => modelfile.tagName === tagName)[0];
-
-			console.log(modelfile);
-
-			imageUrl = modelfile.imageUrl;
-			title = modelfile.title;
-			desc = modelfile.desc;
-			content = modelfile.content;
-			suggestions =
-				modelfile.suggestionPrompts.length != 0
-					? modelfile.suggestionPrompts
-					: [
-							{
-								content: ''
-							}
-					  ];
-
-			for (const category of modelfile.categories) {
-				categories[category.toLowerCase()] = true;
-			}
-		} else {
-			goto('/modelfiles');
-		}
-	});
-
-	const updateModelfile = async (modelfile) => {
-		await updateModelfileByTagName(localStorage.token, modelfile.tagName, modelfile);
-		await modelfiles.set(await getModelfiles(localStorage.token));
-	};
-
-	const updateHandler = async () => {
-		loading = true;
-
-		if (Object.keys(categories).filter((category) => categories[category]).length == 0) {
-			toast.error(
-				'Uh-oh! It looks like you missed selecting a category. Please choose one to complete your modelfile.'
-			);
-		}
-
-		if (
-			title !== '' &&
-			desc !== '' &&
-			content !== '' &&
-			Object.keys(categories).filter((category) => categories[category]).length > 0
-		) {
-			const res = await createModel(localStorage.token, tagName, content);
-
-			if (res) {
-				const reader = res.body
-					.pipeThrough(new TextDecoderStream())
-					.pipeThrough(splitStream('\n'))
-					.getReader();
-
-				while (true) {
-					const { value, done } = await reader.read();
-					if (done) break;
-
-					try {
-						let lines = value.split('\n');
-
-						for (const line of lines) {
-							if (line !== '') {
-								console.log(line);
-								let data = JSON.parse(line);
-								console.log(data);
-
-								if (data.error) {
-									throw data.error;
-								}
-								if (data.detail) {
-									throw data.detail;
-								}
-
-								if (data.status) {
-									if (
-										!data.digest &&
-										!data.status.includes('writing') &&
-										!data.status.includes('sha256')
-									) {
-										toast.success(data.status);
-
-										if (data.status === 'success') {
-											success = true;
-										}
-									} else {
-										if (data.digest) {
-											digest = data.digest;
-
-											if (data.completed) {
-												pullProgress = Math.round((data.completed / data.total) * 1000) / 10;
-											} else {
-												pullProgress = 100;
-											}
-										}
-									}
-								}
-							}
-						}
-					} catch (error) {
-						console.log(error);
-						toast.error(error);
-					}
-				}
-			}
-
-			if (success) {
-				await updateModelfile({
-					tagName: tagName,
-					imageUrl: imageUrl,
-					title: title,
-					desc: desc,
-					content: content,
-					suggestionPrompts: suggestions.filter((prompt) => prompt.content !== ''),
-					categories: Object.keys(categories).filter((category) => categories[category])
-				});
-				await goto('/modelfiles');
-			}
-		}
-		loading = false;
-		success = false;
-	};
-</script>
-
-<div class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white">
-	<div class="flex flex-col justify-between w-full overflow-y-auto">
-		<div class="max-w-2xl mx-auto w-full px-3 md:px-0 my-10">
-			<input
-				bind:this={filesInputElement}
-				bind:files={inputFiles}
-				type="file"
-				hidden
-				accept="image/*"
-				on:change={() => {
-					let reader = new FileReader();
-					reader.onload = (event) => {
-						let originalImageUrl = `${event.target.result}`;
-
-						const img = new Image();
-						img.src = originalImageUrl;
-
-						img.onload = function () {
-							const canvas = document.createElement('canvas');
-							const ctx = canvas.getContext('2d');
-
-							// Calculate the aspect ratio of the image
-							const aspectRatio = img.width / img.height;
-
-							// Calculate the new width and height to fit within 100x100
-							let newWidth, newHeight;
-							if (aspectRatio > 1) {
-								newWidth = 100 * aspectRatio;
-								newHeight = 100;
-							} else {
-								newWidth = 100;
-								newHeight = 100 / aspectRatio;
-							}
-
-							// Set the canvas size
-							canvas.width = 100;
-							canvas.height = 100;
-
-							// Calculate the position to center the image
-							const offsetX = (100 - newWidth) / 2;
-							const offsetY = (100 - newHeight) / 2;
-
-							// Draw the image on the canvas
-							ctx.drawImage(img, offsetX, offsetY, newWidth, newHeight);
-
-							// Get the base64 representation of the compressed image
-							const compressedSrc = canvas.toDataURL('image/jpeg');
-
-							// Display the compressed image
-							imageUrl = compressedSrc;
-
-							inputFiles = null;
-						};
-					};
-
-					if (
-						inputFiles &&
-						inputFiles.length > 0 &&
-						['image/gif', 'image/jpeg', 'image/png'].includes(inputFiles[0]['type'])
-					) {
-						reader.readAsDataURL(inputFiles[0]);
-					} else {
-						console.log(`Unsupported File Type '${inputFiles[0]['type']}'.`);
-						inputFiles = null;
-					}
-				}}
-			/>
-
-			<div class=" text-2xl font-semibold mb-6">{$i18n.t('My Modelfiles')}</div>
-
-			<button
-				class="flex space-x-1"
-				on:click={() => {
-					history.back();
-				}}
-			>
-				<div class=" self-center">
-					<svg
-						xmlns="http://www.w3.org/2000/svg"
-						viewBox="0 0 20 20"
-						fill="currentColor"
-						class="w-4 h-4"
-					>
-						<path
-							fill-rule="evenodd"
-							d="M17 10a.75.75 0 01-.75.75H5.612l4.158 3.96a.75.75 0 11-1.04 1.08l-5.5-5.25a.75.75 0 010-1.08l5.5-5.25a.75.75 0 111.04 1.08L5.612 9.25H16.25A.75.75 0 0117 10z"
-							clip-rule="evenodd"
-						/>
-					</svg>
-				</div>
-				<div class=" self-center font-medium text-sm">{$i18n.t('Back')}</div>
-			</button>
-			<hr class="my-3 dark:border-gray-700" />
-
-			<form
-				class="flex flex-col"
-				on:submit|preventDefault={() => {
-					updateHandler();
-				}}
-			>
-				<div class="flex justify-center my-4">
-					<div class="self-center">
-						<button
-							class=" {imageUrl
-								? ''
-								: 'p-6'} rounded-full dark:bg-gray-700 border border-dashed border-gray-200"
-							type="button"
-							on:click={() => {
-								filesInputElement.click();
-							}}
-						>
-							{#if imageUrl}
-								<img
-									src={imageUrl}
-									alt="modelfile profile"
-									class=" rounded-full w-20 h-20 object-cover"
-								/>
-							{:else}
-								<svg
-									xmlns="http://www.w3.org/2000/svg"
-									viewBox="0 0 24 24"
-									fill="currentColor"
-									class="w-8"
-								>
-									<path
-										fill-rule="evenodd"
-										d="M12 3.75a.75.75 0 01.75.75v6.75h6.75a.75.75 0 010 1.5h-6.75v6.75a.75.75 0 01-1.5 0v-6.75H4.5a.75.75 0 010-1.5h6.75V4.5a.75.75 0 01.75-.75z"
-										clip-rule="evenodd"
-									/>
-								</svg>
-							{/if}
-						</button>
-					</div>
-				</div>
-
-				<div class="my-2 flex space-x-2">
-					<div class="flex-1">
-						<div class=" text-sm font-semibold mb-2">{$i18n.t('Name')}*</div>
-
-						<div>
-							<input
-								class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
-								placeholder={$i18n.t('Name your modelfile')}
-								bind:value={title}
-								required
-							/>
-						</div>
-					</div>
-
-					<div class="flex-1">
-						<div class=" text-sm font-semibold mb-2">{$i18n.t('Model Tag Name')}*</div>
-
-						<div>
-							<input
-								class="px-3 py-1.5 text-sm w-full bg-transparent disabled:text-gray-500 border dark:border-gray-600 outline-none rounded-lg"
-								placeholder={$i18n.t('Add a model tag name')}
-								value={tagName}
-								disabled
-								required
-							/>
-						</div>
-					</div>
-				</div>
-
-				<div class="my-2">
-					<div class=" text-sm font-semibold mb-2">{$i18n.t('Description')}*</div>
-
-					<div>
-						<input
-							class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
-							placeholder={$i18n.t('Add a short description about what this modelfile does')}
-							bind:value={desc}
-							required
-						/>
-					</div>
-				</div>
-
-				<div class="my-2">
-					<div class="flex w-full justify-between">
-						<div class=" self-center text-sm font-semibold">{$i18n.t('Modelfile')}</div>
-					</div>
-
-					<!-- <div class=" text-sm font-semibold mb-2"></div> -->
-
-					<div class="mt-2">
-						<div class=" text-xs font-semibold mb-2">{$i18n.t('Content')}*</div>
-
-						<div>
-							<textarea
-								class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
-								placeholder={`FROM llama2\nPARAMETER temperature 1\nSYSTEM """\nYou are Mario from Super Mario Bros, acting as an assistant.\n"""`}
-								rows="6"
-								bind:value={content}
-								required
-							/>
-						</div>
-					</div>
-				</div>
-
-				<div class="my-2">
-					<div class="flex w-full justify-between mb-2">
-						<div class=" self-center text-sm font-semibold">{$i18n.t('Prompt suggestions')}</div>
-
-						<button
-							class="p-1 px-3 text-xs flex rounded transition"
-							type="button"
-							on:click={() => {
-								if (suggestions.length === 0 || suggestions.at(-1).content !== '') {
-									suggestions = [...suggestions, { content: '' }];
-								}
-							}}
-						>
-							<svg
-								xmlns="http://www.w3.org/2000/svg"
-								viewBox="0 0 20 20"
-								fill="currentColor"
-								class="w-4 h-4"
-							>
-								<path
-									d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z"
-								/>
-							</svg>
-						</button>
-					</div>
-					<div class="flex flex-col space-y-1">
-						{#each suggestions as prompt, promptIdx}
-							<div class=" flex border dark:border-gray-600 rounded-lg">
-								<input
-									class="px-3 py-1.5 text-sm w-full bg-transparent outline-none border-r dark:border-gray-600"
-									placeholder={$i18n.t('Write a prompt suggestion (e.g. Who are you?)')}
-									bind:value={prompt.content}
-								/>
-
-								<button
-									class="px-2"
-									type="button"
-									on:click={() => {
-										suggestions.splice(promptIdx, 1);
-										suggestions = suggestions;
-									}}
-								>
-									<svg
-										xmlns="http://www.w3.org/2000/svg"
-										viewBox="0 0 20 20"
-										fill="currentColor"
-										class="w-4 h-4"
-									>
-										<path
-											d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
-										/>
-									</svg>
-								</button>
-							</div>
-						{/each}
-					</div>
-				</div>
-
-				<div class="my-2">
-					<div class=" text-sm font-semibold mb-2">{$i18n.t('Categories')}</div>
-
-					<div class="grid grid-cols-4">
-						{#each Object.keys(categories) as category}
-							<div class="flex space-x-2 text-sm">
-								<input type="checkbox" bind:checked={categories[category]} />
-
-								<div class=" capitalize">{category}</div>
-							</div>
-						{/each}
-					</div>
-				</div>
-
-				{#if pullProgress !== null}
-					<div class="my-2">
-						<div class=" text-sm font-semibold mb-2">{$i18n.t('Pull Progress')}</div>
-						<div class="w-full rounded-full dark:bg-gray-800">
-							<div
-								class="dark:bg-gray-600 text-xs font-medium text-blue-100 text-center p-0.5 leading-none rounded-full"
-								style="width: {Math.max(15, pullProgress ?? 0)}%"
-							>
-								{pullProgress ?? 0}%
-							</div>
-						</div>
-						<div class="mt-1 text-xs dark:text-gray-500" style="font-size: 0.5rem;">
-							{digest}
-						</div>
-					</div>
-				{/if}
-
-				<div class="my-2 flex justify-end">
-					<button
-						class=" text-sm px-3 py-2 transition rounded-xl {loading
-							? ' cursor-not-allowed bg-gray-100 dark:bg-gray-800'
-							: ' bg-gray-50 hover:bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-800'} flex"
-						type="submit"
-						disabled={loading}
-					>
-						<div class=" self-center font-medium">{$i18n.t('Save & Update')}</div>
-
-						{#if loading}
-							<div class="ml-1.5 self-center">
-								<svg
-									class=" w-4 h-4"
-									viewBox="0 0 24 24"
-									fill="currentColor"
-									xmlns="http://www.w3.org/2000/svg"
-									><style>
-										.spinner_ajPY {
-											transform-origin: center;
-											animation: spinner_AtaB 0.75s infinite linear;
-										}
-										@keyframes spinner_AtaB {
-											100% {
-												transform: rotate(360deg);
-											}
-										}
-									</style><path
-										d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
-										opacity=".25"
-									/><path
-										d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
-										class="spinner_ajPY"
-									/></svg
-								>
-							</div>
-						{/if}
-					</button>
-				</div>
-			</form>
-		</div>
-	</div>
-</div>

+ 0 - 342
src/routes/(app)/prompts/+page.svelte

@@ -1,342 +0,0 @@
-<script lang="ts">
-	import { toast } from 'svelte-sonner';
-	import fileSaver from 'file-saver';
-	const { saveAs } = fileSaver;
-
-	import { onMount, getContext } from 'svelte';
-	import { WEBUI_NAME, prompts } from '$lib/stores';
-	import { createNewPrompt, deletePromptByCommand, getPrompts } from '$lib/apis/prompts';
-	import { error } from '@sveltejs/kit';
-	import { goto } from '$app/navigation';
-
-	const i18n = getContext('i18n');
-
-	let importFiles = '';
-	let query = '';
-	let promptsImportInputElement: HTMLInputElement;
-	const sharePrompt = async (prompt) => {
-		toast.success($i18n.t('Redirecting you to OpenWebUI Community'));
-
-		const url = 'https://openwebui.com';
-
-		const tab = await window.open(`${url}/prompts/create`, '_blank');
-		window.addEventListener(
-			'message',
-			(event) => {
-				if (event.origin !== url) return;
-				if (event.data === 'loaded') {
-					tab.postMessage(JSON.stringify(prompt), '*');
-				}
-			},
-			false
-		);
-	};
-
-	const deletePrompt = async (command) => {
-		await deletePromptByCommand(localStorage.token, command);
-		await prompts.set(await getPrompts(localStorage.token));
-	};
-</script>
-
-<svelte:head>
-	<title>
-		{$i18n.t('Prompts')} | {$WEBUI_NAME}
-	</title>
-</svelte:head>
-
-<div class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white">
-	<div class="flex flex-col justify-between w-full overflow-y-auto">
-		<div class="max-w-2xl mx-auto w-full px-3 md:px-0 my-10">
-			<div class="mb-6 flex justify-between items-center">
-				<div class=" text-2xl font-semibold self-center">{$i18n.t('My Prompts')}</div>
-			</div>
-
-			<div class=" flex w-full space-x-2">
-				<div class="flex flex-1">
-					<div class=" self-center ml-1 mr-3">
-						<svg
-							xmlns="http://www.w3.org/2000/svg"
-							viewBox="0 0 20 20"
-							fill="currentColor"
-							class="w-4 h-4"
-						>
-							<path
-								fill-rule="evenodd"
-								d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z"
-								clip-rule="evenodd"
-							/>
-						</svg>
-					</div>
-					<input
-						class=" w-full text-sm pr-4 py-1 rounded-r-xl outline-none bg-transparent"
-						bind:value={query}
-						placeholder={$i18n.t('Search Prompts')}
-					/>
-				</div>
-
-				<div>
-					<a
-						class=" px-2 py-2 rounded-xl border border-gray-200 dark:border-gray-600 dark:border-0 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 transition font-medium text-sm flex items-center space-x-1"
-						href="/prompts/create"
-					>
-						<svg
-							xmlns="http://www.w3.org/2000/svg"
-							viewBox="0 0 16 16"
-							fill="currentColor"
-							class="w-4 h-4"
-						>
-							<path
-								d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
-							/>
-						</svg>
-					</a>
-				</div>
-			</div>
-			<hr class=" dark:border-gray-700 my-2.5" />
-
-			<div class="my-3 mb-5">
-				{#each $prompts.filter((p) => query === '' || p.command.includes(query)) as prompt}
-					<div
-						class=" flex space-x-4 cursor-pointer w-full px-3 py-2 dark:hover:bg-white/5 hover:bg-black/5 rounded-xl"
-					>
-						<div class=" flex flex-1 space-x-4 cursor-pointer w-full">
-							<a href={`/prompts/edit?command=${encodeURIComponent(prompt.command)}`}>
-								<div class=" flex-1 self-center pl-5">
-									<div class=" font-bold">{prompt.command}</div>
-									<div class=" text-xs overflow-hidden text-ellipsis line-clamp-1">
-										{prompt.title}
-									</div>
-								</div>
-							</a>
-						</div>
-						<div class="flex flex-row space-x-1 self-center">
-							<a
-								class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
-								type="button"
-								href={`/prompts/edit?command=${encodeURIComponent(prompt.command)}`}
-							>
-								<svg
-									xmlns="http://www.w3.org/2000/svg"
-									fill="none"
-									viewBox="0 0 24 24"
-									stroke-width="1.5"
-									stroke="currentColor"
-									class="w-4 h-4"
-								>
-									<path
-										stroke-linecap="round"
-										stroke-linejoin="round"
-										d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125"
-									/>
-								</svg>
-							</a>
-
-							<button
-								class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
-								type="button"
-								on:click={() => {
-									// console.log(modelfile);
-									sessionStorage.prompt = JSON.stringify(prompt);
-									goto('/prompts/create');
-								}}
-							>
-								<svg
-									xmlns="http://www.w3.org/2000/svg"
-									fill="none"
-									viewBox="0 0 24 24"
-									stroke-width="1.5"
-									stroke="currentColor"
-									class="w-4 h-4"
-								>
-									<path
-										stroke-linecap="round"
-										stroke-linejoin="round"
-										d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 0 1-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 0 1 1.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 0 0-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 0 1-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 0 0-3.375-3.375h-1.5a1.125 1.125 0 0 1-1.125-1.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H9.75"
-									/>
-								</svg>
-							</button>
-
-							<button
-								class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
-								type="button"
-								on:click={() => {
-									sharePrompt(prompt);
-								}}
-							>
-								<svg
-									xmlns="http://www.w3.org/2000/svg"
-									fill="none"
-									viewBox="0 0 24 24"
-									stroke-width="1.5"
-									stroke="currentColor"
-									class="w-4 h-4"
-								>
-									<path
-										stroke-linecap="round"
-										stroke-linejoin="round"
-										d="M7.217 10.907a2.25 2.25 0 100 2.186m0-2.186c.18.324.283.696.283 1.093s-.103.77-.283 1.093m0-2.186l9.566-5.314m-9.566 7.5l9.566 5.314m0 0a2.25 2.25 0 103.935 2.186 2.25 2.25 0 00-3.935-2.186zm0-12.814a2.25 2.25 0 103.933-2.185 2.25 2.25 0 00-3.933 2.185z"
-									/>
-								</svg>
-							</button>
-
-							<button
-								class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
-								type="button"
-								on:click={() => {
-									deletePrompt(prompt.command);
-								}}
-							>
-								<svg
-									xmlns="http://www.w3.org/2000/svg"
-									fill="none"
-									viewBox="0 0 24 24"
-									stroke-width="1.5"
-									stroke="currentColor"
-									class="w-4 h-4"
-								>
-									<path
-										stroke-linecap="round"
-										stroke-linejoin="round"
-										d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
-									/>
-								</svg>
-							</button>
-						</div>
-					</div>
-				{/each}
-			</div>
-
-			<div class=" flex justify-end w-full mb-3">
-				<div class="flex space-x-2">
-					<input
-						id="prompts-import-input"
-						bind:this={promptsImportInputElement}
-						bind:files={importFiles}
-						type="file"
-						accept=".json"
-						hidden
-						on:change={() => {
-							console.log(importFiles);
-
-							const reader = new FileReader();
-							reader.onload = async (event) => {
-								const savedPrompts = JSON.parse(event.target.result);
-								console.log(savedPrompts);
-
-								for (const prompt of savedPrompts) {
-									await createNewPrompt(
-										localStorage.token,
-										prompt.command.charAt(0) === '/' ? prompt.command.slice(1) : prompt.command,
-										prompt.title,
-										prompt.content
-									).catch((error) => {
-										toast.error(error);
-										return null;
-									});
-								}
-
-								await prompts.set(await getPrompts(localStorage.token));
-							};
-
-							reader.readAsText(importFiles[0]);
-						}}
-					/>
-
-					<button
-						class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
-						on:click={() => {
-							promptsImportInputElement.click();
-						}}
-					>
-						<div class=" self-center mr-2 font-medium">{$i18n.t('Import Prompts')}</div>
-
-						<div class=" self-center">
-							<svg
-								xmlns="http://www.w3.org/2000/svg"
-								viewBox="0 0 16 16"
-								fill="currentColor"
-								class="w-4 h-4"
-							>
-								<path
-									fill-rule="evenodd"
-									d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 9.5a.75.75 0 0 1-.75-.75V8.06l-.72.72a.75.75 0 0 1-1.06-1.06l2-2a.75.75 0 0 1 1.06 0l2 2a.75.75 0 1 1-1.06 1.06l-.72-.72v2.69a.75.75 0 0 1-.75.75Z"
-									clip-rule="evenodd"
-								/>
-							</svg>
-						</div>
-					</button>
-
-					<button
-						class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
-						on:click={async () => {
-							// promptsImportInputElement.click();
-							let blob = new Blob([JSON.stringify($prompts)], {
-								type: 'application/json'
-							});
-							saveAs(blob, `prompts-export-${Date.now()}.json`);
-						}}
-					>
-						<div class=" self-center mr-2 font-medium">{$i18n.t('Export Prompts')}</div>
-
-						<div class=" self-center">
-							<svg
-								xmlns="http://www.w3.org/2000/svg"
-								viewBox="0 0 16 16"
-								fill="currentColor"
-								class="w-4 h-4"
-							>
-								<path
-									fill-rule="evenodd"
-									d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 3.5a.75.75 0 0 1 .75.75v2.69l.72-.72a.75.75 0 1 1 1.06 1.06l-2 2a.75.75 0 0 1-1.06 0l-2-2a.75.75 0 0 1 1.06-1.06l.72.72V6.25A.75.75 0 0 1 8 5.5Z"
-									clip-rule="evenodd"
-								/>
-							</svg>
-						</div>
-					</button>
-
-					<!-- <button
-						on:click={() => {
-							loadDefaultPrompts();
-						}}
-					>
-						dd
-					</button> -->
-				</div>
-			</div>
-
-			<div class=" my-16">
-				<div class=" text-2xl font-semibold mb-3">{$i18n.t('Made by OpenWebUI Community')}</div>
-
-				<a
-					class=" flex space-x-4 cursor-pointer w-full mb-3 px-3 py-2"
-					href="https://openwebui.com/?type=prompts"
-					target="_blank"
-				>
-					<div class=" self-center w-10">
-						<div
-							class="w-full h-10 flex justify-center rounded-full bg-transparent dark:bg-gray-700 border border-dashed border-gray-200"
-						>
-							<svg
-								xmlns="http://www.w3.org/2000/svg"
-								viewBox="0 0 24 24"
-								fill="currentColor"
-								class="w-6"
-							>
-								<path
-									fill-rule="evenodd"
-									d="M12 3.75a.75.75 0 01.75.75v6.75h6.75a.75.75 0 010 1.5h-6.75v6.75a.75.75 0 01-1.5 0v-6.75H4.5a.75.75 0 010-1.5h6.75V4.5a.75.75 0 01.75-.75z"
-									clip-rule="evenodd"
-								/>
-							</svg>
-						</div>
-					</div>
-
-					<div class=" self-center">
-						<div class=" font-bold">{$i18n.t('Discover a prompt')}</div>
-						<div class=" text-sm">{$i18n.t('Discover, download, and explore custom prompts')}</div>
-					</div>
-				</a>
-			</div>
-		</div>
-	</div>
-</div>

+ 0 - 254
src/routes/(app)/prompts/create/+page.svelte

@@ -1,254 +0,0 @@
-<script>
-	import { toast } from 'svelte-sonner';
-
-	import { goto } from '$app/navigation';
-	import { prompts } from '$lib/stores';
-	import { onMount, tick, getContext } from 'svelte';
-
-	import { createNewPrompt, getPrompts } from '$lib/apis/prompts';
-
-	const i18n = getContext('i18n');
-
-	let loading = false;
-
-	// ///////////
-	// Prompt
-	// ///////////
-
-	let title = '';
-	let command = '';
-	let content = '';
-
-	$: command = title !== '' ? `${title.replace(/\s+/g, '-').toLowerCase()}` : '';
-
-	const submitHandler = async () => {
-		loading = true;
-
-		if (validateCommandString(command)) {
-			const prompt = await createNewPrompt(localStorage.token, command, title, content).catch(
-				(error) => {
-					toast.error(error);
-
-					return null;
-				}
-			);
-
-			if (prompt) {
-				await prompts.set(await getPrompts(localStorage.token));
-				await goto('/prompts');
-			}
-		} else {
-			toast.error(
-				$i18n.t('Only alphanumeric characters and hyphens are allowed in the command string.')
-			);
-		}
-
-		loading = false;
-	};
-
-	const validateCommandString = (inputString) => {
-		// Regular expression to match only alphanumeric characters and hyphen
-		const regex = /^[a-zA-Z0-9-]+$/;
-
-		// Test the input string against the regular expression
-		return regex.test(inputString);
-	};
-
-	onMount(async () => {
-		window.addEventListener('message', async (event) => {
-			if (
-				![
-					'https://ollamahub.com',
-					'https://www.ollamahub.com',
-					'https://openwebui.com',
-					'https://www.openwebui.com',
-					'http://localhost:5173'
-				].includes(event.origin)
-			)
-				return;
-			const prompt = JSON.parse(event.data);
-			console.log(prompt);
-
-			title = prompt.title;
-			await tick();
-			content = prompt.content;
-			command = prompt.command;
-		});
-
-		if (window.opener ?? false) {
-			window.opener.postMessage('loaded', '*');
-		}
-
-		if (sessionStorage.prompt) {
-			const prompt = JSON.parse(sessionStorage.prompt);
-
-			console.log(prompt);
-			title = prompt.title;
-			await tick();
-			content = prompt.content;
-			command = prompt.command.at(0) === '/' ? prompt.command.slice(1) : prompt.command;
-
-			sessionStorage.removeItem('prompt');
-		}
-	});
-</script>
-
-<div class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white">
-	<div class=" flex flex-col justify-between w-full overflow-y-auto">
-		<div class="max-w-2xl mx-auto w-full px-3 md:px-0 my-10">
-			<div class=" text-2xl font-semibold mb-6">{$i18n.t('My Prompts')}</div>
-
-			<button
-				class="flex space-x-1"
-				on:click={() => {
-					history.back();
-				}}
-			>
-				<div class=" self-center">
-					<svg
-						xmlns="http://www.w3.org/2000/svg"
-						viewBox="0 0 20 20"
-						fill="currentColor"
-						class="w-4 h-4"
-					>
-						<path
-							fill-rule="evenodd"
-							d="M17 10a.75.75 0 01-.75.75H5.612l4.158 3.96a.75.75 0 11-1.04 1.08l-5.5-5.25a.75.75 0 010-1.08l5.5-5.25a.75.75 0 111.04 1.08L5.612 9.25H16.25A.75.75 0 0117 10z"
-							clip-rule="evenodd"
-						/>
-					</svg>
-				</div>
-				<div class=" self-center font-medium text-sm">{$i18n.t('Back')}</div>
-			</button>
-			<hr class="my-3 dark:border-gray-700" />
-
-			<form
-				class="flex flex-col"
-				on:submit|preventDefault={() => {
-					submitHandler();
-				}}
-			>
-				<div class="my-2">
-					<div class=" text-sm font-semibold mb-2">{$i18n.t('Title')}*</div>
-
-					<div>
-						<input
-							class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
-							placeholder={$i18n.t('Add a short title for this prompt')}
-							bind:value={title}
-							required
-						/>
-					</div>
-				</div>
-
-				<div class="my-2">
-					<div class=" text-sm font-semibold mb-2">{$i18n.t('Command')}*</div>
-
-					<div class="flex items-center mb-1">
-						<div
-							class="bg-gray-200 dark:bg-gray-600 font-bold px-3 py-1 border border-r-0 dark:border-gray-600 rounded-l-lg"
-						>
-							/
-						</div>
-						<input
-							class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-r-lg"
-							placeholder={$i18n.t('short-summary')}
-							bind:value={command}
-							required
-						/>
-					</div>
-
-					<div class="text-xs text-gray-400 dark:text-gray-500">
-						{$i18n.t('Only')}
-						<span class=" text-gray-600 dark:text-gray-300 font-medium"
-							>{$i18n.t('alphanumeric characters and hyphens')}</span
-						>
-						{$i18n.t('are allowed - Activate this command by typing')}&nbsp;"<span
-							class=" text-gray-600 dark:text-gray-300 font-medium"
-						>
-							/{command}
-						</span>" &nbsp;
-						{$i18n.t('to chat input.')}
-					</div>
-				</div>
-
-				<div class="my-2">
-					<div class="flex w-full justify-between">
-						<div class=" self-center text-sm font-semibold">{$i18n.t('Prompt Content')}*</div>
-					</div>
-
-					<div class="mt-2">
-						<div>
-							<textarea
-								class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
-								placeholder={$i18n.t(
-									'Write a summary in 50 words that summarizes [topic or keyword].'
-								)}
-								rows="6"
-								bind:value={content}
-								required
-							/>
-						</div>
-
-						<div class="text-xs text-gray-400 dark:text-gray-500">
-							ⓘ {$i18n.t('Format your variables using square brackets like this:')}&nbsp;<span
-								class=" text-gray-600 dark:text-gray-300 font-medium">[{$i18n.t('variable')}]</span
-							>.
-							{$i18n.t('Make sure to enclose them with')}
-							<span class=" text-gray-600 dark:text-gray-300 font-medium">'['</span>
-							{$i18n.t('and')}
-							<span class=" text-gray-600 dark:text-gray-300 font-medium">']'</span>.
-						</div>
-
-						<div class="text-xs text-gray-400 dark:text-gray-500">
-							{$i18n.t('Utilize')}<span class=" text-gray-600 dark:text-gray-300 font-medium">
-								{` {{CLIPBOARD}}`}</span
-							>
-							{$i18n.t('variable to have them replaced with clipboard content.')}
-						</div>
-					</div>
-				</div>
-
-				<div class="my-2 flex justify-end">
-					<button
-						class=" text-sm px-3 py-2 transition rounded-xl {loading
-							? ' cursor-not-allowed bg-gray-100 dark:bg-gray-800'
-							: ' bg-gray-50 hover:bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-800'} flex"
-						type="submit"
-						disabled={loading}
-					>
-						<div class=" self-center font-medium">{$i18n.t('Save & Create')}</div>
-
-						{#if loading}
-							<div class="ml-1.5 self-center">
-								<svg
-									class=" w-4 h-4"
-									viewBox="0 0 24 24"
-									fill="currentColor"
-									xmlns="http://www.w3.org/2000/svg"
-									><style>
-										.spinner_ajPY {
-											transform-origin: center;
-											animation: spinner_AtaB 0.75s infinite linear;
-										}
-										@keyframes spinner_AtaB {
-											100% {
-												transform: rotate(360deg);
-											}
-										}
-									</style><path
-										d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
-										opacity=".25"
-									/><path
-										d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
-										class="spinner_ajPY"
-									/></svg
-								>
-							</div>
-						{/if}
-					</button>
-				</div>
-			</form>
-		</div>
-	</div>
-</div>

+ 0 - 237
src/routes/(app)/prompts/edit/+page.svelte

@@ -1,237 +0,0 @@
-<script>
-	import { toast } from 'svelte-sonner';
-
-	import { goto } from '$app/navigation';
-	import { prompts } from '$lib/stores';
-	import { onMount, tick, getContext } from 'svelte';
-
-	const i18n = getContext('i18n');
-
-	import { getPrompts, updatePromptByCommand } from '$lib/apis/prompts';
-	import { page } from '$app/stores';
-
-	let loading = false;
-
-	// ///////////
-	// Prompt
-	// ///////////
-
-	let title = '';
-	let command = '';
-	let content = '';
-
-	const updateHandler = async () => {
-		loading = true;
-
-		if (validateCommandString(command)) {
-			const prompt = await updatePromptByCommand(localStorage.token, command, title, content).catch(
-				(error) => {
-					toast.error(error);
-					return null;
-				}
-			);
-
-			if (prompt) {
-				await prompts.set(await getPrompts(localStorage.token));
-				await goto('/prompts');
-			}
-		} else {
-			toast.error(
-				$i18n.t('Only alphanumeric characters and hyphens are allowed in the command string.')
-			);
-		}
-
-		loading = false;
-	};
-
-	const validateCommandString = (inputString) => {
-		// Regular expression to match only alphanumeric characters and hyphen
-		const regex = /^[a-zA-Z0-9-]+$/;
-
-		// Test the input string against the regular expression
-		return regex.test(inputString);
-	};
-
-	onMount(async () => {
-		command = $page.url.searchParams.get('command');
-		if (command) {
-			const prompt = $prompts.filter((prompt) => prompt.command === command).at(0);
-
-			if (prompt) {
-				console.log(prompt);
-
-				console.log(prompt.command);
-
-				title = prompt.title;
-				await tick();
-				command = prompt.command.slice(1);
-				content = prompt.content;
-			} else {
-				goto('/prompts');
-			}
-		} else {
-			goto('/prompts');
-		}
-	});
-</script>
-
-<div class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white">
-	<div class="flex flex-col justify-between w-full overflow-y-auto">
-		<div class="max-w-2xl mx-auto w-full px-3 md:px-0 my-10">
-			<div class=" text-2xl font-semibold mb-6">{$i18n.t('My Prompts')}</div>
-
-			<button
-				class="flex space-x-1"
-				on:click={() => {
-					history.back();
-				}}
-			>
-				<div class=" self-center">
-					<svg
-						xmlns="http://www.w3.org/2000/svg"
-						viewBox="0 0 20 20"
-						fill="currentColor"
-						class="w-4 h-4"
-					>
-						<path
-							fill-rule="evenodd"
-							d="M17 10a.75.75 0 01-.75.75H5.612l4.158 3.96a.75.75 0 11-1.04 1.08l-5.5-5.25a.75.75 0 010-1.08l5.5-5.25a.75.75 0 111.04 1.08L5.612 9.25H16.25A.75.75 0 0117 10z"
-							clip-rule="evenodd"
-						/>
-					</svg>
-				</div>
-				<div class=" self-center font-medium text-sm">{$i18n.t('Back')}</div>
-			</button>
-			<hr class="my-3 dark:border-gray-700" />
-
-			<form
-				class="flex flex-col"
-				on:submit|preventDefault={() => {
-					updateHandler();
-				}}
-			>
-				<div class="my-2">
-					<div class=" text-sm font-semibold mb-2">{$i18n.t('Title')}*</div>
-
-					<div>
-						<input
-							class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
-							placeholder={$i18n.t('Add a short title for this prompt')}
-							bind:value={title}
-							required
-						/>
-					</div>
-				</div>
-
-				<div class="my-2">
-					<div class=" text-sm font-semibold mb-2">{$i18n.t('Command')}*</div>
-
-					<div class="flex items-center mb-1">
-						<div
-							class="bg-gray-200 dark:bg-gray-600 font-bold px-3 py-1 border border-r-0 dark:border-gray-600 rounded-l-lg"
-						>
-							/
-						</div>
-						<input
-							class="px-3 py-1.5 text-sm w-full bg-transparent border disabled:text-gray-500 dark:border-gray-600 outline-none rounded-r-lg"
-							placeholder="short-summary"
-							bind:value={command}
-							disabled
-							required
-						/>
-					</div>
-
-					<div class="text-xs text-gray-400 dark:text-gray-500">
-						{$i18n.t('Only')}
-						<span class=" text-gray-600 dark:text-gray-300 font-medium"
-							>{$i18n.t('alphanumeric characters and hyphens')}</span
-						>
-						{$i18n.t('are allowed - Activate this command by typing')}&nbsp;"<span
-							class=" text-gray-600 dark:text-gray-300 font-medium"
-						>
-							/{command}
-						</span>" &nbsp;
-						{$i18n.t('to chat input.')}
-					</div>
-				</div>
-
-				<div class="my-2">
-					<div class="flex w-full justify-between">
-						<div class=" self-center text-sm font-semibold">{$i18n.t('Prompt Content')}*</div>
-					</div>
-
-					<div class="mt-2">
-						<div>
-							<textarea
-								class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
-								placeholder={$i18n.t(
-									`Write a summary in 50 words that summarizes [topic or keyword].`
-								)}
-								rows="6"
-								bind:value={content}
-								required
-							/>
-						</div>
-
-						<div class="text-xs text-gray-400 dark:text-gray-500">
-							ⓘ {$i18n.t('Format your variables using square brackets like this:')}&nbsp;<span
-								class=" text-gray-600 dark:text-gray-300 font-medium">[{$i18n.t('variable')}]</span
-							>.
-							{$i18n.t('Make sure to enclose them with')}
-							<span class=" text-gray-600 dark:text-gray-300 font-medium">'['</span>
-							{$i18n.t('and')}
-							<span class=" text-gray-600 dark:text-gray-300 font-medium">']'</span>.
-						</div>
-
-						<div class="text-xs text-gray-400 dark:text-gray-500">
-							{$i18n.t('Utilize')}<span class=" text-gray-600 dark:text-gray-300 font-medium">
-								{` {{CLIPBOARD}}`}</span
-							>
-							{$i18n.t('variable to have them replaced with clipboard content.')}
-						</div>
-					</div>
-				</div>
-
-				<div class="my-2 flex justify-end">
-					<button
-						class=" text-sm px-3 py-2 transition rounded-xl {loading
-							? ' cursor-not-allowed bg-gray-100 dark:bg-gray-800'
-							: ' bg-gray-50 hover:bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-800'} flex"
-						type="submit"
-						disabled={loading}
-					>
-						<div class=" self-center font-medium">{$i18n.t('Save & Update')}</div>
-
-						{#if loading}
-							<div class="ml-1.5 self-center">
-								<svg
-									class=" w-4 h-4"
-									viewBox="0 0 24 24"
-									fill="currentColor"
-									xmlns="http://www.w3.org/2000/svg"
-									><style>
-										.spinner_ajPY {
-											transform-origin: center;
-											animation: spinner_AtaB 0.75s infinite linear;
-										}
-										@keyframes spinner_AtaB {
-											100% {
-												transform: rotate(360deg);
-											}
-										}
-									</style><path
-										d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
-										opacity=".25"
-									/><path
-										d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
-										class="spinner_ajPY"
-									/></svg
-								>
-							</div>
-						{/if}
-					</button>
-				</div>
-			</form>
-		</div>
-	</div>
-</div>

+ 78 - 0
src/routes/(app)/workspace/+layout.svelte

@@ -0,0 +1,78 @@
+<script lang="ts">
+	import { onMount, getContext } from 'svelte';
+
+	import { WEBUI_NAME, showSidebar } from '$lib/stores';
+	import MenuLines from '$lib/components/icons/MenuLines.svelte';
+	import { page } from '$app/stores';
+
+	const i18n = getContext('i18n');
+</script>
+
+<svelte:head>
+	<title>
+		{$i18n.t('Workspace')} | {$WEBUI_NAME}
+	</title>
+</svelte:head>
+
+<div class=" flex flex-col w-full min-h-screen max-h-screen">
+	<div class=" px-4 pt-3 mt-0.5 mb-1">
+		<div class=" flex items-center gap-1">
+			<div class="{$showSidebar ? 'md:hidden' : ''} mr-1 self-start flex flex-none items-center">
+				<button
+					id="sidebar-toggle-button"
+					class="cursor-pointer p-1 flex rounded-xl hover:bg-gray-100 dark:hover:bg-gray-850 transition"
+					on:click={() => {
+						showSidebar.set(!$showSidebar);
+					}}
+				>
+					<div class=" m-auto self-center">
+						<MenuLines />
+					</div>
+				</button>
+			</div>
+			<div class="flex items-center text-xl font-semibold">{$i18n.t('Workspace')}</div>
+		</div>
+	</div>
+
+	<div class="px-4 my-1">
+		<div
+			class="flex scrollbar-none overflow-x-auto w-fit text-center text-sm font-medium rounded-xl bg-transparent/10 p-1"
+		>
+			<a
+				class="min-w-fit rounded-lg p-1.5 px-3 {$page.url.pathname.includes('/workspace/modelfiles')
+					? 'bg-gray-50 dark:bg-gray-850'
+					: ''} transition"
+				href="/workspace/modelfiles">{$i18n.t('Modelfiles')}</a
+			>
+
+			<a
+				class="min-w-fit rounded-lg p-1.5 px-3 {$page.url.pathname.includes('/workspace/prompts')
+					? 'bg-gray-50 dark:bg-gray-850'
+					: ''} transition"
+				href="/workspace/prompts">{$i18n.t('Prompts')}</a
+			>
+
+			<a
+				class="min-w-fit rounded-lg p-1.5 px-3 {$page.url.pathname.includes('/workspace/documents')
+					? 'bg-gray-50 dark:bg-gray-850'
+					: ''} transition"
+				href="/workspace/documents"
+			>
+				{$i18n.t('Documents')}
+			</a>
+
+			<a
+				class="min-w-fit rounded-lg p-1.5 px-3 {$page.url.pathname.includes('/workspace/playground')
+					? 'bg-gray-50 dark:bg-gray-850'
+					: ''} transition"
+				href="/workspace/playground">{$i18n.t('Playground')}</a
+			>
+		</div>
+	</div>
+
+	<hr class=" my-2 dark:border-gray-850" />
+
+	<div class=" py-1 px-5 flex-1 max-h-full overflow-y-auto">
+		<slot />
+	</div>
+</div>

+ 8 - 0
src/routes/(app)/workspace/+page.svelte

@@ -0,0 +1,8 @@
+<script lang="ts">
+	import { goto } from '$app/navigation';
+	import { onMount } from 'svelte';
+
+	onMount(() => {
+		goto('/workspace/modelfiles');
+	});
+</script>

+ 5 - 0
src/routes/(app)/workspace/documents/+page.svelte

@@ -0,0 +1,5 @@
+<script>
+	import Documents from '$lib/components/workspace/Documents.svelte';
+</script>
+
+<Documents />

+ 5 - 0
src/routes/(app)/workspace/modelfiles/+page.svelte

@@ -0,0 +1,5 @@
+<script>
+	import Modelfiles from '$lib/components/workspace/Modelfiles.svelte';
+</script>
+
+<Modelfiles />

+ 721 - 0
src/routes/(app)/workspace/modelfiles/create/+page.svelte

@@ -0,0 +1,721 @@
+<script>
+	import { v4 as uuidv4 } from 'uuid';
+	import { toast } from 'svelte-sonner';
+	import { goto } from '$app/navigation';
+	import { settings, user, config, modelfiles, models } from '$lib/stores';
+
+	import AdvancedParams from '$lib/components/chat/Settings/Advanced/AdvancedParams.svelte';
+	import { splitStream } from '$lib/utils';
+	import { onMount, tick, getContext } from 'svelte';
+	import { createModel } from '$lib/apis/ollama';
+	import { createNewModelfile, getModelfileByTagName, getModelfiles } from '$lib/apis/modelfiles';
+
+	const i18n = getContext('i18n');
+
+	let loading = false;
+
+	let filesInputElement;
+	let inputFiles;
+	let imageUrl = null;
+	let digest = '';
+	let pullProgress = null;
+	let success = false;
+
+	// ///////////
+	// Modelfile
+	// ///////////
+
+	let title = '';
+	let tagName = '';
+	let desc = '';
+
+	let raw = true;
+	let advanced = false;
+
+	// Raw Mode
+	let content = '';
+
+	// Builder Mode
+	let model = '';
+	let system = '';
+	let template = '';
+	let options = {
+		// Advanced
+		seed: 0,
+		stop: '',
+		temperature: '',
+		repeat_penalty: '',
+		repeat_last_n: '',
+		mirostat: '',
+		mirostat_eta: '',
+		mirostat_tau: '',
+		top_k: '',
+		top_p: '',
+		tfs_z: '',
+		num_ctx: '',
+		num_predict: ''
+	};
+
+	let modelfileCreator = null;
+
+	$: tagName = title !== '' ? `${title.replace(/\s+/g, '-').toLowerCase()}:latest` : '';
+
+	$: if (!raw) {
+		content = `FROM ${model}
+${template !== '' ? `TEMPLATE """${template}"""` : ''}
+${options.seed !== 0 ? `PARAMETER seed ${options.seed}` : ''}
+${options.stop !== '' ? `PARAMETER stop ${options.stop}` : ''}
+${options.temperature !== '' ? `PARAMETER temperature ${options.temperature}` : ''}
+${options.repeat_penalty !== '' ? `PARAMETER repeat_penalty ${options.repeat_penalty}` : ''}
+${options.repeat_last_n !== '' ? `PARAMETER repeat_last_n ${options.repeat_last_n}` : ''}
+${options.mirostat !== '' ? `PARAMETER mirostat ${options.mirostat}` : ''}
+${options.mirostat_eta !== '' ? `PARAMETER mirostat_eta ${options.mirostat_eta}` : ''}
+${options.mirostat_tau !== '' ? `PARAMETER mirostat_tau ${options.mirostat_tau}` : ''}
+${options.top_k !== '' ? `PARAMETER top_k ${options.top_k}` : ''}
+${options.top_p !== '' ? `PARAMETER top_p ${options.top_p}` : ''}
+${options.tfs_z !== '' ? `PARAMETER tfs_z ${options.tfs_z}` : ''}
+${options.num_ctx !== '' ? `PARAMETER num_ctx ${options.num_ctx}` : ''}
+${options.num_predict !== '' ? `PARAMETER num_predict ${options.num_predict}` : ''}
+SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
+	}
+
+	let suggestions = [
+		{
+			content: ''
+		}
+	];
+
+	let categories = {
+		character: false,
+		assistant: false,
+		writing: false,
+		productivity: false,
+		programming: false,
+		'data analysis': false,
+		lifestyle: false,
+		education: false,
+		business: false
+	};
+
+	const saveModelfile = async (modelfile) => {
+		await createNewModelfile(localStorage.token, modelfile);
+		await modelfiles.set(await getModelfiles(localStorage.token));
+	};
+
+	const submitHandler = async () => {
+		loading = true;
+
+		if (Object.keys(categories).filter((category) => categories[category]).length == 0) {
+			toast.error(
+				'Uh-oh! It looks like you missed selecting a category. Please choose one to complete your modelfile.'
+			);
+			loading = false;
+			success = false;
+			return success;
+		}
+
+		if (
+			$models.map((model) => model.name).includes(tagName) ||
+			(await getModelfileByTagName(localStorage.token, tagName).catch(() => false))
+		) {
+			toast.error(
+				`Uh-oh! It looks like you already have a model named '${tagName}'. Please choose a different name to complete your modelfile.`
+			);
+			loading = false;
+			success = false;
+			return success;
+		}
+
+		if (
+			title !== '' &&
+			desc !== '' &&
+			content !== '' &&
+			Object.keys(categories).filter((category) => categories[category]).length > 0 &&
+			!$models.includes(tagName)
+		) {
+			const res = await createModel(localStorage.token, tagName, content);
+
+			if (res) {
+				const reader = res.body
+					.pipeThrough(new TextDecoderStream())
+					.pipeThrough(splitStream('\n'))
+					.getReader();
+
+				while (true) {
+					const { value, done } = await reader.read();
+					if (done) break;
+
+					try {
+						let lines = value.split('\n');
+
+						for (const line of lines) {
+							if (line !== '') {
+								console.log(line);
+								let data = JSON.parse(line);
+								console.log(data);
+
+								if (data.error) {
+									throw data.error;
+								}
+								if (data.detail) {
+									throw data.detail;
+								}
+
+								if (data.status) {
+									if (
+										!data.digest &&
+										!data.status.includes('writing') &&
+										!data.status.includes('sha256')
+									) {
+										toast.success(data.status);
+
+										if (data.status === 'success') {
+											success = true;
+										}
+									} else {
+										if (data.digest) {
+											digest = data.digest;
+
+											if (data.completed) {
+												pullProgress = Math.round((data.completed / data.total) * 1000) / 10;
+											} else {
+												pullProgress = 100;
+											}
+										}
+									}
+								}
+							}
+						}
+					} catch (error) {
+						console.log(error);
+						toast.error(error);
+					}
+				}
+			}
+
+			if (success) {
+				await saveModelfile({
+					tagName: tagName,
+					imageUrl: imageUrl,
+					title: title,
+					desc: desc,
+					content: content,
+					suggestionPrompts: suggestions.filter((prompt) => prompt.content !== ''),
+					categories: Object.keys(categories).filter((category) => categories[category]),
+					user: modelfileCreator !== null ? modelfileCreator : undefined
+				});
+				await goto('/workspace/modelfiles');
+			}
+		}
+		loading = false;
+		success = false;
+	};
+
+	onMount(async () => {
+		window.addEventListener('message', async (event) => {
+			if (
+				![
+					'https://ollamahub.com',
+					'https://www.ollamahub.com',
+					'https://openwebui.com',
+					'https://www.openwebui.com',
+					'http://localhost:5173'
+				].includes(event.origin)
+			)
+				return;
+			const modelfile = JSON.parse(event.data);
+			console.log(modelfile);
+
+			imageUrl = modelfile.imageUrl;
+			title = modelfile.title;
+			await tick();
+			tagName = `${modelfile.user.username === 'hub' ? '' : `hub/`}${modelfile.user.username}/${
+				modelfile.tagName
+			}`;
+			desc = modelfile.desc;
+			content = modelfile.content;
+			suggestions =
+				modelfile.suggestionPrompts.length != 0
+					? modelfile.suggestionPrompts
+					: [
+							{
+								content: ''
+							}
+					  ];
+
+			modelfileCreator = {
+				username: modelfile.user.username,
+				name: modelfile.user.name
+			};
+			for (const category of modelfile.categories) {
+				categories[category.toLowerCase()] = true;
+			}
+		});
+
+		if (window.opener ?? false) {
+			window.opener.postMessage('loaded', '*');
+		}
+
+		if (sessionStorage.modelfile) {
+			const modelfile = JSON.parse(sessionStorage.modelfile);
+			console.log(modelfile);
+			imageUrl = modelfile.imageUrl;
+			title = modelfile.title;
+			await tick();
+			tagName = modelfile.tagName;
+			desc = modelfile.desc;
+			content = modelfile.content;
+			suggestions =
+				modelfile.suggestionPrompts.length != 0
+					? modelfile.suggestionPrompts
+					: [
+							{
+								content: ''
+							}
+					  ];
+
+			for (const category of modelfile.categories) {
+				categories[category.toLowerCase()] = true;
+			}
+
+			sessionStorage.removeItem('modelfile');
+		}
+	});
+</script>
+
+<div class="w-full max-h-full">
+	<input
+		bind:this={filesInputElement}
+		bind:files={inputFiles}
+		type="file"
+		hidden
+		accept="image/*"
+		on:change={() => {
+			let reader = new FileReader();
+			reader.onload = (event) => {
+				let originalImageUrl = `${event.target.result}`;
+
+				const img = new Image();
+				img.src = originalImageUrl;
+
+				img.onload = function () {
+					const canvas = document.createElement('canvas');
+					const ctx = canvas.getContext('2d');
+
+					// Calculate the aspect ratio of the image
+					const aspectRatio = img.width / img.height;
+
+					// Calculate the new width and height to fit within 100x100
+					let newWidth, newHeight;
+					if (aspectRatio > 1) {
+						newWidth = 100 * aspectRatio;
+						newHeight = 100;
+					} else {
+						newWidth = 100;
+						newHeight = 100 / aspectRatio;
+					}
+
+					// Set the canvas size
+					canvas.width = 100;
+					canvas.height = 100;
+
+					// Calculate the position to center the image
+					const offsetX = (100 - newWidth) / 2;
+					const offsetY = (100 - newHeight) / 2;
+
+					// Draw the image on the canvas
+					ctx.drawImage(img, offsetX, offsetY, newWidth, newHeight);
+
+					// Get the base64 representation of the compressed image
+					const compressedSrc = canvas.toDataURL('image/jpeg');
+
+					// Display the compressed image
+					imageUrl = compressedSrc;
+
+					inputFiles = null;
+				};
+			};
+
+			if (
+				inputFiles &&
+				inputFiles.length > 0 &&
+				['image/gif', 'image/jpeg', 'image/png'].includes(inputFiles[0]['type'])
+			) {
+				reader.readAsDataURL(inputFiles[0]);
+			} else {
+				console.log(`Unsupported File Type '${inputFiles[0]['type']}'.`);
+				inputFiles = null;
+			}
+		}}
+	/>
+
+	<button
+		class="flex space-x-1"
+		on:click={() => {
+			history.back();
+		}}
+	>
+		<div class=" self-center">
+			<svg
+				xmlns="http://www.w3.org/2000/svg"
+				viewBox="0 0 20 20"
+				fill="currentColor"
+				class="w-4 h-4"
+			>
+				<path
+					fill-rule="evenodd"
+					d="M17 10a.75.75 0 01-.75.75H5.612l4.158 3.96a.75.75 0 11-1.04 1.08l-5.5-5.25a.75.75 0 010-1.08l5.5-5.25a.75.75 0 111.04 1.08L5.612 9.25H16.25A.75.75 0 0117 10z"
+					clip-rule="evenodd"
+				/>
+			</svg>
+		</div>
+		<div class=" self-center font-medium text-sm">{$i18n.t('Back')}</div>
+	</button>
+	<!-- <hr class="my-3 dark:border-gray-700" /> -->
+
+	<form
+		class="flex flex-col max-w-2xl mx-auto mt-4 mb-10"
+		on:submit|preventDefault={() => {
+			submitHandler();
+		}}
+	>
+		<div class="flex justify-center my-4">
+			<div class="self-center">
+				<button
+					class=" {imageUrl
+						? ''
+						: 'p-6'} rounded-full dark:bg-gray-700 border border-dashed border-gray-200"
+					type="button"
+					on:click={() => {
+						filesInputElement.click();
+					}}
+				>
+					{#if imageUrl}
+						<img
+							src={imageUrl}
+							alt="modelfile profile"
+							class=" rounded-full w-20 h-20 object-cover"
+						/>
+					{:else}
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							viewBox="0 0 24 24"
+							fill="currentColor"
+							class="w-8"
+						>
+							<path
+								fill-rule="evenodd"
+								d="M12 3.75a.75.75 0 01.75.75v6.75h6.75a.75.75 0 010 1.5h-6.75v6.75a.75.75 0 01-1.5 0v-6.75H4.5a.75.75 0 010-1.5h6.75V4.5a.75.75 0 01.75-.75z"
+								clip-rule="evenodd"
+							/>
+						</svg>
+					{/if}
+				</button>
+			</div>
+		</div>
+
+		<div class="my-2 flex space-x-2">
+			<div class="flex-1">
+				<div class=" text-sm font-semibold mb-2">{$i18n.t('Name')}*</div>
+
+				<div>
+					<input
+						class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
+						placeholder={$i18n.t('Name your modelfile')}
+						bind:value={title}
+						required
+					/>
+				</div>
+			</div>
+
+			<div class="flex-1">
+				<div class=" text-sm font-semibold mb-2">{$i18n.t('Model Tag Name')}*</div>
+
+				<div>
+					<input
+						class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
+						placeholder={$i18n.t('Add a model tag name')}
+						bind:value={tagName}
+						required
+					/>
+				</div>
+			</div>
+		</div>
+
+		<div class="my-2">
+			<div class=" text-sm font-semibold mb-2">{$i18n.t('Description')}*</div>
+
+			<div>
+				<input
+					class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
+					placeholder={$i18n.t('Add a short description about what this modelfile does')}
+					bind:value={desc}
+					required
+				/>
+			</div>
+		</div>
+
+		<div class="my-2">
+			<div class="flex w-full justify-between">
+				<div class=" self-center text-sm font-semibold">{$i18n.t('Modelfile')}</div>
+
+				<button
+					class="p-1 px-3 text-xs flex rounded transition"
+					type="button"
+					on:click={() => {
+						raw = !raw;
+					}}
+				>
+					{#if raw}
+						<span class="ml-2 self-center"> {$i18n.t('Raw Format')} </span>
+					{:else}
+						<span class="ml-2 self-center"> {$i18n.t('Builder Mode')} </span>
+					{/if}
+				</button>
+			</div>
+
+			<!-- <div class=" text-sm font-semibold mb-2"></div> -->
+
+			{#if raw}
+				<div class="mt-2">
+					<div class=" text-xs font-semibold mb-2">{$i18n.t('Content')}*</div>
+
+					<div>
+						<textarea
+							class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
+							placeholder={`FROM llama2\nPARAMETER temperature 1\nSYSTEM """\nYou are Mario from Super Mario Bros, acting as an assistant.\n"""`}
+							rows="6"
+							bind:value={content}
+							required
+						/>
+					</div>
+
+					<div class="text-xs text-gray-400 dark:text-gray-500">
+						{$i18n.t('Not sure what to write? Switch to')}
+						<button
+							class="text-gray-500 dark:text-gray-300 font-medium cursor-pointer"
+							type="button"
+							on:click={() => {
+								raw = !raw;
+							}}>{$i18n.t('Builder Mode')}</button
+						>
+						or
+						<a
+							class=" text-gray-500 dark:text-gray-300 font-medium"
+							href="https://openwebui.com"
+							target="_blank"
+						>
+							{$i18n.t('Click here to check other modelfiles.')}
+						</a>
+					</div>
+				</div>
+			{:else}
+				<div class="my-2">
+					<div class=" text-xs font-semibold mb-2">{$i18n.t('From (Base Model)')}*</div>
+
+					<div>
+						<input
+							class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
+							placeholder="Write a modelfile base model name (e.g. llama2, mistral)"
+							bind:value={model}
+							required
+						/>
+					</div>
+
+					<div class="mt-1 text-xs text-gray-400 dark:text-gray-500">
+						{$i18n.t('To access the available model names for downloading,')}
+						<a
+							class=" text-gray-500 dark:text-gray-300 font-medium"
+							href="https://ollama.com/library"
+							target="_blank">{$i18n.t('click here.')}</a
+						>
+					</div>
+				</div>
+
+				<div class="my-1">
+					<div class=" text-xs font-semibold mb-2">{$i18n.t('System Prompt')}</div>
+
+					<div>
+						<textarea
+							class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg -mb-1"
+							placeholder={`Write your modelfile system prompt content here\ne.g.) You are Mario from Super Mario Bros, acting as an assistant.`}
+							rows="4"
+							bind:value={system}
+						/>
+					</div>
+				</div>
+
+				<div class="flex w-full justify-between">
+					<div class=" self-center text-sm font-semibold">
+						{$i18n.t('Modelfile Advanced Settings')}
+					</div>
+
+					<button
+						class="p-1 px-3 text-xs flex rounded transition"
+						type="button"
+						on:click={() => {
+							advanced = !advanced;
+						}}
+					>
+						{#if advanced}
+							<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
+						{:else}
+							<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+						{/if}
+					</button>
+				</div>
+
+				{#if advanced}
+					<div class="my-2">
+						<div class=" text-xs font-semibold mb-2">{$i18n.t('Template')}</div>
+
+						<div>
+							<textarea
+								class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg -mb-1"
+								placeholder="Write your modelfile template content here"
+								rows="4"
+								bind:value={template}
+							/>
+						</div>
+					</div>
+
+					<div class="my-2">
+						<div class=" text-xs font-semibold mb-2">{$i18n.t('Parameters')}</div>
+
+						<div>
+							<AdvancedParams bind:options />
+						</div>
+					</div>
+				{/if}
+			{/if}
+		</div>
+
+		<div class="my-2">
+			<div class="flex w-full justify-between mb-2">
+				<div class=" self-center text-sm font-semibold">{$i18n.t('Prompt suggestions')}</div>
+
+				<button
+					class="p-1 px-3 text-xs flex rounded transition"
+					type="button"
+					on:click={() => {
+						if (suggestions.length === 0 || suggestions.at(-1).content !== '') {
+							suggestions = [...suggestions, { content: '' }];
+						}
+					}}
+				>
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						viewBox="0 0 20 20"
+						fill="currentColor"
+						class="w-4 h-4"
+					>
+						<path
+							d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z"
+						/>
+					</svg>
+				</button>
+			</div>
+			<div class="flex flex-col space-y-1">
+				{#each suggestions as prompt, promptIdx}
+					<div class=" flex border dark:border-gray-600 rounded-lg">
+						<input
+							class="px-3 py-1.5 text-sm w-full bg-transparent outline-none border-r dark:border-gray-600"
+							placeholder={$i18n.t('Write a prompt suggestion (e.g. Who are you?)')}
+							bind:value={prompt.content}
+						/>
+
+						<button
+							class="px-2"
+							type="button"
+							on:click={() => {
+								suggestions.splice(promptIdx, 1);
+								suggestions = suggestions;
+							}}
+						>
+							<svg
+								xmlns="http://www.w3.org/2000/svg"
+								viewBox="0 0 20 20"
+								fill="currentColor"
+								class="w-4 h-4"
+							>
+								<path
+									d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
+								/>
+							</svg>
+						</button>
+					</div>
+				{/each}
+			</div>
+		</div>
+
+		<div class="my-2">
+			<div class=" text-sm font-semibold mb-2">{$i18n.t('Categories')}</div>
+
+			<div class="grid grid-cols-4">
+				{#each Object.keys(categories) as category}
+					<div class="flex space-x-2 text-sm">
+						<input type="checkbox" bind:checked={categories[category]} />
+						<div class="capitalize">{category}</div>
+					</div>
+				{/each}
+			</div>
+		</div>
+
+		{#if pullProgress !== null}
+			<div class="my-2">
+				<div class=" text-sm font-semibold mb-2">{$i18n.t('Pull Progress')}</div>
+				<div class="w-full rounded-full dark:bg-gray-800">
+					<div
+						class="dark:bg-gray-600 bg-gray-500 text-xs font-medium text-gray-100 text-center p-0.5 leading-none rounded-full"
+						style="width: {Math.max(15, pullProgress ?? 0)}%"
+					>
+						{pullProgress ?? 0}%
+					</div>
+				</div>
+				<div class="mt-1 text-xs dark:text-gray-500" style="font-size: 0.5rem;">
+					{digest}
+				</div>
+			</div>
+		{/if}
+
+		<div class="my-2 flex justify-end">
+			<button
+				class=" text-sm px-3 py-2 transition rounded-xl {loading
+					? ' cursor-not-allowed bg-gray-100 dark:bg-gray-800'
+					: ' bg-gray-50 hover:bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-800'} flex"
+				type="submit"
+				disabled={loading}
+			>
+				<div class=" self-center font-medium">{$i18n.t('Save & Create')}</div>
+
+				{#if loading}
+					<div class="ml-1.5 self-center">
+						<svg
+							class=" w-4 h-4"
+							viewBox="0 0 24 24"
+							fill="currentColor"
+							xmlns="http://www.w3.org/2000/svg"
+							><style>
+								.spinner_ajPY {
+									transform-origin: center;
+									animation: spinner_AtaB 0.75s infinite linear;
+								}
+								@keyframes spinner_AtaB {
+									100% {
+										transform: rotate(360deg);
+									}
+								}
+							</style><path
+								d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
+								opacity=".25"
+							/><path
+								d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
+								class="spinner_ajPY"
+							/></svg
+						>
+					</div>
+				{/if}
+			</button>
+		</div>
+	</form>
+</div>

+ 507 - 0
src/routes/(app)/workspace/modelfiles/edit/+page.svelte

@@ -0,0 +1,507 @@
+<script>
+	import { v4 as uuidv4 } from 'uuid';
+	import { toast } from 'svelte-sonner';
+	import { goto } from '$app/navigation';
+
+	import { onMount, getContext } from 'svelte';
+	import { page } from '$app/stores';
+
+	import { settings, user, config, modelfiles } from '$lib/stores';
+	import { splitStream } from '$lib/utils';
+
+	import { createModel } from '$lib/apis/ollama';
+	import { getModelfiles, updateModelfileByTagName } from '$lib/apis/modelfiles';
+
+	import AdvancedParams from '$lib/components/chat/Settings/Advanced/AdvancedParams.svelte';
+
+	const i18n = getContext('i18n');
+
+	let loading = false;
+
+	let filesInputElement;
+	let inputFiles;
+	let imageUrl = null;
+	let digest = '';
+	let pullProgress = null;
+	let success = false;
+
+	let modelfile = null;
+	// ///////////
+	// Modelfile
+	// ///////////
+
+	let title = '';
+	let tagName = '';
+	let desc = '';
+
+	// Raw Mode
+	let content = '';
+
+	let suggestions = [
+		{
+			content: ''
+		}
+	];
+
+	let categories = {
+		character: false,
+		assistant: false,
+		writing: false,
+		productivity: false,
+		programming: false,
+		'data analysis': false,
+		lifestyle: false,
+		education: false,
+		business: false
+	};
+
+	onMount(() => {
+		tagName = $page.url.searchParams.get('tag');
+
+		if (tagName) {
+			modelfile = $modelfiles.filter((modelfile) => modelfile.tagName === tagName)[0];
+
+			console.log(modelfile);
+
+			imageUrl = modelfile.imageUrl;
+			title = modelfile.title;
+			desc = modelfile.desc;
+			content = modelfile.content;
+			suggestions =
+				modelfile.suggestionPrompts.length != 0
+					? modelfile.suggestionPrompts
+					: [
+							{
+								content: ''
+							}
+					  ];
+
+			for (const category of modelfile.categories) {
+				categories[category.toLowerCase()] = true;
+			}
+		} else {
+			goto('/workspace/modelfiles');
+		}
+	});
+
+	const updateModelfile = async (modelfile) => {
+		await updateModelfileByTagName(localStorage.token, modelfile.tagName, modelfile);
+		await modelfiles.set(await getModelfiles(localStorage.token));
+	};
+
+	const updateHandler = async () => {
+		loading = true;
+
+		if (Object.keys(categories).filter((category) => categories[category]).length == 0) {
+			toast.error(
+				'Uh-oh! It looks like you missed selecting a category. Please choose one to complete your modelfile.'
+			);
+		}
+
+		if (
+			title !== '' &&
+			desc !== '' &&
+			content !== '' &&
+			Object.keys(categories).filter((category) => categories[category]).length > 0
+		) {
+			const res = await createModel(localStorage.token, tagName, content);
+
+			if (res) {
+				const reader = res.body
+					.pipeThrough(new TextDecoderStream())
+					.pipeThrough(splitStream('\n'))
+					.getReader();
+
+				while (true) {
+					const { value, done } = await reader.read();
+					if (done) break;
+
+					try {
+						let lines = value.split('\n');
+
+						for (const line of lines) {
+							if (line !== '') {
+								console.log(line);
+								let data = JSON.parse(line);
+								console.log(data);
+
+								if (data.error) {
+									throw data.error;
+								}
+								if (data.detail) {
+									throw data.detail;
+								}
+
+								if (data.status) {
+									if (
+										!data.digest &&
+										!data.status.includes('writing') &&
+										!data.status.includes('sha256')
+									) {
+										toast.success(data.status);
+
+										if (data.status === 'success') {
+											success = true;
+										}
+									} else {
+										if (data.digest) {
+											digest = data.digest;
+
+											if (data.completed) {
+												pullProgress = Math.round((data.completed / data.total) * 1000) / 10;
+											} else {
+												pullProgress = 100;
+											}
+										}
+									}
+								}
+							}
+						}
+					} catch (error) {
+						console.log(error);
+						toast.error(error);
+					}
+				}
+			}
+
+			if (success) {
+				await updateModelfile({
+					tagName: tagName,
+					imageUrl: imageUrl,
+					title: title,
+					desc: desc,
+					content: content,
+					suggestionPrompts: suggestions.filter((prompt) => prompt.content !== ''),
+					categories: Object.keys(categories).filter((category) => categories[category])
+				});
+				await goto('/workspace/modelfiles');
+			}
+		}
+		loading = false;
+		success = false;
+	};
+</script>
+
+<div class="w-full max-h-full">
+	<input
+		bind:this={filesInputElement}
+		bind:files={inputFiles}
+		type="file"
+		hidden
+		accept="image/*"
+		on:change={() => {
+			let reader = new FileReader();
+			reader.onload = (event) => {
+				let originalImageUrl = `${event.target.result}`;
+
+				const img = new Image();
+				img.src = originalImageUrl;
+
+				img.onload = function () {
+					const canvas = document.createElement('canvas');
+					const ctx = canvas.getContext('2d');
+
+					// Calculate the aspect ratio of the image
+					const aspectRatio = img.width / img.height;
+
+					// Calculate the new width and height to fit within 100x100
+					let newWidth, newHeight;
+					if (aspectRatio > 1) {
+						newWidth = 100 * aspectRatio;
+						newHeight = 100;
+					} else {
+						newWidth = 100;
+						newHeight = 100 / aspectRatio;
+					}
+
+					// Set the canvas size
+					canvas.width = 100;
+					canvas.height = 100;
+
+					// Calculate the position to center the image
+					const offsetX = (100 - newWidth) / 2;
+					const offsetY = (100 - newHeight) / 2;
+
+					// Draw the image on the canvas
+					ctx.drawImage(img, offsetX, offsetY, newWidth, newHeight);
+
+					// Get the base64 representation of the compressed image
+					const compressedSrc = canvas.toDataURL('image/jpeg');
+
+					// Display the compressed image
+					imageUrl = compressedSrc;
+
+					inputFiles = null;
+				};
+			};
+
+			if (
+				inputFiles &&
+				inputFiles.length > 0 &&
+				['image/gif', 'image/jpeg', 'image/png'].includes(inputFiles[0]['type'])
+			) {
+				reader.readAsDataURL(inputFiles[0]);
+			} else {
+				console.log(`Unsupported File Type '${inputFiles[0]['type']}'.`);
+				inputFiles = null;
+			}
+		}}
+	/>
+
+	<button
+		class="flex space-x-1"
+		on:click={() => {
+			history.back();
+		}}
+	>
+		<div class=" self-center">
+			<svg
+				xmlns="http://www.w3.org/2000/svg"
+				viewBox="0 0 20 20"
+				fill="currentColor"
+				class="w-4 h-4"
+			>
+				<path
+					fill-rule="evenodd"
+					d="M17 10a.75.75 0 01-.75.75H5.612l4.158 3.96a.75.75 0 11-1.04 1.08l-5.5-5.25a.75.75 0 010-1.08l5.5-5.25a.75.75 0 111.04 1.08L5.612 9.25H16.25A.75.75 0 0117 10z"
+					clip-rule="evenodd"
+				/>
+			</svg>
+		</div>
+		<div class=" self-center font-medium text-sm">{$i18n.t('Back')}</div>
+	</button>
+	<form
+		class="flex flex-col max-w-2xl mx-auto mt-4 mb-10"
+		on:submit|preventDefault={() => {
+			updateHandler();
+		}}
+	>
+		<div class="flex justify-center my-4">
+			<div class="self-center">
+				<button
+					class=" {imageUrl
+						? ''
+						: 'p-6'} rounded-full dark:bg-gray-700 border border-dashed border-gray-200"
+					type="button"
+					on:click={() => {
+						filesInputElement.click();
+					}}
+				>
+					{#if imageUrl}
+						<img
+							src={imageUrl}
+							alt="modelfile profile"
+							class=" rounded-full w-20 h-20 object-cover"
+						/>
+					{:else}
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							viewBox="0 0 24 24"
+							fill="currentColor"
+							class="w-8"
+						>
+							<path
+								fill-rule="evenodd"
+								d="M12 3.75a.75.75 0 01.75.75v6.75h6.75a.75.75 0 010 1.5h-6.75v6.75a.75.75 0 01-1.5 0v-6.75H4.5a.75.75 0 010-1.5h6.75V4.5a.75.75 0 01.75-.75z"
+								clip-rule="evenodd"
+							/>
+						</svg>
+					{/if}
+				</button>
+			</div>
+		</div>
+
+		<div class="my-2 flex space-x-2">
+			<div class="flex-1">
+				<div class=" text-sm font-semibold mb-2">{$i18n.t('Name')}*</div>
+
+				<div>
+					<input
+						class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
+						placeholder={$i18n.t('Name your modelfile')}
+						bind:value={title}
+						required
+					/>
+				</div>
+			</div>
+
+			<div class="flex-1">
+				<div class=" text-sm font-semibold mb-2">{$i18n.t('Model Tag Name')}*</div>
+
+				<div>
+					<input
+						class="px-3 py-1.5 text-sm w-full bg-transparent disabled:text-gray-500 border dark:border-gray-600 outline-none rounded-lg"
+						placeholder={$i18n.t('Add a model tag name')}
+						value={tagName}
+						disabled
+						required
+					/>
+				</div>
+			</div>
+		</div>
+
+		<div class="my-2">
+			<div class=" text-sm font-semibold mb-2">{$i18n.t('Description')}*</div>
+
+			<div>
+				<input
+					class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
+					placeholder={$i18n.t('Add a short description about what this modelfile does')}
+					bind:value={desc}
+					required
+				/>
+			</div>
+		</div>
+
+		<div class="my-2">
+			<div class="flex w-full justify-between">
+				<div class=" self-center text-sm font-semibold">{$i18n.t('Modelfile')}</div>
+			</div>
+
+			<!-- <div class=" text-sm font-semibold mb-2"></div> -->
+
+			<div class="mt-2">
+				<div class=" text-xs font-semibold mb-2">{$i18n.t('Content')}*</div>
+
+				<div>
+					<textarea
+						class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
+						placeholder={`FROM llama2\nPARAMETER temperature 1\nSYSTEM """\nYou are Mario from Super Mario Bros, acting as an assistant.\n"""`}
+						rows="6"
+						bind:value={content}
+						required
+					/>
+				</div>
+			</div>
+		</div>
+
+		<div class="my-2">
+			<div class="flex w-full justify-between mb-2">
+				<div class=" self-center text-sm font-semibold">{$i18n.t('Prompt suggestions')}</div>
+
+				<button
+					class="p-1 px-3 text-xs flex rounded transition"
+					type="button"
+					on:click={() => {
+						if (suggestions.length === 0 || suggestions.at(-1).content !== '') {
+							suggestions = [...suggestions, { content: '' }];
+						}
+					}}
+				>
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						viewBox="0 0 20 20"
+						fill="currentColor"
+						class="w-4 h-4"
+					>
+						<path
+							d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z"
+						/>
+					</svg>
+				</button>
+			</div>
+			<div class="flex flex-col space-y-1">
+				{#each suggestions as prompt, promptIdx}
+					<div class=" flex border dark:border-gray-600 rounded-lg">
+						<input
+							class="px-3 py-1.5 text-sm w-full bg-transparent outline-none border-r dark:border-gray-600"
+							placeholder={$i18n.t('Write a prompt suggestion (e.g. Who are you?)')}
+							bind:value={prompt.content}
+						/>
+
+						<button
+							class="px-2"
+							type="button"
+							on:click={() => {
+								suggestions.splice(promptIdx, 1);
+								suggestions = suggestions;
+							}}
+						>
+							<svg
+								xmlns="http://www.w3.org/2000/svg"
+								viewBox="0 0 20 20"
+								fill="currentColor"
+								class="w-4 h-4"
+							>
+								<path
+									d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
+								/>
+							</svg>
+						</button>
+					</div>
+				{/each}
+			</div>
+		</div>
+
+		<div class="my-2">
+			<div class=" text-sm font-semibold mb-2">{$i18n.t('Categories')}</div>
+
+			<div class="grid grid-cols-4">
+				{#each Object.keys(categories) as category}
+					<div class="flex space-x-2 text-sm">
+						<input type="checkbox" bind:checked={categories[category]} />
+
+						<div class=" capitalize">{category}</div>
+					</div>
+				{/each}
+			</div>
+		</div>
+
+		{#if pullProgress !== null}
+			<div class="my-2">
+				<div class=" text-sm font-semibold mb-2">{$i18n.t('Pull Progress')}</div>
+				<div class="w-full rounded-full dark:bg-gray-800">
+					<div
+						class="dark:bg-gray-600 text-xs font-medium text-blue-100 text-center p-0.5 leading-none rounded-full"
+						style="width: {Math.max(15, pullProgress ?? 0)}%"
+					>
+						{pullProgress ?? 0}%
+					</div>
+				</div>
+				<div class="mt-1 text-xs dark:text-gray-500" style="font-size: 0.5rem;">
+					{digest}
+				</div>
+			</div>
+		{/if}
+
+		<div class="my-2 flex justify-end">
+			<button
+				class=" text-sm px-3 py-2 transition rounded-xl {loading
+					? ' cursor-not-allowed bg-gray-100 dark:bg-gray-800'
+					: ' bg-gray-50 hover:bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-800'} flex"
+				type="submit"
+				disabled={loading}
+			>
+				<div class=" self-center font-medium">{$i18n.t('Save & Update')}</div>
+
+				{#if loading}
+					<div class="ml-1.5 self-center">
+						<svg
+							class=" w-4 h-4"
+							viewBox="0 0 24 24"
+							fill="currentColor"
+							xmlns="http://www.w3.org/2000/svg"
+							><style>
+								.spinner_ajPY {
+									transform-origin: center;
+									animation: spinner_AtaB 0.75s infinite linear;
+								}
+								@keyframes spinner_AtaB {
+									100% {
+										transform: rotate(360deg);
+									}
+								}
+							</style><path
+								d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
+								opacity=".25"
+							/><path
+								d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
+								class="spinner_ajPY"
+							/></svg
+						>
+					</div>
+				{/if}
+			</button>
+		</div>
+	</form>
+</div>

+ 5 - 0
src/routes/(app)/workspace/playground/+page.svelte

@@ -0,0 +1,5 @@
+<script>
+	import Playground from '$lib/components/workspace/Playground.svelte';
+</script>
+
+<Playground />

+ 5 - 0
src/routes/(app)/workspace/prompts/+page.svelte

@@ -0,0 +1,5 @@
+<script>
+	import Prompts from '$lib/components/workspace/Prompts.svelte';
+</script>
+
+<Prompts />

+ 245 - 0
src/routes/(app)/workspace/prompts/create/+page.svelte

@@ -0,0 +1,245 @@
+<script>
+	import { toast } from 'svelte-sonner';
+
+	import { goto } from '$app/navigation';
+	import { prompts } from '$lib/stores';
+	import { onMount, tick, getContext } from 'svelte';
+
+	import { createNewPrompt, getPrompts } from '$lib/apis/prompts';
+
+	const i18n = getContext('i18n');
+
+	let loading = false;
+
+	// ///////////
+	// Prompt
+	// ///////////
+
+	let title = '';
+	let command = '';
+	let content = '';
+
+	$: command = title !== '' ? `${title.replace(/\s+/g, '-').toLowerCase()}` : '';
+
+	const submitHandler = async () => {
+		loading = true;
+
+		if (validateCommandString(command)) {
+			const prompt = await createNewPrompt(localStorage.token, command, title, content).catch(
+				(error) => {
+					toast.error(error);
+
+					return null;
+				}
+			);
+
+			if (prompt) {
+				await prompts.set(await getPrompts(localStorage.token));
+				await goto('/workspace/prompts');
+			}
+		} else {
+			toast.error(
+				$i18n.t('Only alphanumeric characters and hyphens are allowed in the command string.')
+			);
+		}
+
+		loading = false;
+	};
+
+	const validateCommandString = (inputString) => {
+		// Regular expression to match only alphanumeric characters and hyphen
+		const regex = /^[a-zA-Z0-9-]+$/;
+
+		// Test the input string against the regular expression
+		return regex.test(inputString);
+	};
+
+	onMount(async () => {
+		window.addEventListener('message', async (event) => {
+			if (
+				![
+					'https://ollamahub.com',
+					'https://www.ollamahub.com',
+					'https://openwebui.com',
+					'https://www.openwebui.com',
+					'http://localhost:5173'
+				].includes(event.origin)
+			)
+				return;
+			const prompt = JSON.parse(event.data);
+			console.log(prompt);
+
+			title = prompt.title;
+			await tick();
+			content = prompt.content;
+			command = prompt.command;
+		});
+
+		if (window.opener ?? false) {
+			window.opener.postMessage('loaded', '*');
+		}
+
+		if (sessionStorage.prompt) {
+			const prompt = JSON.parse(sessionStorage.prompt);
+
+			console.log(prompt);
+			title = prompt.title;
+			await tick();
+			content = prompt.content;
+			command = prompt.command.at(0) === '/' ? prompt.command.slice(1) : prompt.command;
+
+			sessionStorage.removeItem('prompt');
+		}
+	});
+</script>
+
+<div class="w-full max-h-full">
+	<button
+		class="flex space-x-1"
+		on:click={() => {
+			history.back();
+		}}
+	>
+		<div class=" self-center">
+			<svg
+				xmlns="http://www.w3.org/2000/svg"
+				viewBox="0 0 20 20"
+				fill="currentColor"
+				class="w-4 h-4"
+			>
+				<path
+					fill-rule="evenodd"
+					d="M17 10a.75.75 0 01-.75.75H5.612l4.158 3.96a.75.75 0 11-1.04 1.08l-5.5-5.25a.75.75 0 010-1.08l5.5-5.25a.75.75 0 111.04 1.08L5.612 9.25H16.25A.75.75 0 0117 10z"
+					clip-rule="evenodd"
+				/>
+			</svg>
+		</div>
+		<div class=" self-center font-medium text-sm">{$i18n.t('Back')}</div>
+	</button>
+
+	<form
+		class="flex flex-col max-w-2xl mx-auto mt-4 mb-10"
+		on:submit|preventDefault={() => {
+			submitHandler();
+		}}
+	>
+		<div class="my-2">
+			<div class=" text-sm font-semibold mb-2">{$i18n.t('Title')}*</div>
+
+			<div>
+				<input
+					class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
+					placeholder={$i18n.t('Add a short title for this prompt')}
+					bind:value={title}
+					required
+				/>
+			</div>
+		</div>
+
+		<div class="my-2">
+			<div class=" text-sm font-semibold mb-2">{$i18n.t('Command')}*</div>
+
+			<div class="flex items-center mb-1">
+				<div
+					class="bg-gray-200 dark:bg-gray-600 font-bold px-3 py-1 border border-r-0 dark:border-gray-600 rounded-l-lg"
+				>
+					/
+				</div>
+				<input
+					class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-r-lg"
+					placeholder={$i18n.t('short-summary')}
+					bind:value={command}
+					required
+				/>
+			</div>
+
+			<div class="text-xs text-gray-400 dark:text-gray-500">
+				{$i18n.t('Only')}
+				<span class=" text-gray-600 dark:text-gray-300 font-medium"
+					>{$i18n.t('alphanumeric characters and hyphens')}</span
+				>
+				{$i18n.t('are allowed - Activate this command by typing')}&nbsp;"<span
+					class=" text-gray-600 dark:text-gray-300 font-medium"
+				>
+					/{command}
+				</span>" &nbsp;
+				{$i18n.t('to chat input.')}
+			</div>
+		</div>
+
+		<div class="my-2">
+			<div class="flex w-full justify-between">
+				<div class=" self-center text-sm font-semibold">{$i18n.t('Prompt Content')}*</div>
+			</div>
+
+			<div class="mt-2">
+				<div>
+					<textarea
+						class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
+						placeholder={$i18n.t('Write a summary in 50 words that summarizes [topic or keyword].')}
+						rows="6"
+						bind:value={content}
+						required
+					/>
+				</div>
+
+				<div class="text-xs text-gray-400 dark:text-gray-500">
+					ⓘ {$i18n.t('Format your variables using square brackets like this:')}&nbsp;<span
+						class=" text-gray-600 dark:text-gray-300 font-medium">[{$i18n.t('variable')}]</span
+					>.
+					{$i18n.t('Make sure to enclose them with')}
+					<span class=" text-gray-600 dark:text-gray-300 font-medium">'['</span>
+					{$i18n.t('and')}
+					<span class=" text-gray-600 dark:text-gray-300 font-medium">']'</span>.
+				</div>
+
+				<div class="text-xs text-gray-400 dark:text-gray-500">
+					{$i18n.t('Utilize')}<span class=" text-gray-600 dark:text-gray-300 font-medium">
+						{` {{CLIPBOARD}}`}</span
+					>
+					{$i18n.t('variable to have them replaced with clipboard content.')}
+				</div>
+			</div>
+		</div>
+
+		<div class="my-2 flex justify-end">
+			<button
+				class=" text-sm px-3 py-2 transition rounded-xl {loading
+					? ' cursor-not-allowed bg-gray-100 dark:bg-gray-800'
+					: ' bg-gray-50 hover:bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-800'} flex"
+				type="submit"
+				disabled={loading}
+			>
+				<div class=" self-center font-medium">{$i18n.t('Save & Create')}</div>
+
+				{#if loading}
+					<div class="ml-1.5 self-center">
+						<svg
+							class=" w-4 h-4"
+							viewBox="0 0 24 24"
+							fill="currentColor"
+							xmlns="http://www.w3.org/2000/svg"
+							><style>
+								.spinner_ajPY {
+									transform-origin: center;
+									animation: spinner_AtaB 0.75s infinite linear;
+								}
+								@keyframes spinner_AtaB {
+									100% {
+										transform: rotate(360deg);
+									}
+								}
+							</style><path
+								d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
+								opacity=".25"
+							/><path
+								d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
+								class="spinner_ajPY"
+							/></svg
+						>
+					</div>
+				{/if}
+			</button>
+		</div>
+	</form>
+</div>

+ 228 - 0
src/routes/(app)/workspace/prompts/edit/+page.svelte

@@ -0,0 +1,228 @@
+<script>
+	import { toast } from 'svelte-sonner';
+
+	import { goto } from '$app/navigation';
+	import { prompts } from '$lib/stores';
+	import { onMount, tick, getContext } from 'svelte';
+
+	const i18n = getContext('i18n');
+
+	import { getPrompts, updatePromptByCommand } from '$lib/apis/prompts';
+	import { page } from '$app/stores';
+
+	let loading = false;
+
+	// ///////////
+	// Prompt
+	// ///////////
+
+	let title = '';
+	let command = '';
+	let content = '';
+
+	const updateHandler = async () => {
+		loading = true;
+
+		if (validateCommandString(command)) {
+			const prompt = await updatePromptByCommand(localStorage.token, command, title, content).catch(
+				(error) => {
+					toast.error(error);
+					return null;
+				}
+			);
+
+			if (prompt) {
+				await prompts.set(await getPrompts(localStorage.token));
+				await goto('/workspace/prompts');
+			}
+		} else {
+			toast.error(
+				$i18n.t('Only alphanumeric characters and hyphens are allowed in the command string.')
+			);
+		}
+
+		loading = false;
+	};
+
+	const validateCommandString = (inputString) => {
+		// Regular expression to match only alphanumeric characters and hyphen
+		const regex = /^[a-zA-Z0-9-]+$/;
+
+		// Test the input string against the regular expression
+		return regex.test(inputString);
+	};
+
+	onMount(async () => {
+		command = $page.url.searchParams.get('command');
+		if (command) {
+			const prompt = $prompts.filter((prompt) => prompt.command === command).at(0);
+
+			if (prompt) {
+				console.log(prompt);
+
+				console.log(prompt.command);
+
+				title = prompt.title;
+				await tick();
+				command = prompt.command.slice(1);
+				content = prompt.content;
+			} else {
+				goto('/workspace/prompts');
+			}
+		} else {
+			goto('/workspace/prompts');
+		}
+	});
+</script>
+
+<div class="w-full max-h-full">
+	<button
+		class="flex space-x-1"
+		on:click={() => {
+			history.back();
+		}}
+	>
+		<div class=" self-center">
+			<svg
+				xmlns="http://www.w3.org/2000/svg"
+				viewBox="0 0 20 20"
+				fill="currentColor"
+				class="w-4 h-4"
+			>
+				<path
+					fill-rule="evenodd"
+					d="M17 10a.75.75 0 01-.75.75H5.612l4.158 3.96a.75.75 0 11-1.04 1.08l-5.5-5.25a.75.75 0 010-1.08l5.5-5.25a.75.75 0 111.04 1.08L5.612 9.25H16.25A.75.75 0 0117 10z"
+					clip-rule="evenodd"
+				/>
+			</svg>
+		</div>
+		<div class=" self-center font-medium text-sm">{$i18n.t('Back')}</div>
+	</button>
+
+	<form
+		class="flex flex-col max-w-2xl mx-auto mt-4 mb-10"
+		on:submit|preventDefault={() => {
+			updateHandler();
+		}}
+	>
+		<div class="my-2">
+			<div class=" text-sm font-semibold mb-2">{$i18n.t('Title')}*</div>
+
+			<div>
+				<input
+					class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
+					placeholder={$i18n.t('Add a short title for this prompt')}
+					bind:value={title}
+					required
+				/>
+			</div>
+		</div>
+
+		<div class="my-2">
+			<div class=" text-sm font-semibold mb-2">{$i18n.t('Command')}*</div>
+
+			<div class="flex items-center mb-1">
+				<div
+					class="bg-gray-200 dark:bg-gray-600 font-bold px-3 py-1 border border-r-0 dark:border-gray-600 rounded-l-lg"
+				>
+					/
+				</div>
+				<input
+					class="px-3 py-1.5 text-sm w-full bg-transparent border disabled:text-gray-500 dark:border-gray-600 outline-none rounded-r-lg"
+					placeholder="short-summary"
+					bind:value={command}
+					disabled
+					required
+				/>
+			</div>
+
+			<div class="text-xs text-gray-400 dark:text-gray-500">
+				{$i18n.t('Only')}
+				<span class=" text-gray-600 dark:text-gray-300 font-medium"
+					>{$i18n.t('alphanumeric characters and hyphens')}</span
+				>
+				{$i18n.t('are allowed - Activate this command by typing')}&nbsp;"<span
+					class=" text-gray-600 dark:text-gray-300 font-medium"
+				>
+					/{command}
+				</span>" &nbsp;
+				{$i18n.t('to chat input.')}
+			</div>
+		</div>
+
+		<div class="my-2">
+			<div class="flex w-full justify-between">
+				<div class=" self-center text-sm font-semibold">{$i18n.t('Prompt Content')}*</div>
+			</div>
+
+			<div class="mt-2">
+				<div>
+					<textarea
+						class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
+						placeholder={$i18n.t(`Write a summary in 50 words that summarizes [topic or keyword].`)}
+						rows="6"
+						bind:value={content}
+						required
+					/>
+				</div>
+
+				<div class="text-xs text-gray-400 dark:text-gray-500">
+					ⓘ {$i18n.t('Format your variables using square brackets like this:')}&nbsp;<span
+						class=" text-gray-600 dark:text-gray-300 font-medium">[{$i18n.t('variable')}]</span
+					>.
+					{$i18n.t('Make sure to enclose them with')}
+					<span class=" text-gray-600 dark:text-gray-300 font-medium">'['</span>
+					{$i18n.t('and')}
+					<span class=" text-gray-600 dark:text-gray-300 font-medium">']'</span>.
+				</div>
+
+				<div class="text-xs text-gray-400 dark:text-gray-500">
+					{$i18n.t('Utilize')}<span class=" text-gray-600 dark:text-gray-300 font-medium">
+						{` {{CLIPBOARD}}`}</span
+					>
+					{$i18n.t('variable to have them replaced with clipboard content.')}
+				</div>
+			</div>
+		</div>
+
+		<div class="my-2 flex justify-end">
+			<button
+				class=" text-sm px-3 py-2 transition rounded-xl {loading
+					? ' cursor-not-allowed bg-gray-100 dark:bg-gray-800'
+					: ' bg-gray-50 hover:bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-800'} flex"
+				type="submit"
+				disabled={loading}
+			>
+				<div class=" self-center font-medium">{$i18n.t('Save & Update')}</div>
+
+				{#if loading}
+					<div class="ml-1.5 self-center">
+						<svg
+							class=" w-4 h-4"
+							viewBox="0 0 24 24"
+							fill="currentColor"
+							xmlns="http://www.w3.org/2000/svg"
+							><style>
+								.spinner_ajPY {
+									transform-origin: center;
+									animation: spinner_AtaB 0.75s infinite linear;
+								}
+								@keyframes spinner_AtaB {
+									100% {
+										transform: rotate(360deg);
+									}
+								}
+							</style><path
+								d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
+								opacity=".25"
+							/><path
+								d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
+								class="spinner_ajPY"
+							/></svg
+						>
+					</div>
+				{/if}
+			</button>
+		</div>
+	</form>
+</div>

+ 1 - 1
src/routes/+layout.svelte

@@ -18,7 +18,7 @@
 	setContext('i18n', i18n);
 	setContext('i18n', i18n);
 
 
 	let loaded = false;
 	let loaded = false;
-	const BREAKPOINT = 1024;
+	const BREAKPOINT = 768;
 
 
 	onMount(async () => {
 	onMount(async () => {
 		theme.set(localStorage.theme);
 		theme.set(localStorage.theme);