diff --git a/apps/app/src/app/api/chat/lib/prompts.ts b/apps/app/src/app/api/chat/lib/prompts.ts deleted file mode 100644 index 26b3ca7..0000000 --- a/apps/app/src/app/api/chat/lib/prompts.ts +++ /dev/null @@ -1,97 +0,0 @@ -import type { CoreMessage } from "ai"; - -export const systemPrompt = `You are a browser automation agent. -You have these tools: searchGoogle, navigate, takeScreenshot, clickTarget, scrollDown, keyboardAction -Your job is to execute actions automatically on the browser. -The clickTarget tool uses Vision Language Model (VLM) capabilities to understand and locate elements on the page based on natural language descriptions. It can: -- Find and click elements by analyzing the visual content of the page -- Understand spatial relationships and visual context -- Click elements based on their visual appearance and location - -When describing elements to click, be as specific and detailed as possible. Include: -- The exact text content if visible -- Visual appearance (color, shape, size) -- Location relative to other elements -- Any surrounding context or nearby elements - -For example, instead of "click the YouTube link", use more specific descriptions like: -- "Click the YouTube link that says 'Getting Started Tutorial' below the header image" -- "Click the red Subscribe button next to the channel name 'TechTips'" -- "Click the 'Read More' link underneath the paragraph that starts with 'In this article'" -- "Click the search icon (magnifying glass) in the top-right corner of the navigation bar" -- "Click the blue 'Next' button at the bottom of the form, right after the email input field" - -For flight booking scenarios, use detailed descriptions like: -- "Click the 'Departure' input field with the calendar icon on the left side of the search form" -- "Click the 'Round Trip' radio button at the top of the flight search widget" -- "Click the blue 'Search Flights' button located at the bottom of the search panel" -- "Click the 'Add Passenger' dropdown menu showing '1 Adult' next to the passenger icon" -- "Click the cheapest flight option that shows '$299' in the flight results list" -- "Click the 'Select' button next to the 6:30 AM departure time in the outbound flight section" - -The keyboardAction tool can be used to type text into an input element and submit automatically, or press specific keys (like "Enter", "Tab", "ArrowDown" etc). -So you can click an input element, then type text into it. It will automatically try to press enter for you. - -Or you can mention a key, like "Enter" or "Tab" or "ArrowDown" etc. And it will press that key. - -Generally you want to take a screenshot of the page after each action so you can see what's happening. - -When the user's request is satisfied, you can reply with the results, otherwise keep invoking tools in a loop. - -Note avoid doing multiple actions at the same time. I.e first navigate to the page, then take a screenshot, then click the element. etc. - -Try to use the various inputs fields like search bars on webapps to search for things. If you get stuck you can use the searchGoogle tool. - -Note: today's date is ${new Date().toISOString().split("T")[0]}.`; - -export const clickableElementsPrompt = `You are a browser automation agent. -You have these tools: searchGoogle, navigate, takeScreenshot, viewAllClickableElements, browserAction - -Your primary method for interacting with pages is: -1. Use viewAllClickableElements to see all clickable elements on the page with their index numbers -2. Use browserAction with the "click" action and specify the index number to click the desired element - -For example, this workflow: -1. First call viewAllClickableElements() to see all clickable elements -2. Identify the index of the element you want to click from the screenshot -3. Use browserAction({ action: "click", clickIndex: X }) where X is the index number - -Important notes: -- After any action that might change the page content (like clicking dropdowns, submitting forms, etc), - you should call viewAllClickableElements() again to get the updated list of elements and their new index numbers, - as the DOM structure may have changed. -- On Google search results, avoid clicking the ellipsis (...) buttons next to results as these open side panels/menus. - Always click the actual link elements (titles or URLs) to navigate to the websites. -- Scroll the page if needed to ensure elements are in view before clicking. -- For Google search results, prefer clicking the main title link or URL link directly rather than - relying on any numbered shortcuts or badges. - -You can also: -- Navigate to URLs using the navigate tool -- Search Google using searchGoogle -- Type text using browserAction with "type" action -- Press keyboard keys using browserAction with "key" action -- Scroll the page using browserAction with "scroll" action -- Take screenshots using browserAction with "screenshot" action - -When the user's request is satisfied, you can reply with the results, otherwise keep invoking tools in a loop. - -Note: avoid doing multiple actions at the same time. Execute actions one at a time in sequence. - -Try to use the various input fields like search bars on webapps to search for things. If you get stuck you can use the searchGoogle tool. - -Note: today's date is ${new Date().toISOString().split("T")[0]}.`; - -export function getCachedMessages(messages: CoreMessage[]) { - const systemMessage: CoreMessage = { - role: "system", - content: systemPrompt, - experimental_providerMetadata: { - anthropic: { cacheControl: { type: "ephemeral" } }, - }, - }; - - const cachedMessages = [systemMessage, ...messages]; - - return cachedMessages; -} diff --git a/apps/app/src/app/api/chat/lib/script.js b/apps/app/src/app/api/chat/lib/script.js deleted file mode 100644 index e80c012..0000000 --- a/apps/app/src/app/api/chat/lib/script.js +++ /dev/null @@ -1,294 +0,0 @@ -// Self-executing function to avoid polluting global scope -(() => { - // Store references we'll need later - let currentDomState = null; - - function isElementInteractive(el) { - if (!el) return false; - const tag = (el.tagName || "").toLowerCase(); - const interactiveTags = new Set([ - "a", - "button", - "details", - "embed", - "input", - "label", - "menu", - "menuitem", - "object", - "select", - "textarea", - "summary", - ]); - if (interactiveTags.has(tag)) return true; - - const role = el.getAttribute?.("role"); - if ( - role && - /^(button|menu|menuitem|link|checkbox|radio|tab|switch|treeitem)$/i.test( - role, - ) - ) { - return true; - } - - if ( - el.hasAttribute && - (el.hasAttribute("onclick") || - el.hasAttribute("ng-click") || - el.hasAttribute("@click")) - ) { - return true; - } - - const tabIndex = el.getAttribute?.("tabindex"); - if (tabIndex && tabIndex !== "-1") return true; - - if (el.getAttribute?.("data-action")) { - return true; - } - return false; - } - - function isVisible(el) { - if (!el || !el.getBoundingClientRect) return false; - const rect = el.getBoundingClientRect(); - if (rect.width === 0 && rect.height === 0) return false; - const style = window.getComputedStyle(el); - if ( - style.display === "none" || - style.visibility === "hidden" || - Number.parseFloat(style.opacity) < 0.1 - ) { - return false; - } - return true; - } - - function computeXPath(el) { - if (!el || el.nodeType !== Node.ELEMENT_NODE) return ""; - const pathSegments = []; - let current = el; - while (current && current.nodeType === Node.ELEMENT_NODE) { - const tagName = current.nodeName.toLowerCase(); - let index = 1; - let sibling = current.previousSibling; - while (sibling) { - if ( - sibling.nodeType === Node.ELEMENT_NODE && - sibling.nodeName.toLowerCase() === tagName - ) { - index++; - } - sibling = sibling.previousSibling; - } - const segment = index > 1 ? `${tagName}[${index}]` : tagName; - pathSegments.unshift(segment); - current = current.parentNode; - if (!current || !current.parentNode) break; - if (current.nodeName.toLowerCase() === "html") { - pathSegments.unshift("html"); - break; - } - } - return `/${pathSegments.join("/")}`; - } - - function gatherDomTree() { - let highlightCounter = 1; - const selectorMap = {}; - - function processNode(node) { - if (node.nodeType === Node.TEXT_NODE) { - const textContent = node.nodeValue.trim(); - if ( - !textContent || - textContent.length < 2 || - /^[\d\s./$@]+$/.test(textContent) - ) { - return null; - } - return { - type: "TEXT_NODE", - text: textContent, - isVisible: isVisible(node.parentElement), - }; - } - - if (node.nodeType !== Node.ELEMENT_NODE) return null; - - const el = node; - const tagName = el.tagName.toLowerCase(); - - if ( - (tagName === "a" && - !el.textContent.trim() && - !el.querySelector("img")) || - tagName === "script" || - tagName === "style" - ) { - return null; - } - - const attrs = {}; - for (const attr of el.attributes) { - attrs[attr.name] = attr.value; - } - - const childNodes = []; - for (const child of el.childNodes) { - const processed = processNode(child); - if (processed) childNodes.push(processed); - } - - const elVisible = isVisible(el); - const nodeData = { - type: "ELEMENT_NODE", - tagName, - xpath: computeXPath(el), - attributes: attrs, - children: childNodes, - isVisible: elVisible, - isInteractive: false, - isTopElement: false, - element: el, // Store reference to actual DOM element - }; - - if (isElementInteractive(el) && elVisible) { - nodeData.isInteractive = true; - nodeData.highlightIndex = highlightCounter++; - selectorMap[nodeData.highlightIndex] = nodeData; - } - - return nodeData; - } - - const root = document.documentElement; - const elementTree = processNode(root); - if (elementTree) { - elementTree.isTopElement = true; - elementTree.xpath = "/html"; - } - - return { elementTree, selectorMap }; - } - - function addHighlightStyles() { - const styleId = "dom-highlighter-style"; - if (!document.getElementById(styleId)) { - const styleEl = document.createElement("style"); - styleEl.id = styleId; - styleEl.textContent = ` - .dom-highlighter-overlay { - position: fixed; - font-size: 14px; - font-weight: bold; - z-index: 999999; - pointer-events: none; - box-sizing: border-box; - border: 2px solid transparent; - } - .dom-highlighter-number { - position: absolute; - top: -20px; - left: 50%; - transform: translateX(-50%); - padding: 2px 6px; - border-radius: 10px; - font-size: 11px; - font-weight: bold; - white-space: nowrap; - opacity: 0.9; - border: 1px solid rgba(0,0,0,0.2); - } - `; - document.head.appendChild(styleEl); - } - } - - function clearHighlights() { - for (const el of document.querySelectorAll(".dom-highlighter-overlay")) { - el.remove(); - } - } - - // Update the highlighting code to use matching colors - function highlightElement(el, index, rect) { - const colors = [ - { border: "#FF5D5D", bg: "#FF5D5D", highlight: "rgba(255,93,93,0.08)" }, - { border: "#4CAF50", bg: "#4CAF50", highlight: "rgba(76,175,80,0.08)" }, - { border: "#2196F3", bg: "#2196F3", highlight: "rgba(33,150,243,0.08)" }, - { border: "#FFC107", bg: "#FFC107", highlight: "rgba(255,193,7,0.08)" }, - { border: "#9C27B0", bg: "#9C27B0", highlight: "rgba(156,39,176,0.08)" }, - ]; - - const colorStyle = colors[index % colors.length]; - const overlay = document.createElement("div"); - overlay.className = "dom-highlighter-overlay"; - - const numberSpan = document.createElement("span"); - numberSpan.className = "dom-highlighter-number"; - numberSpan.textContent = String(index); - numberSpan.style.backgroundColor = colorStyle.bg; - numberSpan.style.color = "white"; - overlay.appendChild(numberSpan); - - overlay.style.top = `${rect.top}px`; - overlay.style.left = `${rect.left}px`; - overlay.style.width = `${rect.width}px`; - overlay.style.height = `${rect.height}px`; - overlay.style.borderColor = colorStyle.border; - overlay.style.backgroundColor = colorStyle.highlight; - - document.body.appendChild(overlay); - } - - // Exposed global functions - window.highlightInteractiveElements = () => { - clearHighlights(); - currentDomState = gatherDomTree(); - addHighlightStyles(); - - const highlightColors = [ - { border: "#FF5D5D", background: "rgba(255,93,93,0.2)" }, - { border: "#5DFF5D", background: "rgba(93,255,93,0.2)" }, - { border: "#5D5DFF", background: "rgba(93,93,255,0.2)" }, - { border: "#FFB85D", background: "rgba(255,184,93,0.2)" }, - { border: "#FF5DCB", background: "rgba(255,93,203,0.2)" }, - ]; - - for (const nodeObj of Object.values(currentDomState.selectorMap)) { - const el = nodeObj.element; - const highlightIndex = nodeObj.highlightIndex; - - const rect = el.getBoundingClientRect(); - if (rect.width === 0 && rect.height === 0) return; - - highlightElement(el, highlightIndex, rect); - } - - console.log( - "Interactive elements highlighted! Use clickHighlightedElement(index) to click an element.", - ); - }; - - window.clickHighlightedElement = (index) => { - if (!currentDomState) { - console.error("Please run highlightInteractiveElements() first!"); - return; - } - - const nodeData = currentDomState.selectorMap[index]; - if (!nodeData) { - console.error(`No element found with index ${index}`); - return; - } - - nodeData.element.click(); - console.log(`Clicked element with index ${index}`); - }; - - console.log( - "DOM highlighter loaded! Call highlightInteractiveElements() to begin.", - ); -})(); diff --git a/apps/app/src/app/api/chat/lib/vision.ts b/apps/app/src/app/api/chat/lib/vision.ts deleted file mode 100644 index 4d0d204..0000000 --- a/apps/app/src/app/api/chat/lib/vision.ts +++ /dev/null @@ -1,160 +0,0 @@ -import { takeScreenshot } from "@/lib/operator/actions"; -import { - clearDomHighlights, - clickElementByHighlightIndex, - getDomState, - highlightDomElements, -} from "@/lib/operator/dom"; -import { google } from "@ai-sdk/google"; -import { generateObject, generateText } from "ai"; -import type { Page } from "playwright-core"; -import z from "zod"; - -/** - * Gets coordinates for a target object in an image using vlm, doesn't work well atm. - */ -export async function getTargetCoordinates( - imageBuffer: Buffer, - targetDescription: string, -) { - const model = google("gemini-1.5-flash-latest"); - - const data = await generateObject({ - model: model as any, - schema: z.object({ - coordinates: z.array(z.number().min(0).max(1000)).length(4), - }), - system: `You are a vision model that locates objects in images. The user will give you an image and a description of the object you need to find. - You will need to return the bounding box coordinates of the object in the image as an array of 4 numbers: [xmin, xmax, ymin, ymax]. Use coordinates between 0 and 1000.`, - messages: [ - { - role: "user", - content: [ - { - type: "text", - text: `Find the bounding box coordinates of this object: ${targetDescription}. Return the coordinates as an array [xmin, xmax, ymin, ymax]. All values should be between 0 and 1000.`, - }, - { - type: "image", - image: imageBuffer, - }, - ], - }, - ], - }); - - if (!data) { - throw new Error("No coordinates returned from vision model"); - } - - const [xmin, xmax, ymin, ymax] = data.object.coordinates; - - const centerPointX = (xmin + xmax) / 2; - const centerPointY = (ymin + ymax) / 2; - - const centerPoint = { - x: centerPointX / 1000, - y: centerPointY / 1000, - }; - - return { - xmin: xmin / 1000, - xmax: xmax / 1000, - ymin: ymin / 1000, - ymax: ymax / 1000, - centerPoint, - }; -} - -export async function viewAllClickableElements(page: Page) { - const { domState, rawDom } = await getDomState(page, true); - await highlightDomElements(page, rawDom); - const { screenshot } = await takeScreenshot(page); - await clearDomHighlights(page); - return { - screenshot, - domState, - }; -} -export async function clickElementByIndex(page: Page, index: number) { - const { domState, rawDom } = await getDomState(page, true); - const result = await clickElementByHighlightIndex(page, domState, index); - return result; -} - -/** - * Gets the element to click based on a natural language instruction - */ -export async function clickElementByVision(page: Page, instruction: string) { - // First highlight all interactive elements - const { domState, rawDom } = await getDomState(page, true); - // 2) Actually draw the highlight overlays so the screenshot has numbered boxes - await highlightDomElements(page, rawDom); - const { screenshot } = await takeScreenshot(page); - - const model = google("gemini-1.5-flash-latest"); - - const data = await generateObject({ - model: model as any, - schema: z.object({ - reasoning: z.string(), - clickIndex: z.number(), - }), - system: `You are a vision model that helps users interact with web pages. -The image will show a webpage with numbered highlights on clickable elements. -Your task is to identify which highlighted element best matches the user's instruction. -Start with your reasoning and then return the index of the element to click. -Sometimes there will be no clickable element that matches the instruction. In that case, return -1, but explain why. -`, - messages: [ - { - role: "user", - content: [ - { - type: "text", - text: `Looking at the numbered highlights in this screenshot, which element best matches this instruction: "${instruction}"?`, - }, - { - type: "image", - image: screenshot.data, - mimeType: screenshot.mimeType, - }, - ], - }, - ], - }); - - if (!data) { - return { - reasoning: "No element selection returned from vision model", - clickIndex: -1, - }; - } - await clearDomHighlights(page); - - if (data.object.clickIndex === -1) { - return { - reasoning: data.object.reasoning, - clickIndex: -1, - }; - } - - const success = await clickElementByHighlightIndex( - page, - domState, - data.object.clickIndex, - ); - - if (!success) { - return { - reasoning: `Failed to click element ${data.object.clickIndex}`, - clickIndex: -1, - }; - } - console.log("success", data.object); - - return { - reasoning: data.object.reasoning, - clickIndex: data.object.clickIndex, - }; -} \ No newline at end of file diff --git a/apps/app/src/app/api/chat/route.ts b/apps/app/src/app/api/chat/route.ts deleted file mode 100644 index 0100487..0000000 --- a/apps/app/src/app/api/chat/route.ts +++ /dev/null @@ -1,317 +0,0 @@ -import { - scrollDownPage, - searchGooglePage, - takeScreenshot, -} from "@/lib/operator/actions"; -import { getOrCreateBrowser } from "@/lib/operator/browser"; -import { sleep } from "@/lib/utils"; -import { anthropic } from "@ai-sdk/anthropic"; -import { openai } from "@ai-sdk/openai"; -import { - convertToCoreMessages, - createDataStreamResponse, - streamText, -} from "ai"; -import { z } from "zod"; -import { - getCachedMessages, -} from "./lib/prompts"; -import { - clickElementByIndex, - viewAllClickableElements, -} from "./lib/vision"; - -export const maxDuration = 120; - -export async function POST(req: Request) { - const { messages, sessionId } = await req.json(); - - const model = anthropic("claude-3-5-sonnet-latest"); - const model2 = openai("gpt-4o-mini"); - - let stepCount = 0; - - const coreMessages = convertToCoreMessages(messages); - const cachedMessages = getCachedMessages(coreMessages); - return createDataStreamResponse({ - execute: async (dataStream) => { - const result = streamText({ - model: model as any, - messages: cachedMessages, - maxSteps: 15, - onStepFinish: async (stepResult) => { - stepCount++; - dataStream.writeData({ - type: "step", - content: "step_complete", - step: stepCount, - }); - }, - - onFinish: async (result) => { - if (result.finishReason === "stop") { - dataStream.writeData({ - type: "status", - content: "finished", - step: -1, - }); - } - }, - tools: { - searchGoogle: { - description: - 'Navigate to Google and search for a query, i.e. "searchGoogle(query=...)"', - parameters: z.object({ query: z.string() }), - execute: async ({ query }: { query: string }) => { - try { - const { page } = await getOrCreateBrowser(sessionId); - await searchGooglePage(page, query); - const { screenshot } = await takeScreenshot(page); - return { - data: screenshot.data, - mimeType: screenshot.mimeType, - }; - } catch (error) { - return `Error searching Google: ${error instanceof Error ? error.message : String(error) - }`; - } - }, - experimental_toToolResultContent(result) { - return typeof result === "string" - ? [{ type: "text", text: result }] - : [ - { - type: "image", - data: result.data, - mimeType: result.mimeType, - }, - ]; - }, - }, - navigate: { - description: `Navigate in the browser. Available actions: - - url: Navigate to a specific URL (requires url parameter) - - back: Go back one page in history - - forward: Go forward one page in history`, - parameters: z.object({ - action: z.enum(["url", "back", "forward"]), - url: z - .string() - .optional() - .describe("URL to navigate to (required for url action)"), - }), - execute: async ({ - action, - url, - }: { - action: "url" | "back" | "forward"; - url?: string; - }) => { - try { - const { page } = await getOrCreateBrowser(sessionId); - let text = ""; - - if (action === "url") { - if (!url) { - throw new Error("URL parameter required for url action"); - } - const urlToGoTo = url.startsWith("http") - ? url - : `https://${url}`; - await page.goto(urlToGoTo); - text = `Navigated to ${urlToGoTo}`; - } else if (action === "back") { - await page.goBack(); - text = "Navigated back one page"; - } else if (action === "forward") { - await page.goForward(); - text = "Navigated forward one page"; - } else { - throw new Error(`Unknown navigation action: ${action}`); - } - - const { screenshot } = await takeScreenshot(page); - return { - data: screenshot.data, - mimeType: screenshot.mimeType, - }; - } catch (error) { - return `Error navigating: ${error instanceof Error ? error.message : String(error) - }`; - } - }, - experimental_toToolResultContent(result) { - return typeof result === "string" - ? [{ type: "text", text: result }] - : [ - { - type: "image", - data: result.data, - mimeType: result.mimeType, - }, - ]; - }, - }, - viewAllClickableElements: { - description: - "Highlight all clickable elements on the page with indexs and bounding boxes. Use this to see what you can click on the page, then select the index of the element you want to click.", - parameters: z.object({}), - execute: async () => { - const { page } = await getOrCreateBrowser(sessionId); - const elements = await viewAllClickableElements(page); - return { - data: elements.screenshot.data, - mimeType: elements.screenshot.mimeType, - }; - }, - experimental_toToolResultContent(result) { - return [ - { type: "image", data: result.data, mimeType: result.mimeType }, - ]; - }, - }, - browserAction: { - description: `Perform browser actions like keyboard input, clicking, scrolling, and screenshots. - Available actions: - - type: Type text (requires text parameter) - - key: Press a specific key (requires text parameter, e.g. "Enter", "Tab", "ArrowDown") - - scroll: Scroll the page (optional amount parameter, use -1 to scroll to bottom) - - screenshot: Take a screenshot of the current page - - click: Click on elements using natural language description`, - parameters: z.object({ - action: z.enum([ - "type", - "key", - "scroll", - "screenshot", - "click", - "wait", - ]), - text: z - .string() - .optional() - .describe( - 'Text to type or key to press (required for "type" and "key" actions)' - ), - amount: z - .number() - .optional() - .describe( - 'Amount to scroll in pixels. Use -1 to scroll to bottom of page (optional for "scroll" action)' - ), - clickIndex: z - .number() - .optional() - .describe( - "The index of the element to click. Use this when you have a list of clickable elements and you want to click a specific one." - ), - wait: z - .number() - .optional() - .describe( - "The amount of time to wait in milliseconds (optional for 'wait' action). Max 10,000ms (10 seconds)" - ), - }), - execute: async ({ action, text, amount, clickIndex, wait }) => { - try { - const { page } = await getOrCreateBrowser(sessionId); - - if (action === "type") { - if (!text) - throw new Error("Text parameter required for type action"); - const TYPING_DELAY = 5; - await page.keyboard.type(text, { delay: TYPING_DELAY }); - await page.waitForTimeout(50); - await page.keyboard.press("Enter"); - - const { screenshot } = await takeScreenshot(page); - return { - data: screenshot.data, - mimeType: screenshot.mimeType, - }; - } - - if (action === "key") { - if (!text) return "Text parameter required for key action"; - await page.keyboard.press(text); - const { screenshot } = await takeScreenshot(page); - return { - data: screenshot.data, - mimeType: screenshot.mimeType, - }; - } - if (action === "wait") { - if (!wait) return "Wait parameter required for wait action"; - await sleep(wait); - const { screenshot } = await takeScreenshot(page); - return { - data: screenshot.data, - mimeType: screenshot.mimeType, - }; - } - - if (action === "click") { - if (!clickIndex) - return "Click index parameter required for click action"; - const result = await clickElementByIndex(page, clickIndex); - if (typeof result === "string") return result; - const { screenshot } = await viewAllClickableElements(page); - return { - data: screenshot.data, - mimeType: screenshot.mimeType, - }; - } - - if (action === "scroll") { - await scrollDownPage(page, amount); - const { screenshot } = await takeScreenshot(page); - return { - data: screenshot.data, - mimeType: screenshot.mimeType, - }; - } - - if (action === "screenshot") { - const { screenshot } = await takeScreenshot(page); - return { - data: screenshot.data, - mimeType: screenshot.mimeType, - }; - } - - throw new Error(`Unknown action: ${action}`); - } catch (error) { - return `Error performing browser action: ${error instanceof Error ? error.message : String(error) - }`; - } - }, - experimental_toToolResultContent(result) { - if (typeof result === "string") { - return [{ type: "text", text: result }]; - } - if (result.text) { - return [ - { type: "text", text: result.text }, - { - type: "image", - data: result.data, - mimeType: result.mimeType, - }, - ]; - } - return [ - { type: "image", data: result.data, mimeType: result.mimeType }, - ]; - }, - }, - }, - }); - - result.mergeIntoDataStream(dataStream); - }, - onError: (error) => { - // Return error message as string since that's what the type expects - return error instanceof Error ? error.message : String(error); - }, - }); -} \ No newline at end of file diff --git a/bun.lock b/bun.lock index 08f69f4..147bec6 100644 --- a/bun.lock +++ b/bun.lock @@ -4,16 +4,16 @@ "": { "name": "comp", "dependencies": { - "@manypkg/cli": "^0.23.0", - "gitmoji": "^1.1.1", - "gray-matter": "^4.0.3", - "sharp": "^0.33.5", - "use-debounce": "^10.0.4", + "@manypkg/cli": "latest", + "gitmoji": "latest", + "gray-matter": "latest", + "sharp": "latest", + "use-debounce": "latest", }, "devDependencies": { - "@biomejs/biome": "1.9.4", - "turbo": "^2.4.2", - "typescript": "5.7.2", + "@biomejs/biome": "latest", + "turbo": "latest", + "typescript": "latest", }, }, "apps/app": { @@ -2995,7 +2995,7 @@ "typedarray-to-buffer": ["typedarray-to-buffer@3.1.5", "", { "dependencies": { "is-typedarray": "^1.0.0" } }, "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q=="], - "typescript": ["typescript@5.7.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg=="], + "typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="], "uc.micro": ["uc.micro@2.1.0", "", {}, "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="], @@ -3207,18 +3207,6 @@ "@browserbasehq/sdk/@types/node": ["@types/node@18.19.75", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-UIksWtThob6ZVSyxcOqCLOUNg/dyO1Qvx4McgeuhrEtHTLFTf7BBhEazaE4K806FGTPtzd/2sE90qn4fVr7cyw=="], - "@bubba/db/typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="], - - "@bubba/email/typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="], - - "@bubba/integrations/typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="], - - "@bubba/notifications/typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="], - - "@bubba/ui/typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="], - - "@bubba/utils/typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="], - "@formatjs/ecma402-abstract/@formatjs/intl-localematcher": ["@formatjs/intl-localematcher@0.6.0", "", { "dependencies": { "tslib": "2" } }, "sha512-4rB4g+3hESy1bHSBG3tDFaMY2CH67iT7yne1e+0CLTsGLDcmoEWWpJjjpWVaYgYfYuohIRuo0E+N536gd2ZHZA=="], "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], @@ -3317,8 +3305,6 @@ "comp.ai/ai": ["ai@4.1.37", "", { "dependencies": { "@ai-sdk/provider": "1.0.7", "@ai-sdk/provider-utils": "2.1.7", "@ai-sdk/react": "1.1.13", "@ai-sdk/ui-utils": "1.1.13", "@opentelemetry/api": "1.9.0", "jsondiffpatch": "0.6.0" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "zod": "^3.0.0" }, "optionalPeers": ["react", "zod"] }, "sha512-muE4XDrggYKGDM3o1pXpSvJqn2CGz7h30EQtaDgph9Hqmc5d2AfgFftXgl+YNbmr1kYrUR2HZS/Q0Y2QWAwsHA=="], - "comp.ai/typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="], - "condense-newlines/kind-of": ["kind-of@3.2.2", "", { "dependencies": { "is-buffer": "^1.1.5" } }, "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ=="], "data-urls/whatwg-url": ["whatwg-url@14.1.1", "", { "dependencies": { "tr46": "^5.0.0", "webidl-conversions": "^7.0.0" } }, "sha512-mDGf9diDad/giZ/Sm9Xi2YcyzaFpbdLpJPr+E9fSkyQ7KpQD4SdFcugkRQYzhmfI4KeV4Qpnn2sKPdo+kmsgRQ=="], @@ -3505,8 +3491,6 @@ "web/tailwind-merge": ["tailwind-merge@2.6.0", "", {}, "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA=="], - "web/typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="], - "whatwg-encoding/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], "whatwg-url/webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], diff --git a/package.json b/package.json index 6627cbe..a59c1e1 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "devDependencies": { "@biomejs/biome": "1.9.4", "turbo": "^2.4.2", - "typescript": "5.7.2" + "typescript": "5.7.3" }, "engines": { "node": ">=18" diff --git a/yarn.lock b/yarn.lock index bbff54a..d671216 100644 --- a/yarn.lock +++ b/yarn.lock @@ -345,7 +345,7 @@ "@babel/helper-string-parser" "^7.25.9" "@babel/helper-validator-identifier" "^7.25.9" -"@biomejs/biome@1.9.4": +"@biomejs/biome@latest": version "1.9.4" resolved "https://registry.npmjs.org/@biomejs/biome/-/biome-1.9.4.tgz" integrity sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog== @@ -442,7 +442,6 @@ dependencies: "@bubba/ui" "workspace:*" "@bubba/utils" "workspace:*" - dependencies: "@react-email/components" "0.0.31" "@react-email/render" "0.0.10" "@react-email/tailwind" "1.0.4" @@ -1122,7 +1121,7 @@ dependencies: "@lukeed/csprng" "^1.1.0" -"@manypkg/cli@^0.23.0": +"@manypkg/cli@latest": version "0.23.0" resolved "https://registry.npmjs.org/@manypkg/cli/-/cli-0.23.0.tgz" integrity sha512-9N0GuhUZhrDbOS2rer1/ZWaO8RvPOUI+kKTwlq74iQXomL+725E9Vfvl9U64FYwnLkQCxCmPZ9nBs/u8JwFnSw== @@ -6336,7 +6335,7 @@ gettext-parser@^8.0.0: readable-stream "^4.5.2" safe-buffer "^5.2.1" -gitmoji@^1.1.1: +gitmoji@latest: version "1.1.1" resolved "https://registry.npmjs.org/gitmoji/-/gitmoji-1.1.1.tgz" integrity sha512-cPNoMO7jIjV6/MZlOogpcl2trnoj2sQiiboGbJNa2f0mg4zlPN9tacN6sAQ2jPImMDFLyVYcMqLlxHfGTk87NA== @@ -6429,7 +6428,7 @@ gradient-string@^2.0.0: chalk "^4.1.2" tinygradient "^1.1.5" -gray-matter@^4.0.3: +gray-matter@latest: version "4.0.3" resolved "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz" integrity sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q== @@ -10303,7 +10302,7 @@ setprototypeof@1.2.0: resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz" integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== -sharp@^0.33.5: +sharp@^0.33.5, sharp@latest: version "0.33.5" resolved "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz" integrity sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw== @@ -11133,7 +11132,7 @@ tunnel-rat@^0.1.2: dependencies: zustand "^4.3.2" -turbo@^2.4.2: +turbo@latest: version "2.4.2" resolved "https://registry.npmjs.org/turbo/-/turbo-2.4.2.tgz" integrity sha512-Qxi0ioQCxMRUCcHKHZkTnYH8e7XCpNfg9QiJcyfWIc+ZXeaCjzV5rCGlbQlTXMAtI8qgfP8fZADv3CFtPwqdPQ== @@ -11255,12 +11254,12 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" -typescript@*, typescript@5.7.2, typescript@>=5.0.0, typescript@>=5.1.0, typescript@>=5.7.2, typescript@^5.0.0: +typescript@*, typescript@>=5.0.0, typescript@>=5.1.0, typescript@>=5.7.2, typescript@^5.0.0: version "5.7.2" resolved "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz" integrity sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg== -typescript@^5.5.4, typescript@^5.6.3, typescript@^5.7.2, typescript@^5.7.3: +typescript@^5.5.4, typescript@^5.6.3, typescript@^5.7.2, typescript@^5.7.3, typescript@latest: version "5.7.3" resolved "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz" integrity sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw== @@ -11411,7 +11410,7 @@ use-composed-ref@^1.3.0: resolved "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.4.0.tgz" integrity sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w== -use-debounce@^10.0.4: +use-debounce@^10.0.4, use-debounce@latest: version "10.0.4" resolved "https://registry.npmjs.org/use-debounce/-/use-debounce-10.0.4.tgz" integrity sha512-6Cf7Yr7Wk7Kdv77nnJMf6de4HuDE4dTxKij+RqE9rufDsI6zsbjyAxcH5y2ueJCQAnfgKbzXbZHYlkFwmBlWkw==