Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Capture screen like similar to Claude #1604

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions src/lib/components/ScreenshotButton.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
import CarbonCamera from "~icons/carbon/camera";
import { captureScreen } from '$lib/utils/screenshot';

export let classNames = "";

const dispatch = createEventDispatcher<{ capture: File[] }>();

async function handleClick() {
try {
const screenshot = await captureScreen();

// Convert base64 to blob
const base64Response = await fetch(screenshot);
const blob = await base64Response.blob();

// Create a File object from the blob
const file = new File([blob], 'screenshot.png', { type: 'image/png' });

// Dispatch the file as an array since FileDropzone expects an array of files
dispatch('capture', [file]);
} catch (error) {
console.error('Failed to capture screenshot:', error);
}
}
</script>

<button
on:click={handleClick}
class="btn h-8 rounded-lg border bg-white px-3 py-1 text-sm text-gray-500 shadow-sm hover:bg-gray-100 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600 {classNames}"
>
<CarbonCamera class="mr-2 text-xxs" /> Capture
</button>
35 changes: 27 additions & 8 deletions src/lib/components/chat/ChatWindow.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
import type { ToolFront } from "$lib/types/Tool";
import ModelSwitch from "./ModelSwitch.svelte";

import ScreenshotButton from "$lib/components/ScreenshotButton.svelte";

import { fly } from "svelte/transition";
import { cubicInOut } from "svelte/easing";

Expand Down Expand Up @@ -210,7 +212,7 @@
}
});

let chatContainer: HTMLElement;
let chatContainer: HTMLDivElement;

async function scrollToBottom() {
await tick();
Expand All @@ -237,6 +239,16 @@
];

$: isFileUploadEnabled = activeMimeTypes.length > 0;
$: isScreenshotEnabled = currentModel.multimodal;

async function handleScreenshot(event: CustomEvent<File[]>) {
try {
// Add the screenshot file to the files array
files = [...files, ...event.detail];
} catch (error) {
console.error('Failed to handle screenshot:', error);
}
}
</script>

<svelte:window
Expand Down Expand Up @@ -383,11 +395,13 @@
<div class="w-full">
<div class="flex w-full pb-3">
{#if !assistant}
{#if currentModel.tools}
<ToolsMenu {loading} />
{:else if $page.data.settings?.searchEnabled}
<WebSearchToggle />
{/if}
<div class="flex items-center">
{#if currentModel.tools}
<ToolsMenu {loading} />
{:else if $page.data.settings?.searchEnabled}
<WebSearchToggle />
{/if}
</div>
{/if}
{#if loading}
<StopGeneratingBtn classNames="ml-auto" on:click={() => dispatch("stop")} />
Expand All @@ -403,7 +417,10 @@
}}
/>
{:else}
<div class="ml-auto gap-2">
<div class="ml-auto gap-2 flex items-center">
{#if isScreenshotEnabled}
<ScreenshotButton on:capture={handleScreenshot} />
{/if}
{#if isFileUploadEnabled}
<UploadBtn bind:files mimeTypes={activeMimeTypes} classNames="ml-auto" />
{/if}
Expand All @@ -421,11 +438,12 @@
</div>
{/if}
</div>
<div class="flex w-full items-center gap-2">
<form
tabindex="-1"
aria-label={isFileUploadEnabled ? "file dropzone" : undefined}
on:submit|preventDefault={handleSubmit}
class="relative flex w-full max-w-4xl flex-1 items-center rounded-xl border bg-gray-100 focus-within:border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:focus-within:border-gray-500
class="relative flex flex-1 items-center rounded-xl border bg-gray-100 focus-within:border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:focus-within:border-gray-500
{isReadOnly ? 'opacity-30' : ''}"
>
{#if onDrag && isFileUploadEnabled}
Expand Down Expand Up @@ -480,6 +498,7 @@
</div>
{/if}
</form>
</div>
<div
class="mt-2 flex justify-between self-stretch px-1 text-xs text-gray-400/90 max-md:mb-2 max-sm:gap-2"
>
Expand Down
39 changes: 39 additions & 0 deletions src/lib/utils/screenshot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
export async function captureScreen(): Promise<string> {
try {
// This will show the native browser dialog for screen capture
const stream = await navigator.mediaDevices.getDisplayMedia({
video: true,
audio: false
});

const track = stream.getVideoTracks()[0];

// Create a canvas element to capture the screenshot
const canvas = document.createElement('canvas');
const video = document.createElement('video');

// Wait for the video to load metadata
await new Promise((resolve) => {
video.onloadedmetadata = () => {
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
video.play();
resolve(null);
};
video.srcObject = stream;
});

// Draw the video frame to canvas
const context = canvas.getContext('2d');
context?.drawImage(video, 0, 0, canvas.width, canvas.height);

// Stop all tracks
stream.getTracks().forEach(track => track.stop());

// Convert to base64
return canvas.toDataURL('image/png');
} catch (error) {
console.error('Error capturing screenshot:', error);
throw error;
}
}