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';
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();
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 = "mb-2.5" >
< button
class="flex space-x-1"
on:click={() => {
2024-06-20 08:07:55 +00:00
goto('/workspace/functions');
2024-06-20 07:54:58 +00:00
}}
type="button"
>
< 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 >
< / div >
< div class = "flex flex-col flex-1 overflow-auto h-0 rounded-lg" >
< div class = "w-full mb-2 flex flex-col gap-1.5" >
< div class = "flex gap-2 w-full" >
< input
class="w-full px-3 py-2 text-sm font-medium bg-gray-50 dark:bg-gray-850 dark:text-gray-200 rounded-lg outline-none"
type="text"
2024-07-15 12:54:20 +00:00
placeholder={ $i18n . t ( 'Function Name (e.g. My Filter)' )}
2024-06-20 07:54:58 +00:00
bind:value={ name }
required
/>
< input
class="w-full px-3 py-2 text-sm font-medium disabled:text-gray-300 dark:disabled:text-gray-700 bg-gray-50 dark:bg-gray-850 dark:text-gray-200 rounded-lg outline-none"
type="text"
2024-07-15 12:54:20 +00:00
placeholder={ $i18n . t ( 'Function ID (e.g. my_filter)' )}
2024-06-20 07:54:58 +00:00
bind:value={ id }
required
disabled={ edit }
/>
< / div >
< input
class="w-full px-3 py-2 text-sm font-medium bg-gray-50 dark:bg-gray-850 dark:text-gray-200 rounded-lg outline-none"
type="text"
2024-07-15 13:14:43 +00:00
placeholder={ $i18n . t (
'Function Description (e.g. A filter to remove profanity from text)'
)}
2024-06-20 07:54:58 +00:00
bind:value={ meta . description }
required
/>
< / 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
class="px-3 py-1.5 text-sm font-medium bg-emerald-600 hover:bg-emerald-700 text-gray-50 transition rounded-lg"
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 >