changes:
- added retrieval, stock and weather tools and UI - switched to llama 3.1 for question suggestions
This commit is contained in:
parent
84c6074547
commit
1f945118c5
@ -1,7 +1,7 @@
|
||||
'use server';
|
||||
|
||||
import { generateObject } from 'ai';
|
||||
import { openai } from '@ai-sdk/openai';
|
||||
import { createOpenAI as createGroq } from '@ai-sdk/openai';
|
||||
import { z } from 'zod';
|
||||
|
||||
export interface Message {
|
||||
@ -9,11 +9,16 @@ export interface Message {
|
||||
content: string;
|
||||
}
|
||||
|
||||
const groq = createGroq({
|
||||
baseURL: 'https://api.groq.com/openai/v1',
|
||||
apiKey: process.env.GROQ_API_KEY,
|
||||
});
|
||||
|
||||
export async function suggestQuestions(history: Message[]) {
|
||||
'use server';
|
||||
|
||||
const { object } = await generateObject({
|
||||
model: openai('gpt-4o-mini'),
|
||||
model: groq('llama-3.1-70b-versatile'),
|
||||
temperature: 0,
|
||||
system:
|
||||
`You are a search engine query generator. You 'have' to create 3 questions for the search engine based on the message history which has been provided to you.
|
||||
|
||||
@ -2,12 +2,14 @@ import { openai } from "@ai-sdk/openai";
|
||||
import { anthropic } from '@ai-sdk/anthropic'
|
||||
import { convertToCoreMessages, streamText, tool } from "ai";
|
||||
import { z } from "zod";
|
||||
import { geolocation } from '@vercel/functions'
|
||||
|
||||
// Allow streaming responses up to 30 seconds
|
||||
export const maxDuration = 30;
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const { messages, model } = await req.json();
|
||||
const { latitude, longitude, city } = geolocation(req)
|
||||
|
||||
let ansmodel;
|
||||
|
||||
@ -22,12 +24,17 @@ export async function POST(req: Request) {
|
||||
messages: convertToCoreMessages(messages),
|
||||
system:
|
||||
"You are an AI web search engine that helps users find information on the internet." +
|
||||
"The user is located in " + city + " at latitude " + latitude + " and longitude " + longitude + "." +
|
||||
"Use this geolocation data for weather tool." +
|
||||
"You use the 'web_search' tool to search for information on the internet." +
|
||||
"Always call the 'web_search' tool to get the information, no need to do a chain of thought or say anything else, go straight to the point." +
|
||||
"Once you have found the information, you provide the user with the information you found in brief like a news paper detail." +
|
||||
"The detail should be 3-5 paragraphs in 10-12 sentences, some time pointers, each with citations in the [Text](link) format always!" +
|
||||
"Citations can be inline of the text like this: Hey there! [Google](https://google.com) is a search engine." +
|
||||
"Do not start the responses with newline characters, always start with the first sentence." +
|
||||
"When the user asks about a Stock, you should 'always' first gather news about it with web search tool, then show the chart and then write your response. Follow these steps in this order only!" +
|
||||
"Never use the retrieve tool for general search. Always use it when the user provides an url! " +
|
||||
"For weather related questions, use get_weather_data tool and write your response. No need to call any other tool. Put citation to OpenWeatherMaps API everytime." +
|
||||
"The current date is: " +
|
||||
new Date()
|
||||
.toLocaleDateString("en-US", {
|
||||
@ -84,6 +91,74 @@ export async function POST(req: Request) {
|
||||
}
|
||||
}
|
||||
}),
|
||||
retrieve: tool({
|
||||
description: 'Retrieve the information from the web search tool.',
|
||||
parameters: z.object({
|
||||
url: z.string().describe('The URL to retrieve the information from.')
|
||||
}),
|
||||
execute: async ({ url }: { url: string }) => {
|
||||
let hasError = false
|
||||
|
||||
let results;
|
||||
try {
|
||||
const response = await fetch(`https://r.jina.ai/${url}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'X-With-Generated-Alt': 'true'
|
||||
}
|
||||
})
|
||||
const json = await response.json()
|
||||
if (!json.data || json.data.length === 0) {
|
||||
hasError = true
|
||||
} else {
|
||||
// Limit the content to 5000 characters
|
||||
if (json.data.content.length > 5000) {
|
||||
json.data.content = json.data.content.slice(0, 5000)
|
||||
}
|
||||
results = {
|
||||
results: [
|
||||
{
|
||||
title: json.data.title,
|
||||
content: json.data.content,
|
||||
url: json.data.url
|
||||
}
|
||||
],
|
||||
query: '',
|
||||
images: []
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
hasError = true
|
||||
console.error('Retrieve API error:', error)
|
||||
}
|
||||
|
||||
if (hasError || !results) {
|
||||
return results
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
}),
|
||||
get_weather_data: tool({
|
||||
description: "Get the weather data for the given coordinates.",
|
||||
parameters: z.object({
|
||||
lat: z.number().describe('The latitude of the location.'),
|
||||
lon: z.number().describe('The longitude of the location.')
|
||||
}),
|
||||
execute: async ({ lat, lon }: { lat: number, lon: number }) => {
|
||||
const apiKey = process.env.OPENWEATHER_API_KEY
|
||||
const response = await fetch(`https://api.openweathermap.org/data/2.5/forecast?lat=${lat}&lon=${lon}&appid=${apiKey}`)
|
||||
const data = await response.json()
|
||||
return data
|
||||
}
|
||||
}),
|
||||
stock_chart_ui: tool({
|
||||
description: 'Display the stock chart for the given stock symbol after web search.',
|
||||
parameters: z.object({
|
||||
symbol: z.string().describe('The stock symbol to display the chart for.')
|
||||
}),
|
||||
}),
|
||||
},
|
||||
onFinish: async (event) => {
|
||||
console.log(event.text);
|
||||
|
||||
199
app/page.tsx
199
app/page.tsx
@ -29,6 +29,8 @@ import {
|
||||
AlignLeft,
|
||||
Newspaper,
|
||||
Copy,
|
||||
TrendingUp,
|
||||
Cloud,
|
||||
} from 'lucide-react';
|
||||
import {
|
||||
HoverCard,
|
||||
@ -57,8 +59,23 @@ import {
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import StockChart from '@/components/stock-chart';
|
||||
import { Line, LineChart, CartesianGrid, XAxis, YAxis, ResponsiveContainer } from "recharts";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import {
|
||||
ChartConfig,
|
||||
ChartContainer,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
} from "@/components/ui/chart";
|
||||
|
||||
export default function Home() {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
@ -78,7 +95,7 @@ export default function Home() {
|
||||
body: {
|
||||
model: selectedModel === 'Speed' ? 'gpt-4o-mini' : selectedModel === 'Quality (GPT)' ? 'gpt-4o' : 'claude-3-5-sonnet-20240620',
|
||||
},
|
||||
maxToolRoundtrips: 1,
|
||||
maxToolRoundtrips: 2,
|
||||
onFinish: async (message, { finishReason }) => {
|
||||
if (finishReason === 'stop') {
|
||||
const newHistory: Message[] = [{ role: "user", content: lastSubmittedQuery, }, { role: "assistant", content: message.content }];
|
||||
@ -87,6 +104,11 @@ export default function Home() {
|
||||
}
|
||||
setIsAnimating(false);
|
||||
},
|
||||
onToolCall({ toolCall, }) {
|
||||
if (toolCall.toolName === 'stock_chart_ui') {
|
||||
return 'Stock chart was shown to the user.';
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("Chat error:", error);
|
||||
toast.error("An error occurred. Please try again.");
|
||||
@ -189,11 +211,180 @@ export default function Home() {
|
||||
);
|
||||
}
|
||||
|
||||
interface WeatherDataPoint {
|
||||
date: string;
|
||||
minTemp: number;
|
||||
maxTemp: number;
|
||||
}
|
||||
|
||||
const WeatherChart: React.FC<{ result: any }> = React.memo(({ result }) => {
|
||||
const { chartData, minTemp, maxTemp } = useMemo(() => {
|
||||
const weatherData: WeatherDataPoint[] = result.list.map((item: any) => ({
|
||||
date: new Date(item.dt * 1000).toLocaleDateString(),
|
||||
minTemp: Number((item.main.temp_min - 273.15).toFixed(1)),
|
||||
maxTemp: Number((item.main.temp_max - 273.15).toFixed(1)),
|
||||
}));
|
||||
|
||||
// Group data by date and calculate min and max temperatures
|
||||
const groupedData: { [key: string]: WeatherDataPoint } = weatherData.reduce((acc, curr) => {
|
||||
if (!acc[curr.date]) {
|
||||
acc[curr.date] = { ...curr };
|
||||
} else {
|
||||
acc[curr.date].minTemp = Math.min(acc[curr.date].minTemp, curr.minTemp);
|
||||
acc[curr.date].maxTemp = Math.max(acc[curr.date].maxTemp, curr.maxTemp);
|
||||
}
|
||||
return acc;
|
||||
}, {} as { [key: string]: WeatherDataPoint });
|
||||
|
||||
const chartData = Object.values(groupedData);
|
||||
|
||||
// Calculate overall min and max temperatures
|
||||
const minTemp = Math.min(...chartData.map(d => d.minTemp));
|
||||
const maxTemp = Math.max(...chartData.map(d => d.maxTemp));
|
||||
|
||||
return { chartData, minTemp, maxTemp };
|
||||
}, [result]);
|
||||
|
||||
const chartConfig: ChartConfig = useMemo(() => ({
|
||||
minTemp: {
|
||||
label: "Min Temp.",
|
||||
color: "hsl(var(--chart-1))",
|
||||
},
|
||||
maxTemp: {
|
||||
label: "Max Temp.",
|
||||
color: "hsl(var(--chart-2))",
|
||||
},
|
||||
}), []);
|
||||
|
||||
return (
|
||||
<Card className="my-4 shadow-none">
|
||||
<CardHeader>
|
||||
<CardTitle>Weather Forecast for {result.city.name}</CardTitle>
|
||||
<CardDescription>
|
||||
Showing min and max temperatures for the next 5 days
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer config={chartConfig}>
|
||||
<ResponsiveContainer width="100%" height={300}>
|
||||
<LineChart
|
||||
data={chartData}
|
||||
margin={{ top: 10, right: 30, left: 0, bottom: 0 }}
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis
|
||||
dataKey="date"
|
||||
tickFormatter={(value) => new Date(value).toLocaleDateString(undefined, { month: 'short', day: 'numeric' })}
|
||||
/>
|
||||
<YAxis
|
||||
domain={[Math.floor(minTemp) - 2, Math.ceil(maxTemp) + 2]}
|
||||
tickFormatter={(value) => `${value}°C`}
|
||||
/>
|
||||
<ChartTooltip content={<ChartTooltipContent />} />
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="minTemp"
|
||||
stroke="var(--color-minTemp)"
|
||||
strokeWidth={2}
|
||||
dot={false}
|
||||
name="Min Temp."
|
||||
/>
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="maxTemp"
|
||||
stroke="var(--color-maxTemp)"
|
||||
strokeWidth={2}
|
||||
dot={false}
|
||||
name="Max Temp."
|
||||
/>
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<div className="flex w-full items-start gap-2 text-sm">
|
||||
<div className="grid gap-2">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
{result.city.name}, {result.city.country}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 leading-none text-muted-foreground">
|
||||
Next 5 days forecast
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
WeatherChart.displayName = 'WeatherChart';
|
||||
|
||||
|
||||
const renderToolInvocation = (toolInvocation: ToolInvocation, index: number) => {
|
||||
const args = JSON.parse(JSON.stringify(toolInvocation.args));
|
||||
const result = 'result' in toolInvocation ? JSON.parse(JSON.stringify(toolInvocation.result)) : null;
|
||||
|
||||
if (toolInvocation.toolName === 'stock_chart_ui') {
|
||||
return (
|
||||
<div className="my-4">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<TrendingUp className="h-5 w-5 text-primary" />
|
||||
<h2 className="font-semibold text-base">{args.symbol}</h2>
|
||||
</div>
|
||||
<Card className='!border-none !shadow-none !bg-none'>
|
||||
<CardContent className="p-0">
|
||||
<StockChart props={args.symbol} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (toolInvocation.toolName === 'get_weather_data') {
|
||||
if (!result) {
|
||||
return (
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<div className="flex items-center gap-2">
|
||||
<Cloud className="h-5 w-5 text-neutral-700 animate-pulse" />
|
||||
<span className="text-neutral-700 text-lg">Fetching weather data...</span>
|
||||
</div>
|
||||
<div className="flex space-x-1">
|
||||
{[0, 1, 2].map((index) => (
|
||||
<motion.div
|
||||
key={index}
|
||||
className="w-2 h-2 bg-muted-foreground rounded-full"
|
||||
initial={{ opacity: 0.3 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{
|
||||
repeat: Infinity,
|
||||
duration: 0.8,
|
||||
delay: index * 0.2,
|
||||
repeatType: "reverse",
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Card className="my-4 shadow-none">
|
||||
<CardHeader>
|
||||
<CardTitle className="h-6 w-3/4 bg-gray-200 rounded animate-pulse" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="h-[300px] bg-gray-200 rounded animate-pulse" />
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return <WeatherChart result={result} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{!result ? (
|
||||
@ -403,9 +594,9 @@ export default function Home() {
|
||||
}, [append, setMessages]);
|
||||
|
||||
const exampleQueries = [
|
||||
"Meta Llama 3.1 405B",
|
||||
"Weather in Doha",
|
||||
"Latest on Paris Olympics",
|
||||
"What is Github Models?",
|
||||
"Summary: https://openai.com/index/gpt-4o-system-card/",
|
||||
"OpenAI GPT-4o mini"
|
||||
];
|
||||
|
||||
|
||||
85
components/stock-chart.tsx
Normal file
85
components/stock-chart.tsx
Normal file
@ -0,0 +1,85 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
// from https://github.com/bklieger-groq/stockbot-on-groq/blob/main/components/tradingview/stock-chart.tsx
|
||||
'use client'
|
||||
|
||||
import React, { useEffect, useRef, memo } from 'react'
|
||||
|
||||
export function StockChart({ props: symbol }: { props: string }) {
|
||||
const container = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!container.current) return
|
||||
const script = document.createElement('script')
|
||||
script.src =
|
||||
'https://s3.tradingview.com/external-embedding/embed-widget-advanced-chart.js'
|
||||
script.type = 'text/javascript'
|
||||
script.async = true
|
||||
script.innerHTML = JSON.stringify({
|
||||
autosize: true,
|
||||
symbol: symbol,
|
||||
interval: 'D',
|
||||
timezone: 'Etc/UTC',
|
||||
theme: 'light',
|
||||
style: '1',
|
||||
locale: 'en',
|
||||
backgroundColor: 'rgba(255, 255, 255, 1)',
|
||||
gridColor: 'rgba(247, 247, 247, 1)',
|
||||
withdateranges: true,
|
||||
hide_side_toolbar: false,
|
||||
allow_symbol_change: true,
|
||||
calendar: false,
|
||||
hide_top_toolbar: true,
|
||||
support_host: 'https://www.tradingview.com'
|
||||
})
|
||||
|
||||
container.current.appendChild(script)
|
||||
|
||||
return () => {
|
||||
if (container.current) {
|
||||
container.current.removeChild(script)
|
||||
}
|
||||
}
|
||||
}, [symbol])
|
||||
|
||||
return (
|
||||
<div style={{ height: '500px' }}>
|
||||
<div
|
||||
className="tradingview-widget-container"
|
||||
ref={container}
|
||||
style={{ height: '100%', width: '100%', position: 'relative' }}
|
||||
>
|
||||
<div
|
||||
className="tradingview-widget-container__widget"
|
||||
style={{
|
||||
height: 'calc(100% - 32px)',
|
||||
width: '100%',
|
||||
borderRadius: '1rem',
|
||||
overflow: 'hidden'
|
||||
}}
|
||||
></div>
|
||||
<div
|
||||
className="tradingview-widget-copyright"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
padding: '4px',
|
||||
textAlign: 'center',
|
||||
fontSize: '12px'
|
||||
}}
|
||||
>
|
||||
<a
|
||||
href="https://www.tradingview.com/"
|
||||
rel="noopener nofollow"
|
||||
target="_blank"
|
||||
>
|
||||
<span className="">Track all markets on TradingView</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(StockChart)
|
||||
370
components/ui/chart.tsx
Normal file
370
components/ui/chart.tsx
Normal file
@ -0,0 +1,370 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as RechartsPrimitive from "recharts"
|
||||
import {
|
||||
NameType,
|
||||
Payload,
|
||||
ValueType,
|
||||
} from "recharts/types/component/DefaultTooltipContent"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
// Format: { THEME_NAME: CSS_SELECTOR }
|
||||
const THEMES = { light: "", dark: ".dark" } as const
|
||||
|
||||
export type ChartConfig = {
|
||||
[k in string]: {
|
||||
label?: React.ReactNode
|
||||
icon?: React.ComponentType
|
||||
} & (
|
||||
| { color?: string; theme?: never }
|
||||
| { color?: never; theme: Record<keyof typeof THEMES, string> }
|
||||
)
|
||||
}
|
||||
|
||||
type ChartContextProps = {
|
||||
config: ChartConfig
|
||||
}
|
||||
|
||||
const ChartContext = React.createContext<ChartContextProps | null>(null)
|
||||
|
||||
function useChart() {
|
||||
const context = React.useContext(ChartContext)
|
||||
|
||||
if (!context) {
|
||||
throw new Error("useChart must be used within a <ChartContainer />")
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
const ChartContainer = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.ComponentProps<"div"> & {
|
||||
config: ChartConfig
|
||||
children: React.ComponentProps<
|
||||
typeof RechartsPrimitive.ResponsiveContainer
|
||||
>["children"]
|
||||
}
|
||||
>(({ id, className, children, config, ...props }, ref) => {
|
||||
const uniqueId = React.useId()
|
||||
const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
|
||||
|
||||
return (
|
||||
<ChartContext.Provider value={{ config }}>
|
||||
<div
|
||||
data-chart={chartId}
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChartStyle id={chartId} config={config} />
|
||||
<RechartsPrimitive.ResponsiveContainer>
|
||||
{children}
|
||||
</RechartsPrimitive.ResponsiveContainer>
|
||||
</div>
|
||||
</ChartContext.Provider>
|
||||
)
|
||||
})
|
||||
ChartContainer.displayName = "Chart"
|
||||
|
||||
const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
|
||||
const colorConfig = Object.entries(config).filter(
|
||||
([_, config]) => config.theme || config.color
|
||||
)
|
||||
|
||||
if (!colorConfig.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<style
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: Object.entries(THEMES)
|
||||
.map(
|
||||
([theme, prefix]) => `
|
||||
${prefix} [data-chart=${id}] {
|
||||
${colorConfig
|
||||
.map(([key, itemConfig]) => {
|
||||
const color =
|
||||
itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
|
||||
itemConfig.color
|
||||
return color ? ` --color-${key}: ${color};` : null
|
||||
})
|
||||
.join("\n")}
|
||||
}
|
||||
`
|
||||
)
|
||||
.join("\n"),
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const ChartTooltip = RechartsPrimitive.Tooltip
|
||||
|
||||
const ChartTooltipContent = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
|
||||
React.ComponentProps<"div"> & {
|
||||
hideLabel?: boolean
|
||||
hideIndicator?: boolean
|
||||
indicator?: "line" | "dot" | "dashed"
|
||||
nameKey?: string
|
||||
labelKey?: string
|
||||
}
|
||||
>(
|
||||
(
|
||||
{
|
||||
active,
|
||||
payload,
|
||||
className,
|
||||
indicator = "dot",
|
||||
hideLabel = false,
|
||||
hideIndicator = false,
|
||||
label,
|
||||
labelFormatter,
|
||||
labelClassName,
|
||||
formatter,
|
||||
color,
|
||||
nameKey,
|
||||
labelKey,
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const { config } = useChart()
|
||||
|
||||
const tooltipLabel = React.useMemo(() => {
|
||||
if (hideLabel || !payload?.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
const [item] = payload
|
||||
const key = `${labelKey || item.dataKey || item.name || "value"}`
|
||||
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
||||
const value =
|
||||
!labelKey && typeof label === "string"
|
||||
? config[label as keyof typeof config]?.label || label
|
||||
: itemConfig?.label
|
||||
|
||||
if (labelFormatter) {
|
||||
return (
|
||||
<div className={cn("font-medium", labelClassName)}>
|
||||
{labelFormatter(value, payload)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
return null
|
||||
}
|
||||
|
||||
return <div className={cn("font-medium", labelClassName)}>{value}</div>
|
||||
}, [
|
||||
label,
|
||||
labelFormatter,
|
||||
payload,
|
||||
hideLabel,
|
||||
labelClassName,
|
||||
config,
|
||||
labelKey,
|
||||
])
|
||||
|
||||
if (!active || !payload?.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
const nestLabel = payload.length === 1 && indicator !== "dot"
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{!nestLabel ? tooltipLabel : null}
|
||||
<div className="grid gap-1.5">
|
||||
{payload.map((item, index) => {
|
||||
const key = `${nameKey || item.name || item.dataKey || "value"}`
|
||||
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
||||
const indicatorColor = color || item.payload.fill || item.color
|
||||
|
||||
return (
|
||||
<div
|
||||
key={item.dataKey}
|
||||
className={cn(
|
||||
"flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
|
||||
indicator === "dot" && "items-center"
|
||||
)}
|
||||
>
|
||||
{formatter && item?.value !== undefined && item.name ? (
|
||||
formatter(item.value, item.name, item, index, item.payload)
|
||||
) : (
|
||||
<>
|
||||
{itemConfig?.icon ? (
|
||||
<itemConfig.icon />
|
||||
) : (
|
||||
!hideIndicator && (
|
||||
<div
|
||||
className={cn(
|
||||
"shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",
|
||||
{
|
||||
"h-2.5 w-2.5": indicator === "dot",
|
||||
"w-1": indicator === "line",
|
||||
"w-0 border-[1.5px] border-dashed bg-transparent":
|
||||
indicator === "dashed",
|
||||
"my-0.5": nestLabel && indicator === "dashed",
|
||||
}
|
||||
)}
|
||||
style={
|
||||
{
|
||||
"--color-bg": indicatorColor,
|
||||
"--color-border": indicatorColor,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-1 justify-between leading-none",
|
||||
nestLabel ? "items-end" : "items-center"
|
||||
)}
|
||||
>
|
||||
<div className="grid gap-1.5">
|
||||
{nestLabel ? tooltipLabel : null}
|
||||
<span className="text-muted-foreground">
|
||||
{itemConfig?.label || item.name}
|
||||
</span>
|
||||
</div>
|
||||
{item.value && (
|
||||
<span className="font-mono font-medium tabular-nums text-foreground">
|
||||
{item.value.toLocaleString()}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
ChartTooltipContent.displayName = "ChartTooltip"
|
||||
|
||||
const ChartLegend = RechartsPrimitive.Legend
|
||||
|
||||
const ChartLegendContent = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.ComponentProps<"div"> &
|
||||
Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
|
||||
hideIcon?: boolean
|
||||
nameKey?: string
|
||||
}
|
||||
>(
|
||||
(
|
||||
{ className, hideIcon = false, payload, verticalAlign = "bottom", nameKey },
|
||||
ref
|
||||
) => {
|
||||
const { config } = useChart()
|
||||
|
||||
if (!payload?.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex items-center justify-center gap-4",
|
||||
verticalAlign === "top" ? "pb-3" : "pt-3",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{payload.map((item) => {
|
||||
const key = `${nameKey || item.dataKey || "value"}`
|
||||
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
||||
|
||||
return (
|
||||
<div
|
||||
key={item.value}
|
||||
className={cn(
|
||||
"flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
{itemConfig?.icon && !hideIcon ? (
|
||||
<itemConfig.icon />
|
||||
) : (
|
||||
<div
|
||||
className="h-2 w-2 shrink-0 rounded-[2px]"
|
||||
style={{
|
||||
backgroundColor: item.color,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{itemConfig?.label}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
ChartLegendContent.displayName = "ChartLegend"
|
||||
|
||||
// Helper to extract item config from a payload.
|
||||
function getPayloadConfigFromPayload(
|
||||
config: ChartConfig,
|
||||
payload: unknown,
|
||||
key: string
|
||||
) {
|
||||
if (typeof payload !== "object" || payload === null) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const payloadPayload =
|
||||
"payload" in payload &&
|
||||
typeof payload.payload === "object" &&
|
||||
payload.payload !== null
|
||||
? payload.payload
|
||||
: undefined
|
||||
|
||||
let configLabelKey: string = key
|
||||
|
||||
if (
|
||||
key in payload &&
|
||||
typeof payload[key as keyof typeof payload] === "string"
|
||||
) {
|
||||
configLabelKey = payload[key as keyof typeof payload] as string
|
||||
} else if (
|
||||
payloadPayload &&
|
||||
key in payloadPayload &&
|
||||
typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
|
||||
) {
|
||||
configLabelKey = payloadPayload[
|
||||
key as keyof typeof payloadPayload
|
||||
] as string
|
||||
}
|
||||
|
||||
return configLabelKey in config
|
||||
? config[configLabelKey]
|
||||
: config[key as keyof typeof config]
|
||||
}
|
||||
|
||||
export {
|
||||
ChartContainer,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
ChartLegend,
|
||||
ChartLegendContent,
|
||||
ChartStyle,
|
||||
}
|
||||
@ -20,6 +20,7 @@
|
||||
"@radix-ui/react-slot": "^1.1.0",
|
||||
"@tailwindcss/typography": "^0.5.13",
|
||||
"@vercel/analytics": "^1.3.1",
|
||||
"@vercel/functions": "^1.4.0",
|
||||
"ai": "latest",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
@ -30,6 +31,7 @@
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"react-markdown": "^9.0.1",
|
||||
"recharts": "^2.12.7",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"sonner": "^1.5.0",
|
||||
"tailwind-merge": "^2.4.0",
|
||||
|
||||
246
pnpm-lock.yaml
246
pnpm-lock.yaml
@ -38,6 +38,9 @@ dependencies:
|
||||
'@vercel/analytics':
|
||||
specifier: ^1.3.1
|
||||
version: 1.3.1(next@14.2.5)(react@18.3.1)
|
||||
'@vercel/functions':
|
||||
specifier: ^1.4.0
|
||||
version: 1.4.0
|
||||
ai:
|
||||
specifier: latest
|
||||
version: 3.3.5(react@18.3.1)(svelte@4.2.18)(vue@3.4.35)(zod@3.23.8)
|
||||
@ -68,6 +71,9 @@ dependencies:
|
||||
react-markdown:
|
||||
specifier: ^9.0.1
|
||||
version: 9.0.1(@types/react@18.3.3)(react@18.3.1)
|
||||
recharts:
|
||||
specifier: ^2.12.7
|
||||
version: 2.12.7(react-dom@18.3.1)(react@18.3.1)
|
||||
remark-gfm:
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.0
|
||||
@ -316,6 +322,13 @@ packages:
|
||||
'@babel/types': 7.25.2
|
||||
dev: false
|
||||
|
||||
/@babel/runtime@7.25.0:
|
||||
resolution: {integrity: sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
regenerator-runtime: 0.14.1
|
||||
dev: false
|
||||
|
||||
/@babel/types@7.25.2:
|
||||
resolution: {integrity: sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@ -1192,6 +1205,48 @@ packages:
|
||||
tailwindcss: 3.4.7
|
||||
dev: false
|
||||
|
||||
/@types/d3-array@3.2.1:
|
||||
resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==}
|
||||
dev: false
|
||||
|
||||
/@types/d3-color@3.1.3:
|
||||
resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==}
|
||||
dev: false
|
||||
|
||||
/@types/d3-ease@3.0.2:
|
||||
resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==}
|
||||
dev: false
|
||||
|
||||
/@types/d3-interpolate@3.0.4:
|
||||
resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==}
|
||||
dependencies:
|
||||
'@types/d3-color': 3.1.3
|
||||
dev: false
|
||||
|
||||
/@types/d3-path@3.1.0:
|
||||
resolution: {integrity: sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==}
|
||||
dev: false
|
||||
|
||||
/@types/d3-scale@4.0.8:
|
||||
resolution: {integrity: sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==}
|
||||
dependencies:
|
||||
'@types/d3-time': 3.0.3
|
||||
dev: false
|
||||
|
||||
/@types/d3-shape@3.1.6:
|
||||
resolution: {integrity: sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==}
|
||||
dependencies:
|
||||
'@types/d3-path': 3.1.0
|
||||
dev: false
|
||||
|
||||
/@types/d3-time@3.0.3:
|
||||
resolution: {integrity: sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==}
|
||||
dev: false
|
||||
|
||||
/@types/d3-timer@3.0.2:
|
||||
resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==}
|
||||
dev: false
|
||||
|
||||
/@types/debug@4.1.12:
|
||||
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
|
||||
dependencies:
|
||||
@ -1343,6 +1398,16 @@ packages:
|
||||
server-only: 0.0.1
|
||||
dev: false
|
||||
|
||||
/@vercel/functions@1.4.0:
|
||||
resolution: {integrity: sha512-Ln6SpIkms1UJg306X2kbEMyG9ol+mjDr2xx389cvsBxgFyFMI9Bm+LYOG4N3TSik4FI59MECyyc4oz7AIAYmqQ==}
|
||||
engines: {node: '>= 16'}
|
||||
peerDependencies:
|
||||
'@aws-sdk/credential-provider-web-identity': '*'
|
||||
peerDependenciesMeta:
|
||||
'@aws-sdk/credential-provider-web-identity':
|
||||
optional: true
|
||||
dev: false
|
||||
|
||||
/@vue/compiler-core@3.4.35:
|
||||
resolution: {integrity: sha512-gKp0zGoLnMYtw4uS/SJRRO7rsVggLjvot3mcctlMXunYNsX+aRJDqqw/lV5/gHK91nvaAAlWFgdVl020AW1Prg==}
|
||||
dependencies:
|
||||
@ -1839,6 +1904,77 @@ packages:
|
||||
/csstype@3.1.3:
|
||||
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
|
||||
|
||||
/d3-array@3.2.4:
|
||||
resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
internmap: 2.0.3
|
||||
dev: false
|
||||
|
||||
/d3-color@3.1.0:
|
||||
resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
|
||||
engines: {node: '>=12'}
|
||||
dev: false
|
||||
|
||||
/d3-ease@3.0.1:
|
||||
resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==}
|
||||
engines: {node: '>=12'}
|
||||
dev: false
|
||||
|
||||
/d3-format@3.1.0:
|
||||
resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==}
|
||||
engines: {node: '>=12'}
|
||||
dev: false
|
||||
|
||||
/d3-interpolate@3.0.1:
|
||||
resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
d3-color: 3.1.0
|
||||
dev: false
|
||||
|
||||
/d3-path@3.1.0:
|
||||
resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==}
|
||||
engines: {node: '>=12'}
|
||||
dev: false
|
||||
|
||||
/d3-scale@4.0.2:
|
||||
resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
d3-array: 3.2.4
|
||||
d3-format: 3.1.0
|
||||
d3-interpolate: 3.0.1
|
||||
d3-time: 3.1.0
|
||||
d3-time-format: 4.1.0
|
||||
dev: false
|
||||
|
||||
/d3-shape@3.2.0:
|
||||
resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
d3-path: 3.1.0
|
||||
dev: false
|
||||
|
||||
/d3-time-format@4.1.0:
|
||||
resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
d3-time: 3.1.0
|
||||
dev: false
|
||||
|
||||
/d3-time@3.1.0:
|
||||
resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
d3-array: 3.2.4
|
||||
dev: false
|
||||
|
||||
/d3-timer@3.0.1:
|
||||
resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
|
||||
engines: {node: '>=12'}
|
||||
dev: false
|
||||
|
||||
/damerau-levenshtein@1.0.8:
|
||||
resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
|
||||
dev: true
|
||||
@ -1896,6 +2032,10 @@ packages:
|
||||
dependencies:
|
||||
ms: 2.1.2
|
||||
|
||||
/decimal.js-light@2.5.1:
|
||||
resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==}
|
||||
dev: false
|
||||
|
||||
/decode-named-character-reference@1.0.2:
|
||||
resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==}
|
||||
dependencies:
|
||||
@ -1994,6 +2134,13 @@ packages:
|
||||
esutils: 2.0.3
|
||||
dev: true
|
||||
|
||||
/dom-helpers@5.2.1:
|
||||
resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.25.0
|
||||
csstype: 3.1.3
|
||||
dev: false
|
||||
|
||||
/eastasianwidth@0.2.0:
|
||||
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
||||
|
||||
@ -2446,6 +2593,10 @@ packages:
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/eventemitter3@4.0.7:
|
||||
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
|
||||
dev: false
|
||||
|
||||
/eventsource-parser@1.1.2:
|
||||
resolution: {integrity: sha512-v0eOBUbiaFojBu2s2NPBfYUoRR9GjcDNvCXVaqEf5vVfpIAh9f8RCo4vXTP8c63QRKCFwoLpMpTdPwwhEKVgzA==}
|
||||
engines: {node: '>=14.18'}
|
||||
@ -2459,6 +2610,11 @@ packages:
|
||||
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
||||
dev: true
|
||||
|
||||
/fast-equals@5.0.1:
|
||||
resolution: {integrity: sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
dev: false
|
||||
|
||||
/fast-glob@3.3.2:
|
||||
resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==}
|
||||
engines: {node: '>=8.6.0'}
|
||||
@ -2807,6 +2963,11 @@ packages:
|
||||
side-channel: 1.0.6
|
||||
dev: true
|
||||
|
||||
/internmap@2.0.3:
|
||||
resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
|
||||
engines: {node: '>=12'}
|
||||
dev: false
|
||||
|
||||
/invariant@2.2.4:
|
||||
resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==}
|
||||
dependencies:
|
||||
@ -3172,6 +3333,10 @@ packages:
|
||||
/lodash.merge@4.6.2:
|
||||
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
|
||||
|
||||
/lodash@4.17.21:
|
||||
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
||||
dev: false
|
||||
|
||||
/longest-streak@3.1.0:
|
||||
resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
|
||||
dev: false
|
||||
@ -4026,7 +4191,6 @@ packages:
|
||||
loose-envify: 1.4.0
|
||||
object-assign: 4.1.1
|
||||
react-is: 16.13.1
|
||||
dev: true
|
||||
|
||||
/property-information@6.5.0:
|
||||
resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==}
|
||||
@ -4052,7 +4216,6 @@ packages:
|
||||
|
||||
/react-is@16.13.1:
|
||||
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
||||
dev: true
|
||||
|
||||
/react-markdown@9.0.1(@types/react@18.3.3)(react@18.3.1):
|
||||
resolution: {integrity: sha512-186Gw/vF1uRkydbsOIkcGXw7aHq0sZOCRFFjGrr7b9+nVZg4UfA4enXCaxm4fUzecU38sWfrNDitGhshuU7rdg==}
|
||||
@ -4111,6 +4274,19 @@ packages:
|
||||
use-sidecar: 1.1.2(@types/react@18.3.3)(react@18.3.1)
|
||||
dev: false
|
||||
|
||||
/react-smooth@4.0.1(react-dom@18.3.1)(react@18.3.1):
|
||||
resolution: {integrity: sha512-OE4hm7XqR0jNOq3Qmk9mFLyd6p2+j6bvbPJ7qlB7+oo0eNcL2l7WQzG6MBnT3EXY6xzkLMUBec3AfewJdA0J8w==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
fast-equals: 5.0.1
|
||||
prop-types: 15.8.1
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
react-transition-group: 4.4.5(react-dom@18.3.1)(react@18.3.1)
|
||||
dev: false
|
||||
|
||||
/react-style-singleton@2.2.1(@types/react@18.3.3)(react@18.3.1):
|
||||
resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==}
|
||||
engines: {node: '>=10'}
|
||||
@ -4128,6 +4304,20 @@ packages:
|
||||
tslib: 2.6.3
|
||||
dev: false
|
||||
|
||||
/react-transition-group@4.4.5(react-dom@18.3.1)(react@18.3.1):
|
||||
resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==}
|
||||
peerDependencies:
|
||||
react: '>=16.6.0'
|
||||
react-dom: '>=16.6.0'
|
||||
dependencies:
|
||||
'@babel/runtime': 7.25.0
|
||||
dom-helpers: 5.2.1
|
||||
loose-envify: 1.4.0
|
||||
prop-types: 15.8.1
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
dev: false
|
||||
|
||||
/react@18.3.1:
|
||||
resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@ -4146,6 +4336,31 @@ packages:
|
||||
dependencies:
|
||||
picomatch: 2.3.1
|
||||
|
||||
/recharts-scale@0.4.5:
|
||||
resolution: {integrity: sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==}
|
||||
dependencies:
|
||||
decimal.js-light: 2.5.1
|
||||
dev: false
|
||||
|
||||
/recharts@2.12.7(react-dom@18.3.1)(react@18.3.1):
|
||||
resolution: {integrity: sha512-hlLJMhPQfv4/3NBSAyq3gzGg4h2v69RJh6KU7b3pXYNNAELs9kEoXOjbkxdXpALqKBoVmVptGfLpxdaVYqjmXQ==}
|
||||
engines: {node: '>=14'}
|
||||
peerDependencies:
|
||||
react: ^16.0.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
clsx: 2.1.1
|
||||
eventemitter3: 4.0.7
|
||||
lodash: 4.17.21
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
react-is: 16.13.1
|
||||
react-smooth: 4.0.1(react-dom@18.3.1)(react@18.3.1)
|
||||
recharts-scale: 0.4.5
|
||||
tiny-invariant: 1.3.3
|
||||
victory-vendor: 36.9.2
|
||||
dev: false
|
||||
|
||||
/reflect.getprototypeof@1.0.6:
|
||||
resolution: {integrity: sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@ -4159,6 +4374,10 @@ packages:
|
||||
which-builtin-type: 1.1.4
|
||||
dev: true
|
||||
|
||||
/regenerator-runtime@0.14.1:
|
||||
resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
|
||||
dev: false
|
||||
|
||||
/regexp.prototype.flags@1.5.2:
|
||||
resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@ -4643,6 +4862,10 @@ packages:
|
||||
dependencies:
|
||||
any-promise: 1.3.0
|
||||
|
||||
/tiny-invariant@1.3.3:
|
||||
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
|
||||
dev: false
|
||||
|
||||
/to-fast-properties@2.0.0:
|
||||
resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==}
|
||||
engines: {node: '>=4'}
|
||||
@ -4876,6 +5099,25 @@ packages:
|
||||
vfile-message: 4.0.2
|
||||
dev: false
|
||||
|
||||
/victory-vendor@36.9.2:
|
||||
resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==}
|
||||
dependencies:
|
||||
'@types/d3-array': 3.2.1
|
||||
'@types/d3-ease': 3.0.2
|
||||
'@types/d3-interpolate': 3.0.4
|
||||
'@types/d3-scale': 4.0.8
|
||||
'@types/d3-shape': 3.1.6
|
||||
'@types/d3-time': 3.0.3
|
||||
'@types/d3-timer': 3.0.2
|
||||
d3-array: 3.2.4
|
||||
d3-ease: 3.0.1
|
||||
d3-interpolate: 3.0.1
|
||||
d3-scale: 4.0.2
|
||||
d3-shape: 3.2.0
|
||||
d3-time: 3.1.0
|
||||
d3-timer: 3.0.1
|
||||
dev: false
|
||||
|
||||
/vue@3.4.35(typescript@5.5.4):
|
||||
resolution: {integrity: sha512-+fl/GLmI4GPileHftVlCdB7fUL4aziPcqTudpTGXCT8s+iZWuOCeNEB5haX6Uz2IpRrbEXOgIFbe+XciCuGbNQ==}
|
||||
peerDependencies:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user