Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
chenpangpang
open-webui
Commits
e2ec19b4
Unverified
Commit
e2ec19b4
authored
Jan 22, 2024
by
Timothy Jaeryang Baek
Committed by
GitHub
Jan 22, 2024
Browse files
Merge pull request #548 from ollama-webui/custom-interface
feat: custom interface support
parents
b246c62d
ef68719b
Changes
9
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
231 additions
and
73 deletions
+231
-73
backend/apps/web/main.py
backend/apps/web/main.py
+4
-2
backend/apps/web/routers/configs.py
backend/apps/web/routers/configs.py
+29
-3
backend/config.py
backend/config.py
+28
-0
src/lib/apis/configs/index.ts
src/lib/apis/configs/index.ts
+30
-0
src/lib/components/chat/MessageInput/Suggestions.svelte
src/lib/components/chat/MessageInput/Suggestions.svelte
+1
-1
src/lib/components/chat/SettingsModal.svelte
src/lib/components/chat/SettingsModal.svelte
+136
-30
src/lib/components/common/Modal.svelte
src/lib/components/common/Modal.svelte
+1
-1
src/routes/(app)/+page.svelte
src/routes/(app)/+page.svelte
+1
-18
src/routes/(app)/c/[id]/+page.svelte
src/routes/(app)/c/[id]/+page.svelte
+1
-18
No files found.
backend/apps/web/main.py
View file @
e2ec19b4
...
...
@@ -11,14 +11,15 @@ from apps.web.routers import (
configs
,
utils
,
)
from
config
import
WEBUI_VERSION
,
WEBUI_AUTH
from
config
import
WEBUI_VERSION
,
WEBUI_AUTH
,
DEFAULT_MODELS
,
DEFAULT_PROMPT_SUGGESTIONS
app
=
FastAPI
()
origins
=
[
"*"
]
app
.
state
.
ENABLE_SIGNUP
=
True
app
.
state
.
DEFAULT_MODELS
=
None
app
.
state
.
DEFAULT_MODELS
=
DEFAULT_MODELS
app
.
state
.
DEFAULT_PROMPT_SUGGESTIONS
=
DEFAULT_PROMPT_SUGGESTIONS
app
.
add_middleware
(
CORSMiddleware
,
...
...
@@ -46,4 +47,5 @@ async def get_status():
"version"
:
WEBUI_VERSION
,
"auth"
:
WEBUI_AUTH
,
"default_models"
:
app
.
state
.
DEFAULT_MODELS
,
"default_prompt_suggestions"
:
app
.
state
.
DEFAULT_PROMPT_SUGGESTIONS
,
}
backend/apps/web/routers/configs.py
View file @
e2ec19b4
...
...
@@ -21,15 +21,24 @@ class SetDefaultModelsForm(BaseModel):
models
:
str
class
PromptSuggestion
(
BaseModel
):
title
:
List
[
str
]
content
:
str
class
SetDefaultSuggestionsForm
(
BaseModel
):
suggestions
:
List
[
PromptSuggestion
]
############################
# SetDefaultModels
############################
@
router
.
post
(
"/default/models"
,
response_model
=
str
)
async
def
set_global_default_models
(
request
:
Request
,
form_data
:
SetDefaultModelsForm
,
user
=
Depends
(
get_current_user
)
):
async
def
set_global_default_models
(
request
:
Request
,
form_data
:
SetDefaultModelsForm
,
user
=
Depends
(
get_current_user
)
):
if
user
.
role
==
"admin"
:
request
.
app
.
state
.
DEFAULT_MODELS
=
form_data
.
models
return
request
.
app
.
state
.
DEFAULT_MODELS
...
...
@@ -38,3 +47,20 @@ async def set_global_default_models(request: Request,
status_code
=
status
.
HTTP_403_FORBIDDEN
,
detail
=
ERROR_MESSAGES
.
ACCESS_PROHIBITED
,
)
@
router
.
post
(
"/default/suggestions"
,
response_model
=
List
[
PromptSuggestion
])
async
def
set_global_default_suggestions
(
request
:
Request
,
form_data
:
SetDefaultSuggestionsForm
,
user
=
Depends
(
get_current_user
),
):
if
user
.
role
==
"admin"
:
data
=
form_data
.
model_dump
()
request
.
app
.
state
.
DEFAULT_PROMPT_SUGGESTIONS
=
data
[
"suggestions"
]
return
request
.
app
.
state
.
DEFAULT_PROMPT_SUGGESTIONS
else
:
raise
HTTPException
(
status_code
=
status
.
HTTP_403_FORBIDDEN
,
detail
=
ERROR_MESSAGES
.
ACCESS_PROHIBITED
,
)
backend/config.py
View file @
e2ec19b4
...
...
@@ -54,6 +54,34 @@ OPENAI_API_BASE_URL = os.environ.get("OPENAI_API_BASE_URL", "")
if
OPENAI_API_BASE_URL
==
""
:
OPENAI_API_BASE_URL
=
"https://api.openai.com/v1"
####################################
# WEBUI
####################################
DEFAULT_MODELS
=
os
.
environ
.
get
(
"DEFAULT_MODELS"
,
None
)
DEFAULT_PROMPT_SUGGESTIONS
=
os
.
environ
.
get
(
"DEFAULT_PROMPT_SUGGESTIONS"
,
[
{
"title"
:
[
"Help me study"
,
"vocabulary for a college entrance exam"
],
"content"
:
"Help me study vocabulary: write a sentence for me to fill in the blank, and I'll try to pick the correct option."
,
},
{
"title"
:
[
"Give me ideas"
,
"for what to do with my kids' art"
],
"content"
:
"What are 5 creative things I could do with my kids' art? I don't want to throw them away, but it's also so much clutter."
,
},
{
"title"
:
[
"Tell me a fun fact"
,
"about the Roman Empire"
],
"content"
:
"Tell me a random fun fact about the Roman Empire"
,
},
{
"title"
:
[
"Show me a code snippet"
,
"of a website's sticky header"
],
"content"
:
"Show me a code snippet of a website's sticky header in CSS and JavaScript."
,
},
],
)
####################################
# WEBUI_VERSION
####################################
...
...
src/lib/apis/configs/index.ts
View file @
e2ec19b4
...
...
@@ -29,3 +29,33 @@ export const setDefaultModels = async (token: string, models: string) => {
return
res
;
};
export
const
setDefaultPromptSuggestions
=
async
(
token
:
string
,
promptSuggestions
:
string
)
=>
{
let
error
=
null
;
const
res
=
await
fetch
(
`
${
WEBUI_API_BASE_URL
}
/configs/default/suggestions`
,
{
method
:
'
POST
'
,
headers
:
{
'
Content-Type
'
:
'
application/json
'
,
Authorization
:
`Bearer
${
token
}
`
},
body
:
JSON
.
stringify
({
suggestions
:
promptSuggestions
})
})
.
then
(
async
(
res
)
=>
{
if
(
!
res
.
ok
)
throw
await
res
.
json
();
return
res
.
json
();
})
.
catch
((
err
)
=>
{
console
.
log
(
err
);
error
=
err
.
detail
;
return
null
;
});
if
(
error
)
{
throw
error
;
}
return
res
;
};
src/lib/components/chat/MessageInput/Suggestions.svelte
View file @
e2ec19b4
...
...
@@ -13,7 +13,7 @@
}}
>
<div class="flex flex-col text-left self-center">
{#if prompt.title}
{#if prompt.title
&& prompt.title[0] !== ''
}
<div class="text-sm font-medium dark:text-gray-300">{prompt.title[0]}</div>
<div class="text-sm text-gray-500">{prompt.title[1]}</div>
{:else}
...
...
src/lib/components/chat/SettingsModal.svelte
View file @
e2ec19b4
...
...
@@ -34,6 +34,8 @@
updateOpenAIUrl
} from '$lib/apis/openai';
import { resetVectorDB } from '$lib/apis/rag';
import { setDefaultPromptSuggestions } from '$lib/apis/configs';
import { getBackendConfig } from '$lib/apis';
export let show = false;
...
...
@@ -99,6 +101,9 @@
let OPENAI_API_KEY = '';
let OPENAI_API_BASE_URL = '';
// Interface
let promptSuggestions = [];
// Addons
let titleAutoGenerate = true;
let speechAutoSend = false;
...
...
@@ -191,6 +196,11 @@
await models.set(await getModels());
};
const updateInterfaceHandler = async () => {
promptSuggestions = await setDefaultPromptSuggestions(localStorage.token, promptSuggestions);
await config.set(await getBackendConfig());
};
const toggleTheme = async () => {
if (theme === 'dark') {
theme = 'light';
...
...
@@ -577,6 +587,7 @@
API_BASE_URL = await getOllamaAPIUrl(localStorage.token);
OPENAI_API_BASE_URL = await getOpenAIUrl(localStorage.token);
OPENAI_API_KEY = await getOpenAIKey(localStorage.token);
promptSuggestions = $config?.default_prompt_suggestions;
}
let settings = JSON.parse(localStorage.getItem('settings') ?? '{}');
...
...
@@ -745,85 +756,83 @@
</div>
<div class=" self-center">External</div>
</button>
{/if}
<button
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
'addons
'
'interface
'
? 'bg-gray-200 dark:bg-gray-700'
: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
on:click={() => {
selectedTab = '
addons
';
selectedTab = '
interface
';
}}
>
<div class=" self-center mr-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0
20 20
"
viewBox="0 0
16 16
"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M12 4.467c0-.405.262-.75.559-1.027.276-.257.441-.584.441-.94 0-.828-.895-1.5-2-1.5s-2 .672-2 1.5c0 .362.171.694.456.953.29.265.544.6.544.994a.968.968 0 01-1.024.974 39.655 39.655 0 01-3.014-.306.75.75 0 00-.847.847c.14.993.242 1.999.306 3.014A.968.968 0 014.447 10c-.393 0-.729-.253-.994-.544C3.194 9.17 2.862 9 2.5 9 1.672 9 1 9.895 1 11s.672 2 1.5 2c.356 0 .683-.165.94-.441.276-.297.622-.559 1.027-.559a.997.997 0 011.004 1.03 39.747 39.747 0 01-.319 3.734.75.75 0 00.64.842c1.05.146 2.111.252 3.184.318A.97.97 0 0010 16.948c0-.394-.254-.73-.545-.995C9.171 15.693 9 15.362 9 15c0-.828.895-1.5 2-1.5s2 .672 2 1.5c0 .356-.165.683-.441.94-.297.276-.559.622-.559 1.027a.998.998 0 001.03 1.005c1.337-.05 2.659-.162 3.961-.337a.75.75 0 00.644-.644c.175-1.302.288-2.624.337-3.961A.998.998 0 0016.967 12c-.405 0-.75.262-1.027.559-.257.276-.584.441-.94.441-.828 0-1.5-.895-1.5-2s.672-2 1.5-2c.362 0 .694.17.953.455.265.291.601.545.995.545a.97.97 0 00.976-1.024 41.159 41.159 0 00-.318-3.184.75.75 0 00-.842-.64c-1.228.164-2.473.271-3.734.319A.997.997 0 0112 4.467z"
fill-rule="evenodd"
d="M2 4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V4Zm10.5 5.707a.5.5 0 0 0-.146-.353l-1-1a.5.5 0 0 0-.708 0L9.354 9.646a.5.5 0 0 1-.708 0L6.354 7.354a.5.5 0 0 0-.708 0l-2 2a.5.5 0 0 0-.146.353V12a.5.5 0 0 0 .5.5h8a.5.5 0 0 0 .5-.5V9.707ZM12 5a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"
clip-rule="evenodd"
/>
</svg>
</div>
<div class=" self-center">
Add-ons
</div>
<div class=" self-center">
Interface
</div>
</button>
{/if}
<button
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
'
chat
s'
'
addon
s'
? 'bg-gray-200 dark:bg-gray-700'
: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
on:click={() => {
selectedTab = '
chat
s';
selectedTab = '
addon
s';
}}
>
<div class=" self-center mr-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0
16 16
"
viewBox="0 0
20 20
"
fill="currentColor"
class="w-4 h-4"
>
<path
fill-rule="evenodd"
d="M8 2C4.262 2 1 4.57 1 8c0 1.86.98 3.486 2.455 4.566a3.472 3.472 0 0 1-.469 1.26.75.75 0 0 0 .713 1.14 6.961 6.961 0 0 0 3.06-1.06c.403.062.818.094 1.241.094 3.738 0 7-2.57 7-6s-3.262-6-7-6ZM5 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2Zm7-1a1 1 0 1 1-2 0 1 1 0 0 1 2 0ZM8 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z"
clip-rule="evenodd"
d="M12 4.467c0-.405.262-.75.559-1.027.276-.257.441-.584.441-.94 0-.828-.895-1.5-2-1.5s-2 .672-2 1.5c0 .362.171.694.456.953.29.265.544.6.544.994a.968.968 0 01-1.024.974 39.655 39.655 0 01-3.014-.306.75.75 0 00-.847.847c.14.993.242 1.999.306 3.014A.968.968 0 014.447 10c-.393 0-.729-.253-.994-.544C3.194 9.17 2.862 9 2.5 9 1.672 9 1 9.895 1 11s.672 2 1.5 2c.356 0 .683-.165.94-.441.276-.297.622-.559 1.027-.559a.997.997 0 011.004 1.03 39.747 39.747 0 01-.319 3.734.75.75 0 00.64.842c1.05.146 2.111.252 3.184.318A.97.97 0 0010 16.948c0-.394-.254-.73-.545-.995C9.171 15.693 9 15.362 9 15c0-.828.895-1.5 2-1.5s2 .672 2 1.5c0 .356-.165.683-.441.94-.297.276-.559.622-.559 1.027a.998.998 0 001.03 1.005c1.337-.05 2.659-.162 3.961-.337a.75.75 0 00.644-.644c.175-1.302.288-2.624.337-3.961A.998.998 0 0016.967 12c-.405 0-.75.262-1.027.559-.257.276-.584.441-.94.441-.828 0-1.5-.895-1.5-2s.672-2 1.5-2c.362 0 .694.17.953.455.265.291.601.545.995.545a.97.97 0 00.976-1.024 41.159 41.159 0 00-.318-3.184.75.75 0 00-.842-.64c-1.228.164-2.473.271-3.734.319A.997.997 0 0112 4.467z"
/>
</svg>
</div>
<div class=" self-center">
Chat
s</div>
<div class=" self-center">
Add-on
s</div>
</button>
{#if !$config || ($config && !$config.auth)}
<button
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
'auth
'
'chats
'
? 'bg-gray-200 dark:bg-gray-700'
: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
on:click={() => {
selectedTab = '
auth
';
selectedTab = '
chats
';
}}
>
<div class=" self-center mr-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0
24 24
"
viewBox="0 0
16 16
"
fill="currentColor"
class="w-4 h-4"
>
<path
fill-rule="evenodd"
d="M
12.516 2.17a.75.75 0 00-1.032 0 11.209 11.209
0
0
1-
7.877 3.08.75.75 0 00-.722.515A12.74 12.74 0 002.25 9.75c0 5.942 4.064 10.933 9.563 12.348a.749.749 0 00.374 0c5.499-1.415 9.563-6.406 9.563-12.348 0-1.39-.223-2.73-.635-3.985a.75.75 0
00-
.722-.516l-.143.001c-2.996 0-5.717-1.17-7.734-3.08zm3.094 8.016a.75.75 0 10-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 00-1.06 1.06l2.25 2.25a.75.75 0 001.14-.094l3.75-5.25z
"
d="M
8 2C4.262 2 1 4.57 1 8c0 1.86.98 3.486 2.455 4.566a3.472 3.472 0
0 1-
.469 1.26.75.75 0 0 0 .713 1.14 6.961 6.961 0 0 0 3.06-1.06c.403.062.818.094 1.241.094 3.738 0 7-2.57 7-6s-3.262-6-7-6ZM5 9a1 1 0 1
0
0-
2 1 1 0 0 0 0 2Zm7-1a1 1 0 1 1-2 0 1 1 0 0 1 2 0ZM8 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z
"
clip-rule="evenodd"
/>
</svg>
</div>
<div class=" self-center">
Authentication
</div>
<div class=" self-center">
Chats
</div>
</button>
{/if}
<button
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
...
...
@@ -877,7 +886,7 @@
<div class=" self-center">About</div>
</button>
</div>
<div class="flex-1 md:min-h-[3
4
0px]">
<div class="flex-1 md:min-h-[3
8
0px]">
{#if selectedTab === 'general'}
<div class="flex flex-col space-y-3">
<div>
...
...
@@ -1048,7 +1057,7 @@
</div>
{:else if selectedTab === 'advanced'}
<div class="flex flex-col h-full justify-between text-sm">
<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-
72
">
<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-
80
">
<div class=" text-sm font-medium">Parameters</div>
<Advanced bind:options />
...
...
@@ -1483,6 +1492,103 @@
</div>
</div>
<div class="flex justify-end pt-3 text-sm font-medium">
<button
class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
type="submit"
>
Save
</button>
</div>
</form>
{:else if selectedTab === 'interface'}
<form
class="flex flex-col h-full justify-between space-y-3 text-sm"
on:submit|preventDefault={() => {
updateInterfaceHandler();
show = false;
}}
>
<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80">
<div class="flex w-full justify-between mb-2">
<div class=" self-center text-sm font-semibold">Default Prompt Suggestions</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
type="button"
on:click={() => {
if (promptSuggestions.length === 0 || promptSuggestions.at(-1).content !== '') {
promptSuggestions = [...promptSuggestions, { content: '', title: ['', ''] }];
}
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z"
/>
</svg>
</button>
</div>
<div class="flex flex-col space-y-1">
{#each promptSuggestions as prompt, promptIdx}
<div class=" flex border dark:border-gray-600 rounded-lg">
<div class="flex flex-col flex-1">
<div class="flex border-b dark:border-gray-600 w-full">
<input
class="px-3 py-1.5 text-xs w-full bg-transparent outline-none border-r dark:border-gray-600"
placeholder="Title (e.g. Tell me a fun fact)"
bind:value={prompt.title[0]}
/>
<input
class="px-3 py-1.5 text-xs w-full bg-transparent outline-none border-r dark:border-gray-600"
placeholder="Subtitle (e.g. about the Roman Empire)"
bind:value={prompt.title[1]}
/>
</div>
<input
class="px-3 py-1.5 text-xs w-full bg-transparent outline-none border-r dark:border-gray-600"
placeholder="Prompt (e.g. Tell me a fun fact about the Roman Empire)"
bind:value={prompt.content}
/>
</div>
<button
class="px-2"
type="button"
on:click={() => {
promptSuggestions.splice(promptIdx, 1);
promptSuggestions = promptSuggestions;
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
/>
</svg>
</button>
</div>
{/each}
</div>
{#if promptSuggestions.length > 0}
<div class="text-xs text-left w-full mt-2">
Adjusting these settings will apply changes universally to all users.
</div>
{/if}
</div>
<div class="flex justify-end pt-3 text-sm font-medium">
<button
class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
...
...
src/lib/components/common/Modal.svelte
View file @
e2ec19b4
...
...
@@ -13,7 +13,7 @@
} else if (size === 'sm') {
return 'w-[30rem]';
} else {
return 'w-[4
0
rem]';
return 'w-[4
2
rem]';
}
};
...
...
src/routes/(app)/+page.svelte
View file @
e2ec19b4
...
...
@@ -765,24 +765,7 @@
bind:files
bind:prompt
bind:autoScroll
suggestionPrompts={selectedModelfile?.suggestionPrompts ?? [
{
title: ['Help me study', 'vocabulary for a college entrance exam'],
content: `Help me study vocabulary: write a sentence for me to fill in the blank, and I'll try to pick the correct option.`
},
{
title: ['Give me ideas', `for what to do with my kids' art`],
content: `What are 5 creative things I could do with my kids' art? I don't want to throw them away, but it's also so much clutter.`
},
{
title: ['Tell me a fun fact', 'about the Roman Empire'],
content: 'Tell me a random fun fact about the Roman Empire'
},
{
title: ['Show me a code snippet', `of a website's sticky header`],
content: `Show me a code snippet of a website's sticky header in CSS and JavaScript.`
}
]}
suggestionPrompts={selectedModelfile?.suggestionPrompts ?? $config.default_prompt_suggestions}
{messages}
{submitPrompt}
{stopResponse}
...
...
src/routes/(app)/c/[id]/+page.svelte
View file @
e2ec19b4
...
...
@@ -794,24 +794,7 @@
bind:files
bind:prompt
bind:autoScroll
suggestionPrompts={selectedModelfile?.suggestionPrompts ?? [
{
title: ['Help me study', 'vocabulary for a college entrance exam'],
content: `Help me study vocabulary: write a sentence for me to fill in the blank, and I'll try to pick the correct option.`
},
{
title: ['Give me ideas', `for what to do with my kids' art`],
content: `What are 5 creative things I could do with my kids' art? I don't want to throw them away, but it's also so much clutter.`
},
{
title: ['Tell me a fun fact', 'about the Roman Empire'],
content: 'Tell me a random fun fact about the Roman Empire'
},
{
title: ['Show me a code snippet', `of a website's sticky header`],
content: `Show me a code snippet of a website's sticky header in CSS and JavaScript.`
}
]}
suggestionPrompts={selectedModelfile?.suggestionPrompts ?? $config.default_prompt_suggestions}
{messages}
{submitPrompt}
{stopResponse}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment