diff --git a/.env.example b/.env.example index d895c6de..cf864297 100644 --- a/.env.example +++ b/.env.example @@ -23,3 +23,7 @@ POSTGRES_USER="default" POSTGRES_HOST="***" POSTGRES_PASSWORD="***" POSTGRES_DATABASE="***" + +#ai +OPENAI_API_KEY="sk-proj-***" +ANTHROPIC_API_KEY="sk-ant-***" \ No newline at end of file diff --git a/app/(dashboard)/dashboard/page.tsx b/app/(dashboard)/dashboard/page.tsx index 4cbc37cc..39191ad3 100644 --- a/app/(dashboard)/dashboard/page.tsx +++ b/app/(dashboard)/dashboard/page.tsx @@ -4,26 +4,13 @@ import { useState, useEffect } from "react"; import { Button } from "@/components/ui/button"; import { Loader2, AlertCircle } from "lucide-react"; import { PullRequestItem } from "./pull-request"; -import { PullRequest, TestFile } from "./types"; -import { - getAssignedPullRequests, - commitChangesToPullRequest, -} from "@/lib/github"; +import { PullRequest } from "./types"; +import { getAssignedPullRequests } from "@/lib/github"; export default function DashboardPage() { const [pullRequests, setPullRequests] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); - const [selectedPR, setSelectedPR] = useState(null); - const [testFiles, setTestFiles] = useState([]); - const [selectedFiles, setSelectedFiles] = useState>( - {} - ); - const [expandedFiles, setExpandedFiles] = useState>( - {} - ); - const [analyzing, setAnalyzing] = useState(false); - const [loadingPR, setLoadingPR] = useState(null); useEffect(() => { const fetchAndSetPullRequests = async () => { @@ -62,64 +49,8 @@ export default function DashboardPage() { fetchAndSetPullRequests(); }, []); - const handleOpenTests = (pr: PullRequest, mode: "write" | "update") => { - setSelectedPR(pr); - setAnalyzing(true); - setLoadingPR(pr.id); - - // Simulating API call to get test files - // TODO: update this to dynamically generate based on the PR diff and spec file directory - setTimeout(() => { - const mockTestFiles: TestFile[] = []; - - if (mode === "write") { - mockTestFiles.push({ - name: "signup_spec.rb", - oldContent: "", - newContent: - "describe 'Signup' do\n it 'allows new user to sign up' do\n # Test code here\n end\nend", - isEntirelyNew: true, - }); - - mockTestFiles.push({ - name: "login_spec.rb", - oldContent: - "describe 'Login' do\n it 'allows existing user to log in' do\n visit '/login'\n fill_in 'Email', with: 'user@example.com'\n fill_in 'Password', with: 'password123'\n click_button 'Log In'\n expect(page).to have_content('Welcome back!')\n end\nend", - newContent: - "describe 'Login' do\n it 'allows existing user to log in' do\n visit '/login'\n fill_in 'Email', with: 'user@example.com'\n fill_in 'Password', with: 'password123'\n click_button 'Log In'\n expect(page).to have_content('Welcome back!')\n end\n\n\n it 'shows error message for invalid credentials' do\n visit '/login'\n fill_in 'Email', with: 'user@example.com'\n fill_in 'Password', with: 'wrongpassword'\n click_button 'Log In'\n expect(page).to have_content('Invalid email or password')\n end\nend", - isEntirelyNew: false, - }); - } else if (mode === "update") { - mockTestFiles.push({ - name: "logic_spec.rb", - oldContent: - "describe 'BusinessLogic' do\n it 'calculates total correctly' do\n expect(calculate_total(10, 5)).to eq(15)\n end\n\n it 'applies discount' do\n expect(apply_discount(100, 0.1)).to eq(90)\n end\nend", - newContent: - "describe 'BusinessLogic' do\n it 'calculates total correctly' do\n expect(calculate_total(10, 5)).to eq(15)\n end\n\n it 'applies percentage discount' do\n expect(apply_percentage_discount(100, 10)).to eq(90)\n end\n\n it 'applies flat discount' do\n expect(apply_flat_discount(100, 10)).to eq(90)\n end\nend", - isEntirelyNew: false, - }); - } - - setTestFiles(mockTestFiles); - const newSelectedFiles: Record = {}; - const newExpandedFiles: Record = {}; - mockTestFiles.forEach((file) => { - newExpandedFiles[file.name] = true; - if (mode === "update") { - newSelectedFiles[file.name] = true; - } - }); - setSelectedFiles(newSelectedFiles); - setExpandedFiles(newExpandedFiles); - setAnalyzing(false); - setLoadingPR(null); - }, 1000); - }; - const handleReconnectGitHub = async () => { try { - // Redirect to GitHub OAuth flow to get new permissions - // Make sure NEXT_PUBLIC_GITHUB_CLIENT_ID is properly set in your environment variables const clientId = process.env.NEXT_PUBLIC_GITHUB_CLIENT_ID; const baseUrl = process.env.NEXT_PUBLIC_BASE_URL; if (!clientId) { @@ -137,28 +68,6 @@ export default function DashboardPage() { } }; - const handleConfirmChanges = async () => { - if (!selectedPR) return; - - setLoadingPR(selectedPR.id); - try { - const filesToCommit = testFiles.filter( - (file) => selectedFiles[file.name] - ); - // TODO: await commitChangesToPullRequest(selectedPR, filesToCommit); - // Reset state after successful commit - setSelectedPR(null); - setTestFiles([]); - setSelectedFiles({}); - setExpandedFiles({}); - } catch (error) { - console.error("Error committing changes:", error); - setError("Failed to commit changes. Please try again."); - } finally { - setLoadingPR(null); - } - }; - if (loading) { return (
@@ -191,33 +100,7 @@ export default function DashboardPage() { {pr.repository.full_name}
- { - setSelectedFiles((prev) => ({ - ...prev, - [fileName]: !prev[fileName], - })); - setExpandedFiles((prev) => ({ - ...prev, - [fileName]: !prev[fileName], - })); - }} - onConfirmChanges={handleConfirmChanges} - onCancelChanges={() => { - setSelectedPR(null); - setTestFiles([]); - setSelectedFiles({}); - setExpandedFiles({}); - }} - loadingPR={loadingPR} - /> + ))} diff --git a/app/(dashboard)/dashboard/pull-request.tsx b/app/(dashboard)/dashboard/pull-request.tsx index fcbe23fd..55a9591d 100644 --- a/app/(dashboard)/dashboard/pull-request.tsx +++ b/app/(dashboard)/dashboard/pull-request.tsx @@ -1,3 +1,5 @@ +"use client"; + import { useState } from "react"; import { Button } from "@/components/ui/button"; import { @@ -8,12 +10,15 @@ import { Edit, PlusCircle, Loader2, + AlertCircle, } from "lucide-react"; import Link from "next/link"; import { Checkbox } from "@/components/ui/checkbox"; -import { Badge } from "@/components/ui/badge"; import dynamic from "next/dynamic"; import { PullRequest, TestFile } from "./types"; +import { generateTestsResponseSchema } from "@/app/api/generate-tests/schema"; +import { useToast } from "@/hooks/use-toast"; +import { commitChangesToPullRequest, getPullRequestInfo } from "@/lib/github"; const ReactDiffViewer = dynamic(() => import("react-diff-viewer"), { ssr: false, @@ -21,32 +26,154 @@ const ReactDiffViewer = dynamic(() => import("react-diff-viewer"), { interface PullRequestItemProps { pullRequest: PullRequest; - onOpenTests: (pr: PullRequest, mode: "write" | "update") => void; - selectedPR: PullRequest | null; - testFiles: TestFile[]; - selectedFiles: Record; - expandedFiles: Record; - analyzing: boolean; - onFileToggle: (fileName: string) => void; - onConfirmChanges: () => void; - onCancelChanges: () => void; - loadingPR: number | null; } -export function PullRequestItem({ - pullRequest, - onOpenTests, - selectedPR, - testFiles, - selectedFiles, - expandedFiles, - analyzing, - onFileToggle, - onConfirmChanges, - onCancelChanges, - loadingPR, -}: PullRequestItemProps) { - const isLoading = loadingPR === pullRequest.id; +export function PullRequestItem({ pullRequest }: PullRequestItemProps) { + const [testFiles, setTestFiles] = useState([]); + const [selectedFiles, setSelectedFiles] = useState>( + {} + ); + const [expandedFiles, setExpandedFiles] = useState>( + {} + ); + const [analyzing, setAnalyzing] = useState(false); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const { toast } = useToast(); + + const handleTests = async (pr: PullRequest, mode: "write" | "update") => { + setAnalyzing(true); + setLoading(true); + setError(null); + + try { + const { diff, testFiles: oldTestFiles } = await getPullRequestInfo( + pr.repository.owner.login, + pr.repository.name, + pr.number + ); + + const response = await fetch("/api/generate-tests", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + mode, + pr_id: pr.id, + pr_diff: diff, + test_files: oldTestFiles, + }), + }); + + if (!response.ok) { + throw new Error("Failed to generate test files"); + } + + const data = await response.json(); + const parsedData = generateTestsResponseSchema.parse(data); + handleTestFilesUpdate(oldTestFiles, parsedData); + } catch (error) { + console.error("Error generating test files:", error); + setError("Failed to generate test files."); + } finally { + setAnalyzing(false); + setLoading(false); + } + }; + + const handleTestFilesUpdate = ( + oldTestFiles: TestFile[], + newTestFiles: TestFile[] + ) => { + if (newTestFiles.length > 0) { + const filteredTestFiles = newTestFiles + .filter((file): file is TestFile => file !== undefined) + .map((file) => { + const oldFile = oldTestFiles.find( + (oldFile) => oldFile.name === file.name + ); + return { + ...file, + oldContent: oldFile ? oldFile.content : "", + }; + }); + setTestFiles(filteredTestFiles); + const newSelectedFiles: Record = {}; + const newExpandedFiles: Record = {}; + filteredTestFiles.forEach((file) => { + const fileName = file?.name ?? `file_${Math.random()}`; + newExpandedFiles[fileName] = true; + newSelectedFiles[fileName] = true; + }); + setSelectedFiles(newSelectedFiles); + setExpandedFiles(newExpandedFiles); + } + }; + + const commitChanges = async () => { + setLoading(true); + setError(null); + try { + const filesToCommit = testFiles + .filter((file) => selectedFiles[file.name]) + .map((file) => ({ + name: file.name, + content: file.content, + })); + + const newCommitUrl = await commitChangesToPullRequest( + pullRequest.repository.owner.login, + pullRequest.repository.name, + pullRequest.number, + filesToCommit + ); + + toast({ + title: "Changes committed successfully", + description: ( + <> + The test files have been added to the pull request.{" "} + + View commit + + + ), + }); + + setTestFiles([]); + setSelectedFiles({}); + setExpandedFiles({}); + } catch (error) { + console.error("Error committing changes:", error); + setError("Failed to commit changes. Please try again."); + toast({ + title: "Error", + description: "Failed to commit changes. Please try again.", + variant: "destructive", + }); + } finally { + setLoading(false); + } + }; + + const handleCancelChanges = () => { + setTestFiles([]); + setSelectedFiles({}); + setExpandedFiles({}); + setError(null); + }; + + const handleFileToggle = (fileName: string) => { + setSelectedFiles((prev) => ({ + ...prev, + [fileName]: !prev[fileName], + })); + setExpandedFiles((prev) => ({ + ...prev, + [fileName]: !prev[fileName], + })); + }; return (
@@ -70,6 +197,8 @@ export function PullRequestItem({ {pullRequest.buildStatus === "success" ? ( + ) : pullRequest.buildStatus === "pending" ? ( + ) : ( )} @@ -80,12 +209,12 @@ export function PullRequestItem({ Build: {pullRequest.buildStatus} - {selectedPR && selectedPR.id === pullRequest.id ? ( + {testFiles.length > 0 ? ( @@ -93,35 +222,40 @@ export function PullRequestItem({ ) : ( )}
- {selectedPR && selectedPR.id === pullRequest.id && ( + {error && ( +
+ {error} +
+ )} + {(loading || analyzing || testFiles.length > 0) && (
-

Test Files

+

Test files

{analyzing ? (
@@ -136,7 +270,7 @@ export function PullRequestItem({ onFileToggle(file.name)} + onCheckedChange={() => handleFileToggle(file.name)} /> - {file.isEntirelyNew && ( - - New - - )}
{expandedFiles[file.name] && ( @@ -156,7 +285,7 @@ export function PullRequestItem({
@@ -166,18 +295,18 @@ export function PullRequestItem({ ))} )} diff --git a/app/(dashboard)/dashboard/types.ts b/app/(dashboard)/dashboard/types.ts index 8e8bea42..ab5ca7fb 100644 --- a/app/(dashboard)/dashboard/types.ts +++ b/app/(dashboard)/dashboard/types.ts @@ -4,6 +4,7 @@ export interface PullRequest { number: number; buildStatus: string; isDraft: boolean; + branchName: string; repository: { id: number; name: string; @@ -16,7 +17,6 @@ export interface PullRequest { export interface TestFile { name: string; - oldContent: string; - newContent: string; - isEntirelyNew: boolean; -} \ No newline at end of file + content: string; + oldContent?: string; +} diff --git a/app/(dashboard)/layout.tsx b/app/(dashboard)/layout.tsx index f9752adc..233ac82e 100644 --- a/app/(dashboard)/layout.tsx +++ b/app/(dashboard)/layout.tsx @@ -6,6 +6,7 @@ import { UserButton, } from "@clerk/nextjs"; import Link from "next/link"; +import { Toaster } from "@/components/ui/toaster"; export default function Layout({ children }: { children: React.ReactNode }) { return ( @@ -43,6 +44,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
{children}
+ diff --git a/app/api/generate-tests/route.ts b/app/api/generate-tests/route.ts new file mode 100644 index 00000000..93510fd6 --- /dev/null +++ b/app/api/generate-tests/route.ts @@ -0,0 +1,50 @@ +import { anthropic } from "@ai-sdk/anthropic"; +import { generateObject } from "ai"; +import { z } from "zod"; +import { GenerateTestsInput } from "./schema"; + +export const maxDuration = 30; + +export async function POST(req: Request) { + const { mode, pr_diff, test_files } = + (await req.json()) as GenerateTestsInput; + + const prompt = `You are an expert software engineer. ${ + mode === "write" + ? "Write entirely new tests and update relevant existing tests in order to reflect the added/edited/removed functionality." + : "Update existing test files in order to get the PR build back to passing. Make updates to tests solely, do not add or remove tests." + } + + PR Diff: + + ${pr_diff} + + + Existing test files: + + ${test_files + .map((file) => `${file.name}\n${file.content ? `: ${file.content}` : ""}`) + .join("\n")} + + + Respond with an array of test files with their name being the path to the file and the content being the full contents of the updated test file.`; + + const { object } = await generateObject({ + model: anthropic("claude-3-5-sonnet-20240620"), + output: "array", + schema: z.object({ + name: z.string(), + content: z.string(), + }), + prompt, + }); + + const tests = []; + for (const test of object) { + tests.push(test); + } + + return new Response(JSON.stringify(tests), { + headers: { "Content-Type": "application/json" }, + }); +} diff --git a/app/api/generate-tests/schema.ts b/app/api/generate-tests/schema.ts new file mode 100644 index 00000000..16b5925e --- /dev/null +++ b/app/api/generate-tests/schema.ts @@ -0,0 +1,23 @@ +import { z } from "zod"; + +export const generateTestsSchema = z.object({ + mode: z.enum(["write", "update"]), + pr_id: z.number(), + pr_diff: z.string(), + test_files: z.array( + z.object({ + name: z.string(), + content: z.string().optional(), + }) + ), +}); + +export const generateTestsResponseSchema = z.array( + z.object({ + name: z.string(), + content: z.string(), + }) +); + +export type GenerateTestsInput = z.infer; +export type GenerateTestsResponse = z.infer; diff --git a/components/ui/toast.tsx b/components/ui/toast.tsx new file mode 100644 index 00000000..cc4e0ab2 --- /dev/null +++ b/components/ui/toast.tsx @@ -0,0 +1,129 @@ +"use client" + +import * as React from "react" +import { Cross2Icon } from "@radix-ui/react-icons" +import * as ToastPrimitives from "@radix-ui/react-toast" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const ToastProvider = ToastPrimitives.Provider + +const ToastViewport = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +ToastViewport.displayName = ToastPrimitives.Viewport.displayName + +const toastVariants = cva( + "group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full", + { + variants: { + variant: { + default: "border bg-background text-foreground", + destructive: + "destructive group border-destructive bg-destructive text-destructive-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +const Toast = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, variant, ...props }, ref) => { + return ( + + ) +}) +Toast.displayName = ToastPrimitives.Root.displayName + +const ToastAction = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +ToastAction.displayName = ToastPrimitives.Action.displayName + +const ToastClose = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +ToastClose.displayName = ToastPrimitives.Close.displayName + +const ToastTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +ToastTitle.displayName = ToastPrimitives.Title.displayName + +const ToastDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +ToastDescription.displayName = ToastPrimitives.Description.displayName + +type ToastProps = React.ComponentPropsWithoutRef + +type ToastActionElement = React.ReactElement + +export { + type ToastProps, + type ToastActionElement, + ToastProvider, + ToastViewport, + Toast, + ToastTitle, + ToastDescription, + ToastClose, + ToastAction, +} diff --git a/components/ui/toaster.tsx b/components/ui/toaster.tsx new file mode 100644 index 00000000..171beb46 --- /dev/null +++ b/components/ui/toaster.tsx @@ -0,0 +1,35 @@ +"use client" + +import { useToast } from "@/hooks/use-toast" +import { + Toast, + ToastClose, + ToastDescription, + ToastProvider, + ToastTitle, + ToastViewport, +} from "@/components/ui/toast" + +export function Toaster() { + const { toasts } = useToast() + + return ( + + {toasts.map(function ({ id, title, description, action, ...props }) { + return ( + +
+ {title && {title}} + {description && ( + {description} + )} +
+ {action} + +
+ ) + })} + +
+ ) +} diff --git a/hooks/use-toast.ts b/hooks/use-toast.ts new file mode 100644 index 00000000..02e111d8 --- /dev/null +++ b/hooks/use-toast.ts @@ -0,0 +1,194 @@ +"use client" + +// Inspired by react-hot-toast library +import * as React from "react" + +import type { + ToastActionElement, + ToastProps, +} from "@/components/ui/toast" + +const TOAST_LIMIT = 1 +const TOAST_REMOVE_DELAY = 1000000 + +type ToasterToast = ToastProps & { + id: string + title?: React.ReactNode + description?: React.ReactNode + action?: ToastActionElement +} + +const actionTypes = { + ADD_TOAST: "ADD_TOAST", + UPDATE_TOAST: "UPDATE_TOAST", + DISMISS_TOAST: "DISMISS_TOAST", + REMOVE_TOAST: "REMOVE_TOAST", +} as const + +let count = 0 + +function genId() { + count = (count + 1) % Number.MAX_SAFE_INTEGER + return count.toString() +} + +type ActionType = typeof actionTypes + +type Action = + | { + type: ActionType["ADD_TOAST"] + toast: ToasterToast + } + | { + type: ActionType["UPDATE_TOAST"] + toast: Partial + } + | { + type: ActionType["DISMISS_TOAST"] + toastId?: ToasterToast["id"] + } + | { + type: ActionType["REMOVE_TOAST"] + toastId?: ToasterToast["id"] + } + +interface State { + toasts: ToasterToast[] +} + +const toastTimeouts = new Map>() + +const addToRemoveQueue = (toastId: string) => { + if (toastTimeouts.has(toastId)) { + return + } + + const timeout = setTimeout(() => { + toastTimeouts.delete(toastId) + dispatch({ + type: "REMOVE_TOAST", + toastId: toastId, + }) + }, TOAST_REMOVE_DELAY) + + toastTimeouts.set(toastId, timeout) +} + +export const reducer = (state: State, action: Action): State => { + switch (action.type) { + case "ADD_TOAST": + return { + ...state, + toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), + } + + case "UPDATE_TOAST": + return { + ...state, + toasts: state.toasts.map((t) => + t.id === action.toast.id ? { ...t, ...action.toast } : t + ), + } + + case "DISMISS_TOAST": { + const { toastId } = action + + // ! Side effects ! - This could be extracted into a dismissToast() action, + // but I'll keep it here for simplicity + if (toastId) { + addToRemoveQueue(toastId) + } else { + state.toasts.forEach((toast) => { + addToRemoveQueue(toast.id) + }) + } + + return { + ...state, + toasts: state.toasts.map((t) => + t.id === toastId || toastId === undefined + ? { + ...t, + open: false, + } + : t + ), + } + } + case "REMOVE_TOAST": + if (action.toastId === undefined) { + return { + ...state, + toasts: [], + } + } + return { + ...state, + toasts: state.toasts.filter((t) => t.id !== action.toastId), + } + } +} + +const listeners: Array<(state: State) => void> = [] + +let memoryState: State = { toasts: [] } + +function dispatch(action: Action) { + memoryState = reducer(memoryState, action) + listeners.forEach((listener) => { + listener(memoryState) + }) +} + +type Toast = Omit + +function toast({ ...props }: Toast) { + const id = genId() + + const update = (props: ToasterToast) => + dispatch({ + type: "UPDATE_TOAST", + toast: { ...props, id }, + }) + const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }) + + dispatch({ + type: "ADD_TOAST", + toast: { + ...props, + id, + open: true, + onOpenChange: (open) => { + if (!open) dismiss() + }, + }, + }) + + return { + id: id, + dismiss, + update, + } +} + +function useToast() { + const [state, setState] = React.useState(memoryState) + + React.useEffect(() => { + listeners.push(setState) + return () => { + const index = listeners.indexOf(setState) + if (index > -1) { + listeners.splice(index, 1) + } + } + }, [state]) + + return { + ...state, + toast, + dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), + } +} + +export { useToast, toast } diff --git a/lib/db/schema.ts b/lib/db/schema.ts index 38877902..9f3ddb8e 100644 --- a/lib/db/schema.ts +++ b/lib/db/schema.ts @@ -63,13 +63,6 @@ export type NewUser = typeof users.$inferInsert; export type PullRequest = typeof pullRequests.$inferSelect; export type NewPullRequest = typeof pullRequests.$inferInsert; -export interface TestFile { - name: string; - oldContent: string; - newContent: string; - isEntirelyNew: boolean; -} - export interface ExtendedPullRequest extends PullRequest { repository: { owner: string; diff --git a/lib/github.ts b/lib/github.ts index a61cbd37..7c2b4307 100644 --- a/lib/github.ts +++ b/lib/github.ts @@ -7,7 +7,7 @@ import { updateUserGithubToken, createUser, } from "./db/queries"; -import { PullRequest, TestFile } from "./db/schema"; +import { TestFile } from "../app/(dashboard)/dashboard/types"; async function getOctokit() { const { userId } = auth(); @@ -21,8 +21,6 @@ async function getOctokit() { user = await createUser(userId); } - console.log("User:", user); - if (!user.githubAccessToken) { throw new Error("GitHub access token not found"); } @@ -49,7 +47,6 @@ export async function exchangeCodeForAccessToken( if (!response.ok) { const errorData = await response.json(); - console.error("GitHub API error:", errorData); throw new Error( `Failed to exchange code for access token: ${ errorData.error_description || response.statusText @@ -60,14 +57,12 @@ export async function exchangeCodeForAccessToken( const data = await response.json(); if (data.error) { - console.error("GitHub OAuth error:", data); throw new Error( `GitHub OAuth error: ${data.error_description || data.error}` ); } if (!data.access_token) { - console.error("Unexpected response from GitHub:", data); throw new Error("Access token not found in GitHub response"); } @@ -94,31 +89,25 @@ export async function getAssignedPullRequests() { per_page: 100, }); - console.log("GitHub API response:", data); - console.log(`Found ${data.total_count} pull requests`); - const pullRequests = await Promise.all( data.items.map(async (pr) => { const [owner, repo] = pr.repository_url.split("/").slice(-2); - // Get the latest commit for this pull request const { data: pullRequestData } = await octokit.pulls.get({ owner, repo, pull_number: pr.number, }); - const latestCommitSha = pullRequestData.head.sha; + const branchName = pullRequestData.head.ref; const buildStatus = await fetchBuildStatus( octokit, owner, repo, - latestCommitSha + branchName ); - console.log("Build status:", buildStatus); - return { id: pr.id, repoId: pr.repository_url, @@ -132,13 +121,14 @@ export async function getAssignedPullRequests() { isDraft: pr.draft || false, owner, repo, + branchName, }; }) ); return pullRequests; } catch (error) { - console.error("Error fetching assigned GitHub pull requests:", error); + console.error("Error fetching assigned pull requests:", error); return { error: "Failed to fetch assigned GitHub pull requests" }; } } @@ -150,13 +140,24 @@ async function fetchBuildStatus( ref: string ): Promise { try { - const { data } = await octokit.rest.repos.getCombinedStatusForRef({ + const { data } = await octokit.rest.checks.listForRef({ owner, repo, ref, }); - return data.state; + if (data.check_runs.length === 0) { + return "pending"; + } + + const statuses = data.check_runs.map((run) => run.conclusion); + if (statuses.every((status) => status === "success")) { + return "success"; + } else if (statuses.some((status) => status === "failure")) { + return "failure"; + } else { + return "pending"; + } } catch (error) { console.error("Error fetching build status:", error); return "unknown"; @@ -164,54 +165,170 @@ async function fetchBuildStatus( } export async function commitChangesToPullRequest( - pullRequest: PullRequest, + owner: string, + repo: string, + pullNumber: number, filesToCommit: TestFile[] -) { +): Promise { const octokit = await getOctokit(); - const { owner, repo } = pullRequest; - // Get the current commit SHA - const { data: pr } = await octokit.pulls.get({ - owner, - repo, - pull_number: pullRequest.number, - }); + try { + const { data: pr } = await octokit.pulls.get({ + owner, + repo, + pull_number: pullNumber, + }); - const baseSha = pr.base.sha; - - // Create a new tree with the updated files - const tree = await Promise.all( - filesToCommit.map(async (file) => { - return { - path: file.name, - mode: "100644" as const, - type: "blob" as const, - content: file.newContent, - }; - }) - ); - - const { data: newTree } = await octokit.git.createTree({ - owner, - repo, - base_tree: baseSha, - tree, - }); + const { data: ref } = await octokit.git.getRef({ + owner, + repo, + ref: `heads/${pr.head.ref}`, + }); - // Create a new commit - const { data: newCommit } = await octokit.git.createCommit({ - owner, - repo, - message: "Update test files", - tree: newTree.sha, - parents: [baseSha], - }); + const { data: commit } = await octokit.git.getCommit({ + owner, + repo, + commit_sha: ref.object.sha, + }); - // Update the reference of the branch - await octokit.git.updateRef({ - owner, - repo, - ref: `heads/${pr.head.ref}`, - sha: newCommit.sha, - }); + const { data: currentTree } = await octokit.git.getTree({ + owner, + repo, + tree_sha: commit.tree.sha, + recursive: "true", + }); + + const updatedTree = currentTree.tree.map((item) => ({ + path: item.path, + mode: item.mode, + type: item.type, + sha: item.sha, + })); + + for (const file of filesToCommit) { + const { data: blob } = await octokit.git.createBlob({ + owner, + repo, + content: file.content, + encoding: "utf-8", + }); + + const existingFileIndex = updatedTree.findIndex( + (item) => item.path === file.name + ); + if (existingFileIndex !== -1) { + updatedTree[existingFileIndex] = { + path: file.name, + mode: "100644", + type: "blob", + sha: blob.sha, + }; + } else { + updatedTree.push({ + path: file.name, + mode: "100644", + type: "blob", + sha: blob.sha, + }); + } + } + + const { data: newTree } = await octokit.git.createTree({ + owner, + repo, + tree: updatedTree as any, + base_tree: commit.tree.sha, + }); + + const { data: newCommit } = await octokit.git.createCommit({ + owner, + repo, + message: "Update test files", + tree: newTree.sha, + parents: [commit.sha], + }); + + await octokit.git.updateRef({ + owner, + repo, + ref: `heads/${pr.head.ref}`, + sha: newCommit.sha, + }); + + return `https://github.com/${owner}/${repo}/commit/${newCommit.sha}`; + } catch (error) { + console.error("Error committing changes to pull request:", error); + throw error; + } +} + +export async function getPullRequestInfo( + owner: string, + repo: string, + pullNumber: number +) { + const octokit = await getOctokit(); + + try { + const [diffResponse, repoContentsResponse] = await Promise.all([ + octokit.pulls.get({ + owner, + repo, + pull_number: pullNumber, + mediaType: { format: "diff" }, + }), + octokit.rest.repos.getContent({ + owner, + repo, + path: "", + }), + ]); + + const testFiles = []; + const queue = repoContentsResponse.data as { path: string; type: string }[]; + + while (queue.length > 0) { + const item = queue.shift(); + if (item && item.type === "dir") { + const dirContents = await octokit.rest.repos.getContent({ + owner, + repo, + path: item.path, + }); + queue.push(...(dirContents.data as { path: string; type: string }[])); + } else if ( + item && + item.type === "file" && + item.path.toLowerCase().includes(".test.") + ) { + const fileContent = await octokit.rest.repos.getContent({ + owner, + repo, + path: item.path, + }); + + if ( + "content" in fileContent.data && + typeof fileContent.data.content === "string" + ) { + const decodedContent = Buffer.from( + fileContent.data.content, + "base64" + ).toString("utf-8"); + testFiles.push({ + name: item.path, + content: decodedContent, + }); + } + } + } + + return { + diff: diffResponse.data, + testFiles, + }; + } catch (error) { + console.error("Error fetching PR info:", error); + throw new Error("Failed to fetch PR info"); + } } diff --git a/package.json b/package.json index 1c765751..033d637d 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,8 @@ "test": "vitest" }, "dependencies": { + "@ai-sdk/anthropic": "^0.0.50", + "@ai-sdk/openai": "^0.0.61", "@clerk/nextjs": "^5.6.0", "@octokit/rest": "^21.0.2", "@radix-ui/react-avatar": "^1.1.0", @@ -27,11 +29,13 @@ "@radix-ui/react-scroll-area": "^1.1.0", "@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-toast": "^1.2.1", "@types/bcryptjs": "^2.4.6", "@types/node": "^22.5.4", "@types/react": "^18.3.5", "@types/react-dom": "^18.3.0", "@vercel/postgres": "^0.10.0", + "ai": "^3.4.0", "autoprefixer": "^10.4.20", "bcryptjs": "^2.4.3", "class-variance-authority": "^0.7.0", @@ -49,6 +53,7 @@ "react-beautiful-dnd": "^13.1.1", "react-diff-viewer": "^3.1.1", "react-dom": "19.0.0-rc-7771d3a7-20240827", + "run": "^1.5.0", "server-only": "^0.0.1", "stripe": "^16.10.0", "tailwind-merge": "^2.5.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1559316a..2b4fa7a0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,9 +8,15 @@ importers: .: dependencies: + '@ai-sdk/anthropic': + specifier: ^0.0.50 + version: 0.0.50(zod@3.23.8) + '@ai-sdk/openai': + specifier: ^0.0.61 + version: 0.0.61(zod@3.23.8) '@clerk/nextjs': specifier: ^5.6.0 - version: 5.6.0(next@15.0.0-canary.152(@babel/core@7.25.2)(react-dom@19.0.0-rc-7771d3a7-20240827(react@19.0.0-rc-7771d3a7-20240827))(react@19.0.0-rc-7771d3a7-20240827))(react-dom@19.0.0-rc-7771d3a7-20240827(react@19.0.0-rc-7771d3a7-20240827))(react@19.0.0-rc-7771d3a7-20240827) + version: 5.6.0(next@15.0.0-canary.152(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(react-dom@19.0.0-rc-7771d3a7-20240827(react@19.0.0-rc-7771d3a7-20240827))(react@19.0.0-rc-7771d3a7-20240827))(react-dom@19.0.0-rc-7771d3a7-20240827(react@19.0.0-rc-7771d3a7-20240827))(react@19.0.0-rc-7771d3a7-20240827) '@octokit/rest': specifier: ^21.0.2 version: 21.0.2 @@ -47,6 +53,9 @@ importers: '@radix-ui/react-slot': specifier: ^1.1.0 version: 1.1.0(@types/react@18.3.5)(react@19.0.0-rc-7771d3a7-20240827) + '@radix-ui/react-toast': + specifier: ^1.2.1 + version: 1.2.1(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@19.0.0-rc-7771d3a7-20240827(react@19.0.0-rc-7771d3a7-20240827))(react@19.0.0-rc-7771d3a7-20240827) '@types/bcryptjs': specifier: ^2.4.6 version: 2.4.6 @@ -62,6 +71,9 @@ importers: '@vercel/postgres': specifier: ^0.10.0 version: 0.10.0 + ai: + specifier: ^3.4.0 + version: 3.4.2(react@19.0.0-rc-7771d3a7-20240827)(sswr@2.1.0(svelte@4.2.19))(svelte@4.2.19)(vue@3.5.8(typescript@5.6.2))(zod@3.23.8) autoprefixer: specifier: ^10.4.20 version: 10.4.20(postcss@8.4.45) @@ -85,7 +97,7 @@ importers: version: 0.24.2 drizzle-orm: specifier: ^0.33.0 - version: 0.33.0(@neondatabase/serverless@0.9.5)(@types/pg@8.11.6)(@types/react@18.3.5)(@vercel/postgres@0.10.0)(postgres@3.4.4)(react@19.0.0-rc-7771d3a7-20240827) + version: 0.33.0(@neondatabase/serverless@0.9.5)(@opentelemetry/api@1.9.0)(@types/pg@8.11.6)(@types/react@18.3.5)(@vercel/postgres@0.10.0)(postgres@3.4.4)(react@19.0.0-rc-7771d3a7-20240827) jose: specifier: ^5.8.0 version: 5.8.0 @@ -94,7 +106,7 @@ importers: version: 0.439.0(react@19.0.0-rc-7771d3a7-20240827) next: specifier: 15.0.0-canary.152 - version: 15.0.0-canary.152(@babel/core@7.25.2)(react-dom@19.0.0-rc-7771d3a7-20240827(react@19.0.0-rc-7771d3a7-20240827))(react@19.0.0-rc-7771d3a7-20240827) + version: 15.0.0-canary.152(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(react-dom@19.0.0-rc-7771d3a7-20240827(react@19.0.0-rc-7771d3a7-20240827))(react@19.0.0-rc-7771d3a7-20240827) postcss: specifier: ^8.4.45 version: 8.4.45 @@ -113,6 +125,9 @@ importers: react-dom: specifier: 19.0.0-rc-7771d3a7-20240827 version: 19.0.0-rc-7771d3a7-20240827(react@19.0.0-rc-7771d3a7-20240827) + run: + specifier: ^1.5.0 + version: 1.5.0 server-only: specifier: ^0.0.1 version: 0.0.1 @@ -171,6 +186,79 @@ packages: '@adobe/css-tools@4.4.0': resolution: {integrity: sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ==} + '@ai-sdk/anthropic@0.0.50': + resolution: {integrity: sha512-++mqmFcUoQgjoCchAU6eVG3QfKdwkeJVNdMZ+jUiNdawn8diA6BlARlu7xFT4F7W3bcStfYv4hK1jwRyzAQtCg==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.0.0 + + '@ai-sdk/openai@0.0.61': + resolution: {integrity: sha512-yIJ70xU9sbDjVAaNoq+W+0jnAgIUsx4e9VTnoNPXNTIQRpgpLvQ7iG8GYNgujO4oX4sLiHsWpOEMzrSwD0mNmw==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.0.0 + + '@ai-sdk/provider-utils@1.0.19': + resolution: {integrity: sha512-p02Fq5Mnc8T6nwRBN1Iaou8YXvN1sDS6hbmJaD5UaRbXjizbh+8rpFS/o7jqAHTwf3uHCDitP3pnODyHdc/CDQ==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.0.0 + peerDependenciesMeta: + zod: + optional: true + + '@ai-sdk/provider@0.0.23': + resolution: {integrity: sha512-oAc49O5+xypVrKM7EUU5P/Y4DUL4JZUWVxhejoAVOTOl3WZUEWsMbP3QZR+TrimQIsS0WR/n9UuF6U0jPdp0tQ==} + engines: {node: '>=18'} + + '@ai-sdk/react@0.0.60': + resolution: {integrity: sha512-FCOaAh7sxzUK07cXUTIvyjqrx1I2/8CQk6kcZMDSuec6iXcyPlExxfbDo6/7j7xYRqSQGKxou6rBThdHi6oEUg==} + engines: {node: '>=18'} + peerDependencies: + react: ^18 || ^19 + zod: ^3.0.0 + peerDependenciesMeta: + react: + optional: true + zod: + optional: true + + '@ai-sdk/solid@0.0.47': + resolution: {integrity: sha512-lVMxIxtuNqoo/TObSFGflEP2dUeJv7bfPQbS4jHTZGBNlyhgBRY2Xc19yNjA3QKRfvQNDVoQusqxn+18MiHJJQ==} + engines: {node: '>=18'} + peerDependencies: + solid-js: ^1.7.7 + peerDependenciesMeta: + solid-js: + optional: true + + '@ai-sdk/svelte@0.0.49': + resolution: {integrity: sha512-gV0MhaWxkatjf7uJrCAHO3bWrihokNUwGhuMCgyG+y53lwJKAYhR0zCoDRM2HnTJ89fdnx/PVe3R9fOWEVY5qA==} + engines: {node: '>=18'} + peerDependencies: + svelte: ^3.0.0 || ^4.0.0 + peerDependenciesMeta: + svelte: + optional: true + + '@ai-sdk/ui-utils@0.0.44': + resolution: {integrity: sha512-0qiyun/n5zqJzQs/WfQT86dZE5DiDhSHJc7b7ZGLYvNMztHkRQmak2zUCZP4IyGVZEicyEPQK6NEEpBgkmd3Dg==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.0.0 + peerDependenciesMeta: + zod: + optional: true + + '@ai-sdk/vue@0.0.51': + resolution: {integrity: sha512-6RjuuRGf749EjnsfbETJpF0fmq6a1lF6qUUUnd/Q1Ojf0tX8fI4qwvNykbECZHWuIj42EqZ3HDuNNR9c8oG4rA==} + engines: {node: '>=18'} + peerDependencies: + vue: ^3.3.4 + peerDependenciesMeta: + vue: + optional: true + '@alloc/quick-lru@5.2.0': resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} @@ -1018,6 +1106,10 @@ packages: '@octokit/types@13.5.0': resolution: {integrity: sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==} + '@opentelemetry/api@1.9.0': + resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} + engines: {node: '>=8.0.0'} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -1460,6 +1552,19 @@ packages: '@types/react': optional: true + '@radix-ui/react-toast@1.2.1': + resolution: {integrity: sha512-5trl7piMXcZiCq7MW6r8YYmu0bK5qDpTWz+FdEPdKyft2UixkspheYbjbrLXVN5NGKHFbOP7lm8eD0biiSqZqg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-use-callback-ref@1.0.1': resolution: {integrity: sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==} peerDependencies: @@ -1559,6 +1664,19 @@ packages: '@types/react': optional: true + '@radix-ui/react-visually-hidden@1.1.0': + resolution: {integrity: sha512-N8MDZqtgCgG5S3aV60INAB475osJousYpZ4cTJ2cFbMpdHS5Y6loLTH8LPtkj2QN0x93J30HT/M3qJXM0+lyeQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/rect@1.1.0': resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==} @@ -1689,6 +1807,9 @@ packages: '@types/bcryptjs@2.4.6': resolution: {integrity: sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==} + '@types/diff-match-patch@1.0.36': + resolution: {integrity: sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==} + '@types/estree@1.0.5': resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} @@ -1759,10 +1880,65 @@ packages: '@vitest/utils@2.1.1': resolution: {integrity: sha512-Y6Q9TsI+qJ2CC0ZKj6VBb+T8UPz593N113nnUykqwANqhgf3QkZeHFlusgKLTqrnVHbj/XDKZcDHol+dxVT+rQ==} + '@vue/compiler-core@3.5.8': + resolution: {integrity: sha512-Uzlxp91EPjfbpeO5KtC0KnXPkuTfGsNDeaKQJxQN718uz+RqDYarEf7UhQJGK+ZYloD2taUbHTI2J4WrUaZQNA==} + + '@vue/compiler-dom@3.5.8': + resolution: {integrity: sha512-GUNHWvoDSbSa5ZSHT9SnV5WkStWfzJwwTd6NMGzilOE/HM5j+9EB9zGXdtu/fCNEmctBqMs6C9SvVPpVPuk1Eg==} + + '@vue/compiler-sfc@3.5.8': + resolution: {integrity: sha512-taYpngQtSysrvO9GULaOSwcG5q821zCoIQBtQQSx7Uf7DxpR6CIHR90toPr9QfDD2mqHQPCSgoWBvJu0yV9zjg==} + + '@vue/compiler-ssr@3.5.8': + resolution: {integrity: sha512-W96PtryNsNG9u0ZnN5Q5j27Z/feGrFV6zy9q5tzJVyJaLiwYxvC0ek4IXClZygyhjm+XKM7WD9pdKi/wIRVC/Q==} + + '@vue/reactivity@3.5.8': + resolution: {integrity: sha512-mlgUyFHLCUZcAYkqvzYnlBRCh0t5ZQfLYit7nukn1GR96gc48Bp4B7OIcSfVSvlG1k3BPfD+p22gi1t2n9tsXg==} + + '@vue/runtime-core@3.5.8': + resolution: {integrity: sha512-fJuPelh64agZ8vKkZgp5iCkPaEqFJsYzxLk9vSC0X3G8ppknclNDr61gDc45yBGTaN5Xqc1qZWU3/NoaBMHcjQ==} + + '@vue/runtime-dom@3.5.8': + resolution: {integrity: sha512-DpAUz+PKjTZPUOB6zJgkxVI3GuYc2iWZiNeeHQUw53kdrparSTG6HeXUrYDjaam8dVsCdvQxDz6ZWxnyjccUjQ==} + + '@vue/server-renderer@3.5.8': + resolution: {integrity: sha512-7AmC9/mEeV9mmXNVyUIm1a1AjUhyeeGNbkLh39J00E7iPeGks8OGRB5blJiMmvqSh8SkaS7jkLWSpXtxUCeagA==} + peerDependencies: + vue: 3.5.8 + + '@vue/shared@3.5.8': + resolution: {integrity: sha512-mJleSWbAGySd2RJdX1RBtcrUBX6snyOc0qHpgk3lGi4l9/P/3ny3ELqFWqYdkXIwwNN/kdm8nD9ky8o6l/Lx2A==} + + acorn@8.12.1: + resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} + engines: {node: '>=0.4.0'} + hasBin: true + agent-base@7.1.1: resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==} engines: {node: '>= 14'} + ai@3.4.2: + resolution: {integrity: sha512-IwlmUGb72sgi++KvnjtbjJYByPzevmZNkjzpC/ZoZp8OgmxTqU4oO4oOhBAPof4WipEE+iiW0YPQGvM3JYZxHw==} + engines: {node: '>=18'} + peerDependencies: + openai: ^4.42.0 + react: ^18 || ^19 + sswr: ^2.1.0 + svelte: ^3.0.0 || ^4.0.0 + zod: ^3.0.0 + peerDependenciesMeta: + openai: + optional: true + react: + optional: true + sswr: + optional: true + svelte: + optional: true + zod: + optional: true + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -1822,6 +1998,10 @@ packages: peerDependencies: postcss: ^8.1.0 + axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + babel-plugin-emotion@10.2.2: resolution: {integrity: sha512-SMSkGoqTbTyUTDeuVuPIWifPdUGkTk1Kf9BWRiXIOIcuyMfsdp2EjeiiFvOzX8NOBvEh/ypKYvUh2rkgAJMCLA==} @@ -1902,6 +2082,10 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + chalk@5.3.0: + resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + check-error@2.1.1: resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} engines: {node: '>= 16'} @@ -1933,6 +2117,9 @@ packages: react: ^18.0.0 react-dom: ^18.0.0 + code-red@1.0.4: + resolution: {integrity: sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==} + color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} @@ -1992,6 +2179,10 @@ packages: css-box-model@1.2.1: resolution: {integrity: sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==} + css-tree@2.3.1: + resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + css.escape@1.5.1: resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} @@ -2055,6 +2246,9 @@ packages: didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + diff-match-patch@1.0.5: + resolution: {integrity: sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==} + diff@4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} @@ -2226,9 +2420,16 @@ packages: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + eventsource-parser@1.1.2: + resolution: {integrity: sha512-v0eOBUbiaFojBu2s2NPBfYUoRR9GjcDNvCXVaqEf5vVfpIAh9f8RCo4vXTP8c63QRKCFwoLpMpTdPwwhEKVgzA==} + engines: {node: '>=14.18'} + eventsource@2.0.2: resolution: {integrity: sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==} engines: {node: '>=12.0.0'} @@ -2402,6 +2603,9 @@ packages: is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-reference@3.0.2: + resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -2439,11 +2643,19 @@ packages: json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + json-schema@0.4.0: + resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} hasBin: true + jsondiffpatch@0.6.0: + resolution: {integrity: sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + lilconfig@2.1.0: resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} engines: {node: '>=10'} @@ -2455,6 +2667,9 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + locate-character@3.0.0: + resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} + lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} @@ -2490,6 +2705,9 @@ packages: resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} engines: {node: '>=8'} + mdn-data@2.0.30: + resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} + memoize-one@5.2.1: resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==} @@ -2527,6 +2745,11 @@ packages: mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + nanoid@3.3.6: + resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + nanoid@3.3.7: resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -2625,6 +2848,9 @@ packages: resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} engines: {node: '>= 14.16'} + periscopic@3.1.0: + resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==} + pg-int8@1.0.1: resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} engines: {node: '>=4.0.0'} @@ -2700,6 +2926,10 @@ packages: resolution: {integrity: sha512-7KTLTdzdZZYscUc65XmjFiB73vBhBfbPztCYdUNvlaso9PrzjzcmjqBPR0lNGkcVlcO4BjiO5rK/qNz+XAen1Q==} engines: {node: ^10 || ^12 || >=14} + postcss@8.4.47: + resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==} + engines: {node: ^10 || ^12 || >=14} + postgres-array@3.0.2: resolution: {integrity: sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==} engines: {node: '>=12'} @@ -2871,6 +3101,11 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + run@1.5.0: + resolution: {integrity: sha512-CBPzeX6JQZUdhZpSFyNt2vUk44ivKMWZYCNBYoZYEE46mL9nf6WyMP3320WnzIrJuo89+njiUvlo83jUEXjXLg==} + engines: {node: '>=v0.9.0'} + hasBin: true + safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -2881,6 +3116,9 @@ packages: scheduler@0.25.0-rc-7771d3a7-20240827: resolution: {integrity: sha512-n4nHmAoerbIOSrH24w0+fcdCUwQ4Npm7yXfsrn09FL01OWIaxpuo4P0rj3qPyLFgsJDbn18sWvLVB/e/KPnR+A==} + secure-json-parse@2.7.0: + resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -2949,6 +3187,11 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + sswr@2.1.0: + resolution: {integrity: sha512-Cqc355SYlTAaUt8iDPaC/4DPPXK925PePLMxyBKuWd5kKc5mwsG3nT9+Mq2tyguL5s7b4Jg+IRMpTRsNTAfpSQ==} + peerDependencies: + svelte: ^4.0.0 || ^5.0.0-next.0 + stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -3013,11 +3256,28 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + svelte@4.2.19: + resolution: {integrity: sha512-IY1rnGr6izd10B0A8LqsBfmlT5OILVuZ7XsI0vdGPEvuonFV7NYEUK4dAkm9Zg2q0Um92kYjTpS1CAP3Nh/KWw==} + engines: {node: '>=16'} + swr@2.2.0: resolution: {integrity: sha512-AjqHOv2lAhkuUdIiBu9xbuettzAzWXmCEcLONNKJRba87WAefz8Ca9d6ds/SzrPc235n1IxWYdhJ2zF3MNUaoQ==} peerDependencies: react: ^16.11.0 || ^17.0.0 || ^18.0.0 + swr@2.2.5: + resolution: {integrity: sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==} + peerDependencies: + react: ^16.11.0 || ^17.0.0 || ^18.0.0 + + swrev@4.0.0: + resolution: {integrity: sha512-LqVcOHSB4cPGgitD1riJ1Hh4vdmITOp+BkmfmXRh4hSF/t7EnS4iD+SOTmq7w5pPm/SiPeto4ADbKS6dHUDWFA==} + + swrv@1.0.4: + resolution: {integrity: sha512-zjEkcP8Ywmj+xOJW3lIT65ciY/4AL4e/Or7Gj0MzU3zBJNMdJiT8geVZhINavnlHRMMCcJLHhraLTAiDOTmQ9g==} + peerDependencies: + vue: '>=3.2.26 < 4' + symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} @@ -3242,6 +3502,14 @@ packages: jsdom: optional: true + vue@3.5.8: + resolution: {integrity: sha512-hvuvuCy51nP/1fSRvrrIqTLSvrSyz2Pq+KQ8S8SXCxTWVE0nMaOnSDnSOxV1eYmGfvK7mqiwvd1C59CEEz7dAQ==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + w3c-xmlserializer@5.0.0: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} @@ -3315,6 +3583,11 @@ packages: engines: {node: '>= 14'} hasBin: true + zod-to-json-schema@3.23.2: + resolution: {integrity: sha512-uSt90Gzc/tUfyNqxnjlfBs8W6WSGpNBv0rVsNxP/BVSMHMKGdthPYff4xtCHYloJGM0CFxFsb3NbC0eqPhfImw==} + peerDependencies: + zod: ^3.23.3 + zod@3.23.8: resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} @@ -3322,6 +3595,77 @@ snapshots: '@adobe/css-tools@4.4.0': {} + '@ai-sdk/anthropic@0.0.50(zod@3.23.8)': + dependencies: + '@ai-sdk/provider': 0.0.23 + '@ai-sdk/provider-utils': 1.0.19(zod@3.23.8) + zod: 3.23.8 + + '@ai-sdk/openai@0.0.61(zod@3.23.8)': + dependencies: + '@ai-sdk/provider': 0.0.23 + '@ai-sdk/provider-utils': 1.0.19(zod@3.23.8) + zod: 3.23.8 + + '@ai-sdk/provider-utils@1.0.19(zod@3.23.8)': + dependencies: + '@ai-sdk/provider': 0.0.23 + eventsource-parser: 1.1.2 + nanoid: 3.3.6 + secure-json-parse: 2.7.0 + optionalDependencies: + zod: 3.23.8 + + '@ai-sdk/provider@0.0.23': + dependencies: + json-schema: 0.4.0 + + '@ai-sdk/react@0.0.60(react@19.0.0-rc-7771d3a7-20240827)(zod@3.23.8)': + dependencies: + '@ai-sdk/provider-utils': 1.0.19(zod@3.23.8) + '@ai-sdk/ui-utils': 0.0.44(zod@3.23.8) + swr: 2.2.5(react@19.0.0-rc-7771d3a7-20240827) + optionalDependencies: + react: 19.0.0-rc-7771d3a7-20240827 + zod: 3.23.8 + + '@ai-sdk/solid@0.0.47(zod@3.23.8)': + dependencies: + '@ai-sdk/provider-utils': 1.0.19(zod@3.23.8) + '@ai-sdk/ui-utils': 0.0.44(zod@3.23.8) + transitivePeerDependencies: + - zod + + '@ai-sdk/svelte@0.0.49(svelte@4.2.19)(zod@3.23.8)': + dependencies: + '@ai-sdk/provider-utils': 1.0.19(zod@3.23.8) + '@ai-sdk/ui-utils': 0.0.44(zod@3.23.8) + sswr: 2.1.0(svelte@4.2.19) + optionalDependencies: + svelte: 4.2.19 + transitivePeerDependencies: + - zod + + '@ai-sdk/ui-utils@0.0.44(zod@3.23.8)': + dependencies: + '@ai-sdk/provider': 0.0.23 + '@ai-sdk/provider-utils': 1.0.19(zod@3.23.8) + json-schema: 0.4.0 + secure-json-parse: 2.7.0 + zod-to-json-schema: 3.23.2(zod@3.23.8) + optionalDependencies: + zod: 3.23.8 + + '@ai-sdk/vue@0.0.51(vue@3.5.8(typescript@5.6.2))(zod@3.23.8)': + dependencies: + '@ai-sdk/provider-utils': 1.0.19(zod@3.23.8) + '@ai-sdk/ui-utils': 0.0.44(zod@3.23.8) + swrv: 1.0.4(vue@3.5.8(typescript@5.6.2)) + optionalDependencies: + vue: 3.5.8(typescript@5.6.2) + transitivePeerDependencies: + - zod + '@alloc/quick-lru@5.2.0': {} '@ampproject/remapping@2.3.0': @@ -3476,14 +3820,14 @@ snapshots: react-dom: 19.0.0-rc-7771d3a7-20240827(react@19.0.0-rc-7771d3a7-20240827) tslib: 2.4.1 - '@clerk/nextjs@5.6.0(next@15.0.0-canary.152(@babel/core@7.25.2)(react-dom@19.0.0-rc-7771d3a7-20240827(react@19.0.0-rc-7771d3a7-20240827))(react@19.0.0-rc-7771d3a7-20240827))(react-dom@19.0.0-rc-7771d3a7-20240827(react@19.0.0-rc-7771d3a7-20240827))(react@19.0.0-rc-7771d3a7-20240827)': + '@clerk/nextjs@5.6.0(next@15.0.0-canary.152(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(react-dom@19.0.0-rc-7771d3a7-20240827(react@19.0.0-rc-7771d3a7-20240827))(react@19.0.0-rc-7771d3a7-20240827))(react-dom@19.0.0-rc-7771d3a7-20240827(react@19.0.0-rc-7771d3a7-20240827))(react@19.0.0-rc-7771d3a7-20240827)': dependencies: '@clerk/backend': 1.13.2(react-dom@19.0.0-rc-7771d3a7-20240827(react@19.0.0-rc-7771d3a7-20240827))(react@19.0.0-rc-7771d3a7-20240827) '@clerk/clerk-react': 5.9.1(react-dom@19.0.0-rc-7771d3a7-20240827(react@19.0.0-rc-7771d3a7-20240827))(react@19.0.0-rc-7771d3a7-20240827) '@clerk/shared': 2.8.1(react-dom@19.0.0-rc-7771d3a7-20240827(react@19.0.0-rc-7771d3a7-20240827))(react@19.0.0-rc-7771d3a7-20240827) '@clerk/types': 4.21.0 crypto-js: 4.2.0 - next: 15.0.0-canary.152(@babel/core@7.25.2)(react-dom@19.0.0-rc-7771d3a7-20240827(react@19.0.0-rc-7771d3a7-20240827))(react@19.0.0-rc-7771d3a7-20240827) + next: 15.0.0-canary.152(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(react-dom@19.0.0-rc-7771d3a7-20240827(react@19.0.0-rc-7771d3a7-20240827))(react@19.0.0-rc-7771d3a7-20240827) react: 19.0.0-rc-7771d3a7-20240827 react-dom: 19.0.0-rc-7771d3a7-20240827(react@19.0.0-rc-7771d3a7-20240827) server-only: 0.0.1 @@ -3978,6 +4322,8 @@ snapshots: dependencies: '@octokit/openapi-types': 22.2.0 + '@opentelemetry/api@1.9.0': {} + '@pkgjs/parseargs@0.11.0': optional: true @@ -4424,6 +4770,26 @@ snapshots: optionalDependencies: '@types/react': 18.3.5 + '@radix-ui/react-toast@1.2.1(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@19.0.0-rc-7771d3a7-20240827(react@19.0.0-rc-7771d3a7-20240827))(react@19.0.0-rc-7771d3a7-20240827)': + dependencies: + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-collection': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@19.0.0-rc-7771d3a7-20240827(react@19.0.0-rc-7771d3a7-20240827))(react@19.0.0-rc-7771d3a7-20240827) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.5)(react@19.0.0-rc-7771d3a7-20240827) + '@radix-ui/react-context': 1.1.0(@types/react@18.3.5)(react@19.0.0-rc-7771d3a7-20240827) + '@radix-ui/react-dismissable-layer': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@19.0.0-rc-7771d3a7-20240827(react@19.0.0-rc-7771d3a7-20240827))(react@19.0.0-rc-7771d3a7-20240827) + '@radix-ui/react-portal': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@19.0.0-rc-7771d3a7-20240827(react@19.0.0-rc-7771d3a7-20240827))(react@19.0.0-rc-7771d3a7-20240827) + '@radix-ui/react-presence': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@19.0.0-rc-7771d3a7-20240827(react@19.0.0-rc-7771d3a7-20240827))(react@19.0.0-rc-7771d3a7-20240827) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@19.0.0-rc-7771d3a7-20240827(react@19.0.0-rc-7771d3a7-20240827))(react@19.0.0-rc-7771d3a7-20240827) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.5)(react@19.0.0-rc-7771d3a7-20240827) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.5)(react@19.0.0-rc-7771d3a7-20240827) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.5)(react@19.0.0-rc-7771d3a7-20240827) + '@radix-ui/react-visually-hidden': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@19.0.0-rc-7771d3a7-20240827(react@19.0.0-rc-7771d3a7-20240827))(react@19.0.0-rc-7771d3a7-20240827) + react: 19.0.0-rc-7771d3a7-20240827 + react-dom: 19.0.0-rc-7771d3a7-20240827(react@19.0.0-rc-7771d3a7-20240827) + optionalDependencies: + '@types/react': 18.3.5 + '@types/react-dom': 18.3.0 + '@radix-ui/react-use-callback-ref@1.0.1(@types/react@18.3.5)(react@19.0.0-rc-7771d3a7-20240827)': dependencies: '@babel/runtime': 7.25.6 @@ -4500,6 +4866,15 @@ snapshots: optionalDependencies: '@types/react': 18.3.5 + '@radix-ui/react-visually-hidden@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@19.0.0-rc-7771d3a7-20240827(react@19.0.0-rc-7771d3a7-20240827))(react@19.0.0-rc-7771d3a7-20240827)': + dependencies: + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@19.0.0-rc-7771d3a7-20240827(react@19.0.0-rc-7771d3a7-20240827))(react@19.0.0-rc-7771d3a7-20240827) + react: 19.0.0-rc-7771d3a7-20240827 + react-dom: 19.0.0-rc-7771d3a7-20240827(react@19.0.0-rc-7771d3a7-20240827) + optionalDependencies: + '@types/react': 18.3.5 + '@types/react-dom': 18.3.0 + '@radix-ui/rect@1.1.0': {} '@rollup/rollup-android-arm-eabi@4.22.4': @@ -4612,6 +4987,8 @@ snapshots: '@types/bcryptjs@2.4.6': {} + '@types/diff-match-patch@1.0.36': {} + '@types/estree@1.0.5': {} '@types/estree@1.0.6': {} @@ -4710,12 +5087,93 @@ snapshots: loupe: 3.1.1 tinyrainbow: 1.2.0 + '@vue/compiler-core@3.5.8': + dependencies: + '@babel/parser': 7.25.6 + '@vue/shared': 3.5.8 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.8': + dependencies: + '@vue/compiler-core': 3.5.8 + '@vue/shared': 3.5.8 + + '@vue/compiler-sfc@3.5.8': + dependencies: + '@babel/parser': 7.25.6 + '@vue/compiler-core': 3.5.8 + '@vue/compiler-dom': 3.5.8 + '@vue/compiler-ssr': 3.5.8 + '@vue/shared': 3.5.8 + estree-walker: 2.0.2 + magic-string: 0.30.11 + postcss: 8.4.47 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.8': + dependencies: + '@vue/compiler-dom': 3.5.8 + '@vue/shared': 3.5.8 + + '@vue/reactivity@3.5.8': + dependencies: + '@vue/shared': 3.5.8 + + '@vue/runtime-core@3.5.8': + dependencies: + '@vue/reactivity': 3.5.8 + '@vue/shared': 3.5.8 + + '@vue/runtime-dom@3.5.8': + dependencies: + '@vue/reactivity': 3.5.8 + '@vue/runtime-core': 3.5.8 + '@vue/shared': 3.5.8 + csstype: 3.1.3 + + '@vue/server-renderer@3.5.8(vue@3.5.8(typescript@5.6.2))': + dependencies: + '@vue/compiler-ssr': 3.5.8 + '@vue/shared': 3.5.8 + vue: 3.5.8(typescript@5.6.2) + + '@vue/shared@3.5.8': {} + + acorn@8.12.1: {} + agent-base@7.1.1: dependencies: debug: 4.3.7 transitivePeerDependencies: - supports-color + ai@3.4.2(react@19.0.0-rc-7771d3a7-20240827)(sswr@2.1.0(svelte@4.2.19))(svelte@4.2.19)(vue@3.5.8(typescript@5.6.2))(zod@3.23.8): + dependencies: + '@ai-sdk/provider': 0.0.23 + '@ai-sdk/provider-utils': 1.0.19(zod@3.23.8) + '@ai-sdk/react': 0.0.60(react@19.0.0-rc-7771d3a7-20240827)(zod@3.23.8) + '@ai-sdk/solid': 0.0.47(zod@3.23.8) + '@ai-sdk/svelte': 0.0.49(svelte@4.2.19)(zod@3.23.8) + '@ai-sdk/ui-utils': 0.0.44(zod@3.23.8) + '@ai-sdk/vue': 0.0.51(vue@3.5.8(typescript@5.6.2))(zod@3.23.8) + '@opentelemetry/api': 1.9.0 + eventsource-parser: 1.1.2 + json-schema: 0.4.0 + jsondiffpatch: 0.6.0 + nanoid: 3.3.6 + secure-json-parse: 2.7.0 + zod-to-json-schema: 3.23.2(zod@3.23.8) + optionalDependencies: + react: 19.0.0-rc-7771d3a7-20240827 + sswr: 2.1.0(svelte@4.2.19) + svelte: 4.2.19 + zod: 3.23.8 + transitivePeerDependencies: + - solid-js + - vue + ansi-regex@5.0.1: {} ansi-regex@6.1.0: {} @@ -4765,6 +5223,8 @@ snapshots: postcss: 8.4.45 postcss-value-parser: 4.2.0 + axobject-query@4.1.0: {} + babel-plugin-emotion@10.2.2: dependencies: '@babel/helper-module-imports': 7.24.7 @@ -4861,6 +5321,8 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + chalk@5.3.0: {} + check-error@2.1.1: {} chokidar@3.6.0: @@ -4897,6 +5359,14 @@ snapshots: - '@types/react' - '@types/react-dom' + code-red@1.0.4: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + '@types/estree': 1.0.6 + acorn: 8.12.1 + estree-walker: 3.0.3 + periscopic: 3.1.0 + color-convert@1.9.3: dependencies: color-name: 1.1.3 @@ -4962,6 +5432,11 @@ snapshots: dependencies: tiny-invariant: 1.3.3 + css-tree@2.3.1: + dependencies: + mdn-data: 2.0.30 + source-map-js: 1.2.1 + css.escape@1.5.1: {} cssesc@3.0.0: {} @@ -5006,6 +5481,8 @@ snapshots: didyoumean@1.2.2: {} + diff-match-patch@1.0.5: {} + diff@4.0.2: {} dlv@1.1.3: {} @@ -5030,9 +5507,10 @@ snapshots: transitivePeerDependencies: - supports-color - drizzle-orm@0.33.0(@neondatabase/serverless@0.9.5)(@types/pg@8.11.6)(@types/react@18.3.5)(@vercel/postgres@0.10.0)(postgres@3.4.4)(react@19.0.0-rc-7771d3a7-20240827): + drizzle-orm@0.33.0(@neondatabase/serverless@0.9.5)(@opentelemetry/api@1.9.0)(@types/pg@8.11.6)(@types/react@18.3.5)(@vercel/postgres@0.10.0)(postgres@3.4.4)(react@19.0.0-rc-7771d3a7-20240827): optionalDependencies: '@neondatabase/serverless': 0.9.5 + '@opentelemetry/api': 1.9.0 '@types/pg': 8.11.6 '@types/react': 18.3.5 '@vercel/postgres': 0.10.0 @@ -5154,10 +5632,14 @@ snapshots: escape-string-regexp@1.0.5: {} + estree-walker@2.0.2: {} + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.6 + eventsource-parser@1.1.2: {} + eventsource@2.0.2: {} fast-glob@3.3.2: @@ -5327,6 +5809,10 @@ snapshots: is-potential-custom-element-name@1.0.1: {} + is-reference@3.0.2: + dependencies: + '@types/estree': 1.0.6 + isexe@2.0.0: {} jackspeak@3.4.3: @@ -5375,14 +5861,24 @@ snapshots: json-parse-even-better-errors@2.3.1: {} + json-schema@0.4.0: {} + json5@2.2.3: {} + jsondiffpatch@0.6.0: + dependencies: + '@types/diff-match-patch': 1.0.36 + chalk: 5.3.0 + diff-match-patch: 1.0.5 + lilconfig@2.1.0: {} lilconfig@3.1.2: {} lines-and-columns@1.2.4: {} + locate-character@3.0.0: {} + lodash@4.17.21: {} loose-envify@1.4.0: @@ -5415,6 +5911,8 @@ snapshots: map-obj@4.3.0: {} + mdn-data@2.0.30: {} + memoize-one@5.2.1: {} merge2@1.4.1: {} @@ -5446,9 +5944,11 @@ snapshots: object-assign: 4.1.1 thenify-all: 1.6.0 + nanoid@3.3.6: {} + nanoid@3.3.7: {} - next@15.0.0-canary.152(@babel/core@7.25.2)(react-dom@19.0.0-rc-7771d3a7-20240827(react@19.0.0-rc-7771d3a7-20240827))(react@19.0.0-rc-7771d3a7-20240827): + next@15.0.0-canary.152(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(react-dom@19.0.0-rc-7771d3a7-20240827(react@19.0.0-rc-7771d3a7-20240827))(react@19.0.0-rc-7771d3a7-20240827): dependencies: '@next/env': 15.0.0-canary.152 '@swc/counter': 0.1.3 @@ -5470,6 +5970,7 @@ snapshots: '@next/swc-win32-arm64-msvc': 15.0.0-canary.152 '@next/swc-win32-ia32-msvc': 15.0.0-canary.152 '@next/swc-win32-x64-msvc': 15.0.0-canary.152 + '@opentelemetry/api': 1.9.0 sharp: 0.33.5 transitivePeerDependencies: - '@babel/core' @@ -5530,6 +6031,12 @@ snapshots: pathval@2.0.0: {} + periscopic@3.1.0: + dependencies: + '@types/estree': 1.0.6 + estree-walker: 3.0.3 + is-reference: 3.0.2 + pg-int8@1.0.1: {} pg-numeric@1.0.2: {} @@ -5597,6 +6104,12 @@ snapshots: picocolors: 1.1.0 source-map-js: 1.2.1 + postcss@8.4.47: + dependencies: + nanoid: 3.3.7 + picocolors: 1.1.0 + source-map-js: 1.2.1 + postgres-array@3.0.2: {} postgres-bytea@3.0.0: @@ -5783,6 +6296,10 @@ snapshots: dependencies: queue-microtask: 1.2.3 + run@1.5.0: + dependencies: + minimatch: 9.0.5 + safer-buffer@2.1.2: {} saxes@6.0.0: @@ -5791,6 +6308,8 @@ snapshots: scheduler@0.25.0-rc-7771d3a7-20240827: {} + secure-json-parse@2.7.0: {} + semver@6.3.1: {} semver@7.6.3: @@ -5884,6 +6403,11 @@ snapshots: source-map@0.6.1: {} + sswr@2.1.0(svelte@4.2.19): + dependencies: + svelte: 4.2.19 + swrev: 4.0.0 + stackback@0.0.2: {} std-env@3.7.0: {} @@ -5946,11 +6470,40 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + svelte@4.2.19: + dependencies: + '@ampproject/remapping': 2.3.0 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + '@types/estree': 1.0.6 + acorn: 8.12.1 + aria-query: 5.3.2 + axobject-query: 4.1.0 + code-red: 1.0.4 + css-tree: 2.3.1 + estree-walker: 3.0.3 + is-reference: 3.0.2 + locate-character: 3.0.0 + magic-string: 0.30.11 + periscopic: 3.1.0 + swr@2.2.0(react@19.0.0-rc-7771d3a7-20240827): dependencies: react: 19.0.0-rc-7771d3a7-20240827 use-sync-external-store: 1.2.2(react@19.0.0-rc-7771d3a7-20240827) + swr@2.2.5(react@19.0.0-rc-7771d3a7-20240827): + dependencies: + client-only: 0.0.1 + react: 19.0.0-rc-7771d3a7-20240827 + use-sync-external-store: 1.2.2(react@19.0.0-rc-7771d3a7-20240827) + + swrev@4.0.0: {} + + swrv@1.0.4(vue@3.5.8(typescript@5.6.2)): + dependencies: + vue: 3.5.8(typescript@5.6.2) + symbol-tree@3.2.4: {} tailwind-merge@2.5.2: {} @@ -6163,6 +6716,16 @@ snapshots: - supports-color - terser + vue@3.5.8(typescript@5.6.2): + dependencies: + '@vue/compiler-dom': 3.5.8 + '@vue/compiler-sfc': 3.5.8 + '@vue/runtime-dom': 3.5.8 + '@vue/server-renderer': 3.5.8(vue@3.5.8(typescript@5.6.2)) + '@vue/shared': 3.5.8 + optionalDependencies: + typescript: 5.6.2 + w3c-xmlserializer@5.0.0: dependencies: xml-name-validator: 5.0.0 @@ -6217,4 +6780,8 @@ snapshots: yaml@2.5.1: {} + zod-to-json-schema@3.23.2(zod@3.23.8): + dependencies: + zod: 3.23.8 + zod@3.23.8: {}