Unverified Commit 75d71305 authored by perf3ct's avatar perf3ct
Browse files

Merge remote-tracking branch 'upstream/main' into feature-external-db-reconnect

parents ad32a2ef 162643a4
import re
import math
from datetime import datetime
from typing import Optional
def prompt_template(
template: str, user_name: str = None, current_location: str = None
) -> str:
# Get the current date
current_date = datetime.now()
# Format the date to YYYY-MM-DD
formatted_date = current_date.strftime("%Y-%m-%d")
# Replace {{CURRENT_DATE}} in the template with the formatted date
template = template.replace("{{CURRENT_DATE}}", formatted_date)
if user_name:
# Replace {{USER_NAME}} in the template with the user's name
template = template.replace("{{USER_NAME}}", user_name)
if current_location:
# Replace {{CURRENT_LOCATION}} in the template with the current location
template = template.replace("{{CURRENT_LOCATION}}", current_location)
return template
def title_generation_template(
template: str, prompt: str, user: Optional[dict] = None
) -> str:
def replacement_function(match):
full_match = match.group(0)
start_length = match.group(1)
end_length = match.group(2)
middle_length = match.group(3)
if full_match == "{{prompt}}":
return prompt
elif start_length is not None:
return prompt[: int(start_length)]
elif end_length is not None:
return prompt[-int(end_length) :]
elif middle_length is not None:
middle_length = int(middle_length)
if len(prompt) <= middle_length:
return prompt
start = prompt[: math.ceil(middle_length / 2)]
end = prompt[-math.floor(middle_length / 2) :]
return f"{start}...{end}"
return ""
template = re.sub(
r"{{prompt}}|{{prompt:start:(\d+)}}|{{prompt:end:(\d+)}}|{{prompt:middletruncate:(\d+)}}",
replacement_function,
template,
)
template = prompt_template(
template,
**(
{"user_name": user.get("name"), "current_location": user.get("location")}
if user
else {}
),
)
return template
def search_query_generation_template(
template: str, prompt: str, user: Optional[dict] = None
) -> str:
def replacement_function(match):
full_match = match.group(0)
start_length = match.group(1)
end_length = match.group(2)
middle_length = match.group(3)
if full_match == "{{prompt}}":
return prompt
elif start_length is not None:
return prompt[: int(start_length)]
elif end_length is not None:
return prompt[-int(end_length) :]
elif middle_length is not None:
middle_length = int(middle_length)
if len(prompt) <= middle_length:
return prompt
start = prompt[: math.ceil(middle_length / 2)]
end = prompt[-math.floor(middle_length / 2) :]
return f"{start}...{end}"
return ""
template = re.sub(
r"{{prompt}}|{{prompt:start:(\d+)}}|{{prompt:end:(\d+)}}|{{prompt:middletruncate:(\d+)}}",
replacement_function,
template,
)
template = prompt_template(
template,
**(
{"user_name": user.get("name"), "current_location": user.get("location")}
if user
else {}
),
)
return template
def tools_function_calling_generation_template(template: str, tools_specs: str) -> str:
template = template.replace("{{TOOLS}}", tools_specs)
return template
import inspect
from typing import get_type_hints, List, Dict, Any
def doc_to_dict(docstring):
lines = docstring.split("\n")
description = lines[1].strip()
param_dict = {}
for line in lines:
if ":param" in line:
line = line.replace(":param", "").strip()
param, desc = line.split(":", 1)
param_dict[param.strip()] = desc.strip()
ret_dict = {"description": description, "params": param_dict}
return ret_dict
def get_tools_specs(tools) -> List[dict]:
function_list = [
{"name": func, "function": getattr(tools, func)}
for func in dir(tools)
if callable(getattr(tools, func)) and not func.startswith("__")
]
specs = []
for function_item in function_list:
function_name = function_item["name"]
function = function_item["function"]
function_doc = doc_to_dict(function.__doc__ or function_name)
specs.append(
{
"name": function_name,
# TODO: multi-line desc?
"description": function_doc.get("description", function_name),
"parameters": {
"type": "object",
"properties": {
param_name: {
"type": param_annotation.__name__.lower(),
**(
{
"enum": (
str(param_annotation.__args__)
if hasattr(param_annotation, "__args__")
else None
)
}
if hasattr(param_annotation, "__args__")
else {}
),
"description": function_doc.get("params", {}).get(
param_name, param_name
),
}
for param_name, param_annotation in get_type_hints(
function
).items()
if param_name != "return" and param_name != "__user__"
},
"required": [
name
for name, param in inspect.signature(
function
).parameters.items()
if param.default is param.empty
],
},
}
)
return specs
......@@ -28,19 +28,6 @@ describe('Settings', () => {
});
});
context('Connections', () => {
it('user can open the Connections modal and hit save', () => {
cy.get('button').contains('Connections').click();
cy.get('button').contains('Save').click();
});
});
context('Models', () => {
it('user can open the Models modal', () => {
cy.get('button').contains('Models').click();
});
});
context('Interface', () => {
it('user can open the Interface modal and hit save', () => {
cy.get('button').contains('Interface').click();
......@@ -55,14 +42,6 @@ describe('Settings', () => {
});
});
context('Images', () => {
it('user can open the Images modal and hit save', () => {
cy.get('button').contains('Images').click();
// Currently fails because the backend requires a valid URL
// cy.get('button').contains('Save').click();
});
});
context('Chats', () => {
it('user can open the Chats modal', () => {
cy.get('button').contains('Chats').click();
......
demo.gif

4.95 MB | W: | H:

demo.gif

4.13 MB | W: | H:

demo.gif
demo.gif
demo.gif
demo.gif
  • 2-up
  • Swipe
  • Onion skin
......@@ -41,10 +41,11 @@ Looking to contribute? Great! Here's how you can help:
We welcome pull requests. Before submitting one, please:
1. Discuss your idea or issue in the [issues section](https://github.com/open-webui/open-webui/issues).
1. Open a discussion regarding your ideas [here](https://github.com/open-webui/open-webui/discussions/new/choose).
2. Follow the project's coding standards and include tests for new features.
3. Update documentation as necessary.
4. Write clear, descriptive commit messages.
5. It's essential to complete your pull request in a timely manner. We move fast, and having PRs hang around too long is not feasible. If you can't get it done within a reasonable time frame, we may have to close it to keep the project moving forward.
### 📚 Documentation & Tutorials
......
This diff is collapsed.
{
"name": "open-webui",
"version": "0.2.0.dev3",
"version": "0.3.4",
"private": true,
"scripts": {
"dev": "npm run pyodide:fetch && vite dev --host",
......@@ -16,7 +16,7 @@
"format:backend": "black . --exclude \".venv/|/venv/\"",
"i18n:parse": "i18next --config i18next-parser.config.ts && prettier --write \"src/lib/i18n/**/*.{js,json}\"",
"cy:open": "cypress open",
"test:frontend": "vitest",
"test:frontend": "vitest --passWithNoTests",
"pyodide:fetch": "node scripts/prepare-pyodide.js"
},
"devDependencies": {
......@@ -48,10 +48,14 @@
},
"type": "module",
"dependencies": {
"@codemirror/lang-javascript": "^6.2.2",
"@codemirror/lang-python": "^6.1.6",
"@codemirror/theme-one-dark": "^6.1.2",
"@pyscript/core": "^0.4.32",
"@sveltejs/adapter-node": "^1.3.1",
"async": "^3.2.5",
"bits-ui": "^0.19.7",
"codemirror": "^6.0.1",
"dayjs": "^1.11.10",
"eventsource-parser": "^1.1.2",
"file-saver": "^2.0.5",
......@@ -63,7 +67,10 @@
"js-sha256": "^0.10.1",
"katex": "^0.16.9",
"marked": "^9.1.0",
"mermaid": "^10.9.1",
"pyodide": "^0.26.0-alpha.4",
"socket.io-client": "^4.7.5",
"sortablejs": "^1.15.2",
"svelte-sonner": "^0.3.19",
"tippy.js": "^6.3.7",
"uuid": "^9.0.1"
......
......@@ -26,8 +26,6 @@ dependencies = [
"PyMySQL==1.1.0",
"bcrypt==4.1.3",
"litellm[proxy]==1.37.20",
"boto3==1.34.110",
"argon2-cffi==23.1.0",
......@@ -66,6 +64,10 @@ dependencies = [
"langfuse==2.33.0",
"youtube-transcript-api==0.6.2",
"pytube==15.0.0",
"extract_msg",
"pydub",
"duckduckgo-search~=6.1.5"
]
readme = "README.md"
requires-python = ">= 3.11, < 3.12.0a1"
......
This diff is collapsed.
This diff is collapsed.
......@@ -92,10 +92,18 @@ select {
visibility: hidden;
}
.scrollbar-hidden::-webkit-scrollbar-corner {
display: none;
}
.scrollbar-none::-webkit-scrollbar {
display: none; /* for Chrome, Safari and Opera */
}
.scrollbar-none::-webkit-scrollbar-corner {
display: none;
}
.scrollbar-none {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
......@@ -111,3 +119,16 @@ input::-webkit-inner-spin-button {
input[type='number'] {
-moz-appearance: textfield; /* Firefox */
}
.cm-editor {
height: 100%;
width: 100%;
}
.cm-scroller {
@apply scrollbar-hidden;
}
.cm-editor.cm-focused {
outline: none;
}
......@@ -32,6 +32,9 @@
} else if (localStorage.theme && localStorage.theme === 'system') {
systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches;
document.documentElement.classList.add(systemTheme ? 'dark' : 'light');
} else if (localStorage.theme && localStorage.theme === 'her') {
document.documentElement.classList.add('dark');
document.documentElement.classList.add('her');
} else {
document.documentElement.classList.add('dark');
}
......@@ -59,15 +62,7 @@
<div
id="splash-screen"
style="
position: fixed;
z-index: 100;
background: #fff;
top: 0;
left: 0;
width: 100%;
height: 100%;
"
style="position: fixed; z-index: 100; top: 0; left: 0; width: 100%; height: 100%"
>
<style type="text/css" nonce="">
html {
......@@ -76,20 +71,138 @@
</style>
<img
id="logo"
style="
position: absolute;
width: 6rem;
height: 6rem;
top: 46%;
top: 41%;
left: 50%;
margin: -40px 0 0 -40px;
margin-left: -3rem;
"
src="/logo.svg"
/>
<div
style="
position: absolute;
top: 33%;
left: 50%;
width: 24rem;
margin-left: -12rem;
display: flex;
flex-direction: column;
align-items: center;
"
>
<img
id="logo-her"
style="width: 13rem; height: 13rem"
src="/logo.svg"
class="animate-pulse-fast"
/>
<div style="position: relative; width: 24rem; margin-top: 0.5rem">
<div
id="progress-background"
style="
position: absolute;
width: 100%;
height: 0.75rem;
border-radius: 9999px;
background-color: #fafafa9a;
"
></div>
<div
id="progress-bar"
style="
position: absolute;
width: 0%;
height: 0.75rem;
border-radius: 9999px;
background-color: #fff;
"
class="bg-white"
></div>
</div>
</div>
<!-- <span style="position: absolute; bottom: 32px; left: 50%; margin: -36px 0 0 -36px">
Footer content
</span> -->
</div>
</body>
</html>
<style type="text/css" nonce="">
html {
overflow-y: hidden !important;
}
#splash-screen {
background: #fff;
}
html.dark #splash-screen {
background: #000;
}
html.dark #splash-screen img {
filter: invert(1);
}
html.her #splash-screen {
background: #983724;
}
#logo-her {
display: none;
}
#progress-background {
display: none;
}
#progress-bar {
display: none;
}
html.her #logo {
display: none;
}
html.her #logo-her {
display: block;
filter: invert(1);
}
html.her #progress-background {
display: block;
}
html.her #progress-bar {
display: block;
}
@media (max-width: 24rem) {
html.her #progress-background {
display: none;
}
html.her #progress-bar {
display: none;
}
}
@keyframes pulse {
50% {
opacity: 0.65;
}
}
.animate-pulse-fast {
animation: pulse 1.5s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
</style>
......@@ -98,7 +98,7 @@ export const synthesizeOpenAISpeech = async (
token: string = '',
speaker: string = 'alloy',
text: string = '',
model: string = 'tts-1'
model?: string
) => {
let error = null;
......@@ -109,9 +109,9 @@ export const synthesizeOpenAISpeech = async (
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: model,
input: text,
voice: speaker
voice: speaker,
...(model && { model })
})
})
.then(async (res) => {
......
This diff is collapsed.
This diff is collapsed.
......@@ -76,7 +76,10 @@ export const getDocs = async (token: string = '') => {
export const getDocByName = async (token: string, name: string) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/documents/name/${name}`, {
const searchParams = new URLSearchParams();
searchParams.append('name', name);
const res = await fetch(`${WEBUI_API_BASE_URL}/documents/docs?${searchParams.toString()}`, {
method: 'GET',
headers: {
Accept: 'application/json',
......@@ -113,7 +116,10 @@ type DocUpdateForm = {
export const updateDocByName = async (token: string, name: string, form: DocUpdateForm) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/documents/name/${name}/update`, {
const searchParams = new URLSearchParams();
searchParams.append('name', name);
const res = await fetch(`${WEBUI_API_BASE_URL}/documents/doc/update?${searchParams.toString()}`, {
method: 'POST',
headers: {
Accept: 'application/json',
......@@ -154,7 +160,10 @@ type TagDocForm = {
export const tagDocByName = async (token: string, name: string, form: TagDocForm) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/documents/name/${name}/tags`, {
const searchParams = new URLSearchParams();
searchParams.append('name', name);
const res = await fetch(`${WEBUI_API_BASE_URL}/documents/doc/tags?${searchParams.toString()}`, {
method: 'POST',
headers: {
Accept: 'application/json',
......@@ -190,7 +199,10 @@ export const tagDocByName = async (token: string, name: string, form: TagDocForm
export const deleteDocByName = async (token: string, name: string) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/documents/name/${name}/delete`, {
const searchParams = new URLSearchParams();
searchParams.append('name', name);
const res = await fetch(`${WEBUI_API_BASE_URL}/documents/doc/delete?${searchParams.toString()}`, {
method: 'DELETE',
headers: {
Accept: 'application/json',
......
This diff is collapsed.
......@@ -3,7 +3,7 @@ import { WEBUI_API_BASE_URL } from '$lib/constants';
export const getMemories = async (token: string) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/memories`, {
const res = await fetch(`${WEBUI_API_BASE_URL}/memories/`, {
method: 'GET',
headers: {
Accept: 'application/json',
......
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment