浏览代码

Merge remote-tracking branch 'origin/dev' into feat/backend-web-search

Jun Siang Cheah 11 月之前
父节点
当前提交
9021f068b8
共有 97 个文件被更改,包括 5814 次插入4431 次删除
  1. 18 0
      .github/workflows/format-build-frontend.yaml
  2. 29 28
      Dockerfile
  3. 5 0
      backend/main.py
  4. 33 0
      cypress/e2e/chat.cy.ts
  5. 2 6
      cypress/e2e/settings.cy.ts
  6. 990 6
      package-lock.json
  7. 4 2
      package.json
  8. 24 4
      src/app.css
  9. 1 1
      src/lib/components/ChangelogModal.svelte
  10. 2 2
      src/lib/components/admin/Settings/General.svelte
  11. 11 11
      src/lib/components/chat/MessageInput.svelte
  12. 1 1
      src/lib/components/chat/Messages/CitationsModal.svelte
  13. 1 1
      src/lib/components/chat/Messages/Name.svelte
  14. 2 2
      src/lib/components/chat/Messages/ProfileImage.svelte
  15. 38 28
      src/lib/components/chat/Messages/ResponseMessage.svelte
  16. 165 86
      src/lib/components/chat/Messages/UserMessage.svelte
  17. 3 3
      src/lib/components/chat/ModelSelector.svelte
  18. 12 11
      src/lib/components/chat/ModelSelector/Selector.svelte
  19. 1 1
      src/lib/components/chat/Settings/Account.svelte
  20. 1 1
      src/lib/components/chat/Settings/Advanced.svelte
  21. 2 2
      src/lib/components/chat/Settings/Advanced/AdvancedParams.svelte
  22. 2 2
      src/lib/components/chat/Settings/Audio.svelte
  23. 1 1
      src/lib/components/chat/Settings/Connections.svelte
  24. 2 2
      src/lib/components/chat/Settings/General.svelte
  25. 29 2
      src/lib/components/chat/Settings/Interface.svelte
  26. 27 1
      src/lib/components/chat/SettingsModal.svelte
  27. 15 1
      src/lib/components/chat/ShareChatModal.svelte
  28. 2 2
      src/lib/components/common/Modal.svelte
  29. 2 2
      src/lib/components/documents/Settings/General.svelte
  30. 1 1
      src/lib/components/documents/Settings/QueryParams.svelte
  31. 19 0
      src/lib/components/icons/MenuLines.svelte
  32. 65 38
      src/lib/components/layout/Navbar.svelte
  33. 62 88
      src/lib/components/layout/Navbar/Menu.svelte
  34. 112 128
      src/lib/components/layout/Sidebar.svelte
  35. 16 4
      src/lib/components/layout/Sidebar/ChatMenu.svelte
  36. 45 70
      src/lib/components/layout/Sidebar/UserMenu.svelte
  37. 611 0
      src/lib/components/workspace/Documents.svelte
  38. 409 0
      src/lib/components/workspace/Modelfiles.svelte
  39. 114 116
      src/lib/components/workspace/Playground.svelte
  40. 331 0
      src/lib/components/workspace/Prompts.svelte
  41. 2 2
      src/lib/constants.ts
  42. 98 99
      src/lib/i18n/locales/ar-BH/translation.json
  43. 5 6
      src/lib/i18n/locales/bg-BG/translation.json
  44. 5 6
      src/lib/i18n/locales/bn-BD/translation.json
  45. 5 6
      src/lib/i18n/locales/ca-ES/translation.json
  46. 5 6
      src/lib/i18n/locales/de-DE/translation.json
  47. 5 6
      src/lib/i18n/locales/dg-DG/translation.json
  48. 4 5
      src/lib/i18n/locales/en-GB/translation.json
  49. 4 5
      src/lib/i18n/locales/en-US/translation.json
  50. 5 6
      src/lib/i18n/locales/es-ES/translation.json
  51. 5 6
      src/lib/i18n/locales/fa-IR/translation.json
  52. 5 6
      src/lib/i18n/locales/fi-FI/translation.json
  53. 5 6
      src/lib/i18n/locales/fr-CA/translation.json
  54. 5 6
      src/lib/i18n/locales/fr-FR/translation.json
  55. 5 6
      src/lib/i18n/locales/he-IL/translation.json
  56. 5 6
      src/lib/i18n/locales/hi-IN/translation.json
  57. 142 143
      src/lib/i18n/locales/it-IT/translation.json
  58. 5 6
      src/lib/i18n/locales/ja-JP/translation.json
  59. 5 6
      src/lib/i18n/locales/ka-GE/translation.json
  60. 5 6
      src/lib/i18n/locales/ko-KR/translation.json
  61. 5 6
      src/lib/i18n/locales/nl-NL/translation.json
  62. 5 6
      src/lib/i18n/locales/pl-PL/translation.json
  63. 5 6
      src/lib/i18n/locales/pt-BR/translation.json
  64. 5 6
      src/lib/i18n/locales/pt-PT/translation.json
  65. 5 6
      src/lib/i18n/locales/ru-RU/translation.json
  66. 5 6
      src/lib/i18n/locales/sv-SE/translation.json
  67. 5 6
      src/lib/i18n/locales/tr-TR/translation.json
  68. 5 6
      src/lib/i18n/locales/uk-UA/translation.json
  69. 5 6
      src/lib/i18n/locales/vi-VN/translation.json
  70. 5 6
      src/lib/i18n/locales/zh-CN/translation.json
  71. 5 6
      src/lib/i18n/locales/zh-TW/translation.json
  72. 3 0
      src/lib/stores/index.ts
  73. 66 0
      src/lib/utils/index.test.ts
  74. 32 15
      src/lib/utils/index.ts
  75. 1 1
      src/routes/(app)/+page.svelte
  76. 242 232
      src/routes/(app)/admin/+page.svelte
  77. 1 1
      src/routes/(app)/c/[id]/+page.svelte
  78. 0 617
      src/routes/(app)/documents/+page.svelte
  79. 0 425
      src/routes/(app)/modelfiles/+page.svelte
  80. 0 727
      src/routes/(app)/modelfiles/create/+page.svelte
  81. 0 515
      src/routes/(app)/modelfiles/edit/+page.svelte
  82. 0 342
      src/routes/(app)/prompts/+page.svelte
  83. 0 254
      src/routes/(app)/prompts/create/+page.svelte
  84. 0 237
      src/routes/(app)/prompts/edit/+page.svelte
  85. 78 0
      src/routes/(app)/workspace/+layout.svelte
  86. 8 0
      src/routes/(app)/workspace/+page.svelte
  87. 5 0
      src/routes/(app)/workspace/documents/+page.svelte
  88. 5 0
      src/routes/(app)/workspace/modelfiles/+page.svelte
  89. 721 0
      src/routes/(app)/workspace/modelfiles/create/+page.svelte
  90. 507 0
      src/routes/(app)/workspace/modelfiles/edit/+page.svelte
  91. 5 0
      src/routes/(app)/workspace/playground/+page.svelte
  92. 5 0
      src/routes/(app)/workspace/prompts/+page.svelte
  93. 245 0
      src/routes/(app)/workspace/prompts/create/+page.svelte
  94. 228 0
      src/routes/(app)/workspace/prompts/edit/+page.svelte
  95. 18 1
      src/routes/+layout.svelte
  96. 27 0
      src/routes/modelfiles/create/+page.svelte
  97. 27 0
      src/routes/prompts/create/+page.svelte

+ 18 - 0
.github/workflows/format-build-frontend.yaml

@@ -37,3 +37,21 @@ jobs:
 
 
       - name: Build Frontend
       - name: Build Frontend
         run: npm run build
         run: npm run build
+
+  test-frontend:
+    name: 'Frontend Unit Tests'
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout Repository
+        uses: actions/checkout@v4
+
+      - name: Setup Node.js
+        uses: actions/setup-node@v4
+        with:
+          node-version: '20'
+
+      - name: Install Dependencies
+        run: npm ci
+
+      - name: Run vitest
+        run: npm run test:frontend

+ 29 - 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 jq && \
+    # 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,7 @@ COPY ./backend .
 
 
 EXPOSE 8080
 EXPOSE 8080
 
 
-HEALTHCHECK CMD curl --fail http://localhost:8080 || exit 1 
+HEALTHCHECK CMD curl --silent --fail http://localhost:8080/health | jq -e '.status == true' || exit 1
+
 
 
 CMD [ "bash", "start.sh"]
 CMD [ "bash", "start.sh"]

+ 5 - 0
backend/main.py

@@ -379,6 +379,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")
 
 

+ 33 - 0
cypress/e2e/chat.cy.ts

@@ -42,5 +42,38 @@ describe('Settings', () => {
 				.find('div[aria-label="Generation Info"]', { timeout: 120_000 }) // Generation Info is created after the stop token is received
 				.find('div[aria-label="Generation Info"]', { timeout: 120_000 }) // Generation Info is created after the stop token is received
 				.should('exist');
 				.should('exist');
 		});
 		});
+
+		it('user can share chat', () => {
+			// Click on the model selector
+			cy.get('button[aria-label="Select a model"]').click();
+			// Select the first model
+			cy.get('button[aria-label="model-item"]').first().click();
+			// Type a message
+			cy.get('#chat-textarea').type('Hi, what can you do? A single sentence only please.', {
+				force: true
+			});
+			// Send the message
+			cy.get('button[type="submit"]').click();
+			// User's message should be visible
+			cy.get('.chat-user').should('exist');
+			// Wait for the response
+			cy.get('.chat-assistant', { timeout: 120_000 }) // .chat-assistant is created after the first token is received
+				.find('div[aria-label="Generation Info"]', { timeout: 120_000 }) // Generation Info is created after the stop token is received
+				.should('exist');
+			// spy on requests
+			const spy = cy.spy();
+			cy.intercept("GET", "/api/v1/chats/*", spy);
+			// Open context menu
+			cy.get('#chat-context-menu-button').click();
+			// Click share button
+			cy.get('#chat-share-button').click();
+			// Check if the share dialog is visible
+			cy.get('#copy-and-share-chat-button').should('exist');
+			cy.wrap({}, { timeout: 5000 })
+				.should(() => {
+					// Check if the request was made twice (once for to replace chat object and once more due to change event)
+					expect(spy).to.be.callCount(2);
+				});
+		});
 	});
 	});
 });
 });

+ 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();
 	});
 	});

文件差异内容过多而无法显示
+ 990 - 6
package-lock.json


+ 4 - 2
package.json

@@ -15,7 +15,8 @@
 		"format": "prettier --plugin-search-dir --write \"**/*.{js,ts,svelte,css,md,html,json}\"",
 		"format": "prettier --plugin-search-dir --write \"**/*.{js,ts,svelte,css,md,html,json}\"",
 		"format:backend": "black . --exclude \"/venv/\"",
 		"format:backend": "black . --exclude \"/venv/\"",
 		"i18n:parse": "i18next --config i18next-parser.config.ts && prettier --write \"src/lib/i18n/**/*.{js,json}\"",
 		"i18n:parse": "i18next --config i18next-parser.config.ts && prettier --write \"src/lib/i18n/**/*.{js,json}\"",
-		"cy:open": "cypress open"
+		"cy:open": "cypress open",
+		"test:frontend": "vitest"
 	},
 	},
 	"devDependencies": {
 	"devDependencies": {
 		"@sveltejs/adapter-auto": "^2.0.0",
 		"@sveltejs/adapter-auto": "^2.0.0",
@@ -41,7 +42,8 @@
 		"tailwindcss": "^3.3.3",
 		"tailwindcss": "^3.3.3",
 		"tslib": "^2.4.1",
 		"tslib": "^2.4.1",
 		"typescript": "^5.0.0",
 		"typescript": "^5.0.0",
-		"vite": "^4.4.2"
+		"vite": "^4.4.2",
+		"vitest": "^1.6.0"
 	},
 	},
 	"type": "module",
 	"type": "module",
 	"dependencies": {
 	"dependencies": {

+ 24 - 4
src/app.css

@@ -83,11 +83,31 @@ 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 */
+}
+
+input::-webkit-outer-spin-button,
+input::-webkit-inner-spin-button {
+	/* display: none; <- Crashes Chrome on hover */
+	-webkit-appearance: none;
+	margin: 0; /* <-- Apparently some margin are still there even though it's hidden */
+}
+
+input[type='number'] {
+	-moz-appearance: textfield; /* 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}

+ 2 - 2
src/lib/components/admin/Settings/General.svelte

@@ -123,7 +123,7 @@
 
 
 				<div class="flex mt-2 space-x-2">
 				<div class="flex mt-2 space-x-2">
 					<input
 					<input
-						class="w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600"
+						class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
 						type="text"
 						type="text"
 						placeholder={`https://example.com/webhook`}
 						placeholder={`https://example.com/webhook`}
 						bind:value={webhookUrl}
 						bind:value={webhookUrl}
@@ -140,7 +140,7 @@
 
 
 				<div class="flex mt-2 space-x-2">
 				<div class="flex mt-2 space-x-2">
 					<input
 					<input
-						class="w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600"
+						class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
 						type="text"
 						type="text"
 						placeholder={`e.g.) "30m","1h", "10d". `}
 						placeholder={`e.g.) "30m","1h", "10d". `}
 						bind:value={JWTExpiresIn}
 						bind:value={JWTExpiresIn}

+ 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 {
@@ -415,7 +415,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"
@@ -431,9 +431,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}
@@ -538,7 +538,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}
@@ -757,7 +757,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 !== ''
@@ -768,7 +768,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 ||
@@ -1118,12 +1118,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>
@@ -469,7 +471,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 my-2">
 							<textarea
 							<textarea
 								id="message-edit-{message.id}"
 								id="message-edit-{message.id}"
 								bind:this={editTextAreaElement}
 								bind:this={editTextAreaElement}
@@ -481,23 +483,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>
@@ -594,47 +598,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>

+ 165 - 86
src/lib/components/chat/Messages/UserMessage.svelte

@@ -54,49 +54,55 @@
 	};
 	};
 </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">
+	{#if !($settings?.chatBubble ?? true)}
+		<ProfileImage
+			src={message.user
+				? $modelfiles.find((modelfile) => modelfile.tagName === message.user)?.imageUrl ??
+				  '/user.png'
+				: user?.profile_image_url ?? '/user.png'}
+		/>
+	{/if}
 	<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}
+		{#if !($settings?.chatBubble ?? true)}
+			<div>
+				<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}
 					{:else}
 						{$i18n.t('You')}
 						{$i18n.t('You')}
-						<span class=" text-gray-500 text-sm font-medium">{message?.user ?? ''}</span>
 					{/if}
 					{/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>
+
+					{#if message.timestamp}
+						<span
+							class=" invisible group-hover:visible text-gray-400 text-xs font-medium uppercase"
+						>
+							{dayjs(message.timestamp * 1000).format($i18n.t('h:mm a'))}
+						</span>
+					{/if}
+				</Name>
+			</div>
+		{/if}
 
 
 		<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={$settings?.chatBubble ?? true ? '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 +138,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">
@@ -198,7 +204,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}
@@ -222,81 +228,100 @@
 						}}
 						}}
 					/>
 					/>
 
 
-					<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 {$settings?.chatBubble ?? true ? 'justify-end' : ''} mb-2">
+						<div
+							class="rounded-3xl {$settings?.chatBubble ?? true
+								? `max-w-[90%] px-5 py-2  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-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"
+					<div
+						class=" flex {$settings?.chatBubble ?? true
+							? 'justify-end'
+							: ''} space-x-1 text-gray-700 dark:text-gray-500"
+					>
+						{#if !($settings?.chatBubble ?? true)}
+							{#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);
+										}}
 									>
 									>
-										<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>
+										<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-xs font-bold self-center dark:text-gray-100">
-									{siblings.indexOf(message.id) + 1} / {siblings.length}
-								</div>
+									<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 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"
+									<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);
+										}}
 									>
 									>
-										<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>
+										<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}
 						{/if}
 						{/if}
-
 						{#if !readOnly}
 						{#if !readOnly}
 							<Tooltip content={$i18n.t('Edit')} placement="bottom">
 							<Tooltip content={$i18n.t('Edit')} placement="bottom">
 								<button
 								<button
@@ -372,6 +397,60 @@
 								</button>
 								</button>
 							</Tooltip>
 							</Tooltip>
 						{/if}
 						{/if}
+
+						{#if $settings?.chatBubble ?? true}
+							{#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}
+						{/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';
@@ -38,7 +38,7 @@
 	}
 	}
 </script>
 </script>
 
 
-<div class="flex flex-col mt-0.5 w-full">
+<div class="flex flex-col w-full items-center md:items-start">
 	{#each selectedModels as selectedModel, selectedModelIdx}
 	{#each selectedModels as selectedModel, selectedModelIdx}
 		<div class="flex w-full max-w-fit">
 		<div class="flex w-full max-w-fit">
 			<div class="overflow-hidden w-full">
 			<div class="overflow-hidden w-full">
@@ -108,7 +108,7 @@
 	{/each}
 	{/each}
 </div>
 </div>
 
 
-{#if showSetDefault}
+{#if showSetDefault && !$mobile}
 	<div class="text-left mt-0.5 ml-1 text-[0.7rem] text-gray-500">
 	<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>

+ 12 - 11
src/lib/components/chat/ModelSelector/Selector.svelte

@@ -10,7 +10,7 @@
 
 
 	import { cancelOllamaRequest, deleteModel, getOllamaVersion, pullModel } from '$lib/apis/ollama';
 	import { cancelOllamaRequest, deleteModel, getOllamaVersion, pullModel } from '$lib/apis/ollama';
 
 
-	import { user, MODEL_DOWNLOAD_POOL, models } from '$lib/stores';
+	import { user, MODEL_DOWNLOAD_POOL, models, mobile } from '$lib/stores';
 	import { toast } from 'svelte-sonner';
 	import { toast } from 'svelte-sonner';
 	import { capitalizeFirstLetter, getModels, splitStream } from '$lib/utils';
 	import { capitalizeFirstLetter, getModels, splitStream } from '$lib/utils';
 	import Tooltip from '$lib/components/common/Tooltip.svelte';
 	import Tooltip from '$lib/components/common/Tooltip.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;
 
 
@@ -201,10 +201,11 @@
 			<ChevronDown className=" self-center ml-2 size-3" strokeWidth="2.5" />
 			<ChevronDown className=" self-center ml-2 size-3" strokeWidth="2.5" />
 		</div>
 		</div>
 	</DropdownMenu.Trigger>
 	</DropdownMenu.Trigger>
+
 	<DropdownMenu.Content
 	<DropdownMenu.Content
-		class=" z-40 {className} max-w-[calc(100vw-1rem)] justify-start rounded-lg  bg-white dark:bg-gray-900 dark:text-white shadow-lg border border-gray-300/30 dark:border-gray-700/50  outline-none "
+		class=" z-40 {className} max-w-[calc(100vw-1rem)] justify-start rounded-xl  bg-white dark:bg-gray-850 dark:text-white shadow-lg border border-gray-300/30 dark:border-gray-700/50  outline-none "
 		transition={flyAndScale}
 		transition={flyAndScale}
-		side={'bottom-start'}
+		side={$mobile ? 'bottom' : 'bottom-start'}
 		sideOffset={4}
 		sideOffset={4}
 	>
 	>
 		<slot>
 		<slot>
@@ -224,11 +225,11 @@
 				<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"
-						class="flex w-full text-left font-medium line-clamp-1 select-none items-center rounded-button py-2 pl-3 pr-1.5 text-sm text-gray-700 dark:text-gray-100 outline-none transition-all duration-75 hover:bg-gray-100 dark:hover:bg-gray-850 rounded-lg cursor-pointer data-[highlighted]:bg-muted"
+						class="flex w-full text-left font-medium line-clamp-1 select-none items-center rounded-button py-2 pl-3 pr-1.5 text-sm text-gray-700 dark:text-gray-100 outline-none transition-all duration-75 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg cursor-pointer data-[highlighted]:bg-muted"
 						on:click={() => {
 						on:click={() => {
 							value = item.value;
 							value = item.value;
 
 
@@ -312,7 +313,7 @@
 
 
 				{#if !(searchValue.trim() in $MODEL_DOWNLOAD_POOL) && searchValue && ollamaVersion && $user.role === 'admin'}
 				{#if !(searchValue.trim() in $MODEL_DOWNLOAD_POOL) && searchValue && ollamaVersion && $user.role === 'admin'}
 					<button
 					<button
-						class="flex w-full font-medium line-clamp-1 select-none items-center rounded-button py-2 pl-3 pr-1.5 text-sm text-gray-700 dark:text-gray-100 outline-none transition-all duration-75 hover:bg-gray-100 dark:hover:bg-gray-850 rounded-lg cursor-pointer data-[highlighted]:bg-muted"
+						class="flex w-full font-medium line-clamp-1 select-none items-center rounded-button py-2 pl-3 pr-1.5 text-sm text-gray-700 dark:text-gray-100 outline-none transition-all duration-75 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg cursor-pointer data-[highlighted]:bg-muted"
 						on:click={() => {
 						on:click={() => {
 							pullModelHandler();
 							pullModelHandler();
 						}}
 						}}
@@ -406,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>

+ 1 - 1
src/lib/components/chat/Settings/Account.svelte

@@ -71,7 +71,7 @@
 </script>
 </script>
 
 
 <div class="flex flex-col h-full justify-between text-sm">
 <div class="flex flex-col h-full justify-between text-sm">
-	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[22rem]">
+	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[25rem]">
 		<input
 		<input
 			id="profile-image-input"
 			id="profile-image-input"
 			bind:this={profileImageInputElement}
 			bind:this={profileImageInputElement}

+ 1 - 1
src/lib/components/chat/Settings/Advanced.svelte

@@ -84,7 +84,7 @@
 			{#if keepAlive !== null}
 			{#if keepAlive !== null}
 				<div class="flex mt-1 space-x-2">
 				<div class="flex mt-1 space-x-2">
 					<input
 					<input
-						class="w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600"
+						class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
 						type="text"
 						type="text"
 						placeholder={$i18n.t("e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.")}
 						placeholder={$i18n.t("e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.")}
 						bind:value={keepAlive}
 						bind:value={keepAlive}

+ 2 - 2
src/lib/components/chat/Settings/Advanced/AdvancedParams.svelte

@@ -27,7 +27,7 @@
 			<div class=" w-20 text-xs font-medium self-center">{$i18n.t('Seed')}</div>
 			<div class=" w-20 text-xs font-medium self-center">{$i18n.t('Seed')}</div>
 			<div class=" flex-1 self-center">
 			<div class=" flex-1 self-center">
 				<input
 				<input
-					class="w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600"
+					class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
 					type="number"
 					type="number"
 					placeholder="Enter Seed"
 					placeholder="Enter Seed"
 					bind:value={options.seed}
 					bind:value={options.seed}
@@ -43,7 +43,7 @@
 			<div class=" w-20 text-xs font-medium self-center">{$i18n.t('Stop Sequence')}</div>
 			<div class=" w-20 text-xs font-medium self-center">{$i18n.t('Stop Sequence')}</div>
 			<div class=" flex-1 self-center">
 			<div class=" flex-1 self-center">
 				<input
 				<input
-					class="w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600"
+					class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
 					type="text"
 					type="text"
 					placeholder={$i18n.t('Enter stop sequence')}
 					placeholder={$i18n.t('Enter stop sequence')}
 					bind:value={options.stop}
 					bind:value={options.stop}

+ 2 - 2
src/lib/components/chat/Settings/Audio.svelte

@@ -147,7 +147,7 @@
 		dispatch('save');
 		dispatch('save');
 	}}
 	}}
 >
 >
-	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[22rem]">
+	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[25rem]">
 		<div>
 		<div>
 			<div class=" mb-1 text-sm font-medium">{$i18n.t('STT Settings')}</div>
 			<div class=" mb-1 text-sm font-medium">{$i18n.t('STT Settings')}</div>
 
 
@@ -345,7 +345,7 @@
 		{/if}
 		{/if}
 	</div>
 	</div>
 
 
-	<div class="flex justify-end pt-3 text-sm font-medium">
+	<div class="flex justify-end text-sm font-medium">
 		<button
 		<button
 			class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
 			class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
 			type="submit"
 			type="submit"

+ 1 - 1
src/lib/components/chat/Settings/Connections.svelte

@@ -65,7 +65,7 @@
 		dispatch('save');
 		dispatch('save');
 	}}
 	}}
 >
 >
-	<div class="  pr-1.5 overflow-y-scroll max-h-[22rem] space-y-3">
+	<div class="  pr-1.5 overflow-y-scroll max-h-[25rem] space-y-3">
 		<div class=" space-y-3">
 		<div class=" space-y-3">
 			<div class="mt-2 space-y-2 pr-1.5">
 			<div class="mt-2 space-y-2 pr-1.5">
 				<div class="flex justify-between items-center text-sm">
 				<div class="flex justify-between items-center text-sm">

+ 2 - 2
src/lib/components/chat/Settings/General.svelte

@@ -130,7 +130,7 @@
 </script>
 </script>
 
 
 <div class="flex flex-col h-full justify-between text-sm">
 <div class="flex flex-col h-full justify-between text-sm">
-	<div class="  pr-1.5 overflow-y-scroll max-h-[22rem]">
+	<div class="  pr-1.5 overflow-y-scroll max-h-[25rem]">
 		<div class="">
 		<div class="">
 			<div class=" mb-1 text-sm font-medium">{$i18n.t('WebUI Settings')}</div>
 			<div class=" mb-1 text-sm font-medium">{$i18n.t('WebUI Settings')}</div>
 
 
@@ -253,7 +253,7 @@
 					{#if keepAlive !== null}
 					{#if keepAlive !== null}
 						<div class="flex mt-1 space-x-2">
 						<div class="flex mt-1 space-x-2">
 							<input
 							<input
-								class="w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600"
+								class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
 								type="text"
 								type="text"
 								placeholder={$i18n.t("e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.")}
 								placeholder={$i18n.t("e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.")}
 								bind:value={keepAlive}
 								bind:value={keepAlive}

+ 29 - 2
src/lib/components/chat/Settings/Interface.svelte

@@ -23,6 +23,7 @@
 	// Interface
 	// Interface
 	let promptSuggestions = [];
 	let promptSuggestions = [];
 	let showUsername = false;
 	let showUsername = false;
+	let chatBubble = true;
 
 
 	const toggleSplitLargeChunks = async () => {
 	const toggleSplitLargeChunks = async () => {
 		splitLargeChunks = !splitLargeChunks;
 		splitLargeChunks = !splitLargeChunks;
@@ -34,6 +35,11 @@
 		saveSettings({ fullScreenMode: fullScreenMode });
 		saveSettings({ fullScreenMode: fullScreenMode });
 	};
 	};
 
 
+	const toggleChatBubble = async () => {
+		chatBubble = !chatBubble;
+		saveSettings({ chatBubble: chatBubble });
+	};
+
 	const toggleShowUsername = async () => {
 	const toggleShowUsername = async () => {
 		showUsername = !showUsername;
 		showUsername = !showUsername;
 		saveSettings({ showUsername: showUsername });
 		saveSettings({ showUsername: showUsername });
@@ -106,6 +112,7 @@
 
 
 		responseAutoCopy = settings.responseAutoCopy ?? false;
 		responseAutoCopy = settings.responseAutoCopy ?? false;
 		showUsername = settings.showUsername ?? false;
 		showUsername = settings.showUsername ?? false;
+		chatBubble = settings.chatBubble ?? true;
 		fullScreenMode = settings.fullScreenMode ?? false;
 		fullScreenMode = settings.fullScreenMode ?? false;
 		splitLargeChunks = settings.splitLargeChunks ?? false;
 		splitLargeChunks = settings.splitLargeChunks ?? false;
 	});
 	});
@@ -118,10 +125,30 @@
 		dispatch('save');
 		dispatch('save');
 	}}
 	}}
 >
 >
-	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[22rem]">
+	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[25rem]">
 		<div>
 		<div>
 			<div class=" mb-1 text-sm font-medium">{$i18n.t('WebUI Add-ons')}</div>
 			<div class=" mb-1 text-sm font-medium">{$i18n.t('WebUI Add-ons')}</div>
 
 
+			<div>
+				<div class=" py-0.5 flex w-full justify-between">
+					<div class=" self-center text-xs font-medium">{$i18n.t('Chat Bubble UI')}</div>
+
+					<button
+						class="p-1 px-3 text-xs flex rounded transition"
+						on:click={() => {
+							toggleChatBubble();
+						}}
+						type="button"
+					>
+						{#if chatBubble === true}
+							<span class="ml-2 self-center">{$i18n.t('On')}</span>
+						{:else}
+							<span class="ml-2 self-center">{$i18n.t('Off')}</span>
+						{/if}
+					</button>
+				</div>
+			</div>
+
 			<div>
 			<div>
 				<div class=" py-0.5 flex w-full justify-between">
 				<div class=" py-0.5 flex w-full justify-between">
 					<div class=" self-center text-xs font-medium">{$i18n.t('Title Auto-Generation')}</div>
 					<div class=" self-center text-xs font-medium">{$i18n.t('Title Auto-Generation')}</div>
@@ -306,7 +333,7 @@
 		{#if $user.role === 'admin'}
 		{#if $user.role === 'admin'}
 			<hr class=" dark:border-gray-700" />
 			<hr class=" dark:border-gray-700" />
 
 
-			<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80">
+			<div class=" space-y-3 pr-1.5">
 				<div class="flex w-full justify-between mb-2">
 				<div class="flex w-full justify-between mb-2">
 					<div class=" self-center text-sm font-semibold">
 					<div class=" self-center text-sm font-semibold">
 						{$i18n.t('Default Prompt Suggestions')}
 						{$i18n.t('Default Prompt Suggestions')}

+ 27 - 1
src/lib/components/chat/SettingsModal.svelte

@@ -165,6 +165,32 @@
 					<div class=" self-center">{$i18n.t('Interface')}</div>
 					<div class=" self-center">{$i18n.t('Interface')}</div>
 				</button>
 				</button>
 
 
+				<button
+					class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
+					'interface'
+						? 'bg-gray-200 dark:bg-gray-700'
+						: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
+					on:click={() => {
+						selectedTab = 'interface';
+					}}
+				>
+					<div class=" self-center mr-2">
+						<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="M2 4.25A2.25 2.25 0 0 1 4.25 2h7.5A2.25 2.25 0 0 1 14 4.25v5.5A2.25 2.25 0 0 1 11.75 12h-1.312c.1.128.21.248.328.36a.75.75 0 0 1 .234.545v.345a.75.75 0 0 1-.75.75h-4.5a.75.75 0 0 1-.75-.75v-.345a.75.75 0 0 1 .234-.545c.118-.111.228-.232.328-.36H4.25A2.25 2.25 0 0 1 2 9.75v-5.5Zm2.25-.75a.75.75 0 0 0-.75.75v4.5c0 .414.336.75.75.75h7.5a.75.75 0 0 0 .75-.75v-4.5a.75.75 0 0 0-.75-.75h-7.5Z"
+								clip-rule="evenodd"
+							/>
+						</svg>
+					</div>
+					<div class=" self-center">{$i18n.t('Interface')}</div>
+				</button>
+
 				<button
 				<button
 					class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
 					class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
 					'audio'
 					'audio'
@@ -298,7 +324,7 @@
 					<div class=" self-center">{$i18n.t('About')}</div>
 					<div class=" self-center">{$i18n.t('About')}</div>
 				</button>
 				</button>
 			</div>
 			</div>
-			<div class="flex-1 md:min-h-[25rem]">
+			<div class="flex-1 md:min-h-[28rem]">
 				{#if selectedTab === 'general'}
 				{#if selectedTab === 'general'}
 					<General
 					<General
 						{getModels}
 						{getModels}

+ 15 - 1
src/lib/components/chat/ShareChatModal.svelte

@@ -57,10 +57,23 @@
 
 
 	export let show = false;
 	export let show = false;
 
 
+	const isDifferentChat = (_chat) => {
+		if (!chat) {
+			return true;
+		}
+		if (!_chat) {
+			return false;
+		}
+		return chat.id !== _chat.id || chat.share_id !== _chat.share_id;
+	}
+
 	$: if (show) {
 	$: if (show) {
 		(async () => {
 		(async () => {
 			if (chatId) {
 			if (chatId) {
-				chat = await getChatById(localStorage.token, chatId);
+				const _chat = await getChatById(localStorage.token, chatId);
+				if (isDifferentChat(_chat)) {
+					chat = _chat;
+				}
 			} else {
 			} else {
 				chat = null;
 				chat = null;
 				console.log(chat);
 				console.log(chat);
@@ -137,6 +150,7 @@
 							<button
 							<button
 								class=" self-center flex items-center gap-1 px-3.5 py-2 rounded-xl text-sm font-medium bg-emerald-600 hover:bg-emerald-500 text-white"
 								class=" self-center flex items-center gap-1 px-3.5 py-2 rounded-xl text-sm font-medium bg-emerald-600 hover:bg-emerald-500 text-white"
 								type="button"
 								type="button"
+								id="copy-and-share-chat-button"
 								on:click={async () => {
 								on:click={async () => {
 									const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
 									const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
 
 

+ 2 - 2
src/lib/components/common/Modal.svelte

@@ -16,9 +16,9 @@
 		} else if (size === 'sm') {
 		} else if (size === 'sm') {
 			return 'w-[30rem]';
 			return 'w-[30rem]';
 		} else if (size === 'md') {
 		} else if (size === 'md') {
-			return 'w-[44rem]';
-		} else {
 			return 'w-[48rem]';
 			return 'w-[48rem]';
+		} else {
+			return 'w-[50rem]';
 		}
 		}
 	};
 	};
 
 

+ 2 - 2
src/lib/components/documents/Settings/General.svelte

@@ -190,13 +190,13 @@
 		saveHandler();
 		saveHandler();
 	}}
 	}}
 >
 >
-	<div class=" space-y-2.5 pr-1.5 overflow-y-scroll max-h-[22rem]">
+	<div class=" space-y-2.5 pr-1.5 overflow-y-scroll max-h-[28rem]">
 		<div class="flex flex-col gap-0.5">
 		<div class="flex flex-col gap-0.5">
 			<div class=" mb-0.5 text-sm font-medium">{$i18n.t('General Settings')}</div>
 			<div class=" mb-0.5 text-sm font-medium">{$i18n.t('General Settings')}</div>
 
 
 			<div class="  flex w-full justify-between">
 			<div class="  flex w-full justify-between">
 				<div class=" self-center text-xs font-medium">
 				<div class=" self-center text-xs font-medium">
-					{$i18n.t('Scan for documents from {{path}}', { path: '/data/docs' })}
+					{$i18n.t('Scan for documents from {{path}}', { path: 'DOCS_DIR (/data/docs)' })}
 				</div>
 				</div>
 
 
 				<button
 				<button

+ 1 - 1
src/lib/components/documents/Settings/QueryParams.svelte

@@ -46,7 +46,7 @@
 		saveHandler();
 		saveHandler();
 	}}
 	}}
 >
 >
-	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[22rem]">
+	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[25rem]">
 		<div class=" ">
 		<div class=" ">
 			<div class=" text-sm font-medium">{$i18n.t('Query Params')}</div>
 			<div class=" text-sm font-medium">{$i18n.t('Query Params')}</div>
 
 

+ 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>

+ 65 - 38
src/lib/components/layout/Navbar.svelte

@@ -2,7 +2,17 @@
 	import { getContext } from 'svelte';
 	import { getContext } from 'svelte';
 	import { toast } from 'svelte-sonner';
 	import { toast } from 'svelte-sonner';
 
 
-	import { WEBUI_NAME, chatId, modelfiles, settings, showSettings } from '$lib/stores';
+	import {
+		WEBUI_NAME,
+		chatId,
+		mobile,
+		modelfiles,
+		settings,
+		showArchivedChats,
+		showSettings,
+		showSidebar,
+		user
+	} from '$lib/stores';
 
 
 	import { slide } from 'svelte/transition';
 	import { slide } from 'svelte/transition';
 	import ShareChatModal from '../chat/ShareChatModal.svelte';
 	import ShareChatModal from '../chat/ShareChatModal.svelte';
@@ -10,6 +20,8 @@
 	import Tooltip from '../common/Tooltip.svelte';
 	import Tooltip from '../common/Tooltip.svelte';
 	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 MenuLines from '../icons/MenuLines.svelte';
 
 
 	const i18n = getContext('i18n');
 	const i18n = getContext('i18n');
 
 
@@ -28,48 +40,35 @@
 
 
 <ShareChatModal bind:show={showShareChatModal} chatId={$chatId} />
 <ShareChatModal bind:show={showShareChatModal} chatId={$chatId} />
 <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-[1.3rem]">
+	<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 text-gray-600 dark:text-gray-400"
+			>
+				<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"
+					on:click={() => {
+						showSidebar.set(!$showSidebar);
+					}}
+				>
+					<div class=" m-auto self-center">
+						<MenuLines />
+					</div>
+				</button>
+			</div>
 			<div class="flex-1 overflow-hidden max-w-full">
 			<div class="flex-1 overflow-hidden max-w-full">
 				{#if showModelSelector}
 				{#if showModelSelector}
 					<ModelSelector bind:selectedModels showSetDefault={!shareEnabled} />
 					<ModelSelector bind:selectedModels showSetDefault={!shareEnabled} />
 				{/if}
 				{/if}
 			</div>
 			</div>
 
 
-			<div class="self-start flex flex-none items-center">
-				<div class="md:hidden flex self-center w-[1px] h-5 mx-2 bg-gray-300 dark:bg-stone-700" />
+			<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" /> -->
 
 
-				{#if !shareEnabled}
-					<Tooltip content={$i18n.t('Settings')}>
-						<button
-							class="cursor-pointer p-1.5 flex dark:hover:bg-gray-700 rounded-full 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}
@@ -81,8 +80,9 @@
 						}}
 						}}
 					>
 					>
 						<button
 						<button
-							class="cursor-pointer p-1.5 flex dark:hover:bg-gray-700 rounded-full transition"
-						>
+							class="hidden md:flex cursor-pointer px-2 py-2 rounded-xl hover:bg-gray-100 dark:hover:bg-gray-850 transition"
+							id="chat-context-menu-button"
+            >
 							<div class=" m-auto self-center">
 							<div class=" m-auto self-center">
 								<svg
 								<svg
 									xmlns="http://www.w3.org/2000/svg"
 									xmlns="http://www.w3.org/2000/svg"
@@ -105,7 +105,9 @@
 				<Tooltip content={$i18n.t('New Chat')}>
 				<Tooltip content={$i18n.t('New Chat')}>
 					<button
 					<button
 						id="new-chat-button"
 						id="new-chat-button"
-						class=" cursor-pointer p-1.5 flex dark:hover:bg-gray-700 rounded-full transition"
+						class=" flex {$showSidebar
+							? 'md:hidden'
+							: ''} cursor-pointer px-2 py-2 rounded-xl hover:bg-gray-100 dark:hover:bg-gray-850 transition"
 						on:click={() => {
 						on:click={() => {
 							initNewChat();
 							initNewChat();
 						}}
 						}}
@@ -127,6 +129,31 @@
 						</div>
 						</div>
 					</button>
 					</button>
 				</Tooltip>
 				</Tooltip>
+
+				{#if $user !== undefined}
+					<UserMenu
+						className="max-w-[200px]"
+						role={$user.role}
+						on:show={(e) => {
+							if (e.detail === 'archived-chat') {
+								showArchivedChats.set(true);
+							}
+						}}
+					>
+						<button
+							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">
+								<img
+									src={$user.profile_image_url}
+									class=" size-6 object-cover rounded-full"
+									alt="User profile"
+								/>
+							</div>
+						</button>
+					</UserMenu>
+				{/if}
 			</div>
 			</div>
 		</div>
 		</div>
 	</div>
 	</div>

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

@@ -76,14 +76,14 @@
 
 
 	<div slot="content">
 	<div slot="content">
 		<DropdownMenu.Content
 		<DropdownMenu.Content
-			class="w-full max-w-[200px] rounded-lg px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-900 dark:text-white shadow-lg"
+			class="w-full max-w-[200px] 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-lg"
 			sideOffset={8}
 			sideOffset={8}
 			side="bottom"
 			side="bottom"
 			align="end"
 			align="end"
 			transition={flyAndScale}
 			transition={flyAndScale}
 		>
 		>
-			<DropdownMenu.Item
-				class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer dark:hover:bg-gray-850 rounded-md"
+			<!-- <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={async () => {
 				on:click={async () => {
 					await showSettings.set(!$showSettings);
 					await showSettings.set(!$showSettings);
 				}}
 				}}
@@ -108,113 +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-850 rounded-md"
-					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-850 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-900 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-850 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-850 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>

+ 112 - 128
src/lib/components/layout/Sidebar.svelte

@@ -1,6 +1,16 @@
 <script lang="ts">
 <script lang="ts">
 	import { goto } from '$app/navigation';
 	import { goto } from '$app/navigation';
-	import { user, chats, settings, showSettings, chatId, tags, showSidebar } from '$lib/stores';
+	import {
+		user,
+		chats,
+		settings,
+		showSettings,
+		chatId,
+		tags,
+		showSidebar,
+		mobile,
+		showArchivedChats
+	} from '$lib/stores';
 	import { onMount, getContext } from 'svelte';
 	import { onMount, getContext } from 'svelte';
 
 
 	const i18n = getContext('i18n');
 	const i18n = getContext('i18n');
@@ -24,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;
@@ -40,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;
@@ -67,7 +76,24 @@
 		}
 		}
 	});
 	});
 
 
+	mobile;
+	const onResize = () => {
+		if ($showSidebar && window.innerWidth < BREAKPOINT) {
+			showSidebar.set(false);
+		}
+	};
+
 	onMount(async () => {
 	onMount(async () => {
+		mobile.subscribe((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));
 
 
@@ -97,20 +123,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);
 		};
 		};
 	});
 	});
 
 
@@ -177,31 +195,43 @@
 
 
 <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));
 	}}
 	}}
 />
 />
 
 
+<!-- svelte-ignore a11y-no-static-element-interactions -->
+
+{#if $showSidebar}
+	<div
+		class=" fixed md:hidden z-40 top-0 right-0 left-0 bottom-0 bg-black/60 w-full min-h-screen h-screen flex justify-center overflow-hidden overscroll-contain"
+		on:mousedown={() => {
+			showSidebar.set(!$showSidebar);
+		}}
+	/>
+{/if}
+
 <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}
 >
 >
 	<div
 	<div
-		class="py-2.5 my-auto flex flex-col justify-between h-screen max-h-[100dvh] w-[260px] {$showSidebar
+		class="py-2.5 my-auto flex flex-col justify-between h-screen max-h-[100dvh] w-[260px] z-50 {$showSidebar
 			? ''
 			? ''
 			: 'invisible'}"
 			: 'invisible'}"
 	>
 	>
-		<div class="px-2 flex justify-center space-x-2">
+		<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-grow flex justify-between rounded-xl px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-900 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;
 
 
@@ -209,27 +239,29 @@
 					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="flex self-center">
-					<div class="self-center mr-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">{$i18n.t('New Chat')}</div>
+				<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>
-
-				<div class="self-center">
+				<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"
 						fill="currentColor"
 						fill="currentColor"
-						class="w-4 h-4"
+						class="size-5"
 					>
 					>
 						<path
 						<path
 							d="M5.433 13.917l1.262-3.155A4 4 0 017.58 9.42l6.92-6.918a2.121 2.121 0 013 3l-6.92 6.918c-.383.383-.84.685-1.343.886l-3.154 1.262a.5.5 0 01-.65-.65z"
 							d="M5.433 13.917l1.262-3.155A4 4 0 017.58 9.42l6.92-6.918a2.121 2.121 0 013 3l-6.92 6.918c-.383.383-.84.685-1.343.886l-3.154 1.262a.5.5 0 01-.65-.65z"
@@ -240,17 +272,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
@@ -259,7 +316,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"
@@ -270,71 +327,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>
@@ -384,9 +377,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"
@@ -402,7 +395,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={() => {
@@ -413,9 +406,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));
 						}}
 						}}
@@ -424,7 +417,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) {
@@ -440,7 +433,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
@@ -495,7 +488,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);
 									}
 									}
 								}}
 								}}
@@ -610,6 +603,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;
@@ -641,18 +637,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"
@@ -690,7 +674,7 @@
 						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);
 							}
 							}
 						}}
 						}}
 					>
 					>
@@ -715,9 +699,9 @@
 		</div>
 		</div>
 	</div>
 	</div>
 
 
-	<div
+	<!-- <div
 		id="sidebar-handle"
 		id="sidebar-handle"
-		class="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"
 	>
 	>
 		<Tooltip
 		<Tooltip
 			placement="right"
 			placement="right"
@@ -750,16 +734,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>

+ 16 - 4
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,14 +38,14 @@
 
 
 	<div slot="content">
 	<div slot="content">
 		<DropdownMenu.Content
 		<DropdownMenu.Content
-			class="w-full max-w-[180px] rounded-lg px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-900 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"
 			transition={flyAndScale}
 			transition={flyAndScale}
 		>
 		>
 			<DropdownMenu.Item
 			<DropdownMenu.Item
-				class="flex gap-2 items-center px-3 py-2 text-sm  font-medium cursor-pointer dark:hover:bg-gray-850 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={() => {
 					shareHandler();
 					shareHandler();
 				}}
 				}}
@@ -53,7 +55,7 @@
 			</DropdownMenu.Item>
 			</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-850 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={() => {
 					renameHandler();
 					renameHandler();
 				}}
 				}}
@@ -63,7 +65,17 @@
 			</DropdownMenu.Item>
 			</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-850 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={() => {
+					archiveChatHandler();
+				}}
+			>
+				<ArchiveBox strokeWidth="2" />
+				<div class="flex items-center">{$i18n.t('Archive')}</div>
+			</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={() => {
 				on:click={() => {
 					deleteHandler();
 					deleteHandler();
 				}}
 				}}

+ 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-850 z-50 bg-gray-900 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';

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

@@ -0,0 +1,611 @@
+<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();
+
+			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>

+ 2 - 2
src/lib/constants.ts

@@ -1,8 +1,8 @@
-import { dev } from '$app/environment';
+import { browser, dev } from '$app/environment';
 // import { version } from '../../package.json';
 // import { version } from '../../package.json';
 
 
 export const APP_NAME = 'Open WebUI';
 export const APP_NAME = 'Open WebUI';
-export const WEBUI_BASE_URL = dev ? `http://${location.hostname}:8080` : ``;
+export const WEBUI_BASE_URL = browser ? (dev ? `http://${location.hostname}:8080` : ``) : ``;
 
 
 export const WEBUI_API_BASE_URL = `${WEBUI_BASE_URL}/api/v1`;
 export const WEBUI_API_BASE_URL = `${WEBUI_BASE_URL}/api/v1`;
 
 

+ 98 - 99
src/lib/i18n/locales/ar-BH/translation.json

@@ -1,11 +1,11 @@
 {
 {
 	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "",
 	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "",
 	"(Beta)": "(تجريبي)",
 	"(Beta)": "(تجريبي)",
-	"(e.g. `sh webui.sh --api`)": "(مثال `sh webui.sh --api`)",
+	"(e.g. `sh webui.sh --api`)": "( `sh webui.sh --api`مثال)",
 	"(latest)": "(الأخير)",
 	"(latest)": "(الأخير)",
 	"{{modelName}} is thinking...": "{{modelName}} ...يفكر",
 	"{{modelName}} is thinking...": "{{modelName}} ...يفكر",
 	"{{user}}'s Chats": "{{user}}' الدردشات",
 	"{{user}}'s Chats": "{{user}}' الدردشات",
-	"{{webUIName}} Backend Required": "",
+	"{{webUIName}} Backend Required": "{{webUIName}} مطلوب",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "",
 	"a user": "المستخدم",
 	"a user": "المستخدم",
 	"About": "عن",
 	"About": "عن",
@@ -16,20 +16,20 @@
 	"Add a short description about what this modelfile does": "أضف وصفًا قصيرًا حول ما يفعله ملف الموديل هذا",
 	"Add a short description about what this modelfile does": "أضف وصفًا قصيرًا حول ما يفعله ملف الموديل هذا",
 	"Add a short title for this prompt": "أضف عنوانًا قصيرًا لبداء المحادثة",
 	"Add a short title for this prompt": "أضف عنوانًا قصيرًا لبداء المحادثة",
 	"Add a tag": "أضافة تاق",
 	"Add a tag": "أضافة تاق",
-	"Add custom prompt": "",
+	"Add custom prompt": "أضافة مطالبة مخصصه",
 	"Add Docs": "إضافة المستندات",
 	"Add Docs": "إضافة المستندات",
 	"Add Files": "إضافة ملفات",
 	"Add Files": "إضافة ملفات",
 	"Add message": "اضافة رسالة",
 	"Add message": "اضافة رسالة",
 	"Add Model": "اضافة موديل",
 	"Add Model": "اضافة موديل",
 	"Add Tags": "اضافة تاق",
 	"Add Tags": "اضافة تاق",
 	"Add User": "اضافة مستخدم",
 	"Add User": "اضافة مستخدم",
-	"Adjusting these settings will apply changes universally to all users.": "سيؤدي ضبط هذه الإعدادات إلى تطبيق التغييرات بشكل عام على كافة المستخدمين.",
+	"Adjusting these settings will apply changes universally to all users.": "سيؤدي ضبط هذه الإعدادات إلى تطبيق التغييرات بشكل عام على كافة المستخدمين",
 	"admin": "المشرف",
 	"admin": "المشرف",
 	"Admin Panel": "لوحة التحكم",
 	"Admin Panel": "لوحة التحكم",
 	"Admin Settings": "اعدادات المشرف",
 	"Admin Settings": "اعدادات المشرف",
 	"Advanced Parameters": "التعليمات المتقدمة",
 	"Advanced Parameters": "التعليمات المتقدمة",
 	"all": "الكل",
 	"all": "الكل",
-	"All Documents": "",
+	"All Documents": "جميع الملفات",
 	"All Users": "جميع المستخدمين",
 	"All Users": "جميع المستخدمين",
 	"Allow": "يسمح",
 	"Allow": "يسمح",
 	"Allow Chat Deletion": "يستطيع حذف المحادثات",
 	"Allow Chat Deletion": "يستطيع حذف المحادثات",
@@ -43,23 +43,23 @@
 	"API Key created.": "API تم أنشاء المفتاح",
 	"API Key created.": "API تم أنشاء المفتاح",
 	"API keys": "API المفاتيح",
 	"API keys": "API المفاتيح",
 	"API RPM": "API RPM",
 	"API RPM": "API RPM",
-	"April": "",
+	"April": "أبريل",
 	"Archive": "الأرشيف",
 	"Archive": "الأرشيف",
 	"Archived Chats": "الأرشيف المحادثات",
 	"Archived Chats": "الأرشيف المحادثات",
 	"are allowed - Activate this command by typing": "مسموح - قم بتنشيط هذا الأمر عن طريق الكتابة",
 	"are allowed - Activate this command by typing": "مسموح - قم بتنشيط هذا الأمر عن طريق الكتابة",
 	"Are you sure?": "هل أنت متأكد ؟",
 	"Are you sure?": "هل أنت متأكد ؟",
-	"Attach file": "",
+	"Attach file": "أرفق ملف",
 	"Attention to detail": "انتبه للتفاصيل",
 	"Attention to detail": "انتبه للتفاصيل",
 	"Audio": "صوتي",
 	"Audio": "صوتي",
-	"August": "",
+	"August": "أغسطس",
 	"Auto-playback response": "استجابة التشغيل التلقائي",
 	"Auto-playback response": "استجابة التشغيل التلقائي",
-	"Auto-send input after 3 sec.": "إرسال تلقائي للإدخال بعد 3 ثوانٍ.",
+	"Auto-send input after 3 sec.": "إرسال تلقائي للإدخال بعد 3 ثواني.",
 	"AUTOMATIC1111 Base URL": "AUTOMATIC1111 الرابط الرئيسي",
 	"AUTOMATIC1111 Base URL": "AUTOMATIC1111 الرابط الرئيسي",
 	"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 الرابط مطلوب",
 	"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 الرابط مطلوب",
 	"available!": "متاح",
 	"available!": "متاح",
 	"Back": "خلف",
 	"Back": "خلف",
 	"Bad Response": "استجابة خطاء",
 	"Bad Response": "استجابة خطاء",
-	"before": "",
+	"before": "قبل",
 	"Being lazy": "كون كسول",
 	"Being lazy": "كون كسول",
 	"Builder Mode": "بناء الموديل",
 	"Builder Mode": "بناء الموديل",
 	"Bypass SSL verification for Websites": "",
 	"Bypass SSL verification for Websites": "",
@@ -67,8 +67,9 @@
 	"Categories": "التصنيفات",
 	"Categories": "التصنيفات",
 	"Change Password": "تغير الباسورد",
 	"Change Password": "تغير الباسورد",
 	"Chat": "المحادثة",
 	"Chat": "المحادثة",
+	"Chat Bubble UI": "",
 	"Chat History": "تاريخ المحادثة",
 	"Chat History": "تاريخ المحادثة",
-	"Chat History is off for this browser.": "سجل الدردشة معطل لهذا المتصفح.",
+	"Chat History is off for this browser.": "سجل الدردشة معطل لهذا المتصفح",
 	"Chats": "المحادثات",
 	"Chats": "المحادثات",
 	"Check Again": "تحقق مرة اخرى",
 	"Check Again": "تحقق مرة اخرى",
 	"Check for updates": "تحقق من التحديثات",
 	"Check for updates": "تحقق من التحديثات",
@@ -77,13 +78,13 @@
 	"Chunk Overlap": "Chunk تداخل",
 	"Chunk Overlap": "Chunk تداخل",
 	"Chunk Params": "Chunk المتغيرات",
 	"Chunk Params": "Chunk المتغيرات",
 	"Chunk Size": "Chunk حجم",
 	"Chunk Size": "Chunk حجم",
-	"Citation": "",
+	"Citation": "اقتباس",
 	"Click here for help.": "أضغط هنا للمساعدة",
 	"Click here for help.": "أضغط هنا للمساعدة",
-	"Click here to": "",
-	"Click here to check other modelfiles.": "انقر هنا للتحقق من ملفات الموديلات الأخرى.",
+	"Click here to": "أضغط هنا الانتقال",
+	"Click here to check other modelfiles.": "انقر هنا للتحقق من ملفات الموديلات الأخرى",
 	"Click here to select": "أضغط هنا للاختيار",
 	"Click here to select": "أضغط هنا للاختيار",
 	"Click here to select a csv file.": "أضغط هنا للاختيار ملف csv",
 	"Click here to select a csv file.": "أضغط هنا للاختيار ملف csv",
-	"Click here to select documents.": "انقر هنا لاختيار المستندات.",
+	"Click here to select documents.": "انقر هنا لاختيار المستندات",
 	"click here.": "أضغط هنا",
 	"click here.": "أضغط هنا",
 	"Click on the user role button to change a user's role.": "أضغط على أسم الصلاحيات لتغيرها للمستخدم",
 	"Click on the user role button to change a user's role.": "أضغط على أسم الصلاحيات لتغيرها للمستخدم",
 	"Close": "أغلق",
 	"Close": "أغلق",
@@ -98,17 +99,17 @@
 	"Context Length": "طول السياق",
 	"Context Length": "طول السياق",
 	"Continue Response": "متابعة الرد",
 	"Continue Response": "متابعة الرد",
 	"Conversation Mode": "وضع المحادثة",
 	"Conversation Mode": "وضع المحادثة",
-	"Copied shared chat URL to clipboard!": "تم نسخ عنوان URL للدردشة المشتركة إلى الحافظة!",
+	"Copied shared chat URL to clipboard!": "تم نسخ عنوان URL للدردشة المشتركة إلى الحافظة",
 	"Copy": "نسخ",
 	"Copy": "نسخ",
 	"Copy last code block": "انسخ كتلة التعليمات البرمجية الأخيرة",
 	"Copy last code block": "انسخ كتلة التعليمات البرمجية الأخيرة",
 	"Copy last response": "انسخ الرد الأخير",
 	"Copy last response": "انسخ الرد الأخير",
 	"Copy Link": "أنسخ الرابط",
 	"Copy Link": "أنسخ الرابط",
-	"Copying to clipboard was successful!": "تم النسخ إلى الحافظة بنجاح!",
+	"Copying to clipboard was successful!": "تم النسخ إلى الحافظة بنجاح",
 	"Create a concise, 3-5 word phrase as a header for the following query, strictly adhering to the 3-5 word limit and avoiding the use of the word 'title':": "قم بإنشاء عبارة موجزة مكونة من 3-5 كلمات كرأس للاستعلام التالي، مع الالتزام الصارم بالحد الأقصى لعدد الكلمات الذي يتراوح بين 3-5 كلمات وتجنب استخدام الكلمة 'عنوان':",
 	"Create a concise, 3-5 word phrase as a header for the following query, strictly adhering to the 3-5 word limit and avoiding the use of the word 'title':": "قم بإنشاء عبارة موجزة مكونة من 3-5 كلمات كرأس للاستعلام التالي، مع الالتزام الصارم بالحد الأقصى لعدد الكلمات الذي يتراوح بين 3-5 كلمات وتجنب استخدام الكلمة 'عنوان':",
 	"Create a modelfile": "إنشاء ملف نموذجي",
 	"Create a modelfile": "إنشاء ملف نموذجي",
 	"Create Account": "إنشاء حساب",
 	"Create Account": "إنشاء حساب",
-	"Create new key": "",
-	"Create new secret key": "",
+	"Create new key": "عمل مفتاح جديد",
+	"Create new secret key": "عمل سر جديد",
 	"Created at": "أنشئت في",
 	"Created at": "أنشئت في",
 	"Created At": "أنشئت من",
 	"Created At": "أنشئت من",
 	"Current Model": "الموديل المختار",
 	"Current Model": "الموديل المختار",
@@ -116,27 +117,26 @@
 	"Custom": "مخصص",
 	"Custom": "مخصص",
 	"Customize Ollama models for a specific purpose": "تخصيص الموديل Ollama لغرض محدد",
 	"Customize Ollama models for a specific purpose": "تخصيص الموديل Ollama لغرض محدد",
 	"Dark": "مظلم",
 	"Dark": "مظلم",
-	"Dashboard": "",
+	"Dashboard": "لوحة التحكم",
 	"Database": "قاعدة البيانات",
 	"Database": "قاعدة البيانات",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
-	"December": "",
+	"December": "ديسمبر",
 	"Default": "الإفتراضي",
 	"Default": "الإفتراضي",
-	"Default (Automatic1111)": "الإفتراضي (Automatic1111)",
-	"Default (SentenceTransformers)": "الإفتراضي (SentenceTransformers)",
-	"Default (Web API)": "الإفتراضي (Web API)",
+	"Default (Automatic1111)": "(Automatic1111) الإفتراضي",
+	"Default (SentenceTransformers)": "(SentenceTransformers) الإفتراضي",
+	"Default (Web API)": "(Web API) الإفتراضي",
 	"Default model updated": "الإفتراضي تحديث الموديل",
 	"Default model updated": "الإفتراضي تحديث الموديل",
 	"Default Prompt Suggestions": "الإفتراضي Prompt الاقتراحات",
 	"Default Prompt Suggestions": "الإفتراضي Prompt الاقتراحات",
 	"Default User Role": "الإفتراضي صلاحيات المستخدم",
 	"Default User Role": "الإفتراضي صلاحيات المستخدم",
 	"delete": "حذف",
 	"delete": "حذف",
-	"Delete": "حذف.",
+	"Delete": "حذف",
 	"Delete a model": "حذف الموديل",
 	"Delete a model": "حذف الموديل",
 	"Delete chat": "حذف المحادثه",
 	"Delete chat": "حذف المحادثه",
 	"Delete Chat": "حذف المحادثه.",
 	"Delete Chat": "حذف المحادثه.",
-	"Delete Chats": "حذ المحادثات",
-	"delete this link": "",
+	"Delete Chats": "حذف المحادثات",
+	"delete this link": "أحذف هذا الرابط",
 	"Delete User": "حذف المستخدم",
 	"Delete User": "حذف المستخدم",
-	"Deleted {{deleteModelTag}}": "حذف {{deleteModelTag}}",
-	"Deleted {{tagName}}": "حذف {{tagName}}",
+	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} حذف",
+	"Deleted {{tagName}}": "{{tagName}} حذف",
 	"Description": "وصف",
 	"Description": "وصف",
 	"Didn't fully follow instructions": "لم أتبع التعليمات بشكل كامل",
 	"Didn't fully follow instructions": "لم أتبع التعليمات بشكل كامل",
 	"Disabled": "تعطيل",
 	"Disabled": "تعطيل",
@@ -153,7 +153,7 @@
 	"Don't have an account?": "ليس لديك حساب؟",
 	"Don't have an account?": "ليس لديك حساب؟",
 	"Don't like the style": "لا أحب النمط",
 	"Don't like the style": "لا أحب النمط",
 	"Download": "تحميل",
 	"Download": "تحميل",
-	"Download canceled": "",
+	"Download canceled": "تم اللغاء التحميل",
 	"Download Database": "تحميل قاعدة البيانات",
 	"Download Database": "تحميل قاعدة البيانات",
 	"Drop any files here to add to the conversation": "أسقط أية ملفات هنا لإضافتها إلى المحادثة",
 	"Drop any files here to add to the conversation": "أسقط أية ملفات هنا لإضافتها إلى المحادثة",
 	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "e.g. '30s','10m'. الوحدات الزمنية الصالحة هي 's', 'm', 'h'.",
 	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "e.g. '30s','10m'. الوحدات الزمنية الصالحة هي 's', 'm', 'h'.",
@@ -171,15 +171,15 @@
 	"Enter {{role}} message here": "أدخل رسالة {{role}} هنا",
 	"Enter {{role}} message here": "أدخل رسالة {{role}} هنا",
 	"Enter Chunk Overlap": "أدخل Chunk المتداخل",
 	"Enter Chunk Overlap": "أدخل Chunk المتداخل",
 	"Enter Chunk Size": "أدخل Chunk الحجم",
 	"Enter Chunk Size": "أدخل Chunk الحجم",
-	"Enter Image Size (e.g. 512x512)": "أدخل حجم الصورة (e.g. 512x512)",
-	"Enter language codes": "",
+	"Enter Image Size (e.g. 512x512)": "(e.g. 512x512) أدخل حجم الصورة ",
+	"Enter language codes": "أدخل كود اللغة",
 	"Enter LiteLLM API Base URL (litellm_params.api_base)": "أدخل عنوان URL الأساسي لواجهة برمجة تطبيقات LiteLLM (litellm_params.api_base)",
 	"Enter LiteLLM API Base URL (litellm_params.api_base)": "أدخل عنوان URL الأساسي لواجهة برمجة تطبيقات LiteLLM (litellm_params.api_base)",
 	"Enter LiteLLM API Key (litellm_params.api_key)": "أدخل مفتاح LiteLLM API (litellm_params.api_key)",
 	"Enter LiteLLM API Key (litellm_params.api_key)": "أدخل مفتاح LiteLLM API (litellm_params.api_key)",
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "أدخل LiteLLM API RPM (litllm_params.rpm)",
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "أدخل LiteLLM API RPM (litllm_params.rpm)",
 	"Enter LiteLLM Model (litellm_params.model)": "أدخل LiteLLM الموديل (litellm_params.model)",
 	"Enter LiteLLM Model (litellm_params.model)": "أدخل LiteLLM الموديل (litellm_params.model)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "أدخل أكثر Tokens (litellm_params.max_tokens)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "أدخل أكثر Tokens (litellm_params.max_tokens)",
-	"Enter model tag (e.g. {{modelTag}})": "أدخل الموديل تاق (e.g. {{modelTag}})",
-	"Enter Number of Steps (e.g. 50)": "أدخل عدد الخطوات (e.g. 50)",
+	"Enter model tag (e.g. {{modelTag}})": "(e.g. {{modelTag}}) أدخل الموديل تاق",
+	"Enter Number of Steps (e.g. 50)": "(e.g. 50) أدخل عدد الخطوات",
 	"Enter Score": "أدخل النتيجة",
 	"Enter Score": "أدخل النتيجة",
 	"Enter stop sequence": "أدخل تسلسل التوقف",
 	"Enter stop sequence": "أدخل تسلسل التوقف",
 	"Enter Top K": "Enter Top K",
 	"Enter Top K": "Enter Top K",
@@ -197,7 +197,7 @@
 	"Export Prompts": "مطالبات التصدير",
 	"Export Prompts": "مطالبات التصدير",
 	"Failed to create API Key.": "فشل في إنشاء مفتاح API.",
 	"Failed to create API Key.": "فشل في إنشاء مفتاح API.",
 	"Failed to read clipboard contents": "فشل في قراءة محتويات الحافظة",
 	"Failed to read clipboard contents": "فشل في قراءة محتويات الحافظة",
-	"February": "",
+	"February": "فبراير",
 	"Feel free to add specific details": "لا تتردد في إضافة تفاصيل محددة",
 	"Feel free to add specific details": "لا تتردد في إضافة تفاصيل محددة",
 	"File Mode": "وضع الملف",
 	"File Mode": "وضع الملف",
 	"File not found.": "لم يتم العثور على الملف.",
 	"File not found.": "لم يتم العثور على الملف.",
@@ -213,9 +213,10 @@
 	"Generating search query": "",
 	"Generating search query": "",
 	"Generation Info": "معلومات الجيل",
 	"Generation Info": "معلومات الجيل",
 	"Good Response": "استجابة جيدة",
 	"Good Response": "استجابة جيدة",
+	"h:mm a": "",
 	"has no conversations.": "ليس لديه محادثات.",
 	"has no conversations.": "ليس لديه محادثات.",
-	"Hello, {{name}}": "مرحبا, {{name}}",
-	"Help": "",
+	"Hello, {{name}}": " {{name}} مرحبا",
+	"Help": "مساعدة",
 	"Hide": "أخفاء",
 	"Hide": "أخفاء",
 	"Hide Additional Params": "إخفاء المعلمات الإضافية",
 	"Hide Additional Params": "إخفاء المعلمات الإضافية",
 	"How can I help you today?": "كيف استطيع مساعدتك اليوم؟",
 	"How can I help you today?": "كيف استطيع مساعدتك اليوم؟",
@@ -231,12 +232,12 @@
 	"Include `--api` flag when running stable-diffusion-webui": "قم بتضمين علامة `-api` عند تشغيل Stable-diffusion-webui",
 	"Include `--api` flag when running stable-diffusion-webui": "قم بتضمين علامة `-api` عند تشغيل Stable-diffusion-webui",
 	"Input commands": "",
 	"Input commands": "",
 	"Interface": "واجهه المستخدم",
 	"Interface": "واجهه المستخدم",
-	"Invalid Tag": "",
-	"January": "",
+	"Invalid Tag": "تاق غير صالحة",
+	"January": "يناير",
 	"join our Discord for help.": "انضم إلى Discord للحصول على المساعدة.",
 	"join our Discord for help.": "انضم إلى Discord للحصول على المساعدة.",
 	"JSON": "JSON",
 	"JSON": "JSON",
-	"July": "",
-	"June": "",
+	"July": "يوليو",
+	"June": "يونيو",
 	"JWT Expiration": "JWT تجريبي",
 	"JWT Expiration": "JWT تجريبي",
 	"JWT Token": "JWT Token",
 	"JWT Token": "JWT Token",
 	"Keep Alive": "Keep Alive",
 	"Keep Alive": "Keep Alive",
@@ -244,18 +245,18 @@
 	"Language": "اللغة",
 	"Language": "اللغة",
 	"Last Active": "آخر نشاط",
 	"Last Active": "آخر نشاط",
 	"Light": "فاتح",
 	"Light": "فاتح",
-	"Listening...": "جاري الاستماع...",
-	"LLMs can make mistakes. Verify important information.": "يمكن أن يرتكب LLM الأخطاء. التحقق من المعلومات الهامة.",
-	"Made by OpenWebUI Community": "تم إنشاؤه بواسطة مجتمع OpenWebUI",
+	"Listening...": "جاري الاستماع",
+	"LLMs can make mistakes. Verify important information.": "يمكن أن تصدر بعض الأخطاء. لذلك يجب التحقق من المعلومات المهمة",
+	"Made by OpenWebUI Community": "OpenWebUI تم إنشاؤه بواسطة مجتمع ",
 	"Make sure to enclose them with": "تأكد من إرفاقها",
 	"Make sure to enclose them with": "تأكد من إرفاقها",
-	"Manage LiteLLM Models": "إدارة نماذج LiteLLM",
+	"Manage LiteLLM Models": "LiteLLM إدارة نماذج ",
 	"Manage Models": "إدارة النماذج",
 	"Manage Models": "إدارة النماذج",
-	"Manage Ollama Models": "إدارة موديلات Ollama",
+	"Manage Ollama Models": "Ollama إدارة موديلات ",
 	"March": "",
 	"March": "",
 	"Max Tokens": "Max Tokens",
 	"Max Tokens": "Max Tokens",
 	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "يمكن تنزيل 3 نماذج كحد أقصى في وقت واحد. الرجاء معاودة المحاولة في وقت لاحق.",
 	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "يمكن تنزيل 3 نماذج كحد أقصى في وقت واحد. الرجاء معاودة المحاولة في وقت لاحق.",
 	"May": "",
 	"May": "",
-	"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "",
+	"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "لن تتم مشاركة الرسائل التي ترسلها بعد إنشاء الرابط الخاص بك. سيتمكن المستخدمون الذين لديهم عنوان URL من عرض الدردشة المشتركة.",
 	"Minimum Score": "الحد الأدنى من النقاط",
 	"Minimum Score": "الحد الأدنى من النقاط",
 	"Mirostat": "Mirostat",
 	"Mirostat": "Mirostat",
 	"Mirostat Eta": "Mirostat Eta",
 	"Mirostat Eta": "Mirostat Eta",
@@ -265,7 +266,7 @@
 	"Model '{{modelName}}' has been successfully downloaded.": "موديل '{{modelName}}'تم تحميله بنجاح",
 	"Model '{{modelName}}' has been successfully downloaded.": "موديل '{{modelName}}'تم تحميله بنجاح",
 	"Model '{{modelTag}}' is already in queue for downloading.": "موديل '{{modelTag}}' جاري تحميلة الرجاء الانتظار",
 	"Model '{{modelTag}}' is already in queue for downloading.": "موديل '{{modelTag}}' جاري تحميلة الرجاء الانتظار",
 	"Model {{modelId}} not found": "موديل {{modelId}} لم يوجد",
 	"Model {{modelId}} not found": "موديل {{modelId}} لم يوجد",
-	"Model {{modelName}} already exists.": "موديل {{modelName}} موجود",
+	"Model {{modelName}} already exists.": "موجود {{modelName}} موديل ",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "تم اكتشاف مسار نظام الملفات النموذجي. الاسم المختصر للنموذج مطلوب للتحديث، ولا يمكن الاستمرار.",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "تم اكتشاف مسار نظام الملفات النموذجي. الاسم المختصر للنموذج مطلوب للتحديث، ولا يمكن الاستمرار.",
 	"Model Name": "أسم الموديل",
 	"Model Name": "أسم الموديل",
 	"Model not selected": "لم تختار موديل",
 	"Model not selected": "لم تختار موديل",
@@ -278,25 +279,22 @@
 	"Modelfiles": "ملفات الموديل",
 	"Modelfiles": "ملفات الموديل",
 	"Models": "الموديلات",
 	"Models": "الموديلات",
 	"More": "المزيد",
 	"More": "المزيد",
-	"My Documents": "مستنداتي",
-	"My Modelfiles": "ملفاتي النموذجية",
-	"My Prompts": "مطالباتي",
 	"Name": "الأسم",
 	"Name": "الأسم",
 	"Name Tag": "أسم التاق",
 	"Name Tag": "أسم التاق",
 	"Name your modelfile": "قم بتسمية ملف النموذج الخاص بك",
 	"Name your modelfile": "قم بتسمية ملف النموذج الخاص بك",
 	"New Chat": "دردشة جديدة",
 	"New Chat": "دردشة جديدة",
 	"New Password": "كلمة المرور الجديدة",
 	"New Password": "كلمة المرور الجديدة",
-	"No results found": "",
+	"No results found": "لا توجد نتايج",
 	"No search query generated": "",
 	"No search query generated": "",
 	"No search results found": "",
 	"No search results found": "",
-	"No source available": "",
+	"No source available": "لا يوجد مصدر متاح",
 	"Not factually correct": "ليس صحيحا من حيث الواقع",
 	"Not factually correct": "ليس صحيحا من حيث الواقع",
 	"Not sure what to add?": "لست متأكدا ما يجب إضافته؟",
 	"Not sure what to add?": "لست متأكدا ما يجب إضافته؟",
 	"Not sure what to write? Switch to": "لست متأكدا ماذا أكتب؟ التبديل إلى",
 	"Not sure what to write? Switch to": "لست متأكدا ماذا أكتب؟ التبديل إلى",
 	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "ملاحظة: إذا قمت بتعيين الحد الأدنى من النقاط، فلن يؤدي البحث إلا إلى إرجاع المستندات التي لها نقاط أكبر من أو تساوي الحد الأدنى من النقاط.",
 	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "ملاحظة: إذا قمت بتعيين الحد الأدنى من النقاط، فلن يؤدي البحث إلا إلى إرجاع المستندات التي لها نقاط أكبر من أو تساوي الحد الأدنى من النقاط.",
 	"Notifications": "إشعارات",
 	"Notifications": "إشعارات",
-	"November": "",
-	"October": "",
+	"November": "نوفمبر",
+	"October": "اكتوبر",
 	"Off": "أغلاق",
 	"Off": "أغلاق",
 	"Okay, Let's Go!": "حسنا دعنا نذهب!",
 	"Okay, Let's Go!": "حسنا دعنا نذهب!",
 	"OLED Dark": "OLED داكن",
 	"OLED Dark": "OLED داكن",
@@ -310,50 +308,50 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "خطاء! يبدو أن عنوان URL غير صالح. يرجى التحقق مرة أخرى والمحاولة مرة أخرى.",
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "خطاء! يبدو أن عنوان URL غير صالح. يرجى التحقق مرة أخرى والمحاولة مرة أخرى.",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "خطاء! أنت تستخدم طريقة غير مدعومة (الواجهة الأمامية فقط). يرجى تقديم واجهة WebUI من الواجهة الخلفية.",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "خطاء! أنت تستخدم طريقة غير مدعومة (الواجهة الأمامية فقط). يرجى تقديم واجهة WebUI من الواجهة الخلفية.",
 	"Open": "فتح",
 	"Open": "فتح",
-	"Open AI": "فتح AI",
-	"Open AI (Dall-E)": "فتح AI (Dall-E)",
+	"Open AI": "AI فتح",
+	"Open AI (Dall-E)": "AI (Dall-E) فتح",
 	"Open new chat": "فتح محادثة جديده",
 	"Open new chat": "فتح محادثة جديده",
 	"OpenAI": "OpenAI",
 	"OpenAI": "OpenAI",
 	"OpenAI API": "OpenAI API",
 	"OpenAI API": "OpenAI API",
 	"OpenAI API Config": "OpenAI API إعدادات",
 	"OpenAI API Config": "OpenAI API إعدادات",
-	"OpenAI API Key is required.": "مطلوب مفتاح OpenAI API.",
-	"OpenAI URL/Key required.": "مطلوب عنوان URL/مفتاح OpenAI.",
+	"OpenAI API Key is required.": "OpenAI API.مطلوب مفتاح ",
+	"OpenAI URL/Key required.": "URL/مفتاح OpenAI.مطلوب عنوان ",
 	"or": "أو",
 	"or": "أو",
 	"Other": "آخر",
 	"Other": "آخر",
-	"Overview": "",
+	"Overview": "عرض",
 	"Parameters": "Parameters",
 	"Parameters": "Parameters",
 	"Password": "الباسورد",
 	"Password": "الباسورد",
 	"PDF document (.pdf)": "PDF ملف (.pdf)",
 	"PDF document (.pdf)": "PDF ملف (.pdf)",
 	"PDF Extract Images (OCR)": "PDF أستخرج الصور (OCR)",
 	"PDF Extract Images (OCR)": "PDF أستخرج الصور (OCR)",
 	"pending": "قيد الانتظار",
 	"pending": "قيد الانتظار",
-	"Permission denied when accessing microphone: {{error}}": "تم رفض الإذن عند الوصول إلى الميكروفون: {{error}}",
+	"Permission denied when accessing microphone: {{error}}": "{{error}} تم رفض الإذن عند الوصول إلى الميكروفون ",
 	"Plain text (.txt)": "نص عادي (.txt)",
 	"Plain text (.txt)": "نص عادي (.txt)",
 	"Playground": "مكان التجربة",
 	"Playground": "مكان التجربة",
 	"Positive attitude": "موقف ايجابي",
 	"Positive attitude": "موقف ايجابي",
-	"Previous 30 days": "",
-	"Previous 7 days": "",
+	"Previous 30 days": "أخر 30 يوم",
+	"Previous 7 days": "أخر 7 أيام",
 	"Profile Image": "صورة الملف الشخصي",
 	"Profile Image": "صورة الملف الشخصي",
-	"Prompt": "",
+	"Prompt": "مطالبة",
 	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "موجه (على سبيل المثال: أخبرني بحقيقة ممتعة عن الإمبراطورية الرومانية)",
 	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "موجه (على سبيل المثال: أخبرني بحقيقة ممتعة عن الإمبراطورية الرومانية)",
 	"Prompt Content": "محتوى عاجل",
 	"Prompt Content": "محتوى عاجل",
 	"Prompt suggestions": "اقتراحات سريعة",
 	"Prompt suggestions": "اقتراحات سريعة",
-	"Prompts": "حث",
-	"Pull \"{{searchValue}}\" from Ollama.com": "",
-	"Pull a model from Ollama.com": "سحب الموديل من Ollama.com",
+	"Prompts": "مطالبات",
+	"Pull \"{{searchValue}}\" from Ollama.com": "Ollama.com \"{{searchValue}}\" أسحب من ",
+	"Pull a model from Ollama.com": "Ollama.com سحب الموديل من ",
 	"Pull Progress": "سحب التقدم",
 	"Pull Progress": "سحب التقدم",
 	"Query Params": "Query Params",
 	"Query Params": "Query Params",
 	"RAG Template": "RAG تنمبلت",
 	"RAG Template": "RAG تنمبلت",
 	"Raw Format": "Raw فورمات",
 	"Raw Format": "Raw فورمات",
 	"Read Aloud": "أقراء لي",
 	"Read Aloud": "أقراء لي",
 	"Record voice": "سجل صوت",
 	"Record voice": "سجل صوت",
-	"Redirecting you to OpenWebUI Community": "إعادة توجيهك إلى مجتمع OpenWebUI",
+	"Redirecting you to OpenWebUI Community": "OpenWebUI إعادة توجيهك إلى مجتمع ",
 	"Refused when it shouldn't have": "رفض عندما لا ينبغي أن يكون",
 	"Refused when it shouldn't have": "رفض عندما لا ينبغي أن يكون",
 	"Regenerate": "تجديد",
 	"Regenerate": "تجديد",
 	"Release Notes": "ملاحظات الإصدار",
 	"Release Notes": "ملاحظات الإصدار",
 	"Remove": "إزالة",
 	"Remove": "إزالة",
-	"Remove Model": "",
-	"Rename": "",
-	"Repeat Last N": "كرر آخر N",
+	"Remove Model": "حذف الموديل",
+	"Rename": "إعادة تسمية",
+	"Repeat Last N": "N كرر آخر",
 	"Repeat Penalty": "كرر المخالفة",
 	"Repeat Penalty": "كرر المخالفة",
 	"Request Mode": "وضع الطلب",
 	"Request Mode": "وضع الطلب",
 	"Reranking Model": "",
 	"Reranking Model": "",
@@ -366,28 +364,28 @@
 	"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": "مسح",
 	"Scan complete!": "تم المسح",
 	"Scan complete!": "تم المسح",
-	"Scan for documents from {{path}}": " مسح على الملفات من {{path}}",
+	"Scan for documents from {{path}}": "{{path}} مسح على الملفات من",
 	"Search": "البحث",
 	"Search": "البحث",
 	"Search a model": "البحث عن موديل",
 	"Search a model": "البحث عن موديل",
 	"Search Documents": "البحث المستندات",
 	"Search Documents": "البحث المستندات",
 	"Search Prompts": "أبحث حث",
 	"Search Prompts": "أبحث حث",
 	"Search Results": "",
 	"Search Results": "",
 	"Searching the web for '{{searchQuery}}'": "",
 	"Searching the web for '{{searchQuery}}'": "",
-	"See readme.md for instructions": "راجع readme.md للحصول على التعليمات",
+	"See readme.md for instructions": "readme.md للحصول على التعليمات",
 	"See what's new": "ما الجديد",
 	"See what's new": "ما الجديد",
 	"Seed": "Seed",
 	"Seed": "Seed",
 	"Select a mode": "أختار موديل",
 	"Select a mode": "أختار موديل",
 	"Select a model": "أختار الموديل",
 	"Select a model": "أختار الموديل",
-	"Select an Ollama instance": "أختار سيرفر Ollama",
-	"Select model": "",
-	"Send a Message": "أرسل رسالة.",
-	"Send message": "أرسل رسالة",
-	"September": "",
+	"Select an Ollama instance": "أختار سيرفر ",
+	"Select model": " أختار موديل",
+	"Send": "",
+	"Send a Message": "يُرجى إدخال طلبك هنا",
+	"Send message": "يُرجى إدخال طلبك هنا.",
+	"September": "سبتمبر",
 	"Server connection verified": "تم التحقق من اتصال الخادم",
 	"Server connection verified": "تم التحقق من اتصال الخادم",
 	"Set as default": "الافتراضي",
 	"Set as default": "الافتراضي",
 	"Set Default Model": "تفعيد الموديل الافتراضي",
 	"Set Default Model": "تفعيد الموديل الافتراضي",
@@ -402,7 +400,7 @@
 	"Settings saved successfully!": "تم حفظ الاعدادات بنجاح",
 	"Settings saved successfully!": "تم حفظ الاعدادات بنجاح",
 	"Share": "كشاركة",
 	"Share": "كشاركة",
 	"Share Chat": "مشاركة الدردشة",
 	"Share Chat": "مشاركة الدردشة",
-	"Share to OpenWebUI Community": "شارك في مجتمع OpenWebUI",
+	"Share to OpenWebUI Community": "OpenWebUI شارك في مجتمع",
 	"short-summary": "ملخص قصير",
 	"short-summary": "ملخص قصير",
 	"Show": "عرض",
 	"Show": "عرض",
 	"Show Additional Params": "إظهار المعلمات الإضافية",
 	"Show Additional Params": "إظهار المعلمات الإضافية",
@@ -413,17 +411,17 @@
 	"Sign Out": "تسجيل الخروج",
 	"Sign Out": "تسجيل الخروج",
 	"Sign up": "تسجيل",
 	"Sign up": "تسجيل",
 	"Signing in": "جاري الدخول",
 	"Signing in": "جاري الدخول",
-	"Source": "",
-	"Speech recognition error: {{error}}": "خطأ في التعرف على الكلام: {{error}}",
+	"Source": "المصدر",
+	"Speech recognition error: {{error}}": "{{error}} خطأ في التعرف على الكلام",
 	"Speech-to-Text Engine": "محرك تحويل الكلام إلى نص",
 	"Speech-to-Text Engine": "محرك تحويل الكلام إلى نص",
 	"SpeechRecognition API is not supported in this browser.": "API SpeechRecognition غير مدعومة في هذا المتصفح.",
 	"SpeechRecognition API is not supported in this browser.": "API SpeechRecognition غير مدعومة في هذا المتصفح.",
 	"Stop Sequence": "وقف التسلسل",
 	"Stop Sequence": "وقف التسلسل",
 	"STT Settings": "STT اعدادات",
 	"STT Settings": "STT اعدادات",
 	"Submit": "إرسال",
 	"Submit": "إرسال",
-	"Subtitle (e.g. about the Roman Empire)": "الترجمة (e.g. about the Roman Empire)",
+	"Subtitle (e.g. about the Roman Empire)": "(e.g. about the Roman Empire) الترجمة",
 	"Success": "نجاح",
 	"Success": "نجاح",
-	"Successfully updated.": "تم التحديث بنجاح.",
-	"Suggested": "",
+	"Successfully updated.": "تم التحديث بنجاح",
+	"Suggested": "مقترحات",
 	"Sync All": "مزامنة الكل",
 	"Sync All": "مزامنة الكل",
 	"System": "النظام",
 	"System": "النظام",
 	"System Prompt": "محادثة النظام",
 	"System Prompt": "محادثة النظام",
@@ -442,33 +440,33 @@
 	"Thorough explanation": "شرح شامل",
 	"Thorough explanation": "شرح شامل",
 	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "ملاحضة: قم بتحديث عدة فتحات متغيرة على التوالي عن طريق الضغط على مفتاح tab في مدخلات الدردشة بعد كل استبدال.",
 	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "ملاحضة: قم بتحديث عدة فتحات متغيرة على التوالي عن طريق الضغط على مفتاح tab في مدخلات الدردشة بعد كل استبدال.",
 	"Title": "العنوان",
 	"Title": "العنوان",
-	"Title (e.g. Tell me a fun fact)": "العناون (e.g. Tell me a fun fact)",
+	"Title (e.g. Tell me a fun fact)": "(e.g. Tell me a fun fact) العناون",
 	"Title Auto-Generation": "توليد تلقائي للعنوان",
 	"Title Auto-Generation": "توليد تلقائي للعنوان",
-	"Title cannot be an empty string.": "",
+	"Title cannot be an empty string.": "العنوان مطلوب",
 	"Title Generation Prompt": "موجه إنشاء العنوان",
 	"Title Generation Prompt": "موجه إنشاء العنوان",
 	"to": "الى",
 	"to": "الى",
 	"To access the available model names for downloading,": "للوصول إلى أسماء الموديلات المتاحة للتنزيل،",
 	"To access the available model names for downloading,": "للوصول إلى أسماء الموديلات المتاحة للتنزيل،",
 	"To access the GGUF models available for downloading,": "للوصول إلى الموديلات GGUF المتاحة للتنزيل،",
 	"To access the GGUF models available for downloading,": "للوصول إلى الموديلات GGUF المتاحة للتنزيل،",
 	"to chat input.": "الى كتابة المحادثه",
 	"to chat input.": "الى كتابة المحادثه",
-	"Today": "",
+	"Today": "اليوم",
 	"Toggle settings": "فتح وأغلاق الاعدادات",
 	"Toggle settings": "فتح وأغلاق الاعدادات",
 	"Toggle sidebar": "فتح وأغلاق الشريط الجانبي",
 	"Toggle sidebar": "فتح وأغلاق الشريط الجانبي",
 	"Top K": "Top K",
 	"Top K": "Top K",
 	"Top P": "Top P",
 	"Top P": "Top P",
-	"Trouble accessing Ollama?": "هل تواجه مشكلة في الوصول إلى Olma؟",
+	"Trouble accessing Ollama?": "هل تواجه مشكلة في الوصول",
 	"TTS Settings": "TTS اعدادات",
 	"TTS Settings": "TTS اعدادات",
 	"Type Hugging Face Resolve (Download) URL": "اكتب عنوان URL لحل مشكلة الوجه (تنزيل).",
 	"Type Hugging Face Resolve (Download) URL": "اكتب عنوان URL لحل مشكلة الوجه (تنزيل).",
-	"Uh-oh! There was an issue connecting to {{provider}}.": "خطاء أوه! حدثت مشكلة في الاتصال بـ {{provider}}.",
+	"Uh-oh! There was an issue connecting to {{provider}}.": "{{provider}}خطاء أوه! حدثت مشكلة في الاتصال بـ ",
 	"Unknown File Type '{{file_type}}', but accepting and treating as plain text": "نوع ملف غير معروف '{{file_type}}', ولكن القبول والتعامل كنص عادي ",
 	"Unknown File Type '{{file_type}}', but accepting and treating as plain text": "نوع ملف غير معروف '{{file_type}}', ولكن القبول والتعامل كنص عادي ",
 	"Update and Copy Link": "تحديث ونسخ الرابط",
 	"Update and Copy Link": "تحديث ونسخ الرابط",
 	"Update password": "تحديث كلمة المرور",
 	"Update password": "تحديث كلمة المرور",
-	"Upload a GGUF model": "رفع موديل نوع GGUF",
+	"Upload a GGUF model": "GGUF رفع موديل نوع",
 	"Upload files": "رفع الملفات",
 	"Upload files": "رفع الملفات",
 	"Upload Progress": "جاري التحميل",
 	"Upload Progress": "جاري التحميل",
 	"URL Mode": "رابط الموديل",
 	"URL Mode": "رابط الموديل",
 	"Use '#' in the prompt input to load and select your documents.": "أستخدم '#' في المحادثة لربطهامن المستندات",
 	"Use '#' in the prompt input to load and select your documents.": "أستخدم '#' في المحادثة لربطهامن المستندات",
-	"Use Gravatar": "أستخدم Gravatar",
-	"Use Initials": "أستخدم Initials",
+	"Use Gravatar": "Gravatar أستخدم",
+	"Use Initials": "Initials أستخدم",
 	"user": "مستخدم",
 	"user": "مستخدم",
 	"User Permissions": "صلاحيات المستخدم",
 	"User Permissions": "صلاحيات المستخدم",
 	"Users": "المستخدمين",
 	"Users": "المستخدمين",
@@ -490,12 +488,13 @@
 	"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 كلمة يلخص [الموضوع أو الكلمة الرئيسية].",
-	"Yesterday": "",
-	"You": "أنت",
-	"You have no archived conversations.": "",
-	"You have shared this chat": "",
+	"Write a summary in 50 words that summarizes [topic or keyword].": "اكتب ملخصًا في 50 كلمة يلخص [الموضوع أو الكلمة الرئيسية]",
+	"Yesterday": "أمس",
+	"You": "",
+	"You have no archived conversations.": "لا تملك محادثات محفوظه",
+	"You have shared this chat": "تم مشاركة هذه المحادثة",
 	"You're a helpful assistant.": "مساعدك المفيد هنا",
 	"You're a helpful assistant.": "مساعدك المفيد هنا",
 	"You're now logged in.": "لقد قمت الآن بتسجيل الدخول.",
 	"You're now logged in.": "لقد قمت الآن بتسجيل الدخول.",
 	"Youtube": "Youtube",
 	"Youtube": "Youtube",

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

@@ -67,6 +67,7 @@
 	"Categories": "Категории",
 	"Categories": "Категории",
 	"Change Password": "Промяна на Парола",
 	"Change Password": "Промяна на Парола",
 	"Chat": "Чат",
 	"Chat": "Чат",
+	"Chat Bubble UI": "",
 	"Chat History": "Чат История",
 	"Chat History": "Чат История",
 	"Chat History is off for this browser.": "Чат История е изключен за този браузър.",
 	"Chat History is off for this browser.": "Чат История е изключен за този браузър.",
 	"Chats": "Чатове",
 	"Chats": "Чатове",
@@ -118,7 +119,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)",
@@ -213,6 +213,7 @@
 	"Generating search query": "",
 	"Generating search query": "",
 	"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": "",
@@ -278,9 +279,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": "Име на модфайла",
@@ -366,7 +364,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": "Сканиране",
@@ -385,6 +382,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": "",
@@ -490,10 +488,11 @@
 	"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": "",
 	"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.": "Вие сте полезен асистент.",

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

@@ -67,6 +67,7 @@
 	"Categories": "ক্যাটাগরিসমূহ",
 	"Categories": "ক্যাটাগরিসমূহ",
 	"Change Password": "পাসওয়ার্ড পরিবর্তন করুন",
 	"Change Password": "পাসওয়ার্ড পরিবর্তন করুন",
 	"Chat": "চ্যাট",
 	"Chat": "চ্যাট",
+	"Chat Bubble UI": "",
 	"Chat History": "চ্যাট হিস্টোরি",
 	"Chat History": "চ্যাট হিস্টোরি",
 	"Chat History is off for this browser.": "এই ব্রাউজারের জন্য চ্যাট হিস্টোরি বন্ধ আছে",
 	"Chat History is off for this browser.": "এই ব্রাউজারের জন্য চ্যাট হিস্টোরি বন্ধ আছে",
 	"Chats": "চ্যাটসমূহ",
 	"Chats": "চ্যাটসমূহ",
@@ -118,7 +119,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)",
@@ -213,6 +213,7 @@
 	"Generating search query": "",
 	"Generating search query": "",
 	"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": "",
@@ -278,9 +279,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": "আপনার মডেলফাইলের নাম দিন",
@@ -366,7 +364,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": "স্ক্যান",
@@ -385,6 +382,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": "",
@@ -490,10 +488,11 @@
 	"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": "",
 	"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.": "আপনি একজন উপকারী এসিস্ট্যান্ট",

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

@@ -67,6 +67,7 @@
 	"Categories": "Categories",
 	"Categories": "Categories",
 	"Change Password": "Canvia la Contrasenya",
 	"Change Password": "Canvia la Contrasenya",
 	"Chat": "Xat",
 	"Chat": "Xat",
+	"Chat Bubble UI": "",
 	"Chat History": "Històric del Xat",
 	"Chat History": "Històric del Xat",
 	"Chat History is off for this browser.": "L'historial de xat està desactivat per a aquest navegador.",
 	"Chat History is off for this browser.": "L'historial de xat està desactivat per a aquest navegador.",
 	"Chats": "Xats",
 	"Chats": "Xats",
@@ -118,7 +119,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)",
@@ -213,6 +213,7 @@
 	"Generating search query": "",
 	"Generating search query": "",
 	"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": "",
@@ -278,9 +279,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",
@@ -366,7 +364,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",
@@ -385,6 +382,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": "",
@@ -490,10 +488,11 @@
 	"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": "",
 	"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.",

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

@@ -67,6 +67,7 @@
 	"Categories": "Kategorien",
 	"Categories": "Kategorien",
 	"Change Password": "Passwort ändern",
 	"Change Password": "Passwort ändern",
 	"Chat": "Chat",
 	"Chat": "Chat",
+	"Chat Bubble UI": "",
 	"Chat History": "Chat Verlauf",
 	"Chat History": "Chat Verlauf",
 	"Chat History is off for this browser.": "Chat Verlauf ist für diesen Browser ausgeschaltet.",
 	"Chat History is off for this browser.": "Chat Verlauf ist für diesen Browser ausgeschaltet.",
 	"Chats": "Chats",
 	"Chats": "Chats",
@@ -118,7 +119,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)",
@@ -213,6 +213,7 @@
 	"Generating search query": "",
 	"Generating search query": "",
 	"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",
@@ -278,9 +279,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",
@@ -366,7 +364,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",
@@ -385,6 +382,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",
@@ -490,10 +488,11 @@
 	"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": "",
 	"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.",

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

@@ -67,6 +67,7 @@
 	"Categories": "Categories",
 	"Categories": "Categories",
 	"Change Password": "Change Password",
 	"Change Password": "Change Password",
 	"Chat": "Chat",
 	"Chat": "Chat",
+	"Chat Bubble UI": "",
 	"Chat History": "Chat History",
 	"Chat History": "Chat History",
 	"Chat History is off for this browser.": "Chat History off for this browser. Such sadness.",
 	"Chat History is off for this browser.": "Chat History off for this browser. Such sadness.",
 	"Chats": "Chats",
 	"Chats": "Chats",
@@ -118,7 +119,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)",
@@ -213,6 +213,7 @@
 	"Generating search query": "",
 	"Generating search query": "",
 	"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": "",
@@ -278,9 +279,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",
@@ -366,7 +364,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",
@@ -385,6 +382,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": "",
@@ -490,10 +488,11 @@
 	"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": "",
 	"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.",

+ 4 - 5
src/lib/i18n/locales/en-GB/translation.json

@@ -67,6 +67,7 @@
 	"Categories": "",
 	"Categories": "",
 	"Change Password": "",
 	"Change Password": "",
 	"Chat": "",
 	"Chat": "",
+	"Chat Bubble UI": "",
 	"Chat History": "",
 	"Chat History": "",
 	"Chat History is off for this browser.": "",
 	"Chat History is off for this browser.": "",
 	"Chats": "",
 	"Chats": "",
@@ -118,7 +119,6 @@
 	"Dark": "",
 	"Dark": "",
 	"Dashboard": "",
 	"Dashboard": "",
 	"Database": "",
 	"Database": "",
-	"DD/MM/YYYY HH:mm": "",
 	"December": "",
 	"December": "",
 	"Default": "",
 	"Default": "",
 	"Default (Automatic1111)": "",
 	"Default (Automatic1111)": "",
@@ -213,6 +213,7 @@
 	"Generating search query": "",
 	"Generating search query": "",
 	"Generation Info": "",
 	"Generation Info": "",
 	"Good Response": "",
 	"Good Response": "",
+	"h:mm a": "",
 	"has no conversations.": "",
 	"has no conversations.": "",
 	"Hello, {{name}}": "",
 	"Hello, {{name}}": "",
 	"Help": "",
 	"Help": "",
@@ -278,9 +279,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": "",
@@ -366,7 +364,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": "",
@@ -385,6 +382,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": "",
@@ -490,6 +488,7 @@
 	"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": "",

+ 4 - 5
src/lib/i18n/locales/en-US/translation.json

@@ -67,6 +67,7 @@
 	"Categories": "",
 	"Categories": "",
 	"Change Password": "",
 	"Change Password": "",
 	"Chat": "",
 	"Chat": "",
+	"Chat Bubble UI": "",
 	"Chat History": "",
 	"Chat History": "",
 	"Chat History is off for this browser.": "",
 	"Chat History is off for this browser.": "",
 	"Chats": "",
 	"Chats": "",
@@ -118,7 +119,6 @@
 	"Dark": "",
 	"Dark": "",
 	"Dashboard": "",
 	"Dashboard": "",
 	"Database": "",
 	"Database": "",
-	"DD/MM/YYYY HH:mm": "",
 	"December": "",
 	"December": "",
 	"Default": "",
 	"Default": "",
 	"Default (Automatic1111)": "",
 	"Default (Automatic1111)": "",
@@ -213,6 +213,7 @@
 	"Generating search query": "",
 	"Generating search query": "",
 	"Generation Info": "",
 	"Generation Info": "",
 	"Good Response": "",
 	"Good Response": "",
+	"h:mm a": "",
 	"has no conversations.": "",
 	"has no conversations.": "",
 	"Hello, {{name}}": "",
 	"Hello, {{name}}": "",
 	"Help": "",
 	"Help": "",
@@ -278,9 +279,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": "",
@@ -366,7 +364,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": "",
@@ -385,6 +382,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": "",
@@ -490,6 +488,7 @@
 	"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": "",

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

@@ -67,6 +67,7 @@
 	"Categories": "Categorías",
 	"Categories": "Categorías",
 	"Change Password": "Cambia la Contraseña",
 	"Change Password": "Cambia la Contraseña",
 	"Chat": "Chat",
 	"Chat": "Chat",
+	"Chat Bubble UI": "",
 	"Chat History": "Historial del Chat",
 	"Chat History": "Historial del Chat",
 	"Chat History is off for this browser.": "El Historial del Chat está apagado para este navegador.",
 	"Chat History is off for this browser.": "El Historial del Chat está apagado para este navegador.",
 	"Chats": "Chats",
 	"Chats": "Chats",
@@ -118,7 +119,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)",
@@ -213,6 +213,7 @@
 	"Generating search query": "",
 	"Generating search query": "",
 	"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": "",
@@ -278,9 +279,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",
@@ -366,7 +364,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",
@@ -385,6 +382,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": "",
@@ -490,10 +488,11 @@
 	"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": "",
 	"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.",

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

@@ -67,6 +67,7 @@
 	"Categories": "دسته\u200cبندی\u200cها",
 	"Categories": "دسته\u200cبندی\u200cها",
 	"Change Password": "تغییر رمز عبور",
 	"Change Password": "تغییر رمز عبور",
 	"Chat": "گپ",
 	"Chat": "گپ",
+	"Chat Bubble UI": "",
 	"Chat History": "تاریخچه\u200cی گفتگو",
 	"Chat History": "تاریخچه\u200cی گفتگو",
 	"Chat History is off for this browser.": "سابقه گپ برای این مرورگر خاموش است.",
 	"Chat History is off for this browser.": "سابقه گپ برای این مرورگر خاموش است.",
 	"Chats": "گپ\u200cها",
 	"Chats": "گپ\u200cها",
@@ -118,7 +119,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)",
@@ -213,6 +213,7 @@
 	"Generating search query": "",
 	"Generating search query": "",
 	"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": "",
@@ -278,9 +279,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گذاری کنید",
@@ -366,7 +364,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": "اسکن",
@@ -385,6 +382,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": "",
@@ -490,10 +488,11 @@
 	"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": "",
 	"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.": "تو یک دستیار سودمند هستی.",

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

@@ -67,6 +67,7 @@
 	"Categories": "Kategoriat",
 	"Categories": "Kategoriat",
 	"Change Password": "Vaihda salasana",
 	"Change Password": "Vaihda salasana",
 	"Chat": "Keskustelu",
 	"Chat": "Keskustelu",
+	"Chat Bubble UI": "",
 	"Chat History": "Keskusteluhistoria",
 	"Chat History": "Keskusteluhistoria",
 	"Chat History is off for this browser.": "Keskusteluhistoria on pois päältä tällä selaimella.",
 	"Chat History is off for this browser.": "Keskusteluhistoria on pois päältä tällä selaimella.",
 	"Chats": "Keskustelut",
 	"Chats": "Keskustelut",
@@ -118,7 +119,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)",
@@ -213,6 +213,7 @@
 	"Generating search query": "",
 	"Generating search query": "",
 	"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",
@@ -278,9 +279,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",
@@ -366,7 +364,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",
@@ -385,6 +382,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",
@@ -490,10 +488,11 @@
 	"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": "",
 	"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.",

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

@@ -67,6 +67,7 @@
 	"Categories": "Catégories",
 	"Categories": "Catégories",
 	"Change Password": "Changer le mot de passe",
 	"Change Password": "Changer le mot de passe",
 	"Chat": "Discussion",
 	"Chat": "Discussion",
+	"Chat Bubble UI": "",
 	"Chat History": "Historique des discussions",
 	"Chat History": "Historique des discussions",
 	"Chat History is off for this browser.": "L'historique des discussions est désactivé pour ce navigateur.",
 	"Chat History is off for this browser.": "L'historique des discussions est désactivé pour ce navigateur.",
 	"Chats": "Discussions",
 	"Chats": "Discussions",
@@ -118,7 +119,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)",
@@ -213,6 +213,7 @@
 	"Generating search query": "",
 	"Generating search query": "",
 	"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": "",
@@ -278,9 +279,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",
@@ -366,7 +364,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",
@@ -385,6 +382,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": "",
@@ -490,10 +488,11 @@
 	"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": "",
 	"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",

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

@@ -67,6 +67,7 @@
 	"Categories": "Catégories",
 	"Categories": "Catégories",
 	"Change Password": "Changer le mot de passe",
 	"Change Password": "Changer le mot de passe",
 	"Chat": "Chat",
 	"Chat": "Chat",
+	"Chat Bubble UI": "",
 	"Chat History": "Historique du chat",
 	"Chat History": "Historique du chat",
 	"Chat History is off for this browser.": "L'historique du chat est désactivé pour ce navigateur.",
 	"Chat History is off for this browser.": "L'historique du chat est désactivé pour ce navigateur.",
 	"Chats": "Chats",
 	"Chats": "Chats",
@@ -118,7 +119,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)",
@@ -213,6 +213,7 @@
 	"Generating search query": "",
 	"Generating search query": "",
 	"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": "",
@@ -278,9 +279,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",
@@ -366,7 +364,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",
@@ -385,6 +382,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": "",
@@ -490,10 +488,11 @@
 	"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": "",
 	"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",

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

@@ -67,6 +67,7 @@
 	"Categories": "קטגוריות",
 	"Categories": "קטגוריות",
 	"Change Password": "שנה סיסמה",
 	"Change Password": "שנה סיסמה",
 	"Chat": "צ'אט",
 	"Chat": "צ'אט",
+	"Chat Bubble UI": "",
 	"Chat History": "היסטוריית צ'אט",
 	"Chat History": "היסטוריית צ'אט",
 	"Chat History is off for this browser.": "היסטוריית הצ'אט כבויה לדפדפן זה.",
 	"Chat History is off for this browser.": "היסטוריית הצ'אט כבויה לדפדפן זה.",
 	"Chats": "צ'אטים",
 	"Chats": "צ'אטים",
@@ -118,7 +119,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)",
@@ -213,6 +213,7 @@
 	"Generating search query": "",
 	"Generating search query": "",
 	"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": "עזרה",
@@ -278,9 +279,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": "תן שם לקובץ המודל שלך",
@@ -366,7 +364,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": "סרוק",
@@ -385,6 +382,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": "ספטמבר",
@@ -490,10 +488,11 @@
 	"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": "",
 	"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.": "",

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

@@ -67,6 +67,7 @@
 	"Categories": "श्रेणियाँ",
 	"Categories": "श्रेणियाँ",
 	"Change Password": "पासवर्ड बदलें",
 	"Change Password": "पासवर्ड बदलें",
 	"Chat": "चैट करें",
 	"Chat": "चैट करें",
+	"Chat Bubble UI": "",
 	"Chat History": "चैट का इतिहास",
 	"Chat History": "चैट का इतिहास",
 	"Chat History is off for this browser.": "इस ब्राउज़र के लिए चैट इतिहास बंद है।",
 	"Chat History is off for this browser.": "इस ब्राउज़र के लिए चैट इतिहास बंद है।",
 	"Chats": "सभी चैट",
 	"Chats": "सभी चैट",
@@ -118,7 +119,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)",
@@ -213,6 +213,7 @@
 	"Generating search query": "",
 	"Generating search query": "",
 	"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": "",
@@ -278,9 +279,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": "अपनी मॉडलफ़ाइल को नाम दें",
@@ -366,7 +364,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": "स्कैन",
@@ -385,6 +382,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": "",
@@ -490,10 +488,11 @@
 	"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": "",
 	"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.": "आप एक सहायक सहायक हैं",

+ 142 - 143
src/lib/i18n/locales/it-IT/translation.json

@@ -1,35 +1,35 @@
 {
 {
 	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' o '-1' per nessuna scadenza.",
 	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' o '-1' per nessuna scadenza.",
 	"(Beta)": "(Beta)",
 	"(Beta)": "(Beta)",
-	"(e.g. `sh webui.sh --api`)": "",
+	"(e.g. `sh webui.sh --api`)": "(p.e. `sh webui.sh --api`)",
 	"(latest)": "(ultima)",
 	"(latest)": "(ultima)",
 	"{{modelName}} is thinking...": "{{modelName}} sta pensando...",
 	"{{modelName}} is thinking...": "{{modelName}} sta pensando...",
-	"{{user}}'s Chats": "",
+	"{{user}}'s Chats": "{{user}} Chat",
 	"{{webUIName}} Backend Required": "{{webUIName}} Backend richiesto",
 	"{{webUIName}} Backend Required": "{{webUIName}} Backend richiesto",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "",
 	"a user": "un utente",
 	"a user": "un utente",
 	"About": "Informazioni",
 	"About": "Informazioni",
 	"Account": "Account",
 	"Account": "Account",
-	"Accurate information": "",
+	"Accurate information": "Informazioni accurate",
 	"Add a model": "Aggiungi un modello",
 	"Add a model": "Aggiungi un modello",
 	"Add a model tag name": "Aggiungi un nome tag del modello",
 	"Add a model tag name": "Aggiungi un nome tag del modello",
 	"Add a short description about what this modelfile does": "Aggiungi una breve descrizione di ciò che fa questo file modello",
 	"Add a short description about what this modelfile does": "Aggiungi una breve descrizione di ciò che fa questo file modello",
 	"Add a short title for this prompt": "Aggiungi un titolo breve per questo prompt",
 	"Add a short title for this prompt": "Aggiungi un titolo breve per questo prompt",
 	"Add a tag": "Aggiungi un tag",
 	"Add a tag": "Aggiungi un tag",
-	"Add custom prompt": "",
+	"Add custom prompt": "Aggiungi un prompt custom",
 	"Add Docs": "Aggiungi documenti",
 	"Add Docs": "Aggiungi documenti",
 	"Add Files": "Aggiungi file",
 	"Add Files": "Aggiungi file",
 	"Add message": "Aggiungi messaggio",
 	"Add message": "Aggiungi messaggio",
-	"Add Model": "",
-	"Add Tags": "aggiungi tag",
-	"Add User": "",
+	"Add Model": "Aggiungi modello",
+	"Add Tags": "Aggiungi tag",
+	"Add User": "Aggiungi utente",
 	"Adjusting these settings will apply changes universally to all users.": "La modifica di queste impostazioni applicherà le modifiche universalmente a tutti gli utenti.",
 	"Adjusting these settings will apply changes universally to all users.": "La modifica di queste impostazioni applicherà le modifiche universalmente a tutti gli utenti.",
 	"admin": "amministratore",
 	"admin": "amministratore",
 	"Admin Panel": "Pannello di amministrazione",
 	"Admin Panel": "Pannello di amministrazione",
 	"Admin Settings": "Impostazioni amministratore",
 	"Admin Settings": "Impostazioni amministratore",
 	"Advanced Parameters": "Parametri avanzati",
 	"Advanced Parameters": "Parametri avanzati",
 	"all": "tutti",
 	"all": "tutti",
-	"All Documents": "",
+	"All Documents": "Tutti i documenti",
 	"All Users": "Tutti gli utenti",
 	"All Users": "Tutti gli utenti",
 	"Allow": "Consenti",
 	"Allow": "Consenti",
 	"Allow Chat Deletion": "Consenti l'eliminazione della chat",
 	"Allow Chat Deletion": "Consenti l'eliminazione della chat",
@@ -37,36 +37,37 @@
 	"Already have an account?": "Hai già un account?",
 	"Already have an account?": "Hai già un account?",
 	"an assistant": "un assistente",
 	"an assistant": "un assistente",
 	"and": "e",
 	"and": "e",
-	"and create a new shared link.": "",
+	"and create a new shared link.": "e crea un nuovo link condiviso.",
 	"API Base URL": "URL base API",
 	"API Base URL": "URL base API",
 	"API Key": "Chiave API",
 	"API Key": "Chiave API",
-	"API Key created.": "",
-	"API keys": "",
+	"API Key created.": "Chiave API creata.",
+	"API keys": "Chiavi API",
 	"API RPM": "API RPM",
 	"API RPM": "API RPM",
-	"April": "",
-	"Archive": "",
+	"April": "Aprile",
+	"Archive": "Archivio",
 	"Archived Chats": "Chat archiviate",
 	"Archived Chats": "Chat archiviate",
 	"are allowed - Activate this command by typing": "sono consentiti - Attiva questo comando digitando",
 	"are allowed - Activate this command by typing": "sono consentiti - Attiva questo comando digitando",
 	"Are you sure?": "Sei sicuro?",
 	"Are you sure?": "Sei sicuro?",
-	"Attach file": "",
-	"Attention to detail": "",
+	"Attach file": "Allega file",
+	"Attention to detail": "Attenzione ai dettagli",
 	"Audio": "Audio",
 	"Audio": "Audio",
-	"August": "",
+	"August": "Agosto",
 	"Auto-playback response": "Riproduzione automatica della risposta",
 	"Auto-playback response": "Riproduzione automatica della risposta",
 	"Auto-send input after 3 sec.": "Invio automatico dell'input dopo 3 secondi.",
 	"Auto-send input after 3 sec.": "Invio automatico dell'input dopo 3 secondi.",
 	"AUTOMATIC1111 Base URL": "URL base AUTOMATIC1111",
 	"AUTOMATIC1111 Base URL": "URL base AUTOMATIC1111",
 	"AUTOMATIC1111 Base URL is required.": "L'URL base AUTOMATIC1111 è obbligatorio.",
 	"AUTOMATIC1111 Base URL is required.": "L'URL base AUTOMATIC1111 è obbligatorio.",
 	"available!": "disponibile!",
 	"available!": "disponibile!",
 	"Back": "Indietro",
 	"Back": "Indietro",
-	"Bad Response": "",
-	"before": "",
-	"Being lazy": "",
+	"Bad Response": "Risposta non valida",
+	"before": "prima",
+	"Being lazy": "Essere pigri",
 	"Builder Mode": "Modalità costruttore",
 	"Builder Mode": "Modalità costruttore",
-	"Bypass SSL verification for Websites": "",
+	"Bypass SSL verification for Websites": "Aggira la verifica SSL per i siti web",
 	"Cancel": "Annulla",
 	"Cancel": "Annulla",
 	"Categories": "Categorie",
 	"Categories": "Categorie",
 	"Change Password": "Cambia password",
 	"Change Password": "Cambia password",
 	"Chat": "Chat",
 	"Chat": "Chat",
+	"Chat Bubble UI": "",
 	"Chat History": "Cronologia chat",
 	"Chat History": "Cronologia chat",
 	"Chat History is off for this browser.": "La cronologia chat è disattivata per questo browser.",
 	"Chat History is off for this browser.": "La cronologia chat è disattivata per questo browser.",
 	"Chats": "Chat",
 	"Chats": "Chat",
@@ -79,66 +80,65 @@
 	"Chunk Size": "Dimensione chunk",
 	"Chunk Size": "Dimensione chunk",
 	"Citation": "Citazione",
 	"Citation": "Citazione",
 	"Click here for help.": "Clicca qui per aiuto.",
 	"Click here for help.": "Clicca qui per aiuto.",
-	"Click here to": "",
+	"Click here to": "Clicca qui per",
 	"Click here to check other modelfiles.": "Clicca qui per controllare altri file modello.",
 	"Click here to check other modelfiles.": "Clicca qui per controllare altri file modello.",
 	"Click here to select": "Clicca qui per selezionare",
 	"Click here to select": "Clicca qui per selezionare",
-	"Click here to select a csv file.": "",
+	"Click here to select a csv file.": "Clicca qui per selezionare un file csv.",
 	"Click here to select documents.": "Clicca qui per selezionare i documenti.",
 	"Click here to select documents.": "Clicca qui per selezionare i documenti.",
 	"click here.": "clicca qui.",
 	"click here.": "clicca qui.",
 	"Click on the user role button to change a user's role.": "Clicca sul pulsante del ruolo utente per modificare il ruolo di un utente.",
 	"Click on the user role button to change a user's role.": "Clicca sul pulsante del ruolo utente per modificare il ruolo di un utente.",
 	"Close": "Chiudi",
 	"Close": "Chiudi",
 	"Collection": "Collezione",
 	"Collection": "Collezione",
-	"ComfyUI": "",
-	"ComfyUI Base URL": "",
-	"ComfyUI Base URL is required.": "",
+	"ComfyUI": "ComfyUI",
+	"ComfyUI Base URL": "URL base ComfyUI",
+	"ComfyUI Base URL is required.": "L'URL base ComfyUI è obbligatorio.",
 	"Command": "Comando",
 	"Command": "Comando",
 	"Confirm Password": "Conferma password",
 	"Confirm Password": "Conferma password",
 	"Connections": "Connessioni",
 	"Connections": "Connessioni",
 	"Content": "Contenuto",
 	"Content": "Contenuto",
 	"Context Length": "Lunghezza contesto",
 	"Context Length": "Lunghezza contesto",
-	"Continue Response": "",
+	"Continue Response": "Continua risposta",
 	"Conversation Mode": "Modalità conversazione",
 	"Conversation Mode": "Modalità conversazione",
-	"Copied shared chat URL to clipboard!": "",
-	"Copy": "",
+	"Copied shared chat URL to clipboard!": "URL della chat condivisa copiato negli appunti!",
+	"Copy": "Copia",
 	"Copy last code block": "Copia ultimo blocco di codice",
 	"Copy last code block": "Copia ultimo blocco di codice",
 	"Copy last response": "Copia ultima risposta",
 	"Copy last response": "Copia ultima risposta",
-	"Copy Link": "",
+	"Copy Link": "Copia link",
 	"Copying to clipboard was successful!": "Copia negli appunti riuscita!",
 	"Copying to clipboard was successful!": "Copia negli appunti riuscita!",
 	"Create a concise, 3-5 word phrase as a header for the following query, strictly adhering to the 3-5 word limit and avoiding the use of the word 'title':": "Crea una frase concisa di 3-5 parole come intestazione per la seguente query, aderendo rigorosamente al limite di 3-5 parole ed evitando l'uso della parola 'titolo':",
 	"Create a concise, 3-5 word phrase as a header for the following query, strictly adhering to the 3-5 word limit and avoiding the use of the word 'title':": "Crea una frase concisa di 3-5 parole come intestazione per la seguente query, aderendo rigorosamente al limite di 3-5 parole ed evitando l'uso della parola 'titolo':",
 	"Create a modelfile": "Crea un file modello",
 	"Create a modelfile": "Crea un file modello",
 	"Create Account": "Crea account",
 	"Create Account": "Crea account",
-	"Create new key": "",
-	"Create new secret key": "",
+	"Create new key": "Crea nuova chiave",
+	"Create new secret key": "Crea nuova chiave segreta",
 	"Created at": "Creato il",
 	"Created at": "Creato il",
-	"Created At": "",
+	"Created At": "Creato il",
 	"Current Model": "Modello corrente",
 	"Current Model": "Modello corrente",
 	"Current Password": "Password corrente",
 	"Current Password": "Password corrente",
 	"Custom": "Personalizzato",
 	"Custom": "Personalizzato",
 	"Customize Ollama models for a specific purpose": "Personalizza i modelli Ollama per uno scopo specifico",
 	"Customize Ollama models for a specific purpose": "Personalizza i modelli Ollama per uno scopo specifico",
 	"Dark": "Scuro",
 	"Dark": "Scuro",
-	"Dashboard": "",
+	"Dashboard": "Pannello di controllo",
 	"Database": "Database",
 	"Database": "Database",
-	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
-	"December": "",
+	"December": "Dicembre",
 	"Default": "Predefinito",
 	"Default": "Predefinito",
 	"Default (Automatic1111)": "Predefinito (Automatic1111)",
 	"Default (Automatic1111)": "Predefinito (Automatic1111)",
-	"Default (SentenceTransformers)": "",
+	"Default (SentenceTransformers)": "Predefinito (SentenceTransformers)",
 	"Default (Web API)": "Predefinito (API Web)",
 	"Default (Web API)": "Predefinito (API Web)",
 	"Default model updated": "Modello predefinito aggiornato",
 	"Default model updated": "Modello predefinito aggiornato",
 	"Default Prompt Suggestions": "Suggerimenti prompt predefiniti",
 	"Default Prompt Suggestions": "Suggerimenti prompt predefiniti",
 	"Default User Role": "Ruolo utente predefinito",
 	"Default User Role": "Ruolo utente predefinito",
 	"delete": "elimina",
 	"delete": "elimina",
-	"Delete": "",
+	"Delete": "Elimina",
 	"Delete a model": "Elimina un modello",
 	"Delete a model": "Elimina un modello",
 	"Delete chat": "Elimina chat",
 	"Delete chat": "Elimina chat",
-	"Delete Chat": "",
-	"Delete Chats": "Elimina chat",
-	"delete this link": "",
-	"Delete User": "",
+	"Delete Chat": "Elimina chat",
+	"Delete Chats": "Elimina le chat",
+	"delete this link": "elimina questo link",
+	"Delete User": "Elimina utente",
 	"Deleted {{deleteModelTag}}": "Eliminato {{deleteModelTag}}",
 	"Deleted {{deleteModelTag}}": "Eliminato {{deleteModelTag}}",
-	"Deleted {{tagName}}": "",
+	"Deleted {{tagName}}": "Eliminato {{tagName}}",
 	"Description": "Descrizione",
 	"Description": "Descrizione",
-	"Didn't fully follow instructions": "",
+	"Didn't fully follow instructions": "Non ha seguito completamente le istruzioni",
 	"Disabled": "Disabilitato",
 	"Disabled": "Disabilitato",
 	"Discover a modelfile": "Scopri un file modello",
 	"Discover a modelfile": "Scopri un file modello",
 	"Discover a prompt": "Scopri un prompt",
 	"Discover a prompt": "Scopri un prompt",
@@ -151,28 +151,28 @@
 	"does not make any external connections, and your data stays securely on your locally hosted server.": "non effettua connessioni esterne e i tuoi dati rimangono al sicuro sul tuo server ospitato localmente.",
 	"does not make any external connections, and your data stays securely on your locally hosted server.": "non effettua connessioni esterne e i tuoi dati rimangono al sicuro sul tuo server ospitato localmente.",
 	"Don't Allow": "Non consentire",
 	"Don't Allow": "Non consentire",
 	"Don't have an account?": "Non hai un account?",
 	"Don't have an account?": "Non hai un account?",
-	"Don't like the style": "",
-	"Download": "",
-	"Download canceled": "",
+	"Don't like the style": "Non ti piace lo stile",
+	"Download": "Scarica",
+	"Download canceled": "Scaricamento annullato",
 	"Download Database": "Scarica database",
 	"Download Database": "Scarica database",
 	"Drop any files here to add to the conversation": "Trascina qui i file da aggiungere alla conversazione",
 	"Drop any files here to add to the conversation": "Trascina qui i file da aggiungere alla conversazione",
 	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "ad esempio '30s','10m'. Le unità di tempo valide sono 's', 'm', 'h'.",
 	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "ad esempio '30s','10m'. Le unità di tempo valide sono 's', 'm', 'h'.",
-	"Edit": "",
+	"Edit": "Modifica",
 	"Edit Doc": "Modifica documento",
 	"Edit Doc": "Modifica documento",
 	"Edit User": "Modifica utente",
 	"Edit User": "Modifica utente",
 	"Email": "Email",
 	"Email": "Email",
-	"Embedding Model": "",
-	"Embedding Model Engine": "",
-	"Embedding model set to \"{{embedding_model}}\"": "",
+	"Embedding Model": "Modello di embedding",
+	"Embedding Model Engine": "Motore del modello di embedding",
+	"Embedding model set to \"{{embedding_model}}\"": "Modello di embedding impostato su \"{{embedding_model}}\"",
 	"Enable Chat History": "Abilita cronologia chat",
 	"Enable Chat History": "Abilita cronologia chat",
 	"Enable New Sign Ups": "Abilita nuove iscrizioni",
 	"Enable New Sign Ups": "Abilita nuove iscrizioni",
 	"Enabled": "Abilitato",
 	"Enabled": "Abilitato",
-	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "",
+	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Assicurati che il tuo file CSV includa 4 colonne in questo ordine: Nome, Email, Password, Ruolo.",
 	"Enter {{role}} message here": "Inserisci il messaggio per {{role}} qui",
 	"Enter {{role}} message here": "Inserisci il messaggio per {{role}} qui",
 	"Enter Chunk Overlap": "Inserisci la sovrapposizione chunk",
 	"Enter Chunk Overlap": "Inserisci la sovrapposizione chunk",
 	"Enter Chunk Size": "Inserisci la dimensione chunk",
 	"Enter Chunk Size": "Inserisci la dimensione chunk",
 	"Enter Image Size (e.g. 512x512)": "Inserisci la dimensione dell'immagine (ad esempio 512x512)",
 	"Enter Image Size (e.g. 512x512)": "Inserisci la dimensione dell'immagine (ad esempio 512x512)",
-	"Enter language codes": "",
+	"Enter language codes": "Inserisci i codici lingua",
 	"Enter LiteLLM API Base URL (litellm_params.api_base)": "Inserisci l'URL base dell'API LiteLLM (litellm_params.api_base)",
 	"Enter LiteLLM API Base URL (litellm_params.api_base)": "Inserisci l'URL base dell'API LiteLLM (litellm_params.api_base)",
 	"Enter LiteLLM API Key (litellm_params.api_key)": "Inserisci la chiave API LiteLLM (litellm_params.api_key)",
 	"Enter LiteLLM API Key (litellm_params.api_key)": "Inserisci la chiave API LiteLLM (litellm_params.api_key)",
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "Inserisci LiteLLM API RPM (litellm_params.rpm)",
 	"Enter LiteLLM API RPM (litellm_params.rpm)": "Inserisci LiteLLM API RPM (litellm_params.rpm)",
@@ -180,46 +180,47 @@
 	"Enter Max Tokens (litellm_params.max_tokens)": "Inserisci Max Tokens (litellm_params.max_tokens)",
 	"Enter Max Tokens (litellm_params.max_tokens)": "Inserisci Max Tokens (litellm_params.max_tokens)",
 	"Enter model tag (e.g. {{modelTag}})": "Inserisci il tag del modello (ad esempio {{modelTag}})",
 	"Enter model tag (e.g. {{modelTag}})": "Inserisci il tag del modello (ad esempio {{modelTag}})",
 	"Enter Number of Steps (e.g. 50)": "Inserisci il numero di passaggi (ad esempio 50)",
 	"Enter Number of Steps (e.g. 50)": "Inserisci il numero di passaggi (ad esempio 50)",
-	"Enter Score": "",
+	"Enter Score": "Inserisci il punteggio",
 	"Enter stop sequence": "Inserisci la sequenza di arresto",
 	"Enter stop sequence": "Inserisci la sequenza di arresto",
 	"Enter Top K": "Inserisci Top K",
 	"Enter Top K": "Inserisci Top K",
 	"Enter URL (e.g. http://127.0.0.1:7860/)": "Inserisci URL (ad esempio http://127.0.0.1:7860/)",
 	"Enter URL (e.g. http://127.0.0.1:7860/)": "Inserisci URL (ad esempio http://127.0.0.1:7860/)",
-	"Enter URL (e.g. http://localhost:11434)": "",
+	"Enter URL (e.g. http://localhost:11434)": "Inserisci URL (ad esempio http://localhost:11434)",
 	"Enter Your Email": "Inserisci la tua email",
 	"Enter Your Email": "Inserisci la tua email",
 	"Enter Your Full Name": "Inserisci il tuo nome completo",
 	"Enter Your Full Name": "Inserisci il tuo nome completo",
 	"Enter Your Password": "Inserisci la tua password",
 	"Enter Your Password": "Inserisci la tua password",
-	"Enter Your Role": "",
+	"Enter Your Role": "Inserisci il tuo ruolo",
 	"Experimental": "Sperimentale",
 	"Experimental": "Sperimentale",
 	"Export All Chats (All Users)": "Esporta tutte le chat (tutti gli utenti)",
 	"Export All Chats (All Users)": "Esporta tutte le chat (tutti gli utenti)",
 	"Export Chats": "Esporta chat",
 	"Export Chats": "Esporta chat",
 	"Export Documents Mapping": "Esporta mappatura documenti",
 	"Export Documents Mapping": "Esporta mappatura documenti",
 	"Export Modelfiles": "Esporta file modello",
 	"Export Modelfiles": "Esporta file modello",
 	"Export Prompts": "Esporta prompt",
 	"Export Prompts": "Esporta prompt",
-	"Failed to create API Key.": "",
+	"Failed to create API Key.": "Impossibile creare la chiave API.",
 	"Failed to read clipboard contents": "Impossibile leggere il contenuto degli appunti",
 	"Failed to read clipboard contents": "Impossibile leggere il contenuto degli appunti",
-	"February": "",
-	"Feel free to add specific details": "",
+	"February": "Febbraio",
+	"Feel free to add specific details": "Sentiti libero/a di aggiungere dettagli specifici",
 	"File Mode": "Modalità file",
 	"File Mode": "Modalità file",
 	"File not found.": "File non trovato.",
 	"File not found.": "File non trovato.",
-	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "",
+	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "Rilevato spoofing delle impronte digitali: impossibile utilizzare le iniziali come avatar. Ripristino all'immagine del profilo predefinita.",
 	"Fluidly stream large external response chunks": "Trasmetti in modo fluido blocchi di risposta esterni di grandi dimensioni",
 	"Fluidly stream large external response chunks": "Trasmetti in modo fluido blocchi di risposta esterni di grandi dimensioni",
 	"Focus chat input": "Metti a fuoco l'input della chat",
 	"Focus chat input": "Metti a fuoco l'input della chat",
-	"Followed instructions perfectly": "",
+	"Followed instructions perfectly": "Ha seguito le istruzioni alla perfezione",
 	"Format your variables using square brackets like this:": "Formatta le tue variabili usando parentesi quadre come questa:",
 	"Format your variables using square brackets like this:": "Formatta le tue variabili usando parentesi quadre come questa:",
 	"From (Base Model)": "Da (modello base)",
 	"From (Base Model)": "Da (modello base)",
 	"Full Screen Mode": "Modalità a schermo intero",
 	"Full Screen Mode": "Modalità a schermo intero",
 	"General": "Generale",
 	"General": "Generale",
 	"General Settings": "Impostazioni generali",
 	"General Settings": "Impostazioni generali",
 	"Generating search query": "",
 	"Generating search query": "",
-	"Generation Info": "",
-	"Good Response": "",
-	"has no conversations.": "",
+	"Generation Info": "Informazioni generazione",
+	"Good Response": "Buona risposta",
+	"h:mm a": "",
+	"has no conversations.": "non ha conversazioni.",
 	"Hello, {{name}}": "Ciao, {{name}}",
 	"Hello, {{name}}": "Ciao, {{name}}",
-	"Help": "",
+	"Help": "Aiuto",
 	"Hide": "Nascondi",
 	"Hide": "Nascondi",
 	"Hide Additional Params": "Nascondi parametri aggiuntivi",
 	"Hide Additional Params": "Nascondi parametri aggiuntivi",
 	"How can I help you today?": "Come posso aiutarti oggi?",
 	"How can I help you today?": "Come posso aiutarti oggi?",
-	"Hybrid Search": "",
+	"Hybrid Search": "Ricerca ibrida",
 	"Image Generation (Experimental)": "Generazione di immagini (sperimentale)",
 	"Image Generation (Experimental)": "Generazione di immagini (sperimentale)",
 	"Image Generation Engine": "Motore di generazione immagini",
 	"Image Generation Engine": "Motore di generazione immagini",
 	"Image Settings": "Impostazioni immagine",
 	"Image Settings": "Impostazioni immagine",
@@ -228,21 +229,21 @@
 	"Import Documents Mapping": "Importa mappatura documenti",
 	"Import Documents Mapping": "Importa mappatura documenti",
 	"Import Modelfiles": "Importa file modello",
 	"Import Modelfiles": "Importa file modello",
 	"Import Prompts": "Importa prompt",
 	"Import Prompts": "Importa prompt",
-	"Include `--api` flag when running stable-diffusion-webui": "",
-	"Input commands": "",
+	"Include `--api` flag when running stable-diffusion-webui": "Includi il flag `--api` quando esegui stable-diffusion-webui",
+	"Input commands": "Comandi di input",
 	"Interface": "Interfaccia",
 	"Interface": "Interfaccia",
-	"Invalid Tag": "",
-	"January": "",
+	"Invalid Tag": "Tag non valido",
+	"January": "Gennaio",
 	"join our Discord for help.": "unisciti al nostro Discord per ricevere aiuto.",
 	"join our Discord for help.": "unisciti al nostro Discord per ricevere aiuto.",
 	"JSON": "JSON",
 	"JSON": "JSON",
-	"July": "",
-	"June": "",
+	"July": "Luglio",
+	"June": "Giugno",
 	"JWT Expiration": "Scadenza JWT",
 	"JWT Expiration": "Scadenza JWT",
 	"JWT Token": "Token JWT",
 	"JWT Token": "Token JWT",
 	"Keep Alive": "Mantieni attivo",
 	"Keep Alive": "Mantieni attivo",
 	"Keyboard shortcuts": "Scorciatoie da tastiera",
 	"Keyboard shortcuts": "Scorciatoie da tastiera",
 	"Language": "Lingua",
 	"Language": "Lingua",
-	"Last Active": "",
+	"Last Active": "Ultima attività",
 	"Light": "Chiaro",
 	"Light": "Chiaro",
 	"Listening...": "Ascolto...",
 	"Listening...": "Ascolto...",
 	"LLMs can make mistakes. Verify important information.": "Gli LLM possono commettere errori. Verifica le informazioni importanti.",
 	"LLMs can make mistakes. Verify important information.": "Gli LLM possono commettere errori. Verifica le informazioni importanti.",
@@ -251,22 +252,22 @@
 	"Manage LiteLLM Models": "Gestisci modelli LiteLLM",
 	"Manage LiteLLM Models": "Gestisci modelli LiteLLM",
 	"Manage Models": "Gestisci modelli",
 	"Manage Models": "Gestisci modelli",
 	"Manage Ollama Models": "Gestisci modelli Ollama",
 	"Manage Ollama Models": "Gestisci modelli Ollama",
-	"March": "",
+	"March": "Marzo",
 	"Max Tokens": "Max token",
 	"Max Tokens": "Max token",
 	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "È possibile scaricare un massimo di 3 modelli contemporaneamente. Riprova più tardi.",
 	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "È possibile scaricare un massimo di 3 modelli contemporaneamente. Riprova più tardi.",
-	"May": "",
-	"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "",
-	"Minimum Score": "",
+	"May": "Maggio",
+	"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "I messaggi che invii dopo aver creato il tuo link non verranno condivisi. Gli utenti con l'URL potranno visualizzare la chat condivisa.",
+	"Minimum Score": "Punteggio minimo",
 	"Mirostat": "Mirostat",
 	"Mirostat": "Mirostat",
 	"Mirostat Eta": "Mirostat Eta",
 	"Mirostat Eta": "Mirostat Eta",
 	"Mirostat Tau": "Mirostat Tau",
 	"Mirostat Tau": "Mirostat Tau",
 	"MMMM DD, YYYY": "MMMM DD, YYYY",
 	"MMMM DD, YYYY": "MMMM DD, YYYY",
-	"MMMM DD, YYYY HH:mm": "",
+	"MMMM DD, YYYY HH:mm": "MMMM DD, YYYY HH:mm",
 	"Model '{{modelName}}' has been successfully downloaded.": "Il modello '{{modelName}}' è stato scaricato con successo.",
 	"Model '{{modelName}}' has been successfully downloaded.": "Il modello '{{modelName}}' è stato scaricato con successo.",
 	"Model '{{modelTag}}' is already in queue for downloading.": "Il modello '{{modelTag}}' è già in coda per il download.",
 	"Model '{{modelTag}}' is already in queue for downloading.": "Il modello '{{modelTag}}' è già in coda per il download.",
 	"Model {{modelId}} not found": "Modello {{modelId}} non trovato",
 	"Model {{modelId}} not found": "Modello {{modelId}} non trovato",
 	"Model {{modelName}} already exists.": "Il modello {{modelName}} esiste già.",
 	"Model {{modelName}} already exists.": "Il modello {{modelName}} esiste già.",
-	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "",
+	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Percorso del filesystem del modello rilevato. Il nome breve del modello è richiesto per l'aggiornamento, impossibile continuare.",
 	"Model Name": "Nome modello",
 	"Model Name": "Nome modello",
 	"Model not selected": "Modello non selezionato",
 	"Model not selected": "Modello non selezionato",
 	"Model Tag Name": "Nome tag del modello",
 	"Model Tag Name": "Nome tag del modello",
@@ -277,30 +278,27 @@
 	"Modelfile Content": "Contenuto del file modello",
 	"Modelfile Content": "Contenuto del file modello",
 	"Modelfiles": "File modello",
 	"Modelfiles": "File modello",
 	"Models": "Modelli",
 	"Models": "Modelli",
-	"More": "",
-	"My Documents": "I miei documenti",
-	"My Modelfiles": "I miei file modello",
-	"My Prompts": "I miei prompt",
+	"More": "Altro",
 	"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",
 	"New Chat": "Nuova chat",
 	"New Chat": "Nuova chat",
 	"New Password": "Nuova password",
 	"New Password": "Nuova password",
-	"No results found": "",
+	"No results found": "Nessun risultato trovato",
 	"No search query generated": "",
 	"No search query generated": "",
 	"No search results found": "",
 	"No search results found": "",
 	"No source available": "Nessuna fonte disponibile",
 	"No source available": "Nessuna fonte disponibile",
-	"Not factually correct": "",
+	"Not factually correct": "Non corretto dal punto di vista fattuale",
 	"Not sure what to add?": "Non sei sicuro di cosa aggiungere?",
 	"Not sure what to add?": "Non sei sicuro di cosa aggiungere?",
 	"Not sure what to write? Switch to": "Non sei sicuro di cosa scrivere? Passa a",
 	"Not sure what to write? Switch to": "Non sei sicuro di cosa scrivere? Passa a",
-	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "",
+	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Nota: se imposti un punteggio minimo, la ricerca restituirà solo i documenti con un punteggio maggiore o uguale al punteggio minimo.",
 	"Notifications": "Notifiche desktop",
 	"Notifications": "Notifiche desktop",
-	"November": "",
-	"October": "",
+	"November": "Novembre",
+	"October": "Ottobre",
 	"Off": "Disattivato",
 	"Off": "Disattivato",
 	"Okay, Let's Go!": "Ok, andiamo!",
 	"Okay, Let's Go!": "Ok, andiamo!",
-	"OLED Dark": "",
-	"Ollama": "",
+	"OLED Dark": "OLED scuro",
+	"Ollama": "Ollama",
 	"Ollama Base URL": "URL base Ollama",
 	"Ollama Base URL": "URL base Ollama",
 	"Ollama Version": "Versione Ollama",
 	"Ollama Version": "Versione Ollama",
 	"On": "Attivato",
 	"On": "Attivato",
@@ -313,52 +311,52 @@
 	"Open AI": "Open AI",
 	"Open AI": "Open AI",
 	"Open AI (Dall-E)": "Open AI (Dall-E)",
 	"Open AI (Dall-E)": "Open AI (Dall-E)",
 	"Open new chat": "Apri nuova chat",
 	"Open new chat": "Apri nuova chat",
-	"OpenAI": "",
+	"OpenAI": "OpenAI",
 	"OpenAI API": "API OpenAI",
 	"OpenAI API": "API OpenAI",
-	"OpenAI API Config": "",
+	"OpenAI API Config": "Configurazione API OpenAI",
 	"OpenAI API Key is required.": "La chiave API OpenAI è obbligatoria.",
 	"OpenAI API Key is required.": "La chiave API OpenAI è obbligatoria.",
-	"OpenAI URL/Key required.": "",
+	"OpenAI URL/Key required.": "URL/Chiave OpenAI obbligatori.",
 	"or": "o",
 	"or": "o",
-	"Other": "",
-	"Overview": "",
+	"Other": "Altro",
+	"Overview": "Panoramica",
 	"Parameters": "Parametri",
 	"Parameters": "Parametri",
 	"Password": "Password",
 	"Password": "Password",
-	"PDF document (.pdf)": "",
+	"PDF document (.pdf)": "Documento PDF (.pdf)",
 	"PDF Extract Images (OCR)": "Estrazione immagini PDF (OCR)",
 	"PDF Extract Images (OCR)": "Estrazione immagini PDF (OCR)",
 	"pending": "in sospeso",
 	"pending": "in sospeso",
 	"Permission denied when accessing microphone: {{error}}": "Autorizzazione negata durante l'accesso al microfono: {{error}}",
 	"Permission denied when accessing microphone: {{error}}": "Autorizzazione negata durante l'accesso al microfono: {{error}}",
-	"Plain text (.txt)": "",
+	"Plain text (.txt)": "Testo normale (.txt)",
 	"Playground": "Terreno di gioco",
 	"Playground": "Terreno di gioco",
-	"Positive attitude": "",
-	"Previous 30 days": "",
-	"Previous 7 days": "",
-	"Profile Image": "",
-	"Prompt": "",
-	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "",
+	"Positive attitude": "Attitudine positiva",
+	"Previous 30 days": "Ultimi 30 giorni",
+	"Previous 7 days": "Ultimi 7 giorni",
+	"Profile Image": "Immagine del profilo",
+	"Prompt": "Prompt",
+	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Prompt (ad esempio Dimmi un fatto divertente sull'Impero Romano)",
 	"Prompt Content": "Contenuto del prompt",
 	"Prompt Content": "Contenuto del prompt",
 	"Prompt suggestions": "Suggerimenti prompt",
 	"Prompt suggestions": "Suggerimenti prompt",
 	"Prompts": "Prompt",
 	"Prompts": "Prompt",
-	"Pull \"{{searchValue}}\" from Ollama.com": "",
+	"Pull \"{{searchValue}}\" from Ollama.com": "Estrai \"{{searchValue}}\" da Ollama.com",
 	"Pull a model from Ollama.com": "Estrai un modello da Ollama.com",
 	"Pull a model from Ollama.com": "Estrai un modello da Ollama.com",
 	"Pull Progress": "Avanzamento estrazione",
 	"Pull Progress": "Avanzamento estrazione",
 	"Query Params": "Parametri query",
 	"Query Params": "Parametri query",
 	"RAG Template": "Modello RAG",
 	"RAG Template": "Modello RAG",
 	"Raw Format": "Formato raw",
 	"Raw Format": "Formato raw",
-	"Read Aloud": "",
+	"Read Aloud": "Leggi ad alta voce",
 	"Record voice": "Registra voce",
 	"Record voice": "Registra voce",
 	"Redirecting you to OpenWebUI Community": "Reindirizzamento alla comunità OpenWebUI",
 	"Redirecting you to OpenWebUI Community": "Reindirizzamento alla comunità OpenWebUI",
-	"Refused when it shouldn't have": "",
-	"Regenerate": "",
+	"Refused when it shouldn't have": "Rifiutato quando non avrebbe dovuto",
+	"Regenerate": "Rigenera",
 	"Release Notes": "Note di rilascio",
 	"Release Notes": "Note di rilascio",
-	"Remove": "",
-	"Remove Model": "",
-	"Rename": "",
+	"Remove": "Rimuovi",
+	"Remove Model": "Rimuovi modello",
+	"Rename": "Rinomina",
 	"Repeat Last N": "Ripeti ultimi N",
 	"Repeat Last N": "Ripeti ultimi N",
 	"Repeat Penalty": "Penalità di ripetizione",
 	"Repeat Penalty": "Penalità di ripetizione",
 	"Request Mode": "Modalità richiesta",
 	"Request Mode": "Modalità richiesta",
-	"Reranking Model": "",
-	"Reranking model disabled": "",
-	"Reranking model set to \"{{reranking_model}}\"": "",
+	"Reranking Model": "Modello di riclassificazione",
+	"Reranking model disabled": "Modello di riclassificazione disabilitato",
+	"Reranking model set to \"{{reranking_model}}\"": "Modello di riclassificazione impostato su \"{{reranking_model}}\"",
 	"Reset Vector Storage": "Reimposta archivio vettoriale",
 	"Reset Vector Storage": "Reimposta archivio vettoriale",
 	"Response AutoCopy to Clipboard": "Copia automatica della risposta negli appunti",
 	"Response AutoCopy to Clipboard": "Copia automatica della risposta negli appunti",
 	"Role": "Ruolo",
 	"Role": "Ruolo",
@@ -366,14 +364,13 @@
 	"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",
 	"Scan complete!": "Scansione completata!",
 	"Scan complete!": "Scansione completata!",
 	"Scan for documents from {{path}}": "Cerca documenti da {{path}}",
 	"Scan for documents from {{path}}": "Cerca documenti da {{path}}",
 	"Search": "Cerca",
 	"Search": "Cerca",
-	"Search a model": "",
+	"Search a model": "Cerca un modello",
 	"Search Documents": "Cerca documenti",
 	"Search Documents": "Cerca documenti",
 	"Search Prompts": "Cerca prompt",
 	"Search Prompts": "Cerca prompt",
 	"Search Results": "",
 	"Search Results": "",
@@ -384,35 +381,36 @@
 	"Select a mode": "Seleziona una modalità",
 	"Select a mode": "Seleziona una modalità",
 	"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": "",
+	"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": "",
+	"September": "Settembre",
 	"Server connection verified": "Connessione al server verificata",
 	"Server connection verified": "Connessione al server verificata",
 	"Set as default": "Imposta come predefinito",
 	"Set as default": "Imposta come predefinito",
 	"Set Default Model": "Imposta modello predefinito",
 	"Set Default Model": "Imposta modello predefinito",
-	"Set embedding model (e.g. {{model}})": "",
+	"Set embedding model (e.g. {{model}})": "Imposta modello di embedding (ad esempio {{model}})",
 	"Set Image Size": "Imposta dimensione immagine",
 	"Set Image Size": "Imposta dimensione immagine",
 	"Set Model": "Imposta modello",
 	"Set Model": "Imposta modello",
-	"Set reranking model (e.g. {{model}})": "",
+	"Set reranking model (e.g. {{model}})": "Imposta modello di riclassificazione (ad esempio {{model}})",
 	"Set Steps": "Imposta passaggi",
 	"Set Steps": "Imposta passaggi",
 	"Set Task Model": "",
 	"Set Task Model": "",
 	"Set Voice": "Imposta voce",
 	"Set Voice": "Imposta voce",
 	"Settings": "Impostazioni",
 	"Settings": "Impostazioni",
 	"Settings saved successfully!": "Impostazioni salvate con successo!",
 	"Settings saved successfully!": "Impostazioni salvate con successo!",
-	"Share": "",
-	"Share Chat": "",
+	"Share": "Condividi",
+	"Share Chat": "Condividi chat",
 	"Share to OpenWebUI Community": "Condividi con la comunità OpenWebUI",
 	"Share to OpenWebUI Community": "Condividi con la comunità OpenWebUI",
 	"short-summary": "riassunto-breve",
 	"short-summary": "riassunto-breve",
 	"Show": "Mostra",
 	"Show": "Mostra",
 	"Show Additional Params": "Mostra parametri aggiuntivi",
 	"Show Additional Params": "Mostra parametri aggiuntivi",
 	"Show shortcuts": "Mostra",
 	"Show shortcuts": "Mostra",
-	"Showcased creativity": "",
+	"Showcased creativity": "Creatività messa in mostra",
 	"sidebar": "barra laterale",
 	"sidebar": "barra laterale",
 	"Sign in": "Accedi",
 	"Sign in": "Accedi",
 	"Sign Out": "Esci",
 	"Sign Out": "Esci",
 	"Sign up": "Registrati",
 	"Sign up": "Registrati",
-	"Signing in": "",
+	"Signing in": "Accesso in corso",
 	"Source": "Fonte",
 	"Source": "Fonte",
 	"Speech recognition error: {{error}}": "Errore di riconoscimento vocale: {{error}}",
 	"Speech recognition error: {{error}}": "Errore di riconoscimento vocale: {{error}}",
 	"Speech-to-Text Engine": "Motore da voce a testo",
 	"Speech-to-Text Engine": "Motore da voce a testo",
@@ -420,37 +418,37 @@
 	"Stop Sequence": "Sequenza di arresto",
 	"Stop Sequence": "Sequenza di arresto",
 	"STT Settings": "Impostazioni STT",
 	"STT Settings": "Impostazioni STT",
 	"Submit": "Invia",
 	"Submit": "Invia",
-	"Subtitle (e.g. about the Roman Empire)": "",
+	"Subtitle (e.g. about the Roman Empire)": "Sottotitolo (ad esempio sull'Impero Romano)",
 	"Success": "Successo",
 	"Success": "Successo",
 	"Successfully updated.": "Aggiornato con successo.",
 	"Successfully updated.": "Aggiornato con successo.",
-	"Suggested": "",
+	"Suggested": "Suggerito",
 	"Sync All": "Sincronizza tutto",
 	"Sync All": "Sincronizza tutto",
 	"System": "Sistema",
 	"System": "Sistema",
 	"System Prompt": "Prompt di sistema",
 	"System Prompt": "Prompt di sistema",
 	"Tags": "Tag",
 	"Tags": "Tag",
-	"Tell us more:": "",
+	"Tell us more:": "Raccontaci di più:",
 	"Temperature": "Temperatura",
 	"Temperature": "Temperatura",
 	"Template": "Modello",
 	"Template": "Modello",
 	"Text Completion": "Completamento del testo",
 	"Text Completion": "Completamento del testo",
 	"Text-to-Speech Engine": "Motore da testo a voce",
 	"Text-to-Speech Engine": "Motore da testo a voce",
 	"Tfs Z": "Tfs Z",
 	"Tfs Z": "Tfs Z",
-	"Thanks for your feedback!": "",
-	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "",
+	"Thanks for your feedback!": "Grazie per il tuo feedback!",
+	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "Il punteggio dovrebbe essere un valore compreso tra 0.0 (0%) e 1.0 (100%).",
 	"Theme": "Tema",
 	"Theme": "Tema",
 	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Ciò garantisce che le tue preziose conversazioni siano salvate in modo sicuro nel tuo database backend. Grazie!",
 	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Ciò garantisce che le tue preziose conversazioni siano salvate in modo sicuro nel tuo database backend. Grazie!",
 	"This setting does not sync across browsers or devices.": "Questa impostazione non si sincronizza tra browser o dispositivi.",
 	"This setting does not sync across browsers or devices.": "Questa impostazione non si sincronizza tra browser o dispositivi.",
-	"Thorough explanation": "",
+	"Thorough explanation": "Spiegazione dettagliata",
 	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Suggerimento: aggiorna più slot di variabili consecutivamente premendo il tasto tab nell'input della chat dopo ogni sostituzione.",
 	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Suggerimento: aggiorna più slot di variabili consecutivamente premendo il tasto tab nell'input della chat dopo ogni sostituzione.",
 	"Title": "Titolo",
 	"Title": "Titolo",
-	"Title (e.g. Tell me a fun fact)": "",
+	"Title (e.g. Tell me a fun fact)": "Titolo (ad esempio Dimmi un fatto divertente)",
 	"Title Auto-Generation": "Generazione automatica del titolo",
 	"Title Auto-Generation": "Generazione automatica del titolo",
-	"Title cannot be an empty string.": "",
+	"Title cannot be an empty string.": "Il titolo non può essere una stringa vuota.",
 	"Title Generation Prompt": "Prompt di generazione del titolo",
 	"Title Generation Prompt": "Prompt di generazione del titolo",
 	"to": "a",
 	"to": "a",
 	"To access the available model names for downloading,": "Per accedere ai nomi dei modelli disponibili per il download,",
 	"To access the available model names for downloading,": "Per accedere ai nomi dei modelli disponibili per il download,",
 	"To access the GGUF models available for downloading,": "Per accedere ai modelli GGUF disponibili per il download,",
 	"To access the GGUF models available for downloading,": "Per accedere ai modelli GGUF disponibili per il download,",
 	"to chat input.": "all'input della chat.",
 	"to chat input.": "all'input della chat.",
-	"Today": "",
+	"Today": "Oggi",
 	"Toggle settings": "Attiva/disattiva impostazioni",
 	"Toggle settings": "Attiva/disattiva impostazioni",
 	"Toggle sidebar": "Attiva/disattiva barra laterale",
 	"Toggle sidebar": "Attiva/disattiva barra laterale",
 	"Top K": "Top K",
 	"Top K": "Top K",
@@ -460,7 +458,7 @@
 	"Type Hugging Face Resolve (Download) URL": "Digita l'URL di Hugging Face Resolve (Download)",
 	"Type Hugging Face Resolve (Download) URL": "Digita l'URL di Hugging Face Resolve (Download)",
 	"Uh-oh! There was an issue connecting to {{provider}}.": "Uh-oh! Si è verificato un problema durante la connessione a {{provider}}.",
 	"Uh-oh! There was an issue connecting to {{provider}}.": "Uh-oh! Si è verificato un problema durante la connessione a {{provider}}.",
 	"Unknown File Type '{{file_type}}', but accepting and treating as plain text": "Tipo di file sconosciuto '{{file_type}}', ma accettato e trattato come testo normale",
 	"Unknown File Type '{{file_type}}', but accepting and treating as plain text": "Tipo di file sconosciuto '{{file_type}}', ma accettato e trattato come testo normale",
-	"Update and Copy Link": "",
+	"Update and Copy Link": "Aggiorna e copia link",
 	"Update password": "Aggiorna password",
 	"Update password": "Aggiorna password",
 	"Upload a GGUF model": "Carica un modello GGUF",
 	"Upload a GGUF model": "Carica un modello GGUF",
 	"Upload files": "Carica file",
 	"Upload files": "Carica file",
@@ -468,7 +466,7 @@
 	"URL Mode": "Modalità URL",
 	"URL Mode": "Modalità URL",
 	"Use '#' in the prompt input to load and select your documents.": "Usa '#' nell'input del prompt per caricare e selezionare i tuoi documenti.",
 	"Use '#' in the prompt input to load and select your documents.": "Usa '#' nell'input del prompt per caricare e selezionare i tuoi documenti.",
 	"Use Gravatar": "Usa Gravatar",
 	"Use Gravatar": "Usa Gravatar",
-	"Use Initials": "",
+	"Use Initials": "Usa iniziali",
 	"user": "utente",
 	"user": "utente",
 	"User Permissions": "Autorizzazioni utente",
 	"User Permissions": "Autorizzazioni utente",
 	"Users": "Utenti",
 	"Users": "Utenti",
@@ -477,27 +475,28 @@
 	"variable": "variabile",
 	"variable": "variabile",
 	"variable to have them replaced with clipboard content.": "variabile per farli sostituire con il contenuto degli appunti.",
 	"variable to have them replaced with clipboard content.": "variabile per farli sostituire con il contenuto degli appunti.",
 	"Version": "Versione",
 	"Version": "Versione",
-	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "",
+	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "Attenzione: se aggiorni o cambi il tuo modello di embedding, dovrai reimportare tutti i documenti.",
 	"Web": "Web",
 	"Web": "Web",
-	"Web Loader Settings": "",
-	"Web Params": "",
+	"Web Loader Settings": "Impostazioni del caricatore Web",
+	"Web Params": "Parametri Web",
 	"Web Search Disabled": "",
 	"Web Search Disabled": "",
 	"Web Search Enabled": "",
 	"Web Search Enabled": "",
-	"Webhook URL": "",
+	"Webhook URL": "URL webhook",
 	"WebUI Add-ons": "Componenti aggiuntivi WebUI",
 	"WebUI Add-ons": "Componenti aggiuntivi WebUI",
 	"WebUI Settings": "Impostazioni WebUI",
 	"WebUI Settings": "Impostazioni WebUI",
 	"WebUI will make requests to": "WebUI effettuerà richieste a",
 	"WebUI will make requests to": "WebUI effettuerà richieste a",
 	"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": "",
-	"You": "Tu",
-	"You have no archived conversations.": "",
-	"You have shared this chat": "",
+	"Yesterday": "Ieri",
+	"You": "",
+	"You have no archived conversations.": "Non hai conversazioni archiviate.",
+	"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.",
 	"You're now logged in.": "Ora hai effettuato l'accesso.",
 	"You're now logged in.": "Ora hai effettuato l'accesso.",
-	"Youtube": "",
-	"Youtube Loader Settings": ""
+	"Youtube": "Youtube",
+	"Youtube Loader Settings": "Impostazioni del caricatore Youtube"
 }
 }

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

@@ -67,6 +67,7 @@
 	"Categories": "カテゴリ",
 	"Categories": "カテゴリ",
 	"Change Password": "パスワードを変更",
 	"Change Password": "パスワードを変更",
 	"Chat": "チャット",
 	"Chat": "チャット",
+	"Chat Bubble UI": "",
 	"Chat History": "チャット履歴",
 	"Chat History": "チャット履歴",
 	"Chat History is off for this browser.": "このブラウザではチャット履歴が無効になっています。",
 	"Chat History is off for this browser.": "このブラウザではチャット履歴が無効になっています。",
 	"Chats": "チャット",
 	"Chats": "チャット",
@@ -118,7 +119,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)",
@@ -213,6 +213,7 @@
 	"Generating search query": "",
 	"Generating search query": "",
 	"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": "",
@@ -278,9 +279,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": "モデルファイルに名前を付ける",
@@ -366,7 +364,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": "スキャン",
@@ -385,6 +382,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": "",
@@ -490,10 +488,11 @@
 	"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": "",
 	"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.": "あなたは役に立つアシスタントです。",

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

@@ -67,6 +67,7 @@
 	"Categories": "კატეგორიები",
 	"Categories": "კატეგორიები",
 	"Change Password": "პაროლის შეცვლა",
 	"Change Password": "პაროლის შეცვლა",
 	"Chat": "მიმოწერა",
 	"Chat": "მიმოწერა",
+	"Chat Bubble UI": "",
 	"Chat History": "მიმოწერის ისტორია",
 	"Chat History": "მიმოწერის ისტორია",
 	"Chat History is off for this browser.": "მიმოწერის ისტორია ამ ბრაუზერისთვის გათიშულია",
 	"Chat History is off for this browser.": "მიმოწერის ისტორია ამ ბრაუზერისთვის გათიშულია",
 	"Chats": "მიმოწერები",
 	"Chats": "მიმოწერები",
@@ -118,7 +119,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)",
@@ -213,6 +213,7 @@
 	"Generating search query": "",
 	"Generating search query": "",
 	"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": "",
@@ -278,9 +279,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": "თქვენი მოდელური ფაილის სახელი",
@@ -366,7 +364,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": "სკანირება",
@@ -385,6 +382,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": "",
@@ -490,10 +488,11 @@
 	"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": "",
 	"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.": "თქვენ სასარგებლო ასისტენტი ხართ.",

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

@@ -67,6 +67,7 @@
 	"Categories": "분류",
 	"Categories": "분류",
 	"Change Password": "비밀번호 변경",
 	"Change Password": "비밀번호 변경",
 	"Chat": "채팅",
 	"Chat": "채팅",
+	"Chat Bubble UI": "",
 	"Chat History": "채팅 기록",
 	"Chat History": "채팅 기록",
 	"Chat History is off for this browser.": "이 브라우저에서 채팅 기록이 꺼져 있습니다.",
 	"Chat History is off for this browser.": "이 브라우저에서 채팅 기록이 꺼져 있습니다.",
 	"Chats": "채팅",
 	"Chats": "채팅",
@@ -118,7 +119,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)",
@@ -213,6 +213,7 @@
 	"Generating search query": "",
 	"Generating search query": "",
 	"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": "",
@@ -278,9 +279,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": "모델파일 이름 지정",
@@ -366,7 +364,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": "스캔",
@@ -385,6 +382,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": "",
@@ -490,10 +488,11 @@
 	"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": "",
 	"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.": "당신은 유용한 어시스턴트입니다.",

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

@@ -67,6 +67,7 @@
 	"Categories": "Categorieën",
 	"Categories": "Categorieën",
 	"Change Password": "Wijzig Wachtwoord",
 	"Change Password": "Wijzig Wachtwoord",
 	"Chat": "Chat",
 	"Chat": "Chat",
+	"Chat Bubble UI": "",
 	"Chat History": "Chat Geschiedenis",
 	"Chat History": "Chat Geschiedenis",
 	"Chat History is off for this browser.": "Chat Geschiedenis is uitgeschakeld voor deze browser.",
 	"Chat History is off for this browser.": "Chat Geschiedenis is uitgeschakeld voor deze browser.",
 	"Chats": "Chats",
 	"Chats": "Chats",
@@ -118,7 +119,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)",
@@ -213,6 +213,7 @@
 	"Generating search query": "",
 	"Generating search query": "",
 	"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": "",
@@ -278,9 +279,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",
@@ -366,7 +364,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",
@@ -385,6 +382,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": "",
@@ -490,10 +488,11 @@
 	"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": "",
 	"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.",

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

@@ -67,6 +67,7 @@
 	"Categories": "Kategorie",
 	"Categories": "Kategorie",
 	"Change Password": "Zmień hasło",
 	"Change Password": "Zmień hasło",
 	"Chat": "Czat",
 	"Chat": "Czat",
+	"Chat Bubble UI": "",
 	"Chat History": "Historia czatu",
 	"Chat History": "Historia czatu",
 	"Chat History is off for this browser.": "Historia czatu jest wyłączona dla tej przeglądarki.",
 	"Chat History is off for this browser.": "Historia czatu jest wyłączona dla tej przeglądarki.",
 	"Chats": "Czaty",
 	"Chats": "Czaty",
@@ -118,7 +119,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)",
@@ -213,6 +213,7 @@
 	"Generating search query": "",
 	"Generating search query": "",
 	"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",
@@ -278,9 +279,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",
@@ -366,7 +364,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",
@@ -385,6 +382,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ń",
@@ -490,10 +488,11 @@
 	"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": "",
 	"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.",

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

@@ -67,6 +67,7 @@
 	"Categories": "Categorias",
 	"Categories": "Categorias",
 	"Change Password": "Alterar Senha",
 	"Change Password": "Alterar Senha",
 	"Chat": "Bate-papo",
 	"Chat": "Bate-papo",
+	"Chat Bubble UI": "",
 	"Chat History": "Histórico de Bate-papo",
 	"Chat History": "Histórico de Bate-papo",
 	"Chat History is off for this browser.": "O histórico de bate-papo está desativado para este navegador.",
 	"Chat History is off for this browser.": "O histórico de bate-papo está desativado para este navegador.",
 	"Chats": "Bate-papos",
 	"Chats": "Bate-papos",
@@ -118,7 +119,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)",
@@ -213,6 +213,7 @@
 	"Generating search query": "",
 	"Generating search query": "",
 	"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": "",
@@ -278,9 +279,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",
@@ -366,7 +364,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",
@@ -385,6 +382,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": "",
@@ -490,10 +488,11 @@
 	"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": "",
 	"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.",

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

@@ -67,6 +67,7 @@
 	"Categories": "Categorias",
 	"Categories": "Categorias",
 	"Change Password": "Alterar Senha",
 	"Change Password": "Alterar Senha",
 	"Chat": "Bate-papo",
 	"Chat": "Bate-papo",
+	"Chat Bubble UI": "",
 	"Chat History": "Histórico de Bate-papo",
 	"Chat History": "Histórico de Bate-papo",
 	"Chat History is off for this browser.": "O histórico de bate-papo está desativado para este navegador.",
 	"Chat History is off for this browser.": "O histórico de bate-papo está desativado para este navegador.",
 	"Chats": "Bate-papos",
 	"Chats": "Bate-papos",
@@ -118,7 +119,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)",
@@ -213,6 +213,7 @@
 	"Generating search query": "",
 	"Generating search query": "",
 	"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": "",
@@ -278,9 +279,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",
@@ -366,7 +364,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",
@@ -385,6 +382,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": "",
@@ -490,10 +488,11 @@
 	"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": "",
 	"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.",

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

@@ -67,6 +67,7 @@
 	"Categories": "Категории",
 	"Categories": "Категории",
 	"Change Password": "Изменить пароль",
 	"Change Password": "Изменить пароль",
 	"Chat": "Чат",
 	"Chat": "Чат",
+	"Chat Bubble UI": "",
 	"Chat History": "История чат",
 	"Chat History": "История чат",
 	"Chat History is off for this browser.": "История чат отключен для этого браузера.",
 	"Chat History is off for this browser.": "История чат отключен для этого браузера.",
 	"Chats": "Чаты",
 	"Chats": "Чаты",
@@ -118,7 +119,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)",
@@ -213,6 +213,7 @@
 	"Generating search query": "",
 	"Generating search query": "",
 	"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": "",
@@ -278,9 +279,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": "Назовите свой файл модели",
@@ -366,7 +364,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": "Сканировать",
@@ -385,6 +382,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": "",
@@ -490,10 +488,11 @@
 	"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": "",
 	"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.": "Вы полезный ассистент.",

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

@@ -67,6 +67,7 @@
 	"Categories": "Kategorier",
 	"Categories": "Kategorier",
 	"Change Password": "Ändra lösenord",
 	"Change Password": "Ändra lösenord",
 	"Chat": "Chatt",
 	"Chat": "Chatt",
+	"Chat Bubble UI": "",
 	"Chat History": "Chatthistorik",
 	"Chat History": "Chatthistorik",
 	"Chat History is off for this browser.": "Chatthistoriken är avstängd för denna webbläsare.",
 	"Chat History is off for this browser.": "Chatthistoriken är avstängd för denna webbläsare.",
 	"Chats": "Chattar",
 	"Chats": "Chattar",
@@ -118,7 +119,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)",
@@ -213,6 +213,7 @@
 	"Generating search query": "",
 	"Generating search query": "",
 	"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": "",
@@ -278,9 +279,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",
@@ -366,7 +364,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",
@@ -385,6 +382,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": "",
@@ -490,10 +488,11 @@
 	"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": "",
 	"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.",

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

@@ -67,6 +67,7 @@
 	"Categories": "Kategoriler",
 	"Categories": "Kategoriler",
 	"Change Password": "Parola Değiştir",
 	"Change Password": "Parola Değiştir",
 	"Chat": "Sohbet",
 	"Chat": "Sohbet",
+	"Chat Bubble UI": "",
 	"Chat History": "Sohbet Geçmişi",
 	"Chat History": "Sohbet Geçmişi",
 	"Chat History is off for this browser.": "Bu tarayıcı için sohbet geçmişi kapalı.",
 	"Chat History is off for this browser.": "Bu tarayıcı için sohbet geçmişi kapalı.",
 	"Chats": "Sohbetler",
 	"Chats": "Sohbetler",
@@ -118,7 +119,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)",
@@ -213,6 +213,7 @@
 	"Generating search query": "",
 	"Generating search query": "",
 	"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",
@@ -278,9 +279,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",
@@ -366,7 +364,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",
@@ -385,6 +382,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",
@@ -490,10 +488,11 @@
 	"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": "",
 	"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.",

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

@@ -67,6 +67,7 @@
 	"Categories": "Категорії",
 	"Categories": "Категорії",
 	"Change Password": "Змінити пароль",
 	"Change Password": "Змінити пароль",
 	"Chat": "Чат",
 	"Chat": "Чат",
+	"Chat Bubble UI": "",
 	"Chat History": "Історія чату",
 	"Chat History": "Історія чату",
 	"Chat History is off for this browser.": "Історія чату вимкнена для цього браузера.",
 	"Chat History is off for this browser.": "Історія чату вимкнена для цього браузера.",
 	"Chats": "Чати",
 	"Chats": "Чати",
@@ -118,7 +119,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)",
@@ -213,6 +213,7 @@
 	"Generating search query": "",
 	"Generating search query": "",
 	"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": "Допоможіть",
@@ -278,9 +279,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": "Назвіть свій файл моделі",
@@ -366,7 +364,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": "Сканування",
@@ -385,6 +382,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": "Вересень",
@@ -490,10 +488,11 @@
 	"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": "",
 	"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.": "Ви корисний асистент.",

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

@@ -67,6 +67,7 @@
 	"Categories": "Danh mục",
 	"Categories": "Danh mục",
 	"Change Password": "Đổi Mật khẩu",
 	"Change Password": "Đổi Mật khẩu",
 	"Chat": "Trò chuyện",
 	"Chat": "Trò chuyện",
+	"Chat Bubble UI": "",
 	"Chat History": "Lịch sử chat",
 	"Chat History": "Lịch sử chat",
 	"Chat History is off for this browser.": "Lịch sử chat đã tắt cho trình duyệt này.",
 	"Chat History is off for this browser.": "Lịch sử chat đã tắt cho trình duyệt này.",
 	"Chats": "Chat",
 	"Chats": "Chat",
@@ -118,7 +119,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)",
@@ -213,6 +213,7 @@
 	"Generating search query": "",
 	"Generating search query": "",
 	"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": "",
@@ -278,9 +279,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",
@@ -366,7 +364,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",
@@ -385,6 +382,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": "",
@@ -490,10 +488,11 @@
 	"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": "",
 	"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.",

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

@@ -67,6 +67,7 @@
 	"Categories": "分类",
 	"Categories": "分类",
 	"Change Password": "更改密码",
 	"Change Password": "更改密码",
 	"Chat": "聊天",
 	"Chat": "聊天",
+	"Chat Bubble UI": "",
 	"Chat History": "聊天历史",
 	"Chat History": "聊天历史",
 	"Chat History is off for this browser.": "此浏览器已关闭聊天历史功能。",
 	"Chat History is off for this browser.": "此浏览器已关闭聊天历史功能。",
 	"Chats": "聊天",
 	"Chats": "聊天",
@@ -118,7 +119,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)",
@@ -213,6 +213,7 @@
 	"Generating search query": "",
 	"Generating search query": "",
 	"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": "帮助",
@@ -278,9 +279,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": "命名你的模型文件",
@@ -366,7 +364,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": "扫描",
@@ -385,6 +382,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": "",
@@ -490,10 +488,11 @@
 	"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": "",
 	"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.": "你是一个有帮助的助手。",

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

@@ -67,6 +67,7 @@
 	"Categories": "分類",
 	"Categories": "分類",
 	"Change Password": "修改密碼",
 	"Change Password": "修改密碼",
 	"Chat": "聊天",
 	"Chat": "聊天",
+	"Chat Bubble UI": "",
 	"Chat History": "聊天紀錄功能",
 	"Chat History": "聊天紀錄功能",
 	"Chat History is off for this browser.": "此瀏覽器已關閉聊天紀錄功能。",
 	"Chat History is off for this browser.": "此瀏覽器已關閉聊天紀錄功能。",
 	"Chats": "聊天",
 	"Chats": "聊天",
@@ -118,7 +119,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)",
@@ -213,6 +213,7 @@
 	"Generating search query": "",
 	"Generating search query": "",
 	"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": "",
@@ -278,9 +279,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",
@@ -366,7 +364,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": "掃描",
@@ -385,6 +382,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": "九月",
@@ -490,10 +488,11 @@
 	"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": "",
 	"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 - 0
src/lib/stores/index.ts

@@ -9,6 +9,8 @@ export const user: Writable<SessionUser | undefined> = writable(undefined);
 // Frontend
 // Frontend
 export const MODEL_DOWNLOAD_POOL = writable({});
 export const MODEL_DOWNLOAD_POOL = writable({});
 
 
+export const mobile = writable(false);
+
 export const theme = writable('system');
 export const theme = writable('system');
 export const chatId = writable('');
 export const chatId = writable('');
 
 
@@ -37,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);
 
 
 export type Model = OpenAIModel | OllamaModel;
 export type Model = OpenAIModel | OllamaModel;

+ 66 - 0
src/lib/utils/index.test.ts

@@ -0,0 +1,66 @@
+import { promptTemplate } from '$lib/utils/index';
+import { expect, test } from 'vitest';
+
+test('promptTemplate correctly replaces {{prompt}} placeholder', () => {
+	const template = 'Hello {{prompt}}!';
+	const prompt = 'world';
+	const expected = 'Hello world!';
+	const actual = promptTemplate(template, prompt);
+	expect(actual).toBe(expected);
+});
+
+test('promptTemplate correctly replaces {{prompt:start:<length>}} placeholder', () => {
+	const template = 'Hello {{prompt:start:3}}!';
+	const prompt = 'world';
+	const expected = 'Hello wor!';
+	const actual = promptTemplate(template, prompt);
+	expect(actual).toBe(expected);
+});
+
+test('promptTemplate correctly replaces {{prompt:end:<length>}} placeholder', () => {
+	const template = 'Hello {{prompt:end:3}}!';
+	const prompt = 'world';
+	const expected = 'Hello rld!';
+	const actual = promptTemplate(template, prompt);
+	expect(actual).toBe(expected);
+});
+
+test('promptTemplate correctly replaces {{prompt:middletruncate:<length>}} placeholder when prompt length is greater than length', () => {
+	const template = 'Hello {{prompt:middletruncate:4}}!';
+	const prompt = 'world';
+	const expected = 'Hello wo...ld!';
+	const actual = promptTemplate(template, prompt);
+	expect(actual).toBe(expected);
+});
+
+test('promptTemplate correctly replaces {{prompt:middletruncate:<length>}} placeholder when prompt length is less than or equal to length', () => {
+	const template = 'Hello {{prompt:middletruncate:5}}!';
+	const prompt = 'world';
+	const expected = 'Hello world!';
+	const actual = promptTemplate(template, prompt);
+	expect(actual).toBe(expected);
+});
+
+test('promptTemplate returns original template when no placeholders are present', () => {
+	const template = 'Hello world!';
+	const prompt = 'world';
+	const expected = 'Hello world!';
+	const actual = promptTemplate(template, prompt);
+	expect(actual).toBe(expected);
+});
+
+test('promptTemplate does not replace placeholders inside of replaced placeholders', () => {
+	const template = 'Hello {{prompt}}!';
+	const prompt = 'World, {{prompt}} injection';
+	const expected = 'Hello World, {{prompt}} injection!';
+	const actual = promptTemplate(template, prompt);
+	expect(actual).toBe(expected);
+});
+
+test('promptTemplate correctly replaces multiple placeholders', () => {
+	const template = 'Hello {{prompt}}! This is {{prompt:start:3}}!';
+	const prompt = 'world';
+	const expected = 'Hello world! This is wor!';
+	const actual = promptTemplate(template, prompt);
+	expect(actual).toBe(expected);
+});

+ 32 - 15
src/lib/utils/index.ts

@@ -472,22 +472,39 @@ export const blobToFile = (blob, fileName) => {
 	return file;
 	return file;
 };
 };
 
 
-export const promptTemplate = (template: string, prompt: string) => {
-	prompt = prompt.replace(/{{prompt}}|{{prompt:start:\d+}}|{{prompt:end:\d+}}/g, '');
-
-	template = template.replace(/{{prompt}}/g, prompt);
-
-	// Replace all instances of {{prompt:start:<length>}} with the first <length> characters of the prompt
-	template = template.replace(/{{prompt:start:(\d+)}}/g, (match, length) =>
-		prompt.substring(0, parseInt(length))
-	);
-
-	// Replace all instances of {{prompt:end:<length>}} with the last <length> characters of the prompt
-	template = template.replace(/{{prompt:end:(\d+)}}/g, (match, length) =>
-		prompt.slice(-parseInt(length))
+/**
+ * This function is used to replace placeholders in a template string with the provided prompt.
+ * The placeholders can be in the following formats:
+ * - `{{prompt}}`: This will be replaced with the entire prompt.
+ * - `{{prompt:start:<length>}}`: This will be replaced with the first <length> characters of the prompt.
+ * - `{{prompt:end:<length>}}`: This will be replaced with the last <length> characters of the prompt.
+ * - `{{prompt:middletruncate:<length>}}`: This will be replaced with the prompt truncated to <length> characters, with '...' in the middle.
+ *
+ * @param {string} template - The template string containing placeholders.
+ * @param {string} prompt - The string to replace the placeholders with.
+ * @returns {string} The template string with the placeholders replaced by the prompt.
+ */
+export const promptTemplate = (template: string, prompt: string): string => {
+	return template.replace(
+		/{{prompt}}|{{prompt:start:(\d+)}}|{{prompt:end:(\d+)}}|{{prompt:middletruncate:(\d+)}}/g,
+		(match, startLength, endLength, middleLength) => {
+			if (match === '{{prompt}}') {
+				return prompt;
+			} else if (match.startsWith('{{prompt:start:')) {
+				return prompt.substring(0, startLength);
+			} else if (match.startsWith('{{prompt:end:')) {
+				return prompt.slice(-endLength);
+			} else if (match.startsWith('{{prompt:middletruncate:')) {
+				if (prompt.length <= middleLength) {
+					return prompt;
+				}
+				const start = prompt.slice(0, Math.ceil(middleLength / 2));
+				const end = prompt.slice(-Math.floor(middleLength / 2));
+				return `${start}...${end}`;
+			}
+			return '';
+		}
 	);
 	);
-
-	return template;
 };
 };
 
 
 export const approximateToHumanReadable = (nanoseconds: number) => {
 export const approximateToHumanReadable = (nanoseconds: number) => {

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

@@ -956,7 +956,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

@@ -967,7 +967,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>

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

@@ -1,6 +1,6 @@
 <script>
 <script>
 	import { onMount, tick, setContext } from 'svelte';
 	import { onMount, tick, setContext } from 'svelte';
-	import { config, user, theme, WEBUI_NAME } from '$lib/stores';
+	import { config, user, theme, WEBUI_NAME, mobile } from '$lib/stores';
 	import { goto } from '$app/navigation';
 	import { goto } from '$app/navigation';
 	import { Toaster, toast } from 'svelte-sonner';
 	import { Toaster, toast } from 'svelte-sonner';
 
 
@@ -18,9 +18,22 @@
 	setContext('i18n', i18n);
 	setContext('i18n', i18n);
 
 
 	let loaded = false;
 	let loaded = false;
+	const BREAKPOINT = 768;
 
 
 	onMount(async () => {
 	onMount(async () => {
 		theme.set(localStorage.theme);
 		theme.set(localStorage.theme);
+
+		mobile.set(window.innerWidth < BREAKPOINT);
+		const onResize = () => {
+			if (window.innerWidth < BREAKPOINT) {
+				mobile.set(true);
+			} else {
+				mobile.set(false);
+			}
+		};
+
+		window.addEventListener('resize', onResize);
+
 		let backendConfig = null;
 		let backendConfig = null;
 		try {
 		try {
 			backendConfig = await getBackendConfig();
 			backendConfig = await getBackendConfig();
@@ -67,6 +80,10 @@
 
 
 		document.getElementById('splash-screen')?.remove();
 		document.getElementById('splash-screen')?.remove();
 		loaded = true;
 		loaded = true;
+
+		return () => {
+			window.removeEventListener('resize', onResize);
+		};
 	});
 	});
 </script>
 </script>
 
 

+ 27 - 0
src/routes/modelfiles/create/+page.svelte

@@ -0,0 +1,27 @@
+<script lang="ts">
+	import { goto } from '$app/navigation';
+	import { onMount } from 'svelte';
+
+	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);
+			sessionStorage.modelfile = JSON.stringify(modelfile);
+
+			goto('/workspace/modelfiles/create');
+		});
+
+		if (window.opener ?? false) {
+			window.opener.postMessage('loaded', '*');
+		}
+	});
+</script>

+ 27 - 0
src/routes/prompts/create/+page.svelte

@@ -0,0 +1,27 @@
+<script lang="ts">
+	import { goto } from '$app/navigation';
+	import { onMount } from 'svelte';
+
+	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 prompts = JSON.parse(event.data);
+			sessionStorage.modelfile = JSON.stringify(prompts);
+
+			goto('/workspace/prompts/create');
+		});
+
+		if (window.opener ?? false) {
+			window.opener.postMessage('loaded', '*');
+		}
+	});
+</script>

部分文件因为文件数量过多而无法显示