mirror of
https://github.com/open-webui/open-webui
synced 2025-06-25 09:47:41 +00:00
feat: unified models integration
This commit is contained in:
parent
e80e4c304a
commit
468c6398cd
@ -207,7 +207,7 @@ def merge_models_lists(model_lists):
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
**model,
|
**model,
|
||||||
"name": model["id"],
|
"name": model.get("name", model["id"]),
|
||||||
"owned_by": "openai",
|
"owned_by": "openai",
|
||||||
"openai": model,
|
"openai": model,
|
||||||
"urlIdx": idx,
|
"urlIdx": idx,
|
||||||
@ -319,6 +319,8 @@ async def proxy(path: str, request: Request, user=Depends(get_verified_user)):
|
|||||||
body = body.decode("utf-8")
|
body = body.decode("utf-8")
|
||||||
body = json.loads(body)
|
body = json.loads(body)
|
||||||
|
|
||||||
|
print(app.state.MODELS)
|
||||||
|
|
||||||
model = app.state.MODELS[body.get("model")]
|
model = app.state.MODELS[body.get("model")]
|
||||||
|
|
||||||
idx = model["urlIdx"]
|
idx = model["urlIdx"]
|
||||||
|
@ -276,13 +276,11 @@ async def get_models(user=Depends(get_verified_user)):
|
|||||||
|
|
||||||
if app.state.config.ENABLE_OPENAI_API:
|
if app.state.config.ENABLE_OPENAI_API:
|
||||||
openai_models = await get_openai_models()
|
openai_models = await get_openai_models()
|
||||||
openai_app.state.MODELS = openai_models
|
|
||||||
|
|
||||||
openai_models = openai_models["data"]
|
openai_models = openai_models["data"]
|
||||||
|
|
||||||
if app.state.config.ENABLE_OLLAMA_API:
|
if app.state.config.ENABLE_OLLAMA_API:
|
||||||
ollama_models = await get_ollama_models()
|
ollama_models = await get_ollama_models()
|
||||||
ollama_app.state.MODELS = ollama_models
|
|
||||||
|
|
||||||
print(ollama_models)
|
print(ollama_models)
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ export const getModels = async (token: string = '') => {
|
|||||||
|
|
||||||
let models = res?.data ?? [];
|
let models = res?.data ?? [];
|
||||||
|
|
||||||
models = models.filter((models) => models).reduce((a, e, i, arr) => a.concat(e), []);
|
models = models.filter((models) => models).sort((a, b) => (a.name > b.name ? 1 : -1));
|
||||||
|
|
||||||
console.log(models);
|
console.log(models);
|
||||||
return models;
|
return models;
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
chats,
|
chats,
|
||||||
config,
|
config,
|
||||||
type Model,
|
type Model,
|
||||||
modelfiles,
|
|
||||||
models,
|
models,
|
||||||
settings,
|
settings,
|
||||||
showSidebar,
|
showSidebar,
|
||||||
@ -63,24 +62,6 @@
|
|||||||
let selectedModels = [''];
|
let selectedModels = [''];
|
||||||
let atSelectedModel: Model | undefined;
|
let atSelectedModel: Model | undefined;
|
||||||
|
|
||||||
let selectedModelfile = null;
|
|
||||||
$: selectedModelfile =
|
|
||||||
selectedModels.length === 1 &&
|
|
||||||
$modelfiles.filter((modelfile) => modelfile.tagName === selectedModels[0]).length > 0
|
|
||||||
? $modelfiles.filter((modelfile) => modelfile.tagName === selectedModels[0])[0]
|
|
||||||
: null;
|
|
||||||
|
|
||||||
let selectedModelfiles = {};
|
|
||||||
$: selectedModelfiles = selectedModels.reduce((a, tagName, i, arr) => {
|
|
||||||
const modelfile =
|
|
||||||
$modelfiles.filter((modelfile) => modelfile.tagName === tagName)?.at(0) ?? undefined;
|
|
||||||
|
|
||||||
return {
|
|
||||||
...a,
|
|
||||||
...(modelfile && { [tagName]: modelfile })
|
|
||||||
};
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
let chat = null;
|
let chat = null;
|
||||||
let tags = [];
|
let tags = [];
|
||||||
|
|
||||||
@ -345,6 +326,7 @@
|
|||||||
const hasImages = messages.some((message) =>
|
const hasImages = messages.some((message) =>
|
||||||
message.files?.some((file) => file.type === 'image')
|
message.files?.some((file) => file.type === 'image')
|
||||||
);
|
);
|
||||||
|
|
||||||
if (hasImages && !(model.custom_info?.meta.vision_capable ?? true)) {
|
if (hasImages && !(model.custom_info?.meta.vision_capable ?? true)) {
|
||||||
toast.error(
|
toast.error(
|
||||||
$i18n.t('Model {{modelName}} is not vision capable', {
|
$i18n.t('Model {{modelName}} is not vision capable', {
|
||||||
@ -362,7 +344,7 @@
|
|||||||
role: 'assistant',
|
role: 'assistant',
|
||||||
content: '',
|
content: '',
|
||||||
model: model.id,
|
model: model.id,
|
||||||
modelName: model.custom_info?.name ?? model.name ?? model.id,
|
modelName: model.name ?? model.id,
|
||||||
userContext: null,
|
userContext: null,
|
||||||
timestamp: Math.floor(Date.now() / 1000) // Unix epoch
|
timestamp: Math.floor(Date.now() / 1000) // Unix epoch
|
||||||
};
|
};
|
||||||
@ -407,7 +389,7 @@
|
|||||||
}
|
}
|
||||||
responseMessage.userContext = userContext;
|
responseMessage.userContext = userContext;
|
||||||
|
|
||||||
if (model?.external) {
|
if (model?.owned_by === 'openai') {
|
||||||
await sendPromptOpenAI(model, prompt, responseMessageId, _chatId);
|
await sendPromptOpenAI(model, prompt, responseMessageId, _chatId);
|
||||||
} else if (model) {
|
} else if (model) {
|
||||||
await sendPromptOllama(model, prompt, responseMessageId, _chatId);
|
await sendPromptOllama(model, prompt, responseMessageId, _chatId);
|
||||||
@ -956,10 +938,8 @@
|
|||||||
) + ' {{prompt}}',
|
) + ' {{prompt}}',
|
||||||
titleModelId,
|
titleModelId,
|
||||||
userPrompt,
|
userPrompt,
|
||||||
titleModel?.external ?? false
|
titleModel?.owned_by === 'openai' ?? false
|
||||||
? titleModel?.source?.toLowerCase() === 'litellm'
|
? `${OPENAI_API_BASE_URL}`
|
||||||
? `${LITELLM_API_BASE_URL}/v1`
|
|
||||||
: `${OPENAI_API_BASE_URL}`
|
|
||||||
: `${OLLAMA_API_BASE_URL}/v1`
|
: `${OLLAMA_API_BASE_URL}/v1`
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1046,16 +1026,12 @@
|
|||||||
<Messages
|
<Messages
|
||||||
chatId={$chatId}
|
chatId={$chatId}
|
||||||
{selectedModels}
|
{selectedModels}
|
||||||
{selectedModelfiles}
|
|
||||||
{processing}
|
{processing}
|
||||||
bind:history
|
bind:history
|
||||||
bind:messages
|
bind:messages
|
||||||
bind:autoScroll
|
bind:autoScroll
|
||||||
bind:prompt
|
bind:prompt
|
||||||
bottomPadding={files.length > 0}
|
bottomPadding={files.length > 0}
|
||||||
suggestionPrompts={chatIdProp
|
|
||||||
? []
|
|
||||||
: selectedModelfile?.suggestionPrompts ?? $config.default_prompt_suggestions}
|
|
||||||
{sendPrompt}
|
{sendPrompt}
|
||||||
{continueGeneration}
|
{continueGeneration}
|
||||||
{regenerateResponse}
|
{regenerateResponse}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
import { chats, config, modelfiles, settings, user as _user, mobile } from '$lib/stores';
|
import { chats, config, settings, user as _user, mobile } from '$lib/stores';
|
||||||
import { tick, getContext } from 'svelte';
|
import { tick, getContext } from 'svelte';
|
||||||
|
|
||||||
import { toast } from 'svelte-sonner';
|
import { toast } from 'svelte-sonner';
|
||||||
@ -26,7 +26,6 @@
|
|||||||
|
|
||||||
export let user = $_user;
|
export let user = $_user;
|
||||||
export let prompt;
|
export let prompt;
|
||||||
export let suggestionPrompts = [];
|
|
||||||
export let processing = '';
|
export let processing = '';
|
||||||
export let bottomPadding = false;
|
export let bottomPadding = false;
|
||||||
export let autoScroll;
|
export let autoScroll;
|
||||||
@ -34,7 +33,6 @@
|
|||||||
export let messages = [];
|
export let messages = [];
|
||||||
|
|
||||||
export let selectedModels;
|
export let selectedModels;
|
||||||
export let selectedModelfiles = [];
|
|
||||||
|
|
||||||
$: if (autoScroll && bottomPadding) {
|
$: if (autoScroll && bottomPadding) {
|
||||||
(async () => {
|
(async () => {
|
||||||
@ -247,9 +245,7 @@
|
|||||||
<div class="h-full flex mb-16">
|
<div class="h-full flex mb-16">
|
||||||
{#if messages.length == 0}
|
{#if messages.length == 0}
|
||||||
<Placeholder
|
<Placeholder
|
||||||
models={selectedModels}
|
modelIds={selectedModels}
|
||||||
modelfiles={selectedModelfiles}
|
|
||||||
{suggestionPrompts}
|
|
||||||
submitPrompt={async (p) => {
|
submitPrompt={async (p) => {
|
||||||
let text = p;
|
let text = p;
|
||||||
|
|
||||||
@ -316,7 +312,6 @@
|
|||||||
{#key message.id}
|
{#key message.id}
|
||||||
<ResponseMessage
|
<ResponseMessage
|
||||||
{message}
|
{message}
|
||||||
modelfiles={selectedModelfiles}
|
|
||||||
siblings={history.messages[message.parentId]?.childrenIds ?? []}
|
siblings={history.messages[message.parentId]?.childrenIds ?? []}
|
||||||
isLastMessage={messageIdx + 1 === messages.length}
|
isLastMessage={messageIdx + 1 === messages.length}
|
||||||
{readOnly}
|
{readOnly}
|
||||||
@ -348,7 +343,6 @@
|
|||||||
{chatId}
|
{chatId}
|
||||||
parentMessage={history.messages[message.parentId]}
|
parentMessage={history.messages[message.parentId]}
|
||||||
{messageIdx}
|
{messageIdx}
|
||||||
{selectedModelfiles}
|
|
||||||
{updateChatMessages}
|
{updateChatMessages}
|
||||||
{confirmEditResponseMessage}
|
{confirmEditResponseMessage}
|
||||||
{rateMessage}
|
{rateMessage}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { WEBUI_BASE_URL } from '$lib/constants';
|
import { WEBUI_BASE_URL } from '$lib/constants';
|
||||||
import { user } from '$lib/stores';
|
import { config, user, models as _models } from '$lib/stores';
|
||||||
import { onMount, getContext } from 'svelte';
|
import { onMount, getContext } from 'svelte';
|
||||||
|
|
||||||
import { blur, fade } from 'svelte/transition';
|
import { blur, fade } from 'svelte/transition';
|
||||||
@ -9,23 +9,21 @@
|
|||||||
|
|
||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
|
|
||||||
|
export let modelIds = [];
|
||||||
export let models = [];
|
export let models = [];
|
||||||
export let modelfiles = [];
|
|
||||||
|
|
||||||
export let submitPrompt;
|
export let submitPrompt;
|
||||||
export let suggestionPrompts;
|
export let suggestionPrompts;
|
||||||
|
|
||||||
let mounted = false;
|
let mounted = false;
|
||||||
let modelfile = null;
|
|
||||||
let selectedModelIdx = 0;
|
let selectedModelIdx = 0;
|
||||||
|
|
||||||
$: modelfile =
|
$: if (modelIds.length > 0) {
|
||||||
models[selectedModelIdx] in modelfiles ? modelfiles[models[selectedModelIdx]] : null;
|
|
||||||
|
|
||||||
$: if (models.length > 0) {
|
|
||||||
selectedModelIdx = models.length - 1;
|
selectedModelIdx = models.length - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: models = modelIds.map((id) => $_models.find((m) => m.id === id));
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
mounted = true;
|
mounted = true;
|
||||||
});
|
});
|
||||||
@ -41,25 +39,14 @@
|
|||||||
selectedModelIdx = modelIdx;
|
selectedModelIdx = modelIdx;
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{#if model in modelfiles}
|
<img
|
||||||
<img
|
crossorigin="anonymous"
|
||||||
crossorigin="anonymous"
|
src={model?.info?.meta?.profile_image_url ??
|
||||||
src={modelfiles[model]?.imageUrl ?? `${WEBUI_BASE_URL}/static/favicon.png`}
|
($i18n.language === 'dg-DG' ? `/doge.png` : `${WEBUI_BASE_URL}/static/favicon.png`)}
|
||||||
alt="modelfile"
|
class=" size-[2.7rem] rounded-full border-[1px] border-gray-200 dark:border-none"
|
||||||
class=" size-[2.7rem] rounded-full border-[1px] border-gray-200 dark:border-none"
|
alt="logo"
|
||||||
draggable="false"
|
draggable="false"
|
||||||
/>
|
/>
|
||||||
{:else}
|
|
||||||
<img
|
|
||||||
crossorigin="anonymous"
|
|
||||||
src={$i18n.language === 'dg-DG'
|
|
||||||
? `/doge.png`
|
|
||||||
: `${WEBUI_BASE_URL}/static/favicon.png`}
|
|
||||||
class=" size-[2.7rem] rounded-full border-[1px] border-gray-200 dark:border-none"
|
|
||||||
alt="logo"
|
|
||||||
draggable="false"
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
</button>
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
@ -70,23 +57,32 @@
|
|||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<div class=" capitalize line-clamp-1" in:fade={{ duration: 200 }}>
|
<div class=" capitalize line-clamp-1" in:fade={{ duration: 200 }}>
|
||||||
{#if modelfile}
|
{#if models[selectedModelIdx]?.info}
|
||||||
{modelfile.title}
|
{models[selectedModelIdx]?.info?.name}
|
||||||
{:else}
|
{:else}
|
||||||
{$i18n.t('Hello, {{name}}', { name: $user.name })}
|
{$i18n.t('Hello, {{name}}', { name: $user.name })}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div in:fade={{ duration: 200, delay: 200 }}>
|
<div in:fade={{ duration: 200, delay: 200 }}>
|
||||||
{#if modelfile}
|
{#if models[selectedModelIdx]?.info}
|
||||||
<div class="mt-0.5 text-base font-normal text-gray-500 dark:text-gray-400">
|
<div class="mt-0.5 text-base font-normal text-gray-500 dark:text-gray-400">
|
||||||
{modelfile.desc}
|
{models[selectedModelIdx]?.info?.meta?.description}
|
||||||
</div>
|
</div>
|
||||||
{#if modelfile.user}
|
{#if models[selectedModelIdx]?.info?.meta?.user}
|
||||||
<div class="mt-0.5 text-sm font-normal text-gray-400 dark:text-gray-500">
|
<div class="mt-0.5 text-sm font-normal text-gray-400 dark:text-gray-500">
|
||||||
By <a href="https://openwebui.com/m/{modelfile.user.username}"
|
By
|
||||||
>{modelfile.user.name ? modelfile.user.name : `@${modelfile.user.username}`}</a
|
{#if models[selectedModelIdx]?.info?.meta?.user.community}
|
||||||
>
|
<a
|
||||||
|
href="https://openwebui.com/m/{models[selectedModelIdx]?.info?.meta?.user
|
||||||
|
.username}"
|
||||||
|
>{models[selectedModelIdx]?.info?.meta?.user.name
|
||||||
|
? models[selectedModelIdx]?.info?.meta?.user.name
|
||||||
|
: `@${models[selectedModelIdx]?.info?.meta?.user.username}`}</a
|
||||||
|
>
|
||||||
|
{:else}
|
||||||
|
{models[selectedModelIdx]?.info?.meta?.user.name}
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{:else}
|
{:else}
|
||||||
@ -99,7 +95,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class=" w-full" in:fade={{ duration: 200, delay: 300 }}>
|
<div class=" w-full" in:fade={{ duration: 200, delay: 300 }}>
|
||||||
<Suggestions {suggestionPrompts} {submitPrompt} />
|
<Suggestions
|
||||||
|
suggestionPrompts={models[selectedModelIdx]?.info?.meta?.suggestion_prompts ??
|
||||||
|
$config.default_prompt_suggestions}
|
||||||
|
{submitPrompt}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/key}
|
{/key}
|
||||||
|
28
src/lib/components/chat/Messages/test.json
Normal file
28
src/lib/components/chat/Messages/test.json
Normal file
File diff suppressed because one or more lines are too long
@ -45,13 +45,11 @@
|
|||||||
<div class="mr-1 max-w-full">
|
<div class="mr-1 max-w-full">
|
||||||
<Selector
|
<Selector
|
||||||
placeholder={$i18n.t('Select a model')}
|
placeholder={$i18n.t('Select a model')}
|
||||||
items={$models
|
items={$models.map((model) => ({
|
||||||
.filter((model) => model.name !== 'hr')
|
value: model.id,
|
||||||
.map((model) => ({
|
label: model.name,
|
||||||
value: model.id,
|
model: model
|
||||||
label: model.custom_info?.name ?? model.name,
|
}))}
|
||||||
info: model
|
|
||||||
}))}
|
|
||||||
bind:value={selectedModel}
|
bind:value={selectedModel}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -249,15 +249,17 @@
|
|||||||
<div class="line-clamp-1">
|
<div class="line-clamp-1">
|
||||||
{item.label}
|
{item.label}
|
||||||
|
|
||||||
<span class=" text-xs font-medium text-gray-600 dark:text-gray-400"
|
{#if item.model.owned_by === 'ollama'}
|
||||||
>{item.info?.details?.parameter_size ?? ''}</span
|
<span class=" text-xs font-medium text-gray-600 dark:text-gray-400"
|
||||||
>
|
>{item.model.ollama?.details?.parameter_size ?? ''}</span
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- {JSON.stringify(item.info)} -->
|
<!-- {JSON.stringify(item.info)} -->
|
||||||
|
|
||||||
{#if item.info.external}
|
{#if item.model.owned_by === 'openai'}
|
||||||
<Tooltip content={`${item.info?.source ?? 'External'}`}>
|
<Tooltip content={`${'External'}`}>
|
||||||
<div class="">
|
<div class="">
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@ -278,13 +280,17 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{:else}
|
{:else if item.model.owned_by === 'ollama'}
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content={`${
|
content={`${
|
||||||
item.info?.details?.quantization_level
|
item.model.ollama?.details?.quantization_level
|
||||||
? item.info?.details?.quantization_level + ' '
|
? item.model.ollama?.details?.quantization_level + ' '
|
||||||
: ''
|
: ''
|
||||||
}${item.info.size ? `(${(item.info.size / 1024 ** 3).toFixed(1)}GB)` : ''}`}
|
}${
|
||||||
|
item.model.ollama?.size
|
||||||
|
? `(${(item.model.ollama?.size / 1024 ** 3).toFixed(1)}GB)`
|
||||||
|
: ''
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<div class="">
|
<div class="">
|
||||||
<svg
|
<svg
|
||||||
|
Loading…
Reference in New Issue
Block a user