Browse Source

Merge pull request #5197 from open-webui/dev

0.3.19
Timothy Jaeryang Baek 8 months ago
parent
commit
05c0423d6e

+ 15 - 0
CHANGELOG.md

@@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## [0.3.19] - 2024-09-05
+
+### Added
+
+- **🌐 Translation Update**: Improved Chinese translations.
+
+### Fixed
+
+- **📂 DATA_DIR Overriding**: Fixed an issue to avoid overriding DATA_DIR, preventing errors when directories are set identically, ensuring smoother operation and data management.
+- **🛠️ Frontmatter Extraction**: Fixed the extraction process for frontmatter in tools and functions.
+
+### Changed
+
+- **🎨 UI Styling**: Refined the user interface styling for enhanced visual coherence and user experience.
+
 ## [0.3.18] - 2024-09-04
 
 ### Added

+ 18 - 16
backend/open_webui/apps/webui/main.py

@@ -152,29 +152,33 @@ async def get_pipe_models():
 
         # Check if function is a manifold
         if hasattr(function_module, "pipes"):
-            manifold_pipes = []
+            sub_pipes = []
 
             # Check if pipes is a function or a list
-            if callable(function_module.pipes):
-                manifold_pipes = function_module.pipes()
-            else:
-                manifold_pipes = function_module.pipes
 
-            for p in manifold_pipes:
-                manifold_pipe_id = f'{pipe.id}.{p["id"]}'
-                manifold_pipe_name = p["name"]
+            try:
+                if callable(function_module.pipes):
+                    sub_pipes = function_module.pipes()
+                else:
+                    sub_pipes = function_module.pipes
+            except Exception as e:
+                log.exception(e)
+                sub_pipes = []
+
+            print(sub_pipes)
+
+            for p in sub_pipes:
+                sub_pipe_id = f'{pipe.id}.{p["id"]}'
+                sub_pipe_name = p["name"]
 
                 if hasattr(function_module, "name"):
-                    manifold_pipe_name = f"{function_module.name}{manifold_pipe_name}"
+                    sub_pipe_name = f"{function_module.name}{sub_pipe_name}"
 
                 pipe_flag = {"type": pipe.type}
-                if hasattr(function_module, "ChatValves"):
-                    pipe_flag["valves_spec"] = function_module.ChatValves.schema()
-
                 pipe_models.append(
                     {
-                        "id": manifold_pipe_id,
-                        "name": manifold_pipe_name,
+                        "id": sub_pipe_id,
+                        "name": sub_pipe_name,
                         "object": "model",
                         "created": pipe.created_at,
                         "owned_by": "openai",
@@ -183,8 +187,6 @@ async def get_pipe_models():
                 )
         else:
             pipe_flag = {"type": "pipe"}
-            if hasattr(function_module, "ChatValves"):
-                pipe_flag["valves_spec"] = function_module.ChatValves.schema()
 
             pipe_models.append(
                 {

+ 21 - 25
backend/open_webui/apps/webui/utils.py

@@ -11,9 +11,9 @@ from open_webui.apps.webui.models.tools import Tools
 from open_webui.config import FUNCTIONS_DIR, TOOLS_DIR
 
 
-def extract_frontmatter(file_path):
+def extract_frontmatter(content):
     """
-    Extract frontmatter as a dictionary from the specified file path.
+    Extract frontmatter as a dictionary from the provided content string.
     """
     frontmatter = {}
     frontmatter_started = False
@@ -21,29 +21,25 @@ def extract_frontmatter(file_path):
     frontmatter_pattern = re.compile(r"^\s*([a-z_]+):\s*(.*)\s*$", re.IGNORECASE)
 
     try:
-        with open(file_path, "r", encoding="utf-8") as file:
-            first_line = file.readline()
-            if first_line.strip() != '"""':
-                # The file doesn't start with triple quotes
-                return {}
-
-            frontmatter_started = True
-
-            for line in file:
-                if '"""' in line:
-                    if frontmatter_started:
-                        frontmatter_ended = True
-                        break
-
-                if frontmatter_started and not frontmatter_ended:
-                    match = frontmatter_pattern.match(line)
-                    if match:
-                        key, value = match.groups()
-                        frontmatter[key.strip()] = value.strip()
-
-    except FileNotFoundError:
-        print(f"Error: The file {file_path} does not exist.")
-        return {}
+        lines = content.splitlines()
+        if len(lines) < 1 or lines[0].strip() != '"""':
+            # The content doesn't start with triple quotes
+            return {}
+
+        frontmatter_started = True
+
+        for line in lines[1:]:
+            if '"""' in line:
+                if frontmatter_started:
+                    frontmatter_ended = True
+                    break
+
+            if frontmatter_started and not frontmatter_ended:
+                match = frontmatter_pattern.match(line)
+                if match:
+                    key, value = match.groups()
+                    frontmatter[key.strip()] = value.strip()
+
     except Exception as e:
         print(f"An error occurred: {e}")
         return {}

+ 2 - 2
package-lock.json

@@ -1,12 +1,12 @@
 {
 	"name": "open-webui",
-	"version": "0.3.18",
+	"version": "0.3.19",
 	"lockfileVersion": 3,
 	"requires": true,
 	"packages": {
 		"": {
 			"name": "open-webui",
-			"version": "0.3.18",
+			"version": "0.3.19",
 			"dependencies": {
 				"@codemirror/lang-javascript": "^6.2.2",
 				"@codemirror/lang-python": "^6.1.6",

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
 	"name": "open-webui",
-	"version": "0.3.18",
+	"version": "0.3.19",
 	"private": true,
 	"scripts": {
 		"dev": "npm run pyodide:fetch && vite dev --host",

+ 51 - 55
src/lib/components/chat/ChatControls.svelte

@@ -5,6 +5,7 @@
 	import { onMount } from 'svelte';
 	import { mobile, showCallOverlay } from '$lib/stores';
 	import CallOverlay from './MessageInput/CallOverlay.svelte';
+	import Drawer from '../common/Drawer.svelte';
 
 	export let show = false;
 
@@ -43,65 +44,60 @@
 	});
 </script>
 
-{#if largeScreen}
+{#if !largeScreen}
 	{#if show}
-		<div class=" absolute bottom-0 right-0 z-20 h-full pointer-events-none">
-			<div class="pr-4 pt-14 pb-8 w-[24rem] h-full" in:slide={{ duration: 200, axis: 'x' }}>
-				<div
-					class="w-full h-full px-5 py-4 bg-white dark:shadow-lg dark:bg-gray-850 border border-gray-50 dark:border-gray-800 rounded-xl z-50 pointer-events-auto overflow-y-auto scrollbar-hidden"
-				>
-					{#if $showCallOverlay}
-						<CallOverlay
-							bind:files
-							{submitPrompt}
-							{stopResponse}
-							{modelId}
-							{chatId}
-							{eventTarget}
-						/>
-					{:else}
-						<Controls
-							on:close={() => {
-								show = false;
-							}}
-							{models}
-							bind:chatFiles
-							bind:params
-						/>
-					{/if}
-				</div>
+		<Drawer bind:show>
+			<div class="  px-6 py-4 h-full">
+				<Controls
+					on:close={() => {
+						show = false;
+					}}
+					{models}
+					bind:chatFiles
+					bind:params
+				/>
+			</div>
+		</Drawer>
+	{/if}
+
+	{#if $showCallOverlay}
+		<div class=" absolute w-full h-screen max-h-[100dvh] flex z-[999] overflow-hidden">
+			<div
+				class="absolute w-full h-screen max-h-[100dvh] bg-white text-gray-700 dark:bg-black dark:text-gray-300 flex justify-center"
+			>
+				<CallOverlay
+					bind:files
+					{submitPrompt}
+					{stopResponse}
+					{modelId}
+					{chatId}
+					{eventTarget}
+					on:close={() => {
+						show = false;
+					}}
+				/>
 			</div>
 		</div>
 	{/if}
-{:else if $showCallOverlay}
-	<div class=" absolute w-full h-screen max-h-[100dvh] flex z-[999] overflow-hidden">
-		<div
-			class="absolute w-full h-screen max-h-[100dvh] bg-white text-gray-700 dark:bg-black dark:text-gray-300 flex justify-center"
-		>
-			<CallOverlay
-				bind:files
-				{submitPrompt}
-				{stopResponse}
-				{modelId}
-				{chatId}
-				{eventTarget}
-				on:close={() => {
-					show = false;
-				}}
-			/>
+{:else if show}
+	<div class=" absolute bottom-0 right-0 z-20 h-full pointer-events-none">
+		<div class="pr-4 pt-14 pb-8 w-[24rem] h-full" in:slide={{ duration: 200, axis: 'x' }}>
+			<div
+				class="w-full h-full px-5 py-4 bg-white dark:shadow-lg dark:bg-gray-850 border border-gray-50 dark:border-gray-800 rounded-xl z-50 pointer-events-auto overflow-y-auto scrollbar-hidden"
+			>
+				{#if $showCallOverlay}
+					<CallOverlay bind:files {submitPrompt} {stopResponse} {modelId} {chatId} {eventTarget} />
+				{:else}
+					<Controls
+						on:close={() => {
+							show = false;
+						}}
+						{models}
+						bind:chatFiles
+						bind:params
+					/>
+				{/if}
+			</div>
 		</div>
 	</div>
-{:else}
-	<Modal bind:show>
-		<div class="  px-6 py-4 h-full">
-			<Controls
-				on:close={() => {
-					show = false;
-				}}
-				{models}
-				bind:chatFiles
-				bind:params
-			/>
-		</div>
-	</Modal>
 {/if}

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

@@ -34,9 +34,9 @@
 </script>
 
 {#key mounted}
-	<div class="m-auto w-full max-w-6xl px-8 lg:px-20 pb-10">
+	<div class="m-auto w-full max-w-6xl px-8 lg:px-20">
 		<div class="flex justify-start">
-			<div class="flex -space-x-4 mb-1" in:fade={{ duration: 200 }}>
+			<div class="flex -space-x-4 mb-0.5" in:fade={{ duration: 200 }}>
 				{#each models as model, modelIdx}
 					<button
 						on:click={() => {

+ 9 - 5
src/lib/components/chat/ModelSelector.svelte

@@ -55,7 +55,9 @@
 			</div>
 
 			{#if selectedModelIdx === 0}
-				<div class="  self-center mr-2 disabled:text-gray-600 disabled:hover:text-gray-600">
+				<div
+					class="  self-center mx-1 disabled:text-gray-600 disabled:hover:text-gray-600 -translate-y-[0.5px]"
+				>
 					<Tooltip content={$i18n.t('Add Model')}>
 						<button
 							class=" "
@@ -79,7 +81,9 @@
 					</Tooltip>
 				</div>
 			{:else}
-				<div class="  self-center disabled:text-gray-600 disabled:hover:text-gray-600 mr-2">
+				<div
+					class="  self-center mx-1 disabled:text-gray-600 disabled:hover:text-gray-600 -translate-y-[0.5px]"
+				>
 					<Tooltip content={$i18n.t('Remove Model')}>
 						<button
 							{disabled}
@@ -95,7 +99,7 @@
 								viewBox="0 0 24 24"
 								stroke-width="2"
 								stroke="currentColor"
-								class="size-3.5"
+								class="size-3"
 							>
 								<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 12h-15" />
 							</svg>
@@ -107,8 +111,8 @@
 	{/each}
 </div>
 
-{#if showSetDefault && !$mobile}
-	<div class="text-left mt-[1px] ml-1 text-[0.7rem] text-gray-500 font-primary">
+{#if showSetDefault}
+	<div class=" absolute text-left mt-[1px] ml-1 text-[0.7rem] text-gray-500 font-primary">
 		<button on:click={saveDefaultModel}> {$i18n.t('Set as default')}</button>
 	</div>
 {/if}

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

@@ -302,7 +302,7 @@
 					>
 						<div class="flex flex-col">
 							{#if $mobile && (item?.model?.info?.meta?.tags ?? []).length > 0}
-								<div class="flex gap-0.5 self-start h-full mb-0.5 -translate-x-1">
+								<div class="flex gap-0.5 self-start h-full mb-1.5 -translate-x-1">
 									{#each item.model?.info?.meta.tags as tag}
 										<div
 											class=" text-xs font-bold px-1 rounded uppercase line-clamp-1 bg-gray-500/20 text-gray-700 dark:text-gray-200"
@@ -418,7 +418,7 @@
 						</div>
 
 						{#if value === item.value}
-							<div class="ml-auto pl-2">
+							<div class="ml-auto pl-2 pr-2 md:pr-0">
 								<Check />
 							</div>
 						{/if}

+ 94 - 0
src/lib/components/common/Drawer.svelte

@@ -0,0 +1,94 @@
+<script lang="ts">
+	import { onDestroy, onMount, createEventDispatcher } from 'svelte';
+	import { flyAndScale } from '$lib/utils/transitions';
+	import { fade, fly, slide } from 'svelte/transition';
+
+	export let show = false;
+	export let size = 'md';
+
+	let modalElement = null;
+	let mounted = false;
+
+	const sizeToWidth = (size) => {
+		if (size === 'xs') {
+			return 'w-[16rem]';
+		} else if (size === 'sm') {
+			return 'w-[30rem]';
+		} else if (size === 'md') {
+			return 'w-[48rem]';
+		} else {
+			return 'w-[56rem]';
+		}
+	};
+
+	const handleKeyDown = (event: KeyboardEvent) => {
+		if (event.key === 'Escape' && isTopModal()) {
+			console.log('Escape');
+			show = false;
+		}
+	};
+
+	const isTopModal = () => {
+		const modals = document.getElementsByClassName('modal');
+		return modals.length && modals[modals.length - 1] === modalElement;
+	};
+
+	onMount(() => {
+		mounted = true;
+	});
+
+	$: if (show && modalElement) {
+		document.body.appendChild(modalElement);
+		window.addEventListener('keydown', handleKeyDown);
+		document.body.style.overflow = 'hidden';
+	} else if (modalElement) {
+		window.removeEventListener('keydown', handleKeyDown);
+		document.body.removeChild(modalElement);
+		document.body.style.overflow = 'unset';
+	}
+
+	onDestroy(() => {
+		show = false;
+		if (modalElement) {
+			document.body.removeChild(modalElement);
+		}
+	});
+</script>
+
+<!-- svelte-ignore a11y-click-events-have-key-events -->
+<!-- svelte-ignore a11y-no-static-element-interactions -->
+
+<div
+	bind:this={modalElement}
+	class="modal fixed right-0 left-0 bottom-0 bg-black/60 w-full min-h-screen h-screen flex justify-center z-[9999] overflow-hidden overscroll-contain"
+	in:fly={{ y: 100, duration: 100 }}
+	on:mousedown={() => {
+		show = false;
+	}}
+>
+	<div
+		class=" mt-auto max-w-full w-full bg-gray-50 dark:bg-gray-900 max-h-[100dvh] overflow-y-auto scrollbar-hidden"
+		on:mousedown={(e) => {
+			e.stopPropagation();
+		}}
+	>
+		<slot />
+	</div>
+</div>
+
+<style>
+	.modal-content {
+		animation: scaleUp 0.1s ease-out forwards;
+	}
+
+	@keyframes scaleUp {
+		from {
+			transform: scale(0.985);
+			opacity: 0;
+		}
+		to {
+			transform: scale(1);
+			opacity: 1;
+		}
+	}
+</style>

+ 2 - 2
src/lib/components/layout/Sidebar.svelte

@@ -261,7 +261,7 @@
 	id="sidebar"
 	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
         "
 	data-state={$showSidebar}
 >
@@ -273,7 +273,7 @@
 		<div class="px-2.5 flex justify-between space-x-1 text-gray-600 dark:text-gray-400">
 			<a
 				id="sidebar-new-chat-button"
-				class="flex flex-1 justify-between rounded-xl px-2 py-2 hover:bg-gray-100 dark:hover:bg-gray-900 transition"
+				class="flex flex-1 justify-between rounded-xl px-2 h-full hover:bg-gray-100 dark:hover:bg-gray-900 transition"
 				href="/"
 				draggable="false"
 				on:click={async () => {

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

@@ -248,8 +248,8 @@
 	"Enter model tag (e.g. {{modelTag}})": "输入模型标签 (例如:{{modelTag}})",
 	"Enter Number of Steps (e.g. 50)": "输入步骤数 (Steps) (例如:50)",
 	"Enter Score": "输入评分",
-	"Enter SearchApi API Key": "",
-	"Enter SearchApi Engine": "",
+	"Enter SearchApi API Key": "输入 SearchApi API 密钥",
+	"Enter SearchApi Engine": "输入 SearchApi 引擎",
 	"Enter Searxng Query URL": "输入 Searxng 查询地址",
 	"Enter Serper API Key": "输入 Serper API 密钥",
 	"Enter Serply API Key": "输入 Serply API 密钥",
@@ -272,7 +272,7 @@
 	"Export All Chats (All Users)": "导出所有用户对话",
 	"Export chat (.json)": "JSON 文件 (.json)",
 	"Export Chats": "导出对话",
-	"Export Config to JSON File": "",
+	"Export Config to JSON File": "导出配置信息至 JSON 文件中",
 	"Export Documents Mapping": "导出文档映射",
 	"Export Functions": "导出函数",
 	"Export LiteLLM config.yaml": "导出 LteLLM config.yaml 文件",
@@ -337,7 +337,7 @@
 	"Image Settings": "图像设置",
 	"Images": "图像",
 	"Import Chats": "导入对话记录",
-	"Import Config from JSON File": "",
+	"Import Config from JSON File": "导入 JSON 文件中的配置信息",
 	"Import Documents Mapping": "导入文档映射",
 	"Import Functions": "导入函数",
 	"Import Models": "导入模型",
@@ -541,8 +541,8 @@
 	"Search Query Generation Prompt Length Threshold": "搜索查询生成提示长度阈值",
 	"Search Result Count": "搜索结果数量",
 	"Search Tools": "搜索工具",
-	"SearchApi API Key": "",
-	"SearchApi Engine": "",
+	"SearchApi API Key": "SearchApi API 密钥",
+	"SearchApi Engine": "SearchApi 引擎",
 	"Searched {{count}} sites_other": "搜索到 {{count}} 个结果",
 	"Searching \"{{searchQuery}}\"": "搜索 \"{{searchQuery}}\" 中",
 	"Searching Knowledge for \"{{searchQuery}}\"": "检索有关 \"{{searchQuery}}\" 的知识中",