mirror of
https://github.com/open-webui/open-webui
synced 2025-06-04 03:37:35 +00:00
feat: screen capture
This commit is contained in:
parent
e500461dc0
commit
a38934bd23
@ -36,6 +36,7 @@
|
|||||||
import RichTextInput from '../common/RichTextInput.svelte';
|
import RichTextInput from '../common/RichTextInput.svelte';
|
||||||
import { generateAutoCompletion } from '$lib/apis';
|
import { generateAutoCompletion } from '$lib/apis';
|
||||||
import { error, text } from '@sveltejs/kit';
|
import { error, text } from '@sveltejs/kit';
|
||||||
|
import Image from '../common/Image.svelte';
|
||||||
|
|
||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
|
|
||||||
@ -88,6 +89,43 @@
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const screenCaptureHandler = async () => {
|
||||||
|
try {
|
||||||
|
// Request screen media
|
||||||
|
const mediaStream = await navigator.mediaDevices.getDisplayMedia({
|
||||||
|
video: { cursor: 'never' },
|
||||||
|
audio: false
|
||||||
|
});
|
||||||
|
// Once the user selects a screen, temporarily create a video element
|
||||||
|
const video = document.createElement('video');
|
||||||
|
video.srcObject = mediaStream;
|
||||||
|
// Ensure the video loads without affecting user experience or tab switching
|
||||||
|
await video.play();
|
||||||
|
// Set up the canvas to match the video dimensions
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.width = video.videoWidth;
|
||||||
|
canvas.height = video.videoHeight;
|
||||||
|
// Grab a single frame from the video stream using the canvas
|
||||||
|
const context = canvas.getContext('2d');
|
||||||
|
context.drawImage(video, 0, 0, canvas.width, canvas.height);
|
||||||
|
// Stop all video tracks (stop screen sharing) after capturing the image
|
||||||
|
mediaStream.getTracks().forEach((track) => track.stop());
|
||||||
|
|
||||||
|
// bring back focus to this current tab, so that the user can see the screen capture
|
||||||
|
window.focus();
|
||||||
|
|
||||||
|
// Convert the canvas to a Base64 image URL
|
||||||
|
const imageUrl = canvas.toDataURL('image/png');
|
||||||
|
// Add the captured image to the files array to render it
|
||||||
|
files = [...files, { type: 'image', url: imageUrl }];
|
||||||
|
// Clean memory: Clear video srcObject
|
||||||
|
video.srcObject = null;
|
||||||
|
} catch (error) {
|
||||||
|
// Handle any errors (e.g., user cancels screen sharing)
|
||||||
|
console.error('Error capturing screen:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const uploadFileHandler = async (file, fullContext: boolean = false) => {
|
const uploadFileHandler = async (file, fullContext: boolean = false) => {
|
||||||
if ($_user?.role !== 'admin' && !($_user?.permissions?.chat?.file_upload ?? true)) {
|
if ($_user?.role !== 'admin' && !($_user?.permissions?.chat?.file_upload ?? true)) {
|
||||||
toast.error($i18n.t('You do not have permission to upload files.'));
|
toast.error($i18n.t('You do not have permission to upload files.'));
|
||||||
@ -471,10 +509,10 @@
|
|||||||
{#if file.type === 'image'}
|
{#if file.type === 'image'}
|
||||||
<div class=" relative group">
|
<div class=" relative group">
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<img
|
<Image
|
||||||
src={file.url}
|
src={file.url}
|
||||||
alt="input"
|
alt="input"
|
||||||
class=" h-16 w-16 rounded-xl object-cover"
|
imageClassName=" h-16 w-16 rounded-xl object-cover"
|
||||||
/>
|
/>
|
||||||
{#if atSelectedModel ? visionCapableModels.length === 0 : selectedModels.length !== visionCapableModels.length}
|
{#if atSelectedModel ? visionCapableModels.length === 0 : selectedModels.length !== visionCapableModels.length}
|
||||||
<Tooltip
|
<Tooltip
|
||||||
@ -551,6 +589,7 @@
|
|||||||
<InputMenu
|
<InputMenu
|
||||||
bind:webSearchEnabled
|
bind:webSearchEnabled
|
||||||
bind:selectedToolIds
|
bind:selectedToolIds
|
||||||
|
{screenCaptureHandler}
|
||||||
uploadFilesHandler={() => {
|
uploadFilesHandler={() => {
|
||||||
filesInputElement.click();
|
filesInputElement.click();
|
||||||
}}
|
}}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
import { flyAndScale } from '$lib/utils/transitions';
|
import { flyAndScale } from '$lib/utils/transitions';
|
||||||
import { getContext, onMount, tick } from 'svelte';
|
import { getContext, onMount, tick } from 'svelte';
|
||||||
|
|
||||||
import { config, user, tools as _tools } from '$lib/stores';
|
import { config, user, tools as _tools, mobile } from '$lib/stores';
|
||||||
import { getTools } from '$lib/apis/tools';
|
import { getTools } from '$lib/apis/tools';
|
||||||
|
|
||||||
import Dropdown from '$lib/components/common/Dropdown.svelte';
|
import Dropdown from '$lib/components/common/Dropdown.svelte';
|
||||||
@ -12,9 +12,11 @@
|
|||||||
import Switch from '$lib/components/common/Switch.svelte';
|
import Switch from '$lib/components/common/Switch.svelte';
|
||||||
import GlobeAltSolid from '$lib/components/icons/GlobeAltSolid.svelte';
|
import GlobeAltSolid from '$lib/components/icons/GlobeAltSolid.svelte';
|
||||||
import WrenchSolid from '$lib/components/icons/WrenchSolid.svelte';
|
import WrenchSolid from '$lib/components/icons/WrenchSolid.svelte';
|
||||||
|
import CameraSolid from '$lib/components/icons/CameraSolid.svelte';
|
||||||
|
|
||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
|
|
||||||
|
export let screenCaptureHandler: Function;
|
||||||
export let uploadFilesHandler: Function;
|
export let uploadFilesHandler: Function;
|
||||||
export let selectedToolIds: string[] = [];
|
export let selectedToolIds: string[] = [];
|
||||||
|
|
||||||
@ -127,6 +129,18 @@
|
|||||||
<hr class="border-black/5 dark:border-white/5 my-1" />
|
<hr class="border-black/5 dark:border-white/5 my-1" />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if !$mobile}
|
||||||
|
<DropdownMenu.Item
|
||||||
|
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl"
|
||||||
|
on:click={() => {
|
||||||
|
screenCaptureHandler();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CameraSolid />
|
||||||
|
<div class=" line-clamp-1">{$i18n.t('Capture')}</div>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl"
|
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
|
12
src/lib/components/icons/CameraSolid.svelte
Normal file
12
src/lib/components/icons/CameraSolid.svelte
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let className = 'size-4';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class={className}>
|
||||||
|
<path d="M12 9a3.75 3.75 0 1 0 0 7.5A3.75 3.75 0 0 0 12 9Z" />
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M9.344 3.071a49.52 49.52 0 0 1 5.312 0c.967.052 1.83.585 2.332 1.39l.821 1.317c.24.383.645.643 1.11.71.386.054.77.113 1.152.177 1.432.239 2.429 1.493 2.429 2.909V18a3 3 0 0 1-3 3h-15a3 3 0 0 1-3-3V9.574c0-1.416.997-2.67 2.429-2.909.382-.064.766-.123 1.151-.178a1.56 1.56 0 0 0 1.11-.71l.822-1.315a2.942 2.942 0 0 1 2.332-1.39ZM6.75 12.75a5.25 5.25 0 1 1 10.5 0 5.25 5.25 0 0 1-10.5 0Zm12-1.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
Loading…
Reference in New Issue
Block a user