Native images (#6619)
Co-authored-by: Douwe Osinga <douwe@squareup.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -22,6 +22,32 @@ export function errorMessage(err: Error | unknown, default_value?: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function compressImageDataUrl(dataUrl: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new globalThis.Image();
|
||||
img.onload = () => {
|
||||
const maxDim = 1024;
|
||||
const scale = Math.min(1, maxDim / Math.max(img.width, img.height));
|
||||
const width = Math.floor(img.width * scale);
|
||||
const height = Math.floor(img.height * scale);
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) {
|
||||
reject(new Error('Failed to get canvas context'));
|
||||
return;
|
||||
}
|
||||
ctx.drawImage(img, 0, 0, width, height);
|
||||
|
||||
resolve(canvas.toDataURL('image/jpeg', 0.85));
|
||||
};
|
||||
img.onerror = () => reject(new Error('Failed to load image'));
|
||||
img.src = dataUrl;
|
||||
});
|
||||
}
|
||||
|
||||
export function formatAppName(name: string): string {
|
||||
return name
|
||||
.split(/[-_\s]+/)
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
/**
|
||||
* Utility functions for detecting and handling image paths in messages
|
||||
*/
|
||||
|
||||
/**
|
||||
* Extracts image file paths from a message text
|
||||
* Looks for paths that match the pattern of pasted images from the temp directory
|
||||
*
|
||||
* @param text The message text to extract image paths from
|
||||
* @returns An array of image file paths found in the message
|
||||
*/
|
||||
export function extractImagePaths(text: string): string[] {
|
||||
if (!text) return [];
|
||||
|
||||
// Match paths that look like pasted image paths from the temp directory
|
||||
// Pattern: /path/to/goose-pasted-images/pasted-img-TIMESTAMP-RANDOM.ext
|
||||
// This regex looks for:
|
||||
// - Word boundary or start of string
|
||||
// - A path containing "goose-pasted-images"
|
||||
// - Followed by a filename starting with "pasted-"
|
||||
// - Ending with common image extensions
|
||||
// - Word boundary or end of string
|
||||
const regex =
|
||||
/(?:^|\s)((?:[^\s]*\/)?goose-pasted-images\/pasted-[^\s]+\.(png|jpg|jpeg|gif|webp))(?=\s|$)/gi;
|
||||
|
||||
const matches = [];
|
||||
let match;
|
||||
|
||||
while ((match = regex.exec(text)) !== null) {
|
||||
matches.push(match[1]);
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes image paths from the text
|
||||
*
|
||||
* @param text The original text
|
||||
* @param imagePaths Array of image paths to remove
|
||||
* @returns Text with image paths removed
|
||||
*/
|
||||
export function removeImagePathsFromText(text: string, imagePaths: string[]): string {
|
||||
if (!text || imagePaths.length === 0) return text;
|
||||
|
||||
let result = text;
|
||||
|
||||
// Remove each image path from the text
|
||||
imagePaths.forEach((path) => {
|
||||
// Escape special regex characters in the path
|
||||
const escapedPath = path.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
// Create a regex that matches the path with optional surrounding whitespace
|
||||
const pathRegex = new RegExp(`(^|\\s)${escapedPath}(?=\\s|$)`, 'g');
|
||||
result = result.replace(pathRegex, '$1');
|
||||
});
|
||||
|
||||
// Clean up any extra whitespace
|
||||
return result.replace(/\s+/g, ' ').trim();
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { NavigateFunction } from 'react-router-dom';
|
||||
import { Recipe } from '../api/types.gen';
|
||||
import { Recipe } from '../api';
|
||||
import { UserInput } from '../types/message';
|
||||
|
||||
export type View =
|
||||
| 'welcome'
|
||||
@@ -29,7 +30,7 @@ export type ViewOptions = {
|
||||
parentView?: View;
|
||||
parentViewOptions?: ViewOptions;
|
||||
disableAnimation?: boolean;
|
||||
initialMessage?: string;
|
||||
initialMessage?: UserInput;
|
||||
shareToken?: string;
|
||||
resumeSessionId?: string;
|
||||
pendingScheduleDeepLink?: string;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getToolRequests, getTextContent, getToolResponses } from '../types/message';
|
||||
import { getToolRequests, getTextAndImageContent, getToolResponses } from '../types/message';
|
||||
import { Message } from '../api';
|
||||
|
||||
export function identifyConsecutiveToolCalls(messages: Message[]): number[][] {
|
||||
@@ -9,7 +9,7 @@ export function identifyConsecutiveToolCalls(messages: Message[]): number[][] {
|
||||
const message = messages[i];
|
||||
const toolRequests = getToolRequests(message);
|
||||
const toolResponses = getToolResponses(message);
|
||||
const textContent = getTextContent(message);
|
||||
const { textContent } = getTextAndImageContent(message);
|
||||
const hasText = textContent.trim().length > 0;
|
||||
|
||||
if (toolResponses.length > 0 && toolRequests.length === 0) {
|
||||
@@ -47,15 +47,6 @@ export function identifyConsecutiveToolCalls(messages: Message[]): number[][] {
|
||||
return chains;
|
||||
}
|
||||
|
||||
export function shouldHideMessage(messageIndex: number, chains: number[][]): boolean {
|
||||
for (const chain of chains) {
|
||||
if (chain.includes(messageIndex)) {
|
||||
return chain[0] !== messageIndex;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function shouldHideTimestamp(messageIndex: number, chains: number[][]): boolean {
|
||||
for (const chain of chains) {
|
||||
if (chain.includes(messageIndex)) {
|
||||
@@ -69,7 +60,3 @@ export function shouldHideTimestamp(messageIndex: number, chains: number[][]): b
|
||||
export function isInChain(messageIndex: number, chains: number[][]): boolean {
|
||||
return chains.some((chain) => chain.includes(messageIndex));
|
||||
}
|
||||
|
||||
export function getChainForMessage(messageIndex: number, chains: number[][]): number[] | null {
|
||||
return chains.find((chain) => chain.includes(messageIndex)) || null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user