mirror of
				https://github.com/open-webui/open-webui
				synced 2025-06-26 18:26:48 +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,
 | 
			
		||||
                        "name": model["id"],
 | 
			
		||||
                        "name": model.get("name", model["id"]),
 | 
			
		||||
                        "owned_by": "openai",
 | 
			
		||||
                        "openai": model,
 | 
			
		||||
                        "urlIdx": idx,
 | 
			
		||||
@ -319,6 +319,8 @@ async def proxy(path: str, request: Request, user=Depends(get_verified_user)):
 | 
			
		||||
        body = body.decode("utf-8")
 | 
			
		||||
        body = json.loads(body)
 | 
			
		||||
 | 
			
		||||
        print(app.state.MODELS)
 | 
			
		||||
 | 
			
		||||
        model = app.state.MODELS[body.get("model")]
 | 
			
		||||
 | 
			
		||||
        idx = model["urlIdx"]
 | 
			
		||||
 | 
			
		||||
@ -276,13 +276,11 @@ async def get_models(user=Depends(get_verified_user)):
 | 
			
		||||
 | 
			
		||||
    if app.state.config.ENABLE_OPENAI_API:
 | 
			
		||||
        openai_models = await get_openai_models()
 | 
			
		||||
        openai_app.state.MODELS = openai_models
 | 
			
		||||
 | 
			
		||||
        openai_models = openai_models["data"]
 | 
			
		||||
 | 
			
		||||
    if app.state.config.ENABLE_OLLAMA_API:
 | 
			
		||||
        ollama_models = await get_ollama_models()
 | 
			
		||||
        ollama_app.state.MODELS = ollama_models
 | 
			
		||||
 | 
			
		||||
        print(ollama_models)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -27,7 +27,7 @@ export const getModels = async (token: string = '') => {
 | 
			
		||||
 | 
			
		||||
	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);
 | 
			
		||||
	return models;
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,6 @@
 | 
			
		||||
		chats,
 | 
			
		||||
		config,
 | 
			
		||||
		type Model,
 | 
			
		||||
		modelfiles,
 | 
			
		||||
		models,
 | 
			
		||||
		settings,
 | 
			
		||||
		showSidebar,
 | 
			
		||||
@ -63,24 +62,6 @@
 | 
			
		||||
	let selectedModels = [''];
 | 
			
		||||
	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 tags = [];
 | 
			
		||||
 | 
			
		||||
@ -345,6 +326,7 @@
 | 
			
		||||
					const hasImages = messages.some((message) =>
 | 
			
		||||
						message.files?.some((file) => file.type === 'image')
 | 
			
		||||
					);
 | 
			
		||||
 | 
			
		||||
					if (hasImages && !(model.custom_info?.meta.vision_capable ?? true)) {
 | 
			
		||||
						toast.error(
 | 
			
		||||
							$i18n.t('Model {{modelName}} is not vision capable', {
 | 
			
		||||
@ -362,7 +344,7 @@
 | 
			
		||||
						role: 'assistant',
 | 
			
		||||
						content: '',
 | 
			
		||||
						model: model.id,
 | 
			
		||||
						modelName: model.custom_info?.name ?? model.name ?? model.id,
 | 
			
		||||
						modelName: model.name ?? model.id,
 | 
			
		||||
						userContext: null,
 | 
			
		||||
						timestamp: Math.floor(Date.now() / 1000) // Unix epoch
 | 
			
		||||
					};
 | 
			
		||||
@ -407,7 +389,7 @@
 | 
			
		||||
					}
 | 
			
		||||
					responseMessage.userContext = userContext;
 | 
			
		||||
 | 
			
		||||
					if (model?.external) {
 | 
			
		||||
					if (model?.owned_by === 'openai') {
 | 
			
		||||
						await sendPromptOpenAI(model, prompt, responseMessageId, _chatId);
 | 
			
		||||
					} else if (model) {
 | 
			
		||||
						await sendPromptOllama(model, prompt, responseMessageId, _chatId);
 | 
			
		||||
@ -956,10 +938,8 @@
 | 
			
		||||
					) + ' {{prompt}}',
 | 
			
		||||
				titleModelId,
 | 
			
		||||
				userPrompt,
 | 
			
		||||
				titleModel?.external ?? false
 | 
			
		||||
					? titleModel?.source?.toLowerCase() === 'litellm'
 | 
			
		||||
						? `${LITELLM_API_BASE_URL}/v1`
 | 
			
		||||
						: `${OPENAI_API_BASE_URL}`
 | 
			
		||||
				titleModel?.owned_by === 'openai' ?? false
 | 
			
		||||
					? `${OPENAI_API_BASE_URL}`
 | 
			
		||||
					: `${OLLAMA_API_BASE_URL}/v1`
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
@ -1046,16 +1026,12 @@
 | 
			
		||||
					<Messages
 | 
			
		||||
						chatId={$chatId}
 | 
			
		||||
						{selectedModels}
 | 
			
		||||
						{selectedModelfiles}
 | 
			
		||||
						{processing}
 | 
			
		||||
						bind:history
 | 
			
		||||
						bind:messages
 | 
			
		||||
						bind:autoScroll
 | 
			
		||||
						bind:prompt
 | 
			
		||||
						bottomPadding={files.length > 0}
 | 
			
		||||
						suggestionPrompts={chatIdProp
 | 
			
		||||
							? []
 | 
			
		||||
							: selectedModelfile?.suggestionPrompts ?? $config.default_prompt_suggestions}
 | 
			
		||||
						{sendPrompt}
 | 
			
		||||
						{continueGeneration}
 | 
			
		||||
						{regenerateResponse}
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
	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 { toast } from 'svelte-sonner';
 | 
			
		||||
@ -26,7 +26,6 @@
 | 
			
		||||
 | 
			
		||||
	export let user = $_user;
 | 
			
		||||
	export let prompt;
 | 
			
		||||
	export let suggestionPrompts = [];
 | 
			
		||||
	export let processing = '';
 | 
			
		||||
	export let bottomPadding = false;
 | 
			
		||||
	export let autoScroll;
 | 
			
		||||
@ -34,7 +33,6 @@
 | 
			
		||||
	export let messages = [];
 | 
			
		||||
 | 
			
		||||
	export let selectedModels;
 | 
			
		||||
	export let selectedModelfiles = [];
 | 
			
		||||
 | 
			
		||||
	$: if (autoScroll && bottomPadding) {
 | 
			
		||||
		(async () => {
 | 
			
		||||
@ -247,9 +245,7 @@
 | 
			
		||||
<div class="h-full flex mb-16">
 | 
			
		||||
	{#if messages.length == 0}
 | 
			
		||||
		<Placeholder
 | 
			
		||||
			models={selectedModels}
 | 
			
		||||
			modelfiles={selectedModelfiles}
 | 
			
		||||
			{suggestionPrompts}
 | 
			
		||||
			modelIds={selectedModels}
 | 
			
		||||
			submitPrompt={async (p) => {
 | 
			
		||||
				let text = p;
 | 
			
		||||
 | 
			
		||||
@ -316,7 +312,6 @@
 | 
			
		||||
								{#key message.id}
 | 
			
		||||
									<ResponseMessage
 | 
			
		||||
										{message}
 | 
			
		||||
										modelfiles={selectedModelfiles}
 | 
			
		||||
										siblings={history.messages[message.parentId]?.childrenIds ?? []}
 | 
			
		||||
										isLastMessage={messageIdx + 1 === messages.length}
 | 
			
		||||
										{readOnly}
 | 
			
		||||
@ -348,7 +343,6 @@
 | 
			
		||||
										{chatId}
 | 
			
		||||
										parentMessage={history.messages[message.parentId]}
 | 
			
		||||
										{messageIdx}
 | 
			
		||||
										{selectedModelfiles}
 | 
			
		||||
										{updateChatMessages}
 | 
			
		||||
										{confirmEditResponseMessage}
 | 
			
		||||
										{rateMessage}
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
	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 { blur, fade } from 'svelte/transition';
 | 
			
		||||
@ -9,23 +9,21 @@
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
	export let modelIds = [];
 | 
			
		||||
	export let models = [];
 | 
			
		||||
	export let modelfiles = [];
 | 
			
		||||
 | 
			
		||||
	export let submitPrompt;
 | 
			
		||||
	export let suggestionPrompts;
 | 
			
		||||
 | 
			
		||||
	let mounted = false;
 | 
			
		||||
	let modelfile = null;
 | 
			
		||||
	let selectedModelIdx = 0;
 | 
			
		||||
 | 
			
		||||
	$: modelfile =
 | 
			
		||||
		models[selectedModelIdx] in modelfiles ? modelfiles[models[selectedModelIdx]] : null;
 | 
			
		||||
 | 
			
		||||
	$: if (models.length > 0) {
 | 
			
		||||
	$: if (modelIds.length > 0) {
 | 
			
		||||
		selectedModelIdx = models.length - 1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	$: models = modelIds.map((id) => $_models.find((m) => m.id === id));
 | 
			
		||||
 | 
			
		||||
	onMount(() => {
 | 
			
		||||
		mounted = true;
 | 
			
		||||
	});
 | 
			
		||||
@ -41,25 +39,14 @@
 | 
			
		||||
							selectedModelIdx = modelIdx;
 | 
			
		||||
						}}
 | 
			
		||||
					>
 | 
			
		||||
						{#if model in modelfiles}
 | 
			
		||||
							<img
 | 
			
		||||
								crossorigin="anonymous"
 | 
			
		||||
								src={modelfiles[model]?.imageUrl ?? `${WEBUI_BASE_URL}/static/favicon.png`}
 | 
			
		||||
								alt="modelfile"
 | 
			
		||||
								class=" size-[2.7rem] rounded-full border-[1px] border-gray-200 dark:border-none"
 | 
			
		||||
								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}
 | 
			
		||||
						<img
 | 
			
		||||
							crossorigin="anonymous"
 | 
			
		||||
							src={model?.info?.meta?.profile_image_url ??
 | 
			
		||||
								($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"
 | 
			
		||||
						/>
 | 
			
		||||
					</button>
 | 
			
		||||
				{/each}
 | 
			
		||||
			</div>
 | 
			
		||||
@ -70,23 +57,32 @@
 | 
			
		||||
		>
 | 
			
		||||
			<div>
 | 
			
		||||
				<div class=" capitalize line-clamp-1" in:fade={{ duration: 200 }}>
 | 
			
		||||
					{#if modelfile}
 | 
			
		||||
						{modelfile.title}
 | 
			
		||||
					{#if models[selectedModelIdx]?.info}
 | 
			
		||||
						{models[selectedModelIdx]?.info?.name}
 | 
			
		||||
					{:else}
 | 
			
		||||
						{$i18n.t('Hello, {{name}}', { name: $user.name })}
 | 
			
		||||
					{/if}
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
				<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">
 | 
			
		||||
							{modelfile.desc}
 | 
			
		||||
							{models[selectedModelIdx]?.info?.meta?.description}
 | 
			
		||||
						</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">
 | 
			
		||||
								By <a href="https://openwebui.com/m/{modelfile.user.username}"
 | 
			
		||||
									>{modelfile.user.name ? modelfile.user.name : `@${modelfile.user.username}`}</a
 | 
			
		||||
								>
 | 
			
		||||
								By
 | 
			
		||||
								{#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>
 | 
			
		||||
						{/if}
 | 
			
		||||
					{:else}
 | 
			
		||||
@ -99,7 +95,11 @@
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<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>
 | 
			
		||||
{/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">
 | 
			
		||||
					<Selector
 | 
			
		||||
						placeholder={$i18n.t('Select a model')}
 | 
			
		||||
						items={$models
 | 
			
		||||
							.filter((model) => model.name !== 'hr')
 | 
			
		||||
							.map((model) => ({
 | 
			
		||||
								value: model.id,
 | 
			
		||||
								label: model.custom_info?.name ?? model.name,
 | 
			
		||||
								info: model
 | 
			
		||||
							}))}
 | 
			
		||||
						items={$models.map((model) => ({
 | 
			
		||||
							value: model.id,
 | 
			
		||||
							label: model.name,
 | 
			
		||||
							model: model
 | 
			
		||||
						}))}
 | 
			
		||||
						bind:value={selectedModel}
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
@ -249,15 +249,17 @@
 | 
			
		||||
							<div class="line-clamp-1">
 | 
			
		||||
								{item.label}
 | 
			
		||||
 | 
			
		||||
								<span class=" text-xs font-medium text-gray-600 dark:text-gray-400"
 | 
			
		||||
									>{item.info?.details?.parameter_size ?? ''}</span
 | 
			
		||||
								>
 | 
			
		||||
								{#if item.model.owned_by === 'ollama'}
 | 
			
		||||
									<span class=" text-xs font-medium text-gray-600 dark:text-gray-400"
 | 
			
		||||
										>{item.model.ollama?.details?.parameter_size ?? ''}</span
 | 
			
		||||
									>
 | 
			
		||||
								{/if}
 | 
			
		||||
							</div>
 | 
			
		||||
 | 
			
		||||
							<!-- {JSON.stringify(item.info)} -->
 | 
			
		||||
 | 
			
		||||
							{#if item.info.external}
 | 
			
		||||
								<Tooltip content={`${item.info?.source ?? 'External'}`}>
 | 
			
		||||
							{#if item.model.owned_by === 'openai'}
 | 
			
		||||
								<Tooltip content={`${'External'}`}>
 | 
			
		||||
									<div class="">
 | 
			
		||||
										<svg
 | 
			
		||||
											xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
@ -278,13 +280,17 @@
 | 
			
		||||
										</svg>
 | 
			
		||||
									</div>
 | 
			
		||||
								</Tooltip>
 | 
			
		||||
							{:else}
 | 
			
		||||
							{:else if item.model.owned_by === 'ollama'}
 | 
			
		||||
								<Tooltip
 | 
			
		||||
									content={`${
 | 
			
		||||
										item.info?.details?.quantization_level
 | 
			
		||||
											? item.info?.details?.quantization_level + ' '
 | 
			
		||||
										item.model.ollama?.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="">
 | 
			
		||||
										<svg
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user