From ad46aed24f5d78d8184fe5fd717654aa23946be2 Mon Sep 17 00:00:00 2001 From: "Mike P. Sinn" Date: Thu, 2 May 2024 19:06:25 -0500 Subject: [PATCH] Global variable search, conversation to measurements, better date identification in statement2measurments --- .../api/conversation2measurements/route.ts | 17 +- apps/nextjs/app/api/dfda/[dfdaPath]/route.ts | 4 +- .../app/api/image2measurements/route.ts | 5 +- .../nextjs/app/api/text2measurements/route.ts | 32 ++- apps/nextjs/app/api/trigger/route.ts | 10 + apps/nextjs/app/api/user/route.ts | 2 +- .../globalVariables/[variableId]/page.tsx | 2 +- .../[variableId]/settings/page.tsx | 2 +- .../app/dashboard/globalVariables/page.tsx | 22 +- .../app/dashboard/image2measurements/page.tsx | 7 +- .../app/dashboard/text2measurements/page.tsx | 16 +- .../app/dashboard/userVariables/page.tsx | 7 +- apps/nextjs/components/charts/heatmap.tsx | 171 ------------- apps/nextjs/components/charts/linechart.tsx | 58 ----- apps/nextjs/components/charts/piechart.tsx | 58 ----- .../generic-variable-add-button.tsx | 26 ++ .../generic-variable-list.tsx | 9 +- .../generic-variable-search.tsx | 4 +- .../global-variable-add-button.tsx | 98 -------- .../global-variable-charts.tsx | 2 +- .../global-variable-search.tsx | 15 ++ .../userVariables/stats/stats-cards.tsx | 82 ------- .../user-variable-add-button.tsx | 98 -------- .../userVariables/user-variable-search.tsx | 15 ++ apps/nextjs/env.mjs | 2 + apps/nextjs/jobs/examples.ts | 44 ++++ apps/nextjs/jobs/index.ts | 3 + apps/nextjs/lib/conversation2measurements.ts | 16 +- apps/nextjs/lib/dateTimeWithTimezone.ts | 51 ++++ apps/nextjs/lib/dfda.ts | 10 +- apps/nextjs/lib/errorHandler.ts | 12 +- apps/nextjs/lib/labs2Measurements.ts | 0 apps/nextjs/lib/text2measurements.ts | 86 ++++--- apps/nextjs/package.json | 12 +- apps/nextjs/yarn.lock | 226 +++++++++++++++++- 35 files changed, 540 insertions(+), 684 deletions(-) create mode 100644 apps/nextjs/app/api/trigger/route.ts delete mode 100644 apps/nextjs/components/charts/heatmap.tsx delete mode 100644 apps/nextjs/components/charts/linechart.tsx delete mode 100644 apps/nextjs/components/charts/piechart.tsx create mode 100644 apps/nextjs/components/genericVariables/generic-variable-add-button.tsx delete mode 100644 apps/nextjs/components/globalVariables/global-variable-add-button.tsx create mode 100644 apps/nextjs/components/globalVariables/global-variable-search.tsx delete mode 100644 apps/nextjs/components/userVariables/stats/stats-cards.tsx delete mode 100644 apps/nextjs/components/userVariables/user-variable-add-button.tsx create mode 100644 apps/nextjs/components/userVariables/user-variable-search.tsx create mode 100644 apps/nextjs/jobs/examples.ts create mode 100644 apps/nextjs/jobs/index.ts create mode 100644 apps/nextjs/lib/dateTimeWithTimezone.ts create mode 100644 apps/nextjs/lib/labs2Measurements.ts diff --git a/apps/nextjs/app/api/conversation2measurements/route.ts b/apps/nextjs/app/api/conversation2measurements/route.ts index d1f7edf92..093c865f0 100644 --- a/apps/nextjs/app/api/conversation2measurements/route.ts +++ b/apps/nextjs/app/api/conversation2measurements/route.ts @@ -2,20 +2,20 @@ import { NextRequest, NextResponse } from 'next/server'; import { conversation2measurements } from "@/lib/conversation2measurements"; import {postMeasurements} from "@/lib/dfda"; import {getUserId} from "@/lib/getUserId"; +import {handleError} from "@/lib/errorHandler"; export async function POST(request: NextRequest) { - let { statement, localDateTime, previousStatements } = await request.json(); + let { statement, utcDateTime, timeZoneOffset, previousStatements } = await request.json(); try { - const measurements = await conversation2measurements(statement, localDateTime, previousStatements); + const measurements = await conversation2measurements(statement, utcDateTime, timeZoneOffset, previousStatements); const userId = await getUserId(); if(userId){ await postMeasurements(measurements, userId) } return NextResponse.json({ success: true, measurements: measurements }); } catch (error) { - console.error('Error in conversation2measurements:', error); - return NextResponse.json({ success: false, message: 'Error in conversation2measurements' }); + return handleError(error, "conversation2measurements") } } @@ -23,15 +23,16 @@ export async function GET(req: NextRequest) { const urlParams = Object.fromEntries(new URL(req.url).searchParams); const statement = urlParams.statement as string; const previousStatements = urlParams.previousStatements as string | null | undefined; - const localDateTime = urlParams.localDateTime as string | null | undefined; + let timeZoneOffset; + if(urlParams.timeZoneOffset){timeZoneOffset = parseInt(urlParams.timeZoneOffset);} + const utcDateTime = urlParams.utcDateTime as string | null | undefined; try { - const measurements = await conversation2measurements(statement, localDateTime, previousStatements); + const measurements = await conversation2measurements(statement, utcDateTime, timeZoneOffset, previousStatements); const userId = await getUserId(); if(userId){await postMeasurements(measurements, userId)} return NextResponse.json({ success: true, measurements: measurements }); } catch (error) { - console.error('Error sending request to OpenAI:', error); - return NextResponse.json({ success: false, message: 'Error sending request to OpenAI' }); + return handleError(error, "conversation2measurements") } } diff --git a/apps/nextjs/app/api/dfda/[dfdaPath]/route.ts b/apps/nextjs/app/api/dfda/[dfdaPath]/route.ts index ac9b341c7..fe4f8dce7 100644 --- a/apps/nextjs/app/api/dfda/[dfdaPath]/route.ts +++ b/apps/nextjs/app/api/dfda/[dfdaPath]/route.ts @@ -18,7 +18,7 @@ export async function GET(req: Request, context: z.infer - - + - + ) } diff --git a/apps/nextjs/app/dashboard/image2measurements/page.tsx b/apps/nextjs/app/dashboard/image2measurements/page.tsx index 5103094c7..e92f810c5 100644 --- a/apps/nextjs/app/dashboard/image2measurements/page.tsx +++ b/apps/nextjs/app/dashboard/image2measurements/page.tsx @@ -11,9 +11,9 @@ import {DashboardHeader} from "@/components/pages/dashboard/dashboard-header"; import {AnalyzeButton} from "@/components/AnalyzeButton"; import {FileUploader} from "@/components/FileUploader"; import {AnalysisResult} from "@/components/AnalysisResult"; -import {CameraButton} from "@/components/CameraButton"; // Importing the CameraButton component +import {CameraButton} from "@/components/CameraButton"; +import {getUtcDateTimeWithTimezone} from "@/lib/dateTimeWithTimezone"; -// The main App component const App: React.FC = () => { // State management for various functionalities const [file, setFile] = useState(null); // Holds the selected image file @@ -26,7 +26,7 @@ const App: React.FC = () => { // Callback for handling file selection changes const handleFileChange = useCallback(async (selectedFile: File) => { - // Updating state with the new file and its preview URL + // Updating the state with the new file and its preview URL setFile(selectedFile); setPreview(URL.createObjectURL(selectedFile)); setStatusMessage('Image selected. Click "Analyze Image" to proceed.'); @@ -65,6 +65,7 @@ const App: React.FC = () => { body: JSON.stringify({ file: base64Image, prompt: textInput, + utcDateTimeWithTimezone: getUtcDateTimeWithTimezone(), }), }); diff --git a/apps/nextjs/app/dashboard/text2measurements/page.tsx b/apps/nextjs/app/dashboard/text2measurements/page.tsx index e4c9c5fbd..190e1155a 100644 --- a/apps/nextjs/app/dashboard/text2measurements/page.tsx +++ b/apps/nextjs/app/dashboard/text2measurements/page.tsx @@ -4,12 +4,12 @@ import React, { useState } from 'react'; import {Shell} from "@/components/layout/shell"; import {DashboardHeader} from "@/components/pages/dashboard/dashboard-header"; import {Icons} from "@/components/icons"; +import {getTimeZoneOffset, getUtcDateTime} from "@/lib/dateTimeWithTimezone"; // Define a type for the message objects type Message = { type: 'user' | 'response' | 'loading'; text: string; - time: string; }; const App: React.FC = () => { @@ -19,18 +19,24 @@ const App: React.FC = () => { const sendMessage = async () => { if (input.trim()) { - const localDateTime = new Date().toISOString(); // Get current date and time in ISO format - setMessages([...messages, { type: 'user', text: input, time: localDateTime }, { type: 'loading', text: 'Loading...', time: localDateTime }]); + + setMessages([...messages, { type: 'user', text: input }, { type: 'loading', text: 'Loading...' }]); setIsLoading(true); const response = await fetch('/api/text2measurements', { method: 'POST', headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify({ text: input, localDateTime }), // Include localDateTime in the request body + body: JSON.stringify({ + text: input, + timeZoneOffset: getTimeZoneOffset(), + utcDateTime: getUtcDateTime(), + }), }); const data = await response.json(); - setMessages(prevMessages => prevMessages.filter(msg => msg.type !== 'loading').concat({ type: 'response', text: JSON.stringify(data), time: localDateTime })); + setMessages(prevMessages => prevMessages + .filter(msg => msg.type !== 'loading') + .concat({ type: 'response', text: JSON.stringify(data) })); setIsLoading(false); setInput(''); } diff --git a/apps/nextjs/app/dashboard/userVariables/page.tsx b/apps/nextjs/app/dashboard/userVariables/page.tsx index 90b5e9a26..bbb889a7c 100644 --- a/apps/nextjs/app/dashboard/userVariables/page.tsx +++ b/apps/nextjs/app/dashboard/userVariables/page.tsx @@ -3,10 +3,11 @@ import { redirect } from "next/navigation" import { authOptions } from "@/lib/auth" import { getCurrentUser } from "@/lib/session" -import { UserVariableAddButton } from "@/components/userVariables/user-variable-add-button" import { GenericVariableList } from "@/components/genericVariables/generic-variable-list" import { Shell } from "@/components/layout/shell" import { DashboardHeader } from "@/components/pages/dashboard/dashboard-header" +import {GenericVariableAddButton} from "@/components/genericVariables/generic-variable-add-button"; +import {UserVariableSearch} from "@/components/userVariables/user-variable-search"; export const metadata: Metadata = { @@ -33,9 +34,9 @@ export default async function UserVariablesPage() { return ( - + - + ) } diff --git a/apps/nextjs/components/charts/heatmap.tsx b/apps/nextjs/components/charts/heatmap.tsx deleted file mode 100644 index db77f31c5..000000000 --- a/apps/nextjs/components/charts/heatmap.tsx +++ /dev/null @@ -1,171 +0,0 @@ -"use client" - -import * as React from "react" -import { useRouter } from "next/navigation" -import CalendarHeatmap, { ReactCalendarHeatmapValue } from "react-calendar-heatmap" - -import "react-calendar-heatmap/dist/styles.css" - -import { formatDate } from "@/lib/utils" -import { Button } from "@/components/ui/button" -import { Card } from "@/components/ui/card" -import { - Credenza, - CredenzaClose, - CredenzaContent, - CredenzaDescription, - CredenzaFooter, - CredenzaHeader, - CredenzaTitle, -} from "@/components/ui/credenza" -import { toast } from "@/components/ui/use-toast" -import { Icons } from "@/components/icons" - -interface Value { - id: string - date: string - count: number -} - -interface HeatmapProps { - data: { - activity: { - id: string - name: string - } - id: string - date: Date - count: number - }[] - params: { activityId: string } -} - -async function deleteActivity(activityId: string, logsId: string) { - const response = await fetch(`/api/activities/${activityId}/logs/${logsId}`, { - method: "DELETE", - }) - - if (!response?.ok) { - toast({ - title: "Something went wrong.", - description: "Your activity was not deleted. Please try again.", - variant: "destructive", - }) - } else { - toast({ - title: "Item has been deleted.", - description: "Your activity has been deleted successfully.", - variant: "default", - }) - } - - return true -} - -export function Heatmap({ data, params }: HeatmapProps) { - const router = useRouter() - const [showDeleteAlert, setShowDeleteAlert] = React.useState(false) - const [isDeleteLoading, setIsDeleteLoading] = React.useState(false) - const [selectedLog, setSelectedLog] = React.useState(null) - const [selectedDate, setSelectedDate] = React.useState(null) - - const handleDelete = async () => { - if (selectedLog) { - setIsDeleteLoading(true) - const deleted = await deleteActivity(params.activityId, selectedLog.id) - - if (deleted) { - setIsDeleteLoading(false) - setShowDeleteAlert(false) - setSelectedLog(null) - router.refresh() - } - } - } - - const getColorClass = (count: number) => { - if (count < 1) { - return "fill-zinc-200 dark:fill-zinc-800" - } else if (count < 2) { - return "fill-green-300 dark:fill-green-900" - } else if (count < 3) { - return "fill-green-400 dark:fill-green-800" - } else if (count < 5) { - return "fill-green-500 dark:fill-green-700" - } else if (count < 7) { - return "fill-green-600 dark:fill-green-600" - } else if (count < 9) { - return "fill-green-700 dark:fill-green-500" - } else if (count < 11) { - return "fill-green-800 dark:fill-green-400" - } else { - return "fill-green-900 dark:fill-green-300" - } - } - - const getTitle = (value: Value) => { - if (value && value.count) { - return `${value.count} ${ - value.count === 1 ? "log" : "logs" - } on ${formatDate(value.date)}` - } - return "No logs" - } - - const currentDate = new Date() - const startDate = new Date(currentDate) - startDate.setFullYear(currentDate.getFullYear() - 1) - - return ( - -
- ({ - ...value, - date: value.date.toISOString(), - }))} - classForValue={(value) => getColorClass(value ? value.count : 0)} - titleForValue={(value) => getTitle(value as Value)} - onClick={(value: ReactCalendarHeatmapValue | undefined) => { - if (value) { - setSelectedLog(value as Value) - setSelectedDate(new Date(value.date)) - setShowDeleteAlert(true) - } - }} - /> - - - - - Delete logs from {selectedDate ? formatDate(selectedDate) : ""}? - - - This action cannot be undone. - - - - - - - - - - -
-
- ) -} diff --git a/apps/nextjs/components/charts/linechart.tsx b/apps/nextjs/components/charts/linechart.tsx deleted file mode 100644 index 0e24a893d..000000000 --- a/apps/nextjs/components/charts/linechart.tsx +++ /dev/null @@ -1,58 +0,0 @@ -"use client" - -import { ActivityByDate } from "@/types" -import { - CartesianGrid, - Line, - LineChart, - ResponsiveContainer, - Tooltip, - XAxis, - YAxis, -} from "recharts" - -import { formatDate } from "@/lib/utils" -import { Card } from "@/components/ui/card" - -interface LineChartProps { - data: ActivityByDate[] -} - -export function LineChartComponent({ data }: LineChartProps) { - return ( - - - - - - - [`Logs: ${entry.payload.count}`]} - labelStyle={{ color: "#000" }} - labelFormatter={formatDate} - /> - - - - - ) -} diff --git a/apps/nextjs/components/charts/piechart.tsx b/apps/nextjs/components/charts/piechart.tsx deleted file mode 100644 index 7235f8954..000000000 --- a/apps/nextjs/components/charts/piechart.tsx +++ /dev/null @@ -1,58 +0,0 @@ -"use client" - -import { ActivityEntry } from "@/types" -import { Cell, Pie, PieChart, ResponsiveContainer, Tooltip } from "recharts" - -import { Card } from "@/components/ui/card" - -interface PieChartProps { - data: ActivityEntry[] -} - -export function PieChartComponent({ data }: PieChartProps) { - return ( - - - - - {data.map((entry, index) => ( - - ))} - - [ - entry.payload.count, - entry.payload.name, - ]} - /> - - -
-
- {data.map((entry, index) => ( -
- - {entry.name} -
- ))} -
-
-
- ) -} diff --git a/apps/nextjs/components/genericVariables/generic-variable-add-button.tsx b/apps/nextjs/components/genericVariables/generic-variable-add-button.tsx new file mode 100644 index 000000000..2fd66f543 --- /dev/null +++ b/apps/nextjs/components/genericVariables/generic-variable-add-button.tsx @@ -0,0 +1,26 @@ +"use client" + +import * as React from "react" +import { useRouter } from "next/navigation" + +import { Button, ButtonProps } from "@/components/ui/button" +import { Icons } from "@/components/icons" + +interface GlobalVariableAddButtonProps extends ButtonProps {} + +export function GenericVariableAddButton({ ...props }: GlobalVariableAddButtonProps) { + const router = useRouter() + async function onClick() { + router.push(`/dashboard/globalVariables`) + router.refresh() + } + + return ( + <> + + + ) +} diff --git a/apps/nextjs/components/genericVariables/generic-variable-list.tsx b/apps/nextjs/components/genericVariables/generic-variable-list.tsx index 00ad693ce..3574815c3 100644 --- a/apps/nextjs/components/genericVariables/generic-variable-list.tsx +++ b/apps/nextjs/components/genericVariables/generic-variable-list.tsx @@ -2,10 +2,10 @@ import { EmptyPlaceholder } from "@/components/empty-placeholder"; import { Icons } from "@/components/icons"; -import { UserVariableAddButton } from "../userVariables/user-variable-add-button"; import { UserVariableItem } from "../userVariables/user-variable-item"; import { FC, useEffect, useState } from "react"; import { UserVariable } from "@/types/models/UserVariable"; +import {GenericVariableAddButton} from "@/components/genericVariables/generic-variable-add-button"; type UserVariableListProps = { user: { @@ -39,7 +39,10 @@ export const GenericVariableList: FC = ({ user, searchPar }); const queryString = queryParams.toString(); - const url = `/api/dfda/variables${queryString ? `?${queryString}` : ''}`; + let url = `/api/dfda/variables${queryString ? `?${queryString}` : ''}`; + if(!searchParams.includePublic){ + url = `/api/dfda/userVariables${queryString ? `?${queryString}` : ''}`; + } fetch(url) .then(response => response.json()) @@ -73,7 +76,7 @@ export const GenericVariableList: FC = ({ user, searchPar Add a symptom, food or treatment to start tracking! - + )} diff --git a/apps/nextjs/components/genericVariables/generic-variable-search.tsx b/apps/nextjs/components/genericVariables/generic-variable-search.tsx index a0b4fee1a..55ac046a8 100644 --- a/apps/nextjs/components/genericVariables/generic-variable-search.tsx +++ b/apps/nextjs/components/genericVariables/generic-variable-search.tsx @@ -2,7 +2,7 @@ import { FC, useEffect, useState } from "react"; import {GenericVariableList} from "@/components/genericVariables/generic-variable-list"; -type UserVariableSearchProps = { +type GenericVariableSearchProps = { user: { id: string; }; @@ -10,7 +10,7 @@ type UserVariableSearchProps = { sort?: string; // Optional parameter with a default value }; -export const GenericVariableSearch: FC = ({ user, includePublic = true, sort = '-numberOfUserVariables' }) => { +export const GenericVariableSearch: FC = ({ user, includePublic = true, sort = '-numberOfUserVariables' }) => { // State to manage search phrase const [searchPhrase, setSearchPhrase] = useState(""); diff --git a/apps/nextjs/components/globalVariables/global-variable-add-button.tsx b/apps/nextjs/components/globalVariables/global-variable-add-button.tsx deleted file mode 100644 index 6b4681099..000000000 --- a/apps/nextjs/components/globalVariables/global-variable-add-button.tsx +++ /dev/null @@ -1,98 +0,0 @@ -"use client" - -import * as React from "react" -import { useRouter } from "next/navigation" - -import { Button, ButtonProps } from "@/components/ui/button" -import { - Credenza, - CredenzaClose, - CredenzaContent, - CredenzaDescription, - CredenzaFooter, - CredenzaHeader, - CredenzaTitle, -} from "@/components/ui/credenza" -import { toast } from "@/components/ui/use-toast" -import { Icons } from "@/components/icons" - -interface GlobalVariableAddButtonProps extends ButtonProps {} - -export function GlobalVariableAddButton({ ...props }: GlobalVariableAddButtonProps) { - const router = useRouter() - const [showAddAlert, setShowAddAlert] = React.useState(false) - const [isLoading, setIsLoading] = React.useState(false) - - async function onClick() { - setIsLoading(true) - - const response = await fetch("/api/globalVariables", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - name: "New Variable", - }), - }) - - if (!response?.ok) { - setIsLoading(false) - setShowAddAlert(false) - - return toast({ - title: "Something went wrong.", - description: "Your globalVariable was not created. Please try again.", - variant: "destructive", - }) - } - - toast({ - description: "A new variable has been created successfully.", - }) - - const globalVariable = await response.json() - - setIsLoading(false) - setShowAddAlert(false) - - router.push(`/dashboard/globalVariables/${globalVariable.id}/settings`) - router.refresh() - } - - return ( - <> - - - {/* Add Alert */} - - - - - Are you sure you want to create a new variable? - - - This will add a new variable to your account. - - - - - - - - - - - - ) -} diff --git a/apps/nextjs/components/globalVariables/global-variable-charts.tsx b/apps/nextjs/components/globalVariables/global-variable-charts.tsx index 89e16a039..9c59db483 100644 --- a/apps/nextjs/components/globalVariables/global-variable-charts.tsx +++ b/apps/nextjs/components/globalVariables/global-variable-charts.tsx @@ -31,7 +31,7 @@ export const GlobalVariableCharts: FC = ({ variableId const [isLoading, setIsLoading] = useState(true); // Add a loading state useEffect(() => { - const url = `/api/dfda/globalVariables?variableId=${variableId}&includeCharts=1`; + const url = `/api/dfda/variables?variableId=${variableId}&includeCharts=1`; setIsLoading(true); // Set loading to true when the fetch starts fetch(url) diff --git a/apps/nextjs/components/globalVariables/global-variable-search.tsx b/apps/nextjs/components/globalVariables/global-variable-search.tsx new file mode 100644 index 000000000..26d57342c --- /dev/null +++ b/apps/nextjs/components/globalVariables/global-variable-search.tsx @@ -0,0 +1,15 @@ +"use client"; +import { FC } from "react"; +import {GenericVariableSearch} from "@/components/genericVariables/generic-variable-search"; + +type GlobalVariableSearchProps = { + user: { + id: string; + }; +}; + +export const GlobalVariableSearch: FC = ({user}: { user: any; }) => { + return ( + + ); +}; diff --git a/apps/nextjs/components/userVariables/stats/stats-cards.tsx b/apps/nextjs/components/userVariables/stats/stats-cards.tsx deleted file mode 100644 index 0e98a41b1..000000000 --- a/apps/nextjs/components/userVariables/stats/stats-cards.tsx +++ /dev/null @@ -1,82 +0,0 @@ -"use client" - -import { SearchParams } from "@/types" - -import { formatDate } from "@/lib/utils" -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" -import { Icons } from "@/components/icons" - -interface StatsCardsProps { - data: { - streak: { - currentStreak: number - longestStreak: number - } - totalMeasurements: number - dailyAverage: number - } - searchParams: SearchParams -} - -function displayDateRange(searchParams: SearchParams) { - return ( - <> - {searchParams.from && searchParams.to - ? `${formatDate( - new Date(searchParams.from).toISOString() - )} - ${formatDate(new Date(searchParams.to).toISOString())}` - : "Last year"} - - ) -} - -export function StatsCards({ data, searchParams }: StatsCardsProps) { - return ( -
- - - Current Streak - - - -
{data.streak.currentStreak}
-

All time

-
-
- - - Longest Streak - - - -
{data.streak.longestStreak}
-

All time

-
-
- - - Total Measurements - - - -
{data.totalMeasurements}
-

- {displayDateRange(searchParams)} -

-
-
- - - Daily Average - - - -
{data.dailyAverage}
-

- {displayDateRange(searchParams)} -

-
-
-
- ) -} diff --git a/apps/nextjs/components/userVariables/user-variable-add-button.tsx b/apps/nextjs/components/userVariables/user-variable-add-button.tsx deleted file mode 100644 index 55a944d6f..000000000 --- a/apps/nextjs/components/userVariables/user-variable-add-button.tsx +++ /dev/null @@ -1,98 +0,0 @@ -"use client" - -import * as React from "react" -import { useRouter } from "next/navigation" - -import { Button, ButtonProps } from "@/components/ui/button" -import { - Credenza, - CredenzaClose, - CredenzaContent, - CredenzaDescription, - CredenzaFooter, - CredenzaHeader, - CredenzaTitle, -} from "@/components/ui/credenza" -import { toast } from "@/components/ui/use-toast" -import { Icons } from "@/components/icons" - -interface UserVariableAddButtonProps extends ButtonProps {} - -export function UserVariableAddButton({ ...props }: UserVariableAddButtonProps) { - const router = useRouter() - const [showAddAlert, setShowAddAlert] = React.useState(false) - const [isLoading, setIsLoading] = React.useState(false) - - async function onClick() { - setIsLoading(true) - - const response = await fetch("/api/userVariables", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - name: "New Variable", - }), - }) - - if (!response?.ok) { - setIsLoading(false) - setShowAddAlert(false) - - return toast({ - title: "Something went wrong.", - description: "Your userVariable was not created. Please try again.", - variant: "destructive", - }) - } - - toast({ - description: "A new variable has been created successfully.", - }) - - const userVariable = await response.json() - - setIsLoading(false) - setShowAddAlert(false) - - router.push(`/dashboard/userVariables/${userVariable.id}/settings`) - router.refresh() - } - - return ( - <> - - - {/* Add Alert */} - - - - - Are you sure you want to create a new variable? - - - This will add a new variable to your account. - - - - - - - - - - - - ) -} diff --git a/apps/nextjs/components/userVariables/user-variable-search.tsx b/apps/nextjs/components/userVariables/user-variable-search.tsx new file mode 100644 index 000000000..8518421c1 --- /dev/null +++ b/apps/nextjs/components/userVariables/user-variable-search.tsx @@ -0,0 +1,15 @@ +"use client"; +import { FC } from "react"; +import {GenericVariableSearch} from "@/components/genericVariables/generic-variable-search"; + +type UserVariableSearchProps = { + user: { + id: string; + }; +}; + +export const UserVariableSearch: FC = ({user}: { user: any; }) => { + return ( + + ); +}; diff --git a/apps/nextjs/env.mjs b/apps/nextjs/env.mjs index 125c84a18..d88bb3ef4 100644 --- a/apps/nextjs/env.mjs +++ b/apps/nextjs/env.mjs @@ -15,6 +15,7 @@ export const env = createEnv({ EMAIL_SERVER: z.string().min(1), EMAIL_FROM: z.string().min(1), OPENAI_API_KEY: z.string().min(1), + NODE_ENV: z.enum(["development", "production"]), }, client: { NEXT_PUBLIC_APP_URL: z.string().min(1), @@ -33,5 +34,6 @@ export const env = createEnv({ EMAIL_SERVER: process.env.EMAIL_SERVER, EMAIL_FROM: process.env.EMAIL_FROM, OPENAI_API_KEY: process.env.OPENAI_API_KEY, + NODE_ENV: process.env.NODE_ENV, }, }) diff --git a/apps/nextjs/jobs/examples.ts b/apps/nextjs/jobs/examples.ts new file mode 100644 index 000000000..7b97c6b2c --- /dev/null +++ b/apps/nextjs/jobs/examples.ts @@ -0,0 +1,44 @@ +import { eventTrigger } from "@trigger.dev/sdk"; +import { client } from "@/trigger"; + +// Your first job +// This Job will be triggered by an event, log a joke to the console, and then wait 5 seconds before logging the punchline. +client.defineJob({ + // This is the unique identifier for your Job, it must be unique across all Jobs in your project. + id: "example-job", + name: "Example Job: a joke with a delay", + version: "0.0.1", + // This is triggered by an event using eventTrigger. You can also trigger Jobs with webhooks, on schedules, and more: https://trigger.dev/docs/documentation/concepts/triggers/introduction + trigger: eventTrigger({ + name: "example.event", + }), + run: async (payload, io, ctx) => { + // Use a Task to generate a random number. Using a Tasks means it only runs once. + const result = await io.runTask("generate-random-number", async () => { + return { + num: Math.floor(Math.random() * 10000), + }; + }); + + // Use the random number in a joke and log it to the console. + await io.logger.info(`Why was the number ${result.num} afraid of the number 7?`); + + // Wait for 5 seconds, the second parameter is the number of seconds to wait, you can add delays of up to a year. + await io.wait("Wait 5 seconds for the punchline...", 5); + + // Use a Task to display the answer. Tasks are important to use in all Jobs as they allow your Runs to resume again after e.g. a serverless function timeout. Learn more about Tasks in the docs: https://trigger.dev/docs/documentation/concepts/tasks + await io.runTask( + "task-example", + async () => { + return { + foo: "bar", + }; + }, + { name: `Answer: Because 7,8,9! And ${result.num} was next 🤦` } + ); + await io.logger.info( + "✨ Congratulations, You just ran your first successful Trigger.dev Job! ✨" + ); + // To learn how to write much more complex (and probably funnier) Jobs, check out our docs: https://trigger.dev/docs/documentation/guides/create-a-job + }, +}); \ No newline at end of file diff --git a/apps/nextjs/jobs/index.ts b/apps/nextjs/jobs/index.ts new file mode 100644 index 000000000..042a4e582 --- /dev/null +++ b/apps/nextjs/jobs/index.ts @@ -0,0 +1,3 @@ +// export all your job files here + +export * from "./examples"; diff --git a/apps/nextjs/lib/conversation2measurements.ts b/apps/nextjs/lib/conversation2measurements.ts index c12ce72a2..7fe0ed196 100644 --- a/apps/nextjs/lib/conversation2measurements.ts +++ b/apps/nextjs/lib/conversation2measurements.ts @@ -1,18 +1,19 @@ import { Measurement } from "@/types/models/Measurement"; import {textCompletion} from "@/lib/llm"; +import {convertToLocalDateTime, getUtcDateTime} from "@/lib/dateTimeWithTimezone"; // IMPORTANT! Set the runtime to edge export const runtime = 'edge'; export function conversation2MeasurementsPrompt(statement: string, - localDateTime: string | null | undefined, + utcDateTime: string | null | undefined, + timeZoneOffset: number | null | undefined, previousStatements: string | null | undefined): string { - if(!localDateTime) { - const now = new Date(); - localDateTime = now.toISOString().slice(0, 19); - } + if(!utcDateTime) {utcDateTime = getUtcDateTime();} + let localDateTime = utcDateTime; + if(timeZoneOffset) {localDateTime = convertToLocalDateTime(utcDateTime, timeZoneOffset);} return ` You are a robot designed to collect diet, treatment, and symptom data from the user. @@ -57,9 +58,10 @@ The following is the user request translated into a JSON object with 2 spaces of } export async function conversation2measurements(statement: string, - localDateTime: string | null | undefined, + utcDateTime: string | null | undefined, + timeZoneOffset: number | null | undefined, previousStatements: string | null | undefined): Promise { - let promptText = conversation2MeasurementsPrompt(statement, localDateTime, previousStatements); + let promptText = conversation2MeasurementsPrompt(statement, utcDateTime, timeZoneOffset, previousStatements); const maxTokenLength = 1500; if(promptText.length > maxTokenLength) { // truncate to less than 1500 characters diff --git a/apps/nextjs/lib/dateTimeWithTimezone.ts b/apps/nextjs/lib/dateTimeWithTimezone.ts new file mode 100644 index 000000000..142141b5f --- /dev/null +++ b/apps/nextjs/lib/dateTimeWithTimezone.ts @@ -0,0 +1,51 @@ +import {textCompletion} from "@/lib/llm"; + +export function getUtcDateTimeWithTimezone() { + const date = new Date(); + const timezoneOffset = date.getTimezoneOffset(); + return new Date(date.getTime() - timezoneOffset * 60000).toISOString(); +} + +export function convertToUTC(localDateTime: string, utcDateTimeWithTimezone: string) { + const localDate = new Date(localDateTime); + const timezoneOffset = parseInt(utcDateTimeWithTimezone.split(':')[0]); + const utcDate = new Date(localDate.getTime() + timezoneOffset * 60 * 60 * 1000); + return utcDate.toISOString(); +} + +export function throwErrorIfDateInFuture(utcDateTime: string) { + const localDate = new Date(utcDateTime); + const now = new Date(); + if (localDate > now) { + throw new Error("Date cannot be in the future"); + } +} + +export function getUtcDateTime(){ + return new Date().toISOString(); +} + +export function getTimeZoneOffset(){ + return new Date().getTimezoneOffset(); +} + +export function convertToLocalDateTime(utcDateTime: string | number | Date, timeZoneOffset: number){ + const utcDate = new Date(utcDateTime); + const localDate = new Date(utcDate.getTime() - timeZoneOffset * 60 * 60 * 1000); + return localDate.toISOString(); +} + +export async function getDateTimeFromStatement(statement: string): Promise { + const currentDate = getUtcDateTime(); + const promptText = ` + estimate the date and time of the user statement based on the current date and time ${currentDate} + and the following user statement: +\`\`\` +${statement} +\`\`\` + Return a single string in the format "YYYY-MM-DDThh:mm:ss"`; + let result = await textCompletion(promptText, "text"); + // Remove quote marks + result = result.replace(/['"]+/g, ''); + return result; +} diff --git a/apps/nextjs/lib/dfda.ts b/apps/nextjs/lib/dfda.ts index f9271f4cd..336fc1dc6 100644 --- a/apps/nextjs/lib/dfda.ts +++ b/apps/nextjs/lib/dfda.ts @@ -1,5 +1,6 @@ import {db} from "@/lib/db" import {UserVariable} from "@/types/models/UserVariable"; +import {env} from "@/env.mjs"; async function getYourUser(yourUserId: any) { let user = await db.user.findUnique({ @@ -105,7 +106,14 @@ async function dfdaFetch( console.log(`Making ${method} request to ${dfdaUrl}`); const response = await fetch(dfdaUrl, init); - return await response.json(); + const json = await response.json(); + if(env.NODE_ENV === "development") { + console.log(`${dfdaUrl} Response status: ${response.status}`, json); + } + if(json.error) { + console.error("Error in dfdaFetch to ${dfdaUrl}", json.error) + } + return json; } export async function dfdaGET( diff --git a/apps/nextjs/lib/errorHandler.ts b/apps/nextjs/lib/errorHandler.ts index 3b107229a..9cdbd0283 100644 --- a/apps/nextjs/lib/errorHandler.ts +++ b/apps/nextjs/lib/errorHandler.ts @@ -1,7 +1,11 @@ // lib/errorHandler.ts import {z} from "zod"; +import {env} from "@/env.mjs"; -export function handleError(error: unknown) { +export function handleError(error: unknown, context?: string) { + if(!context){ + context = 'handleError' + } if (error instanceof z.ZodError) { return new Response(JSON.stringify(error.issues), { status: 422, @@ -9,9 +13,9 @@ export function handleError(error: unknown) { }) } - console.error(error) + console.error(context+": ", error) - if (process.env.NODE_ENV === "development") { + if (env.NODE_ENV === "development") { return new Response( JSON.stringify({ error: "Internal Server Error", details: error }), { @@ -28,4 +32,4 @@ export function handleError(error: unknown) { } ) } -} \ No newline at end of file +} diff --git a/apps/nextjs/lib/labs2Measurements.ts b/apps/nextjs/lib/labs2Measurements.ts new file mode 100644 index 000000000..e69de29bb diff --git a/apps/nextjs/lib/text2measurements.ts b/apps/nextjs/lib/text2measurements.ts index f05215fd7..d80313f27 100644 --- a/apps/nextjs/lib/text2measurements.ts +++ b/apps/nextjs/lib/text2measurements.ts @@ -1,14 +1,28 @@ -import { Measurement } from "@/types/models/Measurement"; +import {Measurement} from "@/types/models/Measurement"; import {textCompletion} from "@/lib/llm"; +import {getUserId} from "@/lib/getUserId"; +import {postMeasurements} from "@/lib/dfda"; +import { + convertToLocalDateTime, + convertToUTC, getDateTimeFromStatement, + getUtcDateTime, + throwErrorIfDateInFuture +} from "@/lib/dateTimeWithTimezone"; export function generateText2MeasurementsPrompt(statement: string, - localDateTime: string | null | undefined): string { - if(!localDateTime) { + utcDateTime: string, + timeZoneOffset: number): string { + if(!utcDateTime) { const now = new Date(); - localDateTime = now.toISOString().slice(0, 19); + utcDateTime = now.toISOString().slice(0, 19); } + throwErrorIfDateInFuture(utcDateTime); + + const localDateTime = convertToLocalDateTime(utcDateTime, timeZoneOffset); + const localDate = utcDateTime.split('T')[0]; + return ` - You are a service that translates user requests into JSON objects of type "MeasurementSet" according to the following TypeScript definitions: + You are a service that translates user requests into an array of JSON objects of type "Measurement" according to the following TypeScript definitions: \`\`\` export const VariableCategoryNames = [ 'Emotions', @@ -112,12 +126,6 @@ export const UnitNames = [ // Then you can use this array to define your type: export type UnitName = typeof UnitNames[number]; // This will be a union of the array values - -// a set of measurements logged by the user -export type MeasurementSet = { - measurements: (Measurement | UnknownText)[]; -}; - export interface Measurement { itemType: 'measurement', variableName: string; // variableName is the name of the treatment, symptom, food, drink, etc. @@ -127,30 +135,23 @@ export interface Measurement { // For example, if the answer is "I took 5 mg of NMN", then this value is 5. // For example, if the answer is "I have been feeling very tired and fatigued today", you would return two measurements // with the value 5 like this: - // {variableName: "Tiredness", value: 5, unitName: "1 to 5 Rating", startAt: "00:00:00", endAt: "23:59:59", combinationOperation: "MEAN", variableCategoryName: "Symptoms"} - // {variableName: "Fatigue", value: 5, unitName: "1 to 5 Rating", startAt: "00:00:00", endAt: "23:59:59", combinationOperation: "MEAN", variableCategoryName: "Symptoms"} + // {variableName: "Tiredness", value: 5, unitName: "1 to 5 Rating", startAt: "${localDate}T00:00:00", endAt: "${localDate}T23:59:59", combinationOperation: "MEAN", variableCategoryName: "Symptoms"} + // {variableName: "Fatigue", value: 5, unitName: "1 to 5 Rating", startAt: "${localDate}T00:00:00", endAt: "${localDate}T23:59:59", combinationOperation: "MEAN", variableCategoryName: "Symptoms"} // For example, if the answer is "I have been having trouble concentrating today", then this value is 1 and the object - // would be {variableName: "Concentration", value: 1, unitName: "1 to 5 Rating", startAt: "00:00:00", endAt: "23:59:59", combinationOperation: "MEAN", variableCategoryName: "Symptoms"} + // would be {variableName: "Concentration", value: 1, unitName: "1 to 5 Rating", startAt: "${localDate}T00:00:00", endAt: "${localDate}T23:59:59", combinationOperation: "MEAN", variableCategoryName: "Symptoms"} // For example, if the answer is "I also took magnesium 200mg, Omega3 one capsule 500mg", then the measurements would be: - // {variableName: "Magnesium", value: 200, unitName: "Milligrams", startAt: "00:00:00", endAt: "23:59:59", combinationOperation: "SUM", variableCategoryName: "Treatments"} - // {variableName: "Omega3", value: 500, unitName: "Milligrams", startAt: "00:00:00", endAt: "23:59:59", combinationOperation: "SUM", variableCategoryName: "Treatments"} + // {variableName: "Magnesium", value: 200, unitName: "Milligrams", startAt: "${localDate}T00:00:00", endAt: "${localDate}T23:59:59", combinationOperation: "SUM", variableCategoryName: "Treatments"} + // {variableName: "Omega3", value: 500, unitName: "Milligrams", startAt: "${localDate}T00:00:00", endAt: "${localDate}T23:59:59", combinationOperation: "SUM", variableCategoryName: "Treatments"} + // (I just used the current date in those examples, but you should use the correct date if the user specifies a different date or time range.) unitName: UnitName; // unitName is the unit of the treatment, symptom, food, drink, etc. // For example, if the answer is "I took 5 mg of NMN", then this unitName is "Milligrams". - startDateLocal: string|null; // startDate should be the date the measurement was taken in the format "YYYY-MM-DD" or null if no date is known - startTimeLocal: string|null; // startAt should be the time the measurement was taken in - // the format "HH:MM:SS". For instance, midday would be "12:00:00". - // ex. The term \`breakfast\` would be a typical breakfast time of "08:00:00". - // ex. The term \`lunch\` would be a typical lunchtime of "12:00:00". - // ex. The term \`dinner\` would be a typical dinner time of "18:00:00". - // If no time or date is known, then startTime should be null. - endDateLocal: string|null; - endTimeLocal: string|null; - // If a time range is given, then endAt should be the end of that period. It should also be in the format "HH:MM:SS". + startAt: string; // startAt should be the local datetime the measurement was taken in the format "YYYY-MM-DDThh:mm:ss" inferred from the USER STATEMENT relative to and sometime before the current local datetime ${localDateTime}. + endAt: string|null; // If a time range is suggested, then endAt should be the end of that period. It should also be in the format "YYYY-MM-DDThh:mm:ss" and should not be in the future relative to the current time ${localDateTime} . combinationOperation: "SUM" | "MEAN"; // combinationOperation is the operation used to combine multiple measurements of the same variableName variableCategoryName: VariableCategoryName; // variableCategoryName is the category of the variableName // For example, if the answer is "I took 5 mg of NMN", then this variableCategoryName is "Treatments". - originalText: string; // the text fragment that was used to create this measurement + note: string; // the text fragment that was used to create this measurement } // Use this type for measurement items that match nothing else @@ -180,8 +181,9 @@ export type Symptom = Measurement & { unitName: '/5'; }; -// Use the current local datetime ${localDateTime} to determine startDateLocal. If specified, also determine startTimeLocal, endDateLocal, and endTimeLocal or just leave them null.\`\`\` -The following is a user request: +Remember, startAt and endAt should be in the format "YYYY-MM-DDThh:mm:ss" and should not be in the future relative to the current time ${localDateTime}. + +USER STATEMENT TO CONVERT TO AN ARRAY OF MEASUREMENTS: """ ${statement} """ @@ -191,13 +193,31 @@ The following is the user request translated into a JSON object with 2 spaces of } export async function text2measurements(statement: string, - localDateTime: string | null | undefined): Promise { - const promptText = generateText2MeasurementsPrompt(statement, localDateTime); + utcDateTime: string, + timeZoneOffset: number): Promise { + const promptText = generateText2MeasurementsPrompt(statement, utcDateTime, timeZoneOffset); const str = await textCompletion(promptText, "json_object"); - const json = JSON.parse(str); + const dateTime = await getDateTimeFromStatement(statement); + let json = JSON.parse(str); + if(!Array.isArray(str)){ + json = [json]; + } const measurements: Measurement[] = []; - json.measurements.forEach((measurement: Measurement) => { + json.forEach((measurement: Measurement) => { + // Convert the startAt to UTC based on the timezone from the utcDateTime + measurement.startAt = dateTime; + // replace with the current time if greater + const now = new Date().toISOString(); + if(measurement.startAt > now){ + console.error(`startAt is in the future: ${measurement.startAt}`); + measurement.startAt = now; + } measurements.push(measurement); }); + const userId = await getUserId(); + if(userId){ + const response = await postMeasurements(measurements, userId); + } return measurements; } + diff --git a/apps/nextjs/package.json b/apps/nextjs/package.json index eb936b647..02420386b 100644 --- a/apps/nextjs/package.json +++ b/apps/nextjs/package.json @@ -14,6 +14,7 @@ "prisma:migrate": "prisma migrate dev", "prisma:studio": "prisma studio", "test": "jest --watch", + "trigger": "npx @trigger.dev/cli@latest dev", "test:ci": "jest --ci", "format": "pnpm prettier . --write", "format:check": "pnpm prettier . --check", @@ -21,7 +22,6 @@ "generate:rapini": "npx rapini react-query -p ../../docs/api-reference/openapi.yml" }, "dependencies": { - "ai": "^2.2.26", "@copilotkit/backend": "^0.8.0", "@copilotkit/react-core": "^0.24.0", "@copilotkit/react-ui": "^0.21.0", @@ -46,9 +46,14 @@ "@radix-ui/react-toast": "^1.1.5", "@t3-oss/env-nextjs": "^0.9.2", "@tanstack/react-table": "^8.11.8", + "@trigger.dev/nextjs": "^2.3.18", + "@trigger.dev/react": "^2.3.18", + "@trigger.dev/sdk": "^2.3.18", + "@types/chai": "^4.3.14", "@types/mdx": "^2.0.12", "@types/node": "20.11.17", "@types/react-dom": "18.2.19", + "ai": "^2.2.26", "autoprefixer": "10.4.17", "axios": "^1.6.8", "chart.js": "^4.4.2", @@ -95,9 +100,9 @@ "tailwind-merge": "^2.2.1", "tailwindcss": "3.4.1", "tailwindcss-animate": "^1.0.7", + "typechat": "^0.0.10", "typescript": "5.3.3", "typewriter-effect": "^2.20.1", - "typechat": "^0.0.10", "vaul": "^0.9.0", "zod": "^3.22.4", "zustand": "^4.3.9" @@ -119,5 +124,8 @@ "prettier-plugin-tailwindcss": "^0.5.11", "prisma": "^5.9.1", "ts-jest": "^29.1.2" + }, + "trigger.dev": { + "endpointId": "the-decentralized-fda-1W-N" } } diff --git a/apps/nextjs/yarn.lock b/apps/nextjs/yarn.lock index 50f3d6f1a..6e9700ea8 100644 --- a/apps/nextjs/yarn.lock +++ b/apps/nextjs/yarn.lock @@ -2590,6 +2590,19 @@ dependencies: "@t3-oss/env-core" "0.9.2" +"@tanstack/query-core@5.0.0-beta.0": + version "5.0.0-beta.0" + resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.0.0-beta.0.tgz#6d3b6c7bb0e1479655303e307aea2b563800b3f8" + integrity sha512-VGq/H3PuRoj0shOcg1S5Flv3YD2qNz2ttk8w5xe5AHQE1I8NO9EHSBUxezIpk4dD6M7bQDtwHBMqqU2EwMwyUw== + +"@tanstack/react-query@5.0.0-beta.2": + version "5.0.0-beta.2" + resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.0.0-beta.2.tgz#1ba1ad2061600798477d93d48eec1320685fa0c8" + integrity sha512-JdK1HRw20tuwg3GfT3QZTkuS7s2KDa9FeozuJ7jZULlwPczZagouqYmM6+PL0ad6jfCnw8NzmLFtZdlBx6cTmA== + dependencies: + "@tanstack/query-core" "5.0.0-beta.0" + client-only "0.0.1" + "@tanstack/react-table@^8.11.8": version "8.15.3" resolved "https://registry.yarnpkg.com/@tanstack/react-table/-/react-table-8.15.3.tgz#9933222642d5d5bdaea5a78cf6c5d42aa86a1c22" @@ -2649,6 +2662,58 @@ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== +"@trigger.dev/core-backend@^2.3.18": + version "2.3.18" + resolved "https://registry.yarnpkg.com/@trigger.dev/core-backend/-/core-backend-2.3.18.tgz#56b2eb8867ee16e5a13d37581eaa4c33cedb97cd" + integrity sha512-LVeeerraGeqKNd2gtajQY+mnGWqkYW7Q2r5oWpL5xIZ8aQg3HRhSIfZs1dryexwKlfqnRjGWueGTy2+j1tbzcg== + +"@trigger.dev/core@^2.3.18": + version "2.3.18" + resolved "https://registry.yarnpkg.com/@trigger.dev/core/-/core-2.3.18.tgz#5477f4cf7d2e6a8c8aecca266a073f4078cf1331" + integrity sha512-j2EdCeyMkZ+zlVnnHl5zmBb+YURSw4x75NqQU1G5X08pQAza7G0qEn8DDGIMR5ieUMiHP0WS9oYy/voYdNfibQ== + dependencies: + ulidx "^2.2.1" + zod "3.22.3" + zod-error "1.5.0" + +"@trigger.dev/nextjs@^2.3.18": + version "2.3.18" + resolved "https://registry.yarnpkg.com/@trigger.dev/nextjs/-/nextjs-2.3.18.tgz#d8a2cc59c803d42081bc4b9690997446aa8c13be" + integrity sha512-ZS0RTZNrzGEKfOLQLYt3iqlNquD7pd39Hpd/+2tvRCaPSQ3qPYQvdjBSueW0OURZSQSiNno5VUYR5vbVBcAaXA== + dependencies: + debug "^4.3.4" + +"@trigger.dev/react@^2.3.18": + version "2.3.18" + resolved "https://registry.yarnpkg.com/@trigger.dev/react/-/react-2.3.18.tgz#4acdef98ca62e20d4d742d7ce104b0ffb0fc3437" + integrity sha512-S+frKgYJoT5JEzYejqlL5rkeHVxTR7wuJvvBMk+cFwA+nKV6isr8VnWmGQivGXGDlKMdF8IihwO+Guqs5tv/Hw== + dependencies: + "@tanstack/react-query" "5.0.0-beta.2" + "@trigger.dev/core" "^2.3.18" + debug "^4.3.4" + zod "3.22.3" + +"@trigger.dev/sdk@^2.3.18": + version "2.3.18" + resolved "https://registry.yarnpkg.com/@trigger.dev/sdk/-/sdk-2.3.18.tgz#0e10724347160c1b78b73dbaf3928d586057a791" + integrity sha512-Bjxgl4BbWOAL8rhxeBkl7SzvLLRBMJjiftq/7W7u96MDyPRFUoZZvVMSZzTJufnLBf/xS2JTi8LWU8gzhDJDvw== + dependencies: + "@trigger.dev/core" "^2.3.18" + "@trigger.dev/core-backend" "^2.3.18" + chalk "^5.2.0" + cronstrue "^2.21.0" + debug "^4.3.4" + evt "^2.4.13" + get-caller-file "^2.0.5" + git-remote-origin-url "^4.0.0" + git-repo-info "^2.1.1" + slug "^6.0.0" + terminal-link "^3.0.0" + ulid "^2.3.0" + uuid "^9.0.0" + ws "^8.11.0" + zod "3.22.3" + "@ts-stack/markdown@^1.4.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@ts-stack/markdown/-/markdown-1.5.0.tgz#5dc298a20dc3dc040143c5a5948201eb6bf5419d" @@ -2701,6 +2766,11 @@ dependencies: "@babel/types" "^7.20.7" +"@types/chai@^4.3.14": + version "4.3.14" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.14.tgz#ae3055ea2be43c91c9fd700a36d67820026d96e6" + integrity sha512-Wj71sXE4Q4AkGdG9Tvq1u/fquNz9EdG4LIJMwVVII7ashjD/8cf8fyIfJAjRr6YcsXnSE8cOGQPq1gqeR8z+3w== + "@types/d3-array@^3.0.3": version "3.2.1" resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-3.2.1.tgz#1f6658e3d2006c4fceac53fde464166859f8b8c5" @@ -3159,6 +3229,13 @@ ansi-escapes@^4.2.1: dependencies: type-fest "^0.21.3" +ansi-escapes@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-5.0.0.tgz#b6a0caf0eef0c41af190e9a749e0c00ec04bb2a6" + integrity sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA== + dependencies: + type-fest "^1.0.2" + ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" @@ -3980,6 +4057,11 @@ create-jest@^29.7.0: jest-util "^29.7.0" prompts "^2.0.1" +cronstrue@^2.21.0: + version "2.49.0" + resolved "https://registry.yarnpkg.com/cronstrue/-/cronstrue-2.49.0.tgz#d59f6d19e33030d45d9ecd3b845d4ccd79c6bfbd" + integrity sha512-FWZBqdStQaPR8ZTBQGALh1EK9Hl1HcG70dyGvD1rKLPafFO3H73o38dz/e8YkIlbLn3JxmBI/f6Doe3Nh+DcEQ== + cross-spawn-async@^2.1.1: version "2.2.5" resolved "https://registry.yarnpkg.com/cross-spawn-async/-/cross-spawn-async-2.2.5.tgz#845ff0c0834a3ded9d160daca6d390906bb288cc" @@ -4983,6 +5065,15 @@ eventsource-parser@1.0.0: resolved "https://registry.yarnpkg.com/eventsource-parser/-/eventsource-parser-1.0.0.tgz#6332e37fd5512e3c8d9df05773b2bf9e152ccc04" integrity sha512-9jgfSCa3dmEme2ES3mPByGXfgZ87VbP97tng1G2nWwWx6bV2nYxm2AWCrbQjXToSe+yYlqaZNtxffR9IeQr95g== +evt@^2.4.13: + version "2.5.7" + resolved "https://registry.yarnpkg.com/evt/-/evt-2.5.7.tgz#55c5f8ff910f4b7531bfac91e963d4cb3231f253" + integrity sha512-dr7Wd16ry5F8WNU1xXLKpFpO3HsoAGg8zC48e08vDdzMzGWCP9/QFGt1PQptEEDh8SwYP3EL8M+d/Gb0kgUp6g== + dependencies: + minimal-polyfills "^2.2.3" + run-exclusive "^2.2.19" + tsafe "^1.6.6" + execa@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/execa/-/execa-0.2.2.tgz#e2ead472c2c31aad6f73f1ac956eef45e12320cb" @@ -5376,6 +5467,25 @@ get-tsconfig@^4.5.0: dependencies: resolve-pkg-maps "^1.0.0" +git-remote-origin-url@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/git-remote-origin-url/-/git-remote-origin-url-4.0.0.tgz#712649112d88ca42b7d585e1b9fa6cafe22c8f63" + integrity sha512-EAxDksNdjuWgmVW9pVvA9jQDi/dmTaiDONktIy7qiRRhBZUI4FQK1YvBvteuTSX24aNKg9lfgxNYJEeeSXe6DA== + dependencies: + gitconfiglocal "^2.1.0" + +git-repo-info@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/git-repo-info/-/git-repo-info-2.1.1.tgz#220ffed8cbae74ef8a80e3052f2ccb5179aed058" + integrity sha512-8aCohiDo4jwjOwma4FmYFd3i97urZulL8XL24nIPxuE+GZnfsAyy/g2Shqx6OjUiFKUXZM+Yy+KHnOmmA3FVcg== + +gitconfiglocal@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/gitconfiglocal/-/gitconfiglocal-2.1.0.tgz#07c28685c55cc5338b27b5acbcfe34aeb92e43d1" + integrity sha512-qoerOEliJn3z+Zyn1HW2F6eoYJqKwS6MgC9cztTLUB/xLWX8gD/6T60pKn4+t/d6tP7JlybI7Z3z+I572CR/Vg== + dependencies: + ini "^1.3.2" + glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -5869,6 +5979,11 @@ inherits@2, inherits@2.0.4: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +ini@^1.3.2: + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + inline-style-parser@0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz#ec8a3b429274e9c0a1f1c4ffa9453a7fef72cea1" @@ -6875,6 +6990,11 @@ language-tags@^1.0.9: dependencies: language-subtag-registry "^0.3.20" +layerr@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/layerr/-/layerr-2.1.0.tgz#7b2aa335837b856fd25b3dd4fb44dc17d0785491" + integrity sha512-xDD9suWxfBYeXgqffRVH/Wqh+mqZrQcqPRn0I0ijl7iJQ7vu8gMGPt1Qop59pEW/jaIDNUN7+PX1Qk40+vuflg== + leven@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" @@ -8091,6 +8211,11 @@ min-indent@^1.0.0: resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== +minimal-polyfills@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/minimal-polyfills/-/minimal-polyfills-2.2.3.tgz#22af58de16807b325f29b83ca38ffb83e75ec3f4" + integrity sha512-oxdmJ9cL+xV72h0xYxp4tP2d5/fTBpP45H8DIOn9pASuF8a3IYTf+25fMGDYGiWW+MFsuog6KD6nfmhZJQ+uUw== + minimatch@9.0.3: version "9.0.3" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825" @@ -9508,6 +9633,13 @@ run-applescript@^5.0.0: dependencies: execa "^5.0.0" +run-exclusive@^2.2.19: + version "2.2.19" + resolved "https://registry.yarnpkg.com/run-exclusive/-/run-exclusive-2.2.19.tgz#37a2fb6e3671f8ae0d63521ebd1865fc796cf307" + integrity sha512-K3mdoAi7tjJ/qT7Flj90L7QyPozwUaAG+CVhkdDje4HLKXUYC3N/Jzkau3flHVDLQVhiHBtcimVodMjN9egYbA== + dependencies: + minimal-polyfills "^2.2.3" + run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" @@ -9693,6 +9825,11 @@ slash@^4.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7" integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== +slug@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/slug/-/slug-6.1.0.tgz#a3523f49533ea4a6bee6fa684064f4a0f70a3861" + integrity sha512-x6vLHCMasg4DR2LPiyFGI0gJJhywY6DTiGhCrOMzb3SOk/0JVLIaL4UhyFSHu04SD3uAavrKY/K3zZ3i6iRcgA== + socket.io-client@^4.6.2: version "4.7.5" resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-4.7.5.tgz#919be76916989758bdc20eec63f7ee0ae45c05b7" @@ -9813,7 +9950,16 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -9885,7 +10031,14 @@ stringify-entities@^4.0.0: character-entities-html4 "^2.0.0" character-entities-legacy "^3.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -9985,7 +10138,7 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" -supports-color@^7.1.0: +supports-color@^7.0.0, supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== @@ -10004,6 +10157,14 @@ supports-color@^9.4.0: resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-9.4.0.tgz#17bfcf686288f531db3dea3215510621ccb55954" integrity sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw== +supports-hyperlinks@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz#3943544347c1ff90b15effb03fc14ae45ec10624" + integrity sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA== + dependencies: + has-flag "^4.0.0" + supports-color "^7.0.0" + supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" @@ -10083,6 +10244,14 @@ tapable@^2.2.0: resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== +terminal-link@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-3.0.0.tgz#91c82a66b52fc1684123297ce384429faf72ac5c" + integrity sha512-flFL3m4wuixmf6IfhFJd1YPiLiMuxEc8uHRM1buzIeZPm22Au2pDqBJQgdo7n1WfPU1ONFGv7YDwpFBmHGF6lg== + dependencies: + ansi-escapes "^5.0.0" + supports-hyperlinks "^2.2.0" + test-exclude@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" @@ -10199,6 +10368,11 @@ ts-jest@^29.1.2: semver "^7.5.3" yargs-parser "^21.0.1" +tsafe@^1.6.6: + version "1.6.6" + resolved "https://registry.yarnpkg.com/tsafe/-/tsafe-1.6.6.tgz#fd93e64d6eb13ef83ed1650669cc24bad4f5df9f" + integrity sha512-gzkapsdbMNwBnTIjgO758GujLCj031IgHK/PKr2mrmkCSJMhSOR5FeOuSxKLMUoYc0vAA4RGEYYbjt/v6afD3g== + tsconfck@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/tsconfck/-/tsconfck-3.0.3.tgz#d9bda0e87d05b1c360e996c9050473c7e6f8084f" @@ -10241,6 +10415,11 @@ type-fest@^0.21.3: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== +type-fest@^1.0.2: + version "1.4.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-1.4.0.tgz#e9fb813fe3bf1744ec359d55d1affefa76f14be1" + integrity sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA== + type-fest@^2.13.0: version "2.19.0" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b" @@ -10324,6 +10503,18 @@ typewriter-effect@^2.20.1: prop-types "^15.8.1" raf "^3.4.1" +ulid@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/ulid/-/ulid-2.3.0.tgz#93063522771a9774121a84d126ecd3eb9804071f" + integrity sha512-keqHubrlpvT6G2wH0OEfSW4mquYRcbe/J8NMmveoQOjUqmo+hXtO+ORCpWhdbZ7k72UtY61BL7haGxW6enBnjw== + +ulidx@^2.2.1: + version "2.3.0" + resolved "https://registry.yarnpkg.com/ulidx/-/ulidx-2.3.0.tgz#0ed0360b40b7f5ddb88f636d470c51ffebdbee58" + integrity sha512-36piWNqcdp9hKlQewyeehCaALy4lyx3FodsCxHuV6i0YdexSkjDOubwxEVr2yi4kh62L/0MgyrxqG4K+qtovnw== + dependencies: + layerr "^2.0.1" + unbox-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" @@ -10855,8 +11046,16 @@ widest-line@^4.0.1: dependencies: string-width "^5.0.1" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: - name wrap-ansi-cjs +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -10973,11 +11172,28 @@ yocto-queue@^0.1.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== +zod-error@1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/zod-error/-/zod-error-1.5.0.tgz#bfdc20532746d564c88c51bd36267d6b7d9b9a5d" + integrity sha512-zzopKZ/skI9iXpqCEPj+iLCKl9b88E43ehcU+sbRoHuwGd9F1IDVGQ70TyO6kmfiRL1g4IXkjsXK+g1gLYl4WQ== + dependencies: + zod "^3.20.2" + zod-to-json-schema@^3.22.3, zod-to-json-schema@^3.22.5: version "3.23.0" resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.23.0.tgz#4fc60e88d3c709eedbfaae3f92f8a7bf786469f2" integrity sha512-az0uJ243PxsRIa2x1WmNE/pnuA05gUq/JB8Lwe1EDCCL/Fz9MgjYQ0fPlyc2Tcv6aF2ZA7WM5TWaRZVEFaAIag== +zod@3.22.3: + version "3.22.3" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.3.tgz#2fbc96118b174290d94e8896371c95629e87a060" + integrity sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug== + +zod@^3.20.2: + version "3.23.4" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.4.tgz#c63805b2f39e10d4ab3d55eb3c8cdb472c79dfb1" + integrity sha512-/AtWOKbBgjzEYYQRNfoGKHObgfAZag6qUJX1VbHo2PRBgS+wfWagEY2mizjfyAPcGesrJOcx/wcl0L9WnVrHFw== + zod@^3.22.3: version "3.23.3" resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.3.tgz#eeb068f83acb55310174673dee631dfa0be5510d"