Merge branch 'open-webui:dev' into dev
This commit is contained in:
@@ -1415,6 +1415,9 @@
|
||||
console.error('OneDrive Error:', error);
|
||||
}
|
||||
}}
|
||||
onUpload={async (e) => {
|
||||
dispatch('upload', e);
|
||||
}}
|
||||
onClose={async () => {
|
||||
await tick();
|
||||
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
<script lang="ts">
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
import { getContext } from 'svelte';
|
||||
const i18n = getContext('i18n');
|
||||
import { settings } from '$lib/stores';
|
||||
|
||||
import Modal from '$lib/components/common/Modal.svelte';
|
||||
import XMark from '$lib/components/icons/XMark.svelte';
|
||||
import { isValidHttpUrl, isYoutubeUrl } from '$lib/utils';
|
||||
|
||||
export let show = false;
|
||||
export let onSubmit: (e) => void;
|
||||
|
||||
let url = '';
|
||||
|
||||
const submitHandler = () => {
|
||||
if (isValidHttpUrl(url)) {
|
||||
onSubmit({
|
||||
type: isYoutubeUrl(url) ? 'youtube' : 'web',
|
||||
data: url
|
||||
});
|
||||
|
||||
show = false;
|
||||
url = '';
|
||||
} else {
|
||||
toast.error($i18n.t('Please enter a valid URL.'));
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<Modal bind:show size="sm">
|
||||
<div class="flex flex-col h-full">
|
||||
<div class="flex justify-between items-center dark:text-gray-100 px-5 pt-4 pb-1.5">
|
||||
<h1 class="text-lg font-medium self-center font-primary">
|
||||
{$i18n.t('Attach Webpage')}
|
||||
</h1>
|
||||
<button
|
||||
class="self-center"
|
||||
aria-label={$i18n.t('Close modal')}
|
||||
on:click={() => {
|
||||
show = false;
|
||||
}}
|
||||
>
|
||||
<XMark className="size-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="px-5 pb-4">
|
||||
<form
|
||||
on:submit={(e) => {
|
||||
e.preventDefault();
|
||||
submitHandler();
|
||||
}}
|
||||
>
|
||||
<div class="flex justify-between mb-0.5">
|
||||
<label
|
||||
for="webpage-url"
|
||||
class={`text-xs ${($settings?.highContrastMode ?? false) ? 'text-gray-800 dark:text-gray-100' : 'text-gray-500'}`}
|
||||
>{$i18n.t('Webpage URL')}</label
|
||||
>
|
||||
</div>
|
||||
|
||||
<input
|
||||
id="webpage-url"
|
||||
class={`w-full flex-1 text-sm bg-transparent ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
|
||||
type="text"
|
||||
bind:value={url}
|
||||
placeholder={'https://example.com'}
|
||||
autocomplete="off"
|
||||
required
|
||||
/>
|
||||
|
||||
<div class="flex justify-end gap-2 pt-3 bg-gray-50 dark:bg-gray-900/50">
|
||||
<button
|
||||
class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-800 text-white dark:bg-white dark:text-black dark:hover:bg-gray-200 transition rounded-full"
|
||||
type="submit"
|
||||
>
|
||||
{$i18n.t('Add')}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
@@ -7,7 +7,7 @@
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
import { tick, getContext, onMount, onDestroy } from 'svelte';
|
||||
import { removeLastWordFromString, isValidHttpUrl } from '$lib/utils';
|
||||
import { removeLastWordFromString, isValidHttpUrl, isYoutubeUrl } from '$lib/utils';
|
||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||
import DocumentPage from '$lib/components/icons/DocumentPage.svelte';
|
||||
import Database from '$lib/components/icons/Database.svelte';
|
||||
@@ -36,7 +36,7 @@
|
||||
: items),
|
||||
|
||||
...(query.startsWith('http')
|
||||
? query.startsWith('https://www.youtube.com') || query.startsWith('https://youtu.be')
|
||||
? isYoutubeUrl(query)
|
||||
? [{ type: 'youtube', name: query, description: query }]
|
||||
: [
|
||||
{
|
||||
@@ -228,7 +228,7 @@
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
{#if query.startsWith('https://www.youtube.com') || query.startsWith('https://youtu.be')}
|
||||
{#if isYoutubeUrl(query)}
|
||||
<button
|
||||
class="px-2 py-1 rounded-xl w-full text-left bg-gray-50 dark:bg-gray-800 dark:text-gray-100 selected-command-option-button"
|
||||
type="button"
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
import Chats from './InputMenu/Chats.svelte';
|
||||
import Notes from './InputMenu/Notes.svelte';
|
||||
import Knowledge from './InputMenu/Knowledge.svelte';
|
||||
import AttachWebpageModal from './AttachWebpageModal.svelte';
|
||||
import GlobeAlt from '$lib/components/icons/GlobeAlt.svelte';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
@@ -39,11 +41,14 @@
|
||||
export let uploadGoogleDriveHandler: Function;
|
||||
export let uploadOneDriveHandler: Function;
|
||||
|
||||
export let onUpload: Function;
|
||||
export let onClose: Function;
|
||||
|
||||
let show = false;
|
||||
let tab = '';
|
||||
|
||||
let showAttachWebpageModal = false;
|
||||
|
||||
let fileUploadEnabled = true;
|
||||
$: fileUploadEnabled =
|
||||
fileUploadCapableModels.length === selectedModels.length &&
|
||||
@@ -78,6 +83,13 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
<AttachWebpageModal
|
||||
bind:show={showAttachWebpageModal}
|
||||
onSubmit={(e) => {
|
||||
onUpload(e);
|
||||
}}
|
||||
/>
|
||||
|
||||
<!-- Hidden file input used to open the camera on mobile -->
|
||||
<input
|
||||
id="camera-input"
|
||||
@@ -166,6 +178,16 @@
|
||||
</DropdownMenu.Item>
|
||||
</Tooltip>
|
||||
|
||||
<DropdownMenu.Item
|
||||
class="flex gap-2 items-center px-3 py-1.5 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl"
|
||||
on:click={() => {
|
||||
showAttachWebpageModal = true;
|
||||
}}
|
||||
>
|
||||
<GlobeAlt />
|
||||
<div class="line-clamp-1">{$i18n.t('Attach Webpage')}</div>
|
||||
</DropdownMenu.Item>
|
||||
|
||||
{#if $config?.features?.enable_notes ?? false}
|
||||
<Tooltip
|
||||
content={fileUploadCapableModels.length !== selectedModels.length
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
const getItemsPage = async () => {
|
||||
itemsLoading = true;
|
||||
let res = await getChatList(localStorage.token, page).catch(() => {
|
||||
let res = await getChatList(localStorage.token, page, true).catch(() => {
|
||||
return [];
|
||||
});
|
||||
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
import Terminal from '$lib/components/icons/Terminal.svelte';
|
||||
import ChevronRight from '$lib/components/icons/ChevronRight.svelte';
|
||||
import ChevronLeft from '$lib/components/icons/ChevronLeft.svelte';
|
||||
import ValvesModal from '$lib/components/workspace/common/ValvesModal.svelte';
|
||||
import { getOAuthClientAuthorizationUrl } from '$lib/apis/configs';
|
||||
import { partition } from 'd3-hierarchy';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
@@ -43,6 +46,11 @@
|
||||
let show = false;
|
||||
let tab = '';
|
||||
|
||||
let showValvesModal = false;
|
||||
|
||||
let selectedValvesType = 'tool';
|
||||
let selectedValvesItemId = null;
|
||||
|
||||
let tools = null;
|
||||
|
||||
$: if (show) {
|
||||
@@ -64,7 +72,8 @@
|
||||
a[tool.id] = {
|
||||
name: tool.name,
|
||||
description: tool.meta.description,
|
||||
enabled: selectedToolIds.includes(tool.id)
|
||||
enabled: selectedToolIds.includes(tool.id),
|
||||
...tool
|
||||
};
|
||||
return a;
|
||||
}, {});
|
||||
@@ -87,6 +96,16 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
<ValvesModal
|
||||
bind:show={showValvesModal}
|
||||
userValves={true}
|
||||
type={selectedValvesType}
|
||||
id={selectedValvesItemId ?? null}
|
||||
on:save={async () => {
|
||||
await tick();
|
||||
}}
|
||||
/>
|
||||
|
||||
<Dropdown
|
||||
bind:show
|
||||
on:change={(e) => {
|
||||
@@ -304,11 +323,25 @@
|
||||
|
||||
{#each Object.keys(tools) as toolId}
|
||||
<button
|
||||
class="flex w-full justify-between gap-2 items-center px-3 py-1.5 text-sm cursor-pointer rounded-xl hover:bg-gray-50 dark:hover:bg-gray-800/50"
|
||||
on:click={() => {
|
||||
tools[toolId].enabled = !tools[toolId].enabled;
|
||||
class="relative flex w-full justify-between gap-2 items-center px-3 py-1.5 text-sm cursor-pointer rounded-xl hover:bg-gray-50 dark:hover:bg-gray-800/50"
|
||||
on:click={(e) => {
|
||||
if (!(tools[toolId]?.authenticated ?? true)) {
|
||||
e.preventDefault();
|
||||
|
||||
let parts = toolId.split(':');
|
||||
let serverId = parts?.at(-1) ?? toolId;
|
||||
|
||||
const authUrl = getOAuthClientAuthorizationUrl(serverId, 'mcp');
|
||||
window.open(authUrl, '_blank', 'noopener');
|
||||
} else {
|
||||
tools[toolId].enabled = !tools[toolId].enabled;
|
||||
}
|
||||
}}
|
||||
>
|
||||
{#if !(tools[toolId]?.authenticated ?? true)}
|
||||
<!-- make it slighly darker and not clickable -->
|
||||
<div class="absolute inset-0 opacity-50 rounded-xl cursor-not-allowed z-10" />
|
||||
{/if}
|
||||
<div class="flex-1 truncate">
|
||||
<div class="flex flex-1 gap-2 items-center">
|
||||
<Tooltip content={tools[toolId]?.name ?? ''} placement="top">
|
||||
@@ -322,6 +355,44 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if tools[toolId]?.has_user_valves}
|
||||
<div class=" shrink-0">
|
||||
<Tooltip content={$i18n.t('Valves')}>
|
||||
<button
|
||||
class="self-center w-fit text-sm text-gray-600 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 transition rounded-full"
|
||||
type="button"
|
||||
on:click={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
selectedValvesType = 'tool';
|
||||
selectedValvesItemId = toolId;
|
||||
showValvesModal = true;
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="size-4"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z"
|
||||
/>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class=" shrink-0">
|
||||
<Switch
|
||||
state={tools[toolId].enabled}
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
<script lang="ts">
|
||||
import hljs from 'highlight.js';
|
||||
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { getContext, onMount, tick, onDestroy } from 'svelte';
|
||||
import { config } from '$lib/stores';
|
||||
|
||||
import PyodideWorker from '$lib/workers/pyodide.worker?worker';
|
||||
import { executeCode } from '$lib/apis/utils';
|
||||
import { copyToClipboard, renderMermaidDiagram } from '$lib/utils';
|
||||
|
||||
import 'highlight.js/styles/github-dark.min.css';
|
||||
|
||||
import PyodideWorker from '$lib/workers/pyodide.worker?worker';
|
||||
import CodeEditor from '$lib/components/common/CodeEditor.svelte';
|
||||
import SvgPanZoom from '$lib/components/common/SVGPanZoom.svelte';
|
||||
import { config } from '$lib/stores';
|
||||
import { executeCode } from '$lib/apis/utils';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
import ChevronUp from '$lib/components/icons/ChevronUp.svelte';
|
||||
import ChevronUpDown from '$lib/components/icons/ChevronUpDown.svelte';
|
||||
import CommandLine from '$lib/components/icons/CommandLine.svelte';
|
||||
@@ -480,19 +482,17 @@
|
||||
|
||||
{#if !collapsed}
|
||||
{#if edit}
|
||||
{#await import('$lib/components/common/CodeEditor.svelte') then { default: CodeEditor }}
|
||||
<CodeEditor
|
||||
value={code}
|
||||
{id}
|
||||
{lang}
|
||||
onSave={() => {
|
||||
saveCode();
|
||||
}}
|
||||
onChange={(value) => {
|
||||
_code = value;
|
||||
}}
|
||||
/>
|
||||
{/await}
|
||||
<CodeEditor
|
||||
value={code}
|
||||
{id}
|
||||
{lang}
|
||||
onSave={() => {
|
||||
saveCode();
|
||||
}}
|
||||
onChange={(value) => {
|
||||
_code = value;
|
||||
}}
|
||||
/>
|
||||
{:else}
|
||||
<pre
|
||||
class=" hljs p-4 px-5 overflow-x-auto"
|
||||
|
||||
Reference in New Issue
Block a user