2024-06-20 07:54:58 +00:00
< script >
2024-10-05 23:16:51 +00:00
import { getContext , createEventDispatcher , onMount , tick } from 'svelte';
2024-06-20 08:07:55 +00:00
import { goto } from '$app/navigation';
2024-06-20 07:54:58 +00:00
2024-06-20 08:07:55 +00:00
const dispatch = createEventDispatcher();
2024-06-20 07:54:58 +00:00
const i18n = getContext('i18n');
import CodeEditor from '$lib/components/common/CodeEditor.svelte';
import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
2024-10-21 01:24:50 +00:00
import Badge from '$lib/components/common/Badge.svelte';
2024-10-21 01:31:24 +00:00
import Tooltip from '$lib/components/common/Tooltip.svelte';
import ChevronLeft from '$lib/components/icons/ChevronLeft.svelte';
2024-06-20 07:54:58 +00:00
let formElement = null;
let loading = false;
let showConfirm = false;
export let edit = false;
export let clone = false;
export let id = '';
export let name = '';
export let meta = {
description: ''
};
export let content = '';
2024-10-05 19:04:36 +00:00
let _content = '';
$: if (content) {
updateContent();
}
const updateContent = () => {
_content = content;
};
2024-06-20 07:54:58 +00:00
$: if (name && !edit && !clone) {
id = name.replace(/\s+/g, '_').toLowerCase();
}
let codeEditor;
2024-06-30 02:49:16 +00:00
let boilerplate = `"""
title: Example Filter
author: open-webui
author_url: https://github.com/open-webui
funding_url: https://github.com/open-webui
version: 0.1
"""
from pydantic import BaseModel, Field
2024-06-20 08:44:52 +00:00
from typing import Optional
2024-06-20 07:54:58 +00:00
2024-06-20 08:51:39 +00:00
2024-06-20 08:07:55 +00:00
class Filter:
2024-06-20 08:44:52 +00:00
class Valves(BaseModel):
2024-06-30 02:49:16 +00:00
priority: int = Field(
default=0, description="Priority level for the filter operations."
)
max_turns: int = Field(
default=8, description="Maximum allowable conversation turns for a user."
)
pass
class UserValves(BaseModel):
max_turns: int = Field(
default=4, description="Maximum allowable conversation turns for a user."
)
2024-06-20 08:44:52 +00:00
pass
2024-06-20 07:54:58 +00:00
def __init__(self):
2024-06-20 08:44:52 +00:00
# Indicates custom file handling logic. This flag helps disengage default routines in favor of custom
# implementations, informing the WebUI to defer file-related operations to designated methods within this class.
2024-06-20 09:42:56 +00:00
# Alternatively, you can remove the files directly from the body in from the inlet hook
2024-06-30 02:49:16 +00:00
# self.file_handler = True
2024-06-20 08:44:52 +00:00
# Initialize 'valves' with specific configurations. Using 'Valves' instance helps encapsulate settings,
# which ensures settings are managed cohesively and not confused with operational flags like 'file_handler'.
2024-06-30 02:49:16 +00:00
self.valves = self.Valves()
2024-06-20 07:54:58 +00:00
pass
2024-06-30 02:49:16 +00:00
def inlet(self, body: dict, __user__: Optional[dict] = None) -> dict:
2024-06-20 08:44:52 +00:00
# Modify the request body or validate it before processing by the chat completion API.
# This function is the pre-processor for the API where various checks on the input can be performed.
# It can also modify the request before sending it to the API.
2024-06-20 09:42:56 +00:00
print(f"inlet:{ __name__ } ")
print(f"inlet:body:{ body } ")
2024-06-30 02:49:16 +00:00
print(f"inlet:user:{ __user__ } ")
2024-06-20 08:51:39 +00:00
2024-06-30 02:49:16 +00:00
if __user__.get("role", "admin") in ["user", "admin"]:
2024-06-20 08:07:55 +00:00
messages = body.get("messages", [])
2024-06-30 02:49:16 +00:00
max_turns = min(__user__["valves"].max_turns, self.valves.max_turns)
if len(messages) > max_turns:
2024-06-20 08:07:55 +00:00
raise Exception(
2024-06-30 02:49:16 +00:00
f"Conversation turn limit exceeded. Max turns: { max_turns } "
2024-06-20 08:07:55 +00:00
)
2024-06-20 07:54:58 +00:00
2024-06-20 08:07:55 +00:00
return body
2024-06-20 07:54:58 +00:00
2024-06-30 02:49:16 +00:00
def outlet(self, body: dict, __user__: Optional[dict] = None) -> dict:
2024-06-20 08:44:52 +00:00
# Modify or analyze the response body after processing by the API.
2024-06-20 08:51:39 +00:00
# This function is the post-processor for the API, which can be used to modify the response
2024-06-20 08:44:52 +00:00
# or perform additional checks and analytics.
2024-06-20 09:42:56 +00:00
print(f"outlet:{ __name__ } ")
print(f"outlet:body:{ body } ")
2024-06-30 02:49:16 +00:00
print(f"outlet:user:{ __user__ } ")
2024-06-20 10:35:21 +00:00
2024-06-30 02:49:16 +00:00
return body
2024-06-20 20:49:04 +00:00
`;
const _boilerplate = `from pydantic import BaseModel
from typing import Optional, Union, Generator, Iterator
2024-09-04 14:54:48 +00:00
from open_webui.utils.misc import get_last_user_message
2024-06-20 20:49:04 +00:00
import os
import requests
# Filter Class: This class is designed to serve as a pre-processor and post-processor
# for request and response modifications. It checks and transforms requests and responses
# to ensure they meet specific criteria before further processing or returning to the user.
class Filter:
class Valves(BaseModel):
max_turns: int = 4
pass
def __init__(self):
# Indicates custom file handling logic. This flag helps disengage default routines in favor of custom
# implementations, informing the WebUI to defer file-related operations to designated methods within this class.
# Alternatively, you can remove the files directly from the body in from the inlet hook
self.file_handler = True
# Initialize 'valves' with specific configurations. Using 'Valves' instance helps encapsulate settings,
# which ensures settings are managed cohesively and not confused with operational flags like 'file_handler'.
self.valves = self.Valves(**{ "max_turns" : 2 } )
pass
def inlet(self, body: dict, user: Optional[dict] = None) -> dict:
# Modify the request body or validate it before processing by the chat completion API.
# This function is the pre-processor for the API where various checks on the input can be performed.
# It can also modify the request before sending it to the API.
print(f"inlet:{ __name__ } ")
print(f"inlet:body:{ body } ")
print(f"inlet:user:{ user } ")
if user.get("role", "admin") in ["user", "admin"]:
messages = body.get("messages", [])
if len(messages) > self.valves.max_turns:
raise Exception(
f"Conversation turn limit exceeded. Max turns: { self . valves . max_turns } "
)
return body
def outlet(self, body: dict, user: Optional[dict] = None) -> dict:
# Modify or analyze the response body after processing by the API.
# This function is the post-processor for the API, which can be used to modify the response
# or perform additional checks and analytics.
print(f"outlet:{ __name__ } ")
print(f"outlet:body:{ body } ")
print(f"outlet:user:{ user } ")
messages = [
{
**message,
"content": f"{ message [ 'content' ]} - @@Modified from Filter Outlet",
}
for message in body.get("messages", [])
]
return { "messages" : messages }
# Pipe Class: This class functions as a customizable pipeline.
# It can be adapted to work with any external or internal models,
# making it versatile for various use cases outside of just OpenAI models.
class Pipe:
class Valves(BaseModel):
OPENAI_API_BASE_URL: str = "https://api.openai.com/v1"
OPENAI_API_KEY: str = "your-key"
pass
def __init__(self):
self.type = "manifold"
self.valves = self.Valves()
self.pipes = self.get_openai_models()
pass
def get_openai_models(self):
if self.valves.OPENAI_API_KEY:
try:
headers = {}
headers["Authorization"] = f"Bearer { self . valves . OPENAI_API_KEY } "
headers["Content-Type"] = "application/json"
r = requests.get(
f"{ self . valves . OPENAI_API_BASE_URL } /models", headers=headers
)
models = r.json()
return [
{
"id": model["id"],
"name": model["name"] if "name" in model else model["id"],
}
for model in models["data"]
if "gpt" in model["id"]
]
except Exception as e:
print(f"Error: { e } ")
return [
{
"id": "error",
"name": "Could not fetch models from OpenAI, please update the API Key in the valves.",
},
]
else:
return []
def pipe(self, body: dict) -> Union[str, Generator, Iterator]:
# This is where you can add your custom pipelines like RAG.
print(f"pipe:{ __name__ } ")
if "user" in body:
print(body["user"])
del body["user"]
headers = {}
headers["Authorization"] = f"Bearer { self . valves . OPENAI_API_KEY } "
headers["Content-Type"] = "application/json"
model_id = body["model"][body["model"].find(".") + 1 :]
payload = { ** body , "model" : model_id }
print(payload)
try:
r = requests.post(
url=f"{ self . valves . OPENAI_API_BASE_URL } /chat/completions",
json=payload,
headers=headers,
stream=True,
)
r.raise_for_status()
if body["stream"]:
return r.iter_lines()
else:
return r.json()
except Exception as e:
return f"Error: { e } "
2024-06-20 10:29:50 +00:00
`;
2024-06-20 07:54:58 +00:00
const saveHandler = async () => {
loading = true;
dispatch('save', {
id,
name,
meta,
content
});
};
const submitHandler = async () => {
if (codeEditor) {
2024-10-05 23:16:51 +00:00
content = _content;
await tick();
2024-06-20 07:54:58 +00:00
const res = await codeEditor.formatPythonCodeHandler();
2024-10-06 19:07:45 +00:00
await tick();
content = _content;
await tick();
2024-06-20 07:54:58 +00:00
if (res) {
console.log('Code formatted successfully');
2024-10-05 23:16:51 +00:00
2024-06-20 07:54:58 +00:00
saveHandler();
}
}
};
< / script >
< 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" >
< form
bind:this={ formElement }
class=" flex flex-col max-h-[100dvh] h-full"
on:submit| preventDefault={() => {
if (edit) {
submitHandler();
} else {
showConfirm = true;
}
}}
>
< div class = "flex flex-col flex-1 overflow-auto h-0 rounded-lg" >
2024-10-21 01:24:50 +00:00
< div class = "w-full mb-2 flex flex-col gap-0.5" >
2024-10-21 01:31:24 +00:00
< div class = "flex w-full items-center" >
< div class = " flex-shrink-0 mr-2" >
< Tooltip content = { $i18n . t ( 'Back' )} >
< button
2024-10-21 01:35:19 +00:00
class="w-full text-left text-sm py-1.5 px-1 rounded-lg dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-gray-850"
2024-10-21 01:31:24 +00:00
on:click={() => {
goto('/workspace/functions');
}}
type="button"
>
< ChevronLeft strokeWidth = "2.5" / >
< / button >
< / Tooltip >
< / div >
2024-10-21 01:24:50 +00:00
< div class = "flex-1" >
< input
2024-10-21 01:35:19 +00:00
class="w-full text-2xl font-medium bg-transparent outline-none font-primary"
2024-10-21 01:24:50 +00:00
type="text"
placeholder={ $i18n . t ( 'Function Name (e.g. My Filter)' )}
bind:value={ name }
required
/>
< / div >
< div >
< Badge type = "muted" content = { $i18n . t ( 'Function' )} / >
< / div >
< / div >
2024-10-21 01:31:24 +00:00
< div class = " flex gap-2 px-1" >
2024-10-21 01:24:50 +00:00
{ #if edit }
< div class = "text-sm text-gray-500 flex-shrink-0" >
{ id }
< / div >
{ : else }
< input
class="w-full text-sm disabled:text-gray-500 bg-transparent outline-none"
type="text"
placeholder={ $i18n . t ( 'Function ID (e.g. my_filter)' )}
bind:value={ id }
required
disabled={ edit }
/>
{ /if }
2024-06-20 07:54:58 +00:00
< input
2024-10-21 01:24:50 +00:00
class="w-full text-sm bg-transparent outline-none"
2024-06-20 07:54:58 +00:00
type="text"
2024-10-21 01:24:50 +00:00
placeholder={ $i18n . t (
'Function Description (e.g. A filter to remove profanity from text)'
)}
bind:value={ meta . description }
2024-06-20 07:54:58 +00:00
required
/>
< / div >
< / div >
< div class = "mb-2 flex-1 overflow-auto h-0 rounded-lg" >
< CodeEditor
bind:this={ codeEditor }
2024-10-05 19:04:36 +00:00
value={ content }
2024-10-05 19:38:44 +00:00
lang="python"
2024-06-20 07:54:58 +00:00
{ boilerplate }
2024-10-05 19:04:36 +00:00
on:change={( e ) => {
_content = e.detail.value;
}}
2024-10-05 23:16:51 +00:00
on:save={ async () => {
2024-06-20 07:54:58 +00:00
if (formElement) {
formElement.requestSubmit();
}
}}
/>
< / div >
< div class = "pb-3 flex justify-between" >
< div class = "flex-1 pr-3" >
< div class = "text-xs text-gray-500 line-clamp-2" >
2024-07-15 12:54:20 +00:00
< span class = " font-semibold dark:text-gray-200" > { $i18n . t ( 'Warning:' )} </ span >
{ $i18n . t ( 'Functions allow arbitrary code execution' )} < br /> —
2024-06-20 07:54:58 +00:00
< span class = " font-medium dark:text-gray-400"
2024-07-15 13:01:02 +00:00
>{ $i18n . t ( `don't install random functions from sources you don't trust.` )} < /span
2024-06-20 07:54:58 +00:00
>
< / div >
< / div >
< button
2024-10-21 01:35:19 +00:00
class="px-3 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
2024-06-20 07:54:58 +00:00
type="submit"
>
{ $i18n . t ( 'Save' )}
< / button >
< / div >
< / div >
< / form >
< / div >
< / div >
< ConfirmDialog
bind:show={ showConfirm }
on:confirm={() => {
submitHandler();
}}
>
< div class = "text-sm text-gray-500" >
< div class = " bg-yellow-500/20 text-yellow-700 dark:text-yellow-200 rounded-lg px-4 py-3" >
2024-07-15 12:54:20 +00:00
< div > { $i18n . t ( 'Please carefully review the following warnings:' )} </ div >
2024-06-20 07:54:58 +00:00
< ul class = " mt-1 list-disc pl-4 text-xs" >
2024-07-15 12:54:20 +00:00
< li > { $i18n . t ( 'Functions allow arbitrary code execution.' )} </ li >
< li > { $i18n . t ( 'Do not install functions from sources you do not fully trust.' )} </ li >
2024-06-20 07:54:58 +00:00
< / ul >
< / div >
< div class = "my-3" >
2024-07-15 12:54:20 +00:00
{ $i18n . t (
'I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.'
)}
2024-06-20 07:54:58 +00:00
< / div >
< / div >
< / ConfirmDialog >