Merge pull request #5197 from open-webui/dev

0.3.19
This commit is contained in:
Timothy Jaeryang Baek 2024-09-05 20:47:33 +02:00 committed by GitHub
commit 05c0423d6e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 219 additions and 112 deletions

View File

@ -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/), 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). 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 ## [0.3.18] - 2024-09-04
### Added ### Added

View File

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

View File

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

4
package-lock.json generated
View File

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

View File

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

View File

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

View File

@ -34,9 +34,9 @@
</script> </script>
{#key mounted} {#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 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} {#each models as model, modelIdx}
<button <button
on:click={() => { on:click={() => {

View File

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

View File

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

View File

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

View File

@ -261,7 +261,7 @@
id="sidebar" id="sidebar"
class="h-screen max-h-[100dvh] min-h-screen select-none {$showSidebar class="h-screen max-h-[100dvh] min-h-screen select-none {$showSidebar
? 'md:relative w-[260px]' ? '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} 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"> <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 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="/" href="/"
draggable="false" draggable="false"
on:click={async () => { on:click={async () => {

View File

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