Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
Capture screen like similar to Claude (#1604)
Browse files* feat: capture screen
* feat: make it work with latest tool UI
* fix: add screenshot icon
* Update src/lib/components/icons/iconScreenshot.svelte
Co-authored-by: Victor Muštar <[email protected]>
* fix: capitalization
* fix: stop tracks in finally block
* fix: revert chatwindow changes that shouldnt be there
* fix: tooltip classes
---------
Co-authored-by: Nathan Sarrazin <[email protected]>
Co-authored-by: Victor Muštar <[email protected]>
src/lib/components/chat/ChatInput.svelte
CHANGED
@@ -21,6 +21,8 @@
|
|
21 |
import { goto } from "$app/navigation";
|
22 |
import { base } from "$app/paths";
|
23 |
import IconAdd from "~icons/carbon/add";
|
|
|
|
|
24 |
|
25 |
export let files: File[] = [];
|
26 |
export let mimeTypes: string[] = [];
|
@@ -250,6 +252,31 @@
|
|
250 |
</label>
|
251 |
</HoverTooltip>
|
252 |
</form>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
253 |
{/if}
|
254 |
{#if modelHasTools}
|
255 |
{#each extraTools as tool}
|
|
|
21 |
import { goto } from "$app/navigation";
|
22 |
import { base } from "$app/paths";
|
23 |
import IconAdd from "~icons/carbon/add";
|
24 |
+
import { captureScreen } from "$lib/utils/screenshot";
|
25 |
+
import IconScreenshot from "../icons/IconScreenshot.svelte";
|
26 |
|
27 |
export let files: File[] = [];
|
28 |
export let mimeTypes: string[] = [];
|
|
|
252 |
</label>
|
253 |
</HoverTooltip>
|
254 |
</form>
|
255 |
+
{#if mimeTypes.includes("image/*")}
|
256 |
+
<HoverTooltip
|
257 |
+
label="Capture screenshot"
|
258 |
+
position="top"
|
259 |
+
TooltipClassNames="text-xs !text-left !w-auto whitespace-nowrap !py-1 !mb-0 max-sm:hidden"
|
260 |
+
>
|
261 |
+
<button
|
262 |
+
class="base-tool"
|
263 |
+
on:click|preventDefault={async () => {
|
264 |
+
const screenshot = await captureScreen();
|
265 |
+
|
266 |
+
// Convert base64 to blob
|
267 |
+
const base64Response = await fetch(screenshot);
|
268 |
+
const blob = await base64Response.blob();
|
269 |
+
|
270 |
+
// Create a File object from the blob
|
271 |
+
const file = new File([blob], "screenshot.png", { type: "image/png" });
|
272 |
+
|
273 |
+
files = [...files, file];
|
274 |
+
}}
|
275 |
+
>
|
276 |
+
<IconScreenshot classNames="text-xl" />
|
277 |
+
</button>
|
278 |
+
</HoverTooltip>
|
279 |
+
{/if}
|
280 |
{/if}
|
281 |
{#if modelHasTools}
|
282 |
{#each extraTools as tool}
|
src/lib/components/icons/IconScreenshot.svelte
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
export let classNames = "";
|
3 |
+
</script>
|
4 |
+
|
5 |
+
<svg
|
6 |
+
class={classNames}
|
7 |
+
xmlns="http://www.w3.org/2000/svg"
|
8 |
+
aria-hidden="true"
|
9 |
+
focusable="false"
|
10 |
+
role="img"
|
11 |
+
width="1em"
|
12 |
+
height="1em"
|
13 |
+
fill="currentColor"
|
14 |
+
viewBox="0 0 32 32"
|
15 |
+
><path
|
16 |
+
fill-rule="evenodd"
|
17 |
+
clip-rule="evenodd"
|
18 |
+
d="M5.98 6.01h20.04v16.1H5.98V6.02Zm-2.1 0c0-1.16.94-2.1 2.1-2.1h20.04c1.16 0 2.1.94 2.1 2.1v16.1a2.1 2.1 0 0 1-2.1 2.11H5.98a2.1 2.1 0 0 1-2.1-2.1V6.02Zm5.7 1.65a2.1 2.1 0 0 0-2.1 2.1v2.61a1.05 1.05 0 0 0 2.1 0v-2.6h2.96a1.05 1.05 0 1 0 0-2.11H9.58ZM24.41 18.4a2.1 2.1 0 0 1-2.1 2.1h-2.95a1.05 1.05 0 1 1 0-2.1h2.95v-2.61a1.05 1.05 0 0 1 2.1 0v2.6ZM10.1 25.9a1.05 1.05 0 1 0 0 2.1H22.3a1.05 1.05 0 1 0 0-2.1H10.1Z"
|
19 |
+
/>
|
20 |
+
</svg>
|
src/lib/utils/screenshot.ts
ADDED
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
export async function captureScreen(): Promise<string> {
|
2 |
+
let stream: MediaStream | undefined;
|
3 |
+
try {
|
4 |
+
// This will show the native browser dialog for screen capture
|
5 |
+
stream = await navigator.mediaDevices.getDisplayMedia({
|
6 |
+
video: true,
|
7 |
+
audio: false,
|
8 |
+
});
|
9 |
+
|
10 |
+
// Create a canvas element to capture the screenshot
|
11 |
+
const canvas = document.createElement("canvas");
|
12 |
+
const video = document.createElement("video");
|
13 |
+
|
14 |
+
// Wait for the video to load metadata
|
15 |
+
await new Promise((resolve) => {
|
16 |
+
video.onloadedmetadata = () => {
|
17 |
+
canvas.width = video.videoWidth;
|
18 |
+
canvas.height = video.videoHeight;
|
19 |
+
video.play();
|
20 |
+
resolve(null);
|
21 |
+
};
|
22 |
+
if (stream) {
|
23 |
+
video.srcObject = stream;
|
24 |
+
} else {
|
25 |
+
throw Error("No stream available");
|
26 |
+
}
|
27 |
+
});
|
28 |
+
|
29 |
+
// Draw the video frame to canvas
|
30 |
+
const context = canvas.getContext("2d");
|
31 |
+
context?.drawImage(video, 0, 0, canvas.width, canvas.height);
|
32 |
+
// Convert to base64
|
33 |
+
return canvas.toDataURL("image/png");
|
34 |
+
} catch (error) {
|
35 |
+
console.error("Error capturing screenshot:", error);
|
36 |
+
throw error;
|
37 |
+
} finally {
|
38 |
+
// Stop all tracks
|
39 |
+
if (stream) {
|
40 |
+
stream.getTracks().forEach((track) => track.stop());
|
41 |
+
}
|
42 |
+
}
|
43 |
+
}
|