Compare commits

...

14 Commits

Author SHA1 Message Date
Francisco Bischoff 51302db253
Merge eee187ed00 into d72f1af361 2024-04-26 21:21:04 -04:00
Timothy Carambat d72f1af361
Improve uploader experience (#1205)
* Improve uploader expierence
- Wipe upload container (fadeout) after uploading
- debounce fetchKeys by 1s

* patch unneded exports
2024-04-26 17:41:42 -07:00
Sean Hatfield 360f17cd58
[FIX] Move to Workspace popup UI bug fix (#1204)
fix for popup menu transparent container
2024-04-26 17:38:41 -07:00
Sean Hatfield 8eda75d624
[FIX] Loading message in document picker bug (#1202)
* fix loading message in document picker bug

* linting

---------

Co-authored-by: timothycarambat <rambat1010@gmail.com>
2024-04-26 17:08:10 -07:00
Timothy Carambat 1b35bcbeab
Strengthen field validations on user Updates (#1201)
* Strengthen field validations on user Updates

* update writables
2024-04-26 16:46:04 -07:00
timothycarambat df2c01b176 patch OpenRouter model fetcher when key is not present 2024-04-26 15:58:30 -07:00
Timothy Carambat 2e813846dc
Agent skill: chart generation (#1103)
* WIP agent support

* move agent folder

* wip frontend socket

* checkpoint

* fix schema

* Checkpoint for plugins and AgentHandler

* refactor plugins and agent arch

* agent error reporting and handling

* add frontend elements for agents in prompt input

* WIP integrations for agents

* enable web-search agent config from frontend

* persist chat history

* update alert

* update migration
remove console logs
update close state for agent invocations

* add examples to dockerignore
Extract statusResponse to its own component

* update close method

* wrap scraping rejections

* add RAG search as funciton

* Add telem and link highlight

* chat support

* patch memory

* Add rechart as a plugin option

* Toggles for abilites of default agent (system wide)
Validate values for agent skills
Enable dynamic loading of skills
UI for toggle of skills

* add UI for toggle of configs for agent

* toggle WS or WSS protocol

* update NGNIX proxy pass

* move components around and capture failed websocket creation

* fix name

* tmp docker image

* reset workflow

* safety mark functions

* telem on tool calls

* remove hardcode short circuit

* separate web-browser from scrape

* extract summarizer to util
add abort handlers and controller for langchain stuff so socket close kills process

* langchain summarize verbose when in dev

* chart styling improvements + add title to chart

* fix legend from being cutoff in chart downloads

* remove cursor blink

---------

Co-authored-by: shatfield4 <seanhatfield5@gmail.com>
2024-04-26 11:18:55 -07:00
Francisco Bischoff eee187ed00
Merge branch 'Mintplex-Labs:master' into feature/devcontv2 2024-03-21 22:50:01 +00:00
Francisco Bischoff e1ac84eb4e
Subject: Centralize prettier ignores and refine
config

Body:
Centralized all prettier ignore rules by removing individual
`.prettierignore` files in subprojects and updating the root
`.prettierignore` to include previously ignored patterns, ensuring
consistency across the workspace. Additionally, the prettier
configuration was refined by making the file pattern for `.config.js`
files consistent and adjusting quote styles for better readability. All
lint scripts across the project were updated to respect the centralized
ignore path, enhancing maintainability.

The consolidation simplifies the process of managing ignore rules as the
project scales, ensuring developers can focus on writing code without
worrying about divergent formatting standards. These changes also align
with introducing comprehensive linting across multiple environments to
keep the codebase clean and consistent.

This adjustment is a foundational step towards a more streamlined and
unified code base, making it easier for new contributors to adhere to
established coding standards and reducing the cognitive load associated
with managing multiple configuration files across the project.
2024-03-01 00:07:29 +00:00
Francisco Bischoff bc0d95f092
Merge branch 'Mintplex-Labs:master' into feature/devcontv2 2024-02-29 22:19:42 +00:00
Francisco Bischoff 8fb66218f7
Merge branch 'Mintplex-Labs:master' into feature/devcontv2 2024-02-19 01:45:48 +00:00
Francisco Bischoff 2f6f73157f
Merge branch 'Mintplex-Labs:master' into feature/devcontv2 2024-02-08 03:08:12 +00:00
Francisco Bischoff fd49edba70
Merge branch 'Mintplex-Labs:master' into feature/devcontv2 2024-01-18 23:37:28 +00:00
Francisco Bischoff 43c6618372
Updated apt-packages source for devcontainer
Switched the devcontainer's package source to a different repository to
align with updated dependencies and package availability. The previous
source from 'rocker-org' is replaced with 'devcontainers-contrib', which
may offer more recent or relevant development tools.
2024-01-13 23:00:33 +00:00
38 changed files with 1611 additions and 101 deletions

View File

@ -22,7 +22,7 @@
// Terraform support
"ghcr.io/devcontainers/features/terraform:1": {},
// Just a wrap to install needed packages
"ghcr.io/rocker-org/devcontainer-features/apt-packages:1": {
"ghcr.io/devcontainers-contrib/features/apt-packages:1": {
// Dependencies copied from ../docker/Dockerfile plus some dev stuff
"packages": [
"build-essential",

View File

@ -10,3 +10,7 @@ frontend/bundleinspector.html
#server
server/swagger/openapi.json
#embed
**/static/**
embed/src/utils/chat/hljs.js

View File

@ -17,7 +17,7 @@
}
},
{
"files": "*.config.js",
"files": ["*.config.js"],
"options": {
"semi": false,
"parser": "flow",

View File

@ -5,7 +5,10 @@
"aibitat",
"anythingllm",
"Astra",
"Chartable",
"comkey",
"cooldown",
"cooldowns",
"Deduplicator",
"Dockerized",
"Embeddable",

View File

@ -12,7 +12,7 @@
"scripts": {
"dev": "NODE_ENV=development nodemon --ignore hotdir --ignore storage --trace-warnings index.js",
"start": "NODE_ENV=production node index.js",
"lint": "yarn prettier --write ./processSingleFile ./processLink ./utils index.js"
"lint": "yarn prettier --ignore-path ../.prettierignore --write ./processSingleFile ./processLink ./utils index.js"
},
"dependencies": {
"@googleapis/youtube": "^9.0.0",

View File

@ -1,9 +0,0 @@
# defaults
**/.git
**/.svn
**/.hg
**/node_modules
**/dist
**/static/**
src/utils/chat/hljs.js

View File

@ -4,9 +4,7 @@
"target": "esnext",
"jsx": "react",
"paths": {
"@/*": [
"./src/*"
],
}
}
}
"@/*": ["./src/*"],
},
},
}

View File

@ -1,6 +1,7 @@
{
"name": "anythingllm-embedded-chat",
"private": false,
"license": "MIT",
"type": "module",
"scripts": {
"dev": "nodemon -e js,jsx,css --watch src --exec \"yarn run dev:preview\"",
@ -8,7 +9,7 @@
"dev:build": "vite build && cat src/static/tailwind@3.4.1.js >> dist/anythingllm-chat-widget.js",
"build": "vite build && cat src/static/tailwind@3.4.1.js >> dist/anythingllm-chat-widget.js && npx terser --compress -o dist/anythingllm-chat-widget.min.js -- dist/anythingllm-chat-widget.js",
"build:publish": "yarn build && mkdir -p ../frontend/public/embed && cp -r dist/anythingllm-chat-widget.min.js ../frontend/public/embed/anythingllm-chat-widget.min.js",
"lint": "yarn prettier --write ./src"
"lint": "yarn prettier --ignore-path ../.prettierignore --write ./src"
},
"dependencies": {
"@microsoft/fetch-event-source": "^2.0.1",

View File

@ -38,7 +38,7 @@ export default defineConfig({
rollupOptions: {
external: [
// Reduces transformation time by 50% and we don't even use this variant, so we can ignore.
/@phosphor-icons\/react\/dist\/ssr/,
/@phosphor-icons\/react\/dist\/ssr/
]
},
commonjsOptions: {
@ -51,7 +51,7 @@ export default defineConfig({
emptyOutDir: true,
inlineDynamicImports: true,
assetsDir: "",
sourcemap: 'inline',
sourcemap: "inline"
},
optimizeDeps: {
esbuildOptions: {
@ -60,5 +60,5 @@ export default defineConfig({
},
plugins: []
}
},
}
})

View File

@ -4,9 +4,7 @@
"target": "esnext",
"jsx": "react",
"paths": {
"@/*": [
"./src/*"
],
"@/*": ["./src/*"]
}
}
}
}

View File

@ -7,13 +7,14 @@
"start": "vite --open",
"dev": "NODE_ENV=development vite --debug --host=0.0.0.0",
"build": "vite build",
"lint": "yarn prettier --write ./src",
"lint": "yarn prettier --ignore-path ../.prettierignore --write ./src",
"preview": "vite preview"
},
"dependencies": {
"@metamask/jazzicon": "^2.0.0",
"@microsoft/fetch-event-source": "^2.0.1",
"@phosphor-icons/react": "^2.0.13",
"@tremor/react": "^3.15.1",
"dompurify": "^3.0.8",
"file-saver": "^2.0.5",
"he": "^1.2.0",
@ -30,6 +31,8 @@
"react-tag-input-component": "^2.0.2",
"react-toastify": "^9.1.3",
"react-tooltip": "^5.25.2",
"recharts": "^2.12.5",
"recharts-to-png": "^2.3.1",
"text-case": "^1.0.9",
"truncate": "^3.0.0",
"uuid": "^9.0.0"

View File

@ -261,8 +261,8 @@ function Directory({
)}
</div>
{amountSelected !== 0 && (
<div className="absolute bottom-[12px] left-0 right-0 flex justify-center">
<div className="mx-auto bg-white/40 rounded-lg py-1 px-2">
<div className="absolute bottom-[12px] left-0 right-0 flex justify-center pointer-events-none">
<div className="mx-auto bg-white/40 rounded-lg py-1 px-2 pointer-events-auto">
<div className="flex flex-row items-center gap-x-2">
<button
onClick={moveToWorkspace}
@ -306,6 +306,7 @@ function Directory({
workspace={workspace}
fetchKeys={fetchKeys}
setLoading={setLoading}
setLoadingMessage={setLoadingMessage}
/>
</div>
</div>

View File

@ -7,18 +7,37 @@ import PreLoader from "../../../../../Preloader";
function FileUploadProgressComponent({
slug,
uuid,
file,
setFiles,
rejected = false,
reason = null,
onUploadSuccess,
onUploadError,
setLoading,
setLoadingMessage,
}) {
const [timerMs, setTimerMs] = useState(10);
const [status, setStatus] = useState("pending");
const [error, setError] = useState("");
const [isFadingOut, setIsFadingOut] = useState(false);
const fadeOut = (cb) => {
setIsFadingOut(true);
cb?.();
};
const beginFadeOut = () => {
setIsFadingOut(false);
setFiles((prev) => {
return prev.filter((item) => item.uid !== uuid);
});
};
useEffect(() => {
async function uploadFile() {
setLoading(true);
setLoadingMessage("Uploading file...");
const start = Number(new Date());
const formData = new FormData();
formData.append("file", file, file.name);
@ -34,17 +53,28 @@ function FileUploadProgressComponent({
onUploadError(data.error);
setError(data.error);
} else {
setLoading(false);
setLoadingMessage("");
setStatus("complete");
clearInterval(timer);
onUploadSuccess();
}
// Begin fadeout timer to clear uploader queue.
setTimeout(() => {
fadeOut(() => setTimeout(() => beginFadeOut(), 300));
}, 5000);
}
!!file && !rejected && uploadFile();
}, []);
if (rejected) {
return (
<div className="h-14 px-2 py-2 flex items-center gap-x-4 rounded-lg bg-white/5 border border-white/40">
<div
className={`${
isFadingOut ? "file-upload-fadeout" : "file-upload"
} h-14 px-2 py-2 flex items-center gap-x-4 rounded-lg bg-white/5 border border-white/40`}
>
<div className="w-6 h-6 flex-shrink-0">
<XCircle className="w-6 h-6 stroke-white bg-red-500 rounded-full p-1 w-full h-full" />
</div>
@ -60,7 +90,11 @@ function FileUploadProgressComponent({
if (status === "failed") {
return (
<div className="h-14 px-2 py-2 flex items-center gap-x-4 rounded-lg bg-white/5 border border-white/40 overflow-y-auto">
<div
className={`${
isFadingOut ? "file-upload-fadeout" : "file-upload"
} h-14 px-2 py-2 flex items-center gap-x-4 rounded-lg bg-white/5 border border-white/40 overflow-y-auto`}
>
<div className="w-6 h-6 flex-shrink-0">
<XCircle className="w-6 h-6 stroke-white bg-red-500 rounded-full p-1 w-full h-full" />
</div>
@ -75,7 +109,11 @@ function FileUploadProgressComponent({
}
return (
<div className="h-14 px-2 py-2 flex items-center gap-x-4 rounded-lg bg-white/5 border border-white/40">
<div
className={`${
isFadingOut ? "file-upload-fadeout" : "file-upload"
} h-14 px-2 py-2 flex items-center gap-x-4 rounded-lg bg-white/5 border border-white/40`}
>
<div className="w-6 h-6 flex-shrink-0">
{status !== "complete" ? (
<div className="flex items-center justify-center">

View File

@ -6,8 +6,14 @@ import { useDropzone } from "react-dropzone";
import { v4 } from "uuid";
import FileUploadProgress from "./FileUploadProgress";
import Workspace from "../../../../../models/workspace";
import debounce from "lodash.debounce";
export default function UploadFile({ workspace, fetchKeys, setLoading }) {
export default function UploadFile({
workspace,
fetchKeys,
setLoading,
setLoadingMessage,
}) {
const [ready, setReady] = useState(false);
const [files, setFiles] = useState([]);
const [fetchingUrl, setFetchingUrl] = useState(false);
@ -15,6 +21,7 @@ export default function UploadFile({ workspace, fetchKeys, setLoading }) {
const handleSendLink = async (e) => {
e.preventDefault();
setLoading(true);
setLoadingMessage("Scraping link...");
setFetchingUrl(true);
const formEl = e.target;
const form = new FormData(formEl);
@ -33,14 +40,9 @@ export default function UploadFile({ workspace, fetchKeys, setLoading }) {
setFetchingUrl(false);
};
const handleUploadSuccess = () => {
fetchKeys(true);
showToast("File uploaded successfully", "success", { clear: true });
};
const handleUploadError = (message) => {
showToast(`Error uploading file: ${message}`, "error");
};
// Don't spam fetchKeys, wait 1s between calls at least.
const handleUploadSuccess = debounce(() => fetchKeys(true), 1000);
const handleUploadError = (_msg) => null; // stubbed.
const onDrop = async (acceptedFiles, rejections) => {
const newAccepted = acceptedFiles.map((file) => {
@ -109,11 +111,15 @@ export default function UploadFile({ workspace, fetchKeys, setLoading }) {
<FileUploadProgress
key={file.uid}
file={file.file}
uuid={file.uid}
setFiles={setFiles}
slug={workspace.slug}
rejected={file?.rejected}
reason={file?.reason}
onUploadSuccess={handleUploadSuccess}
onUploadError={handleUploadError}
setLoading={setLoading}
setLoadingMessage={setLoadingMessage}
/>
))}
</div>

View File

@ -0,0 +1,50 @@
export default function CustomCell({ ...props }) {
const {
root,
depth,
x,
y,
width,
height,
index,
payload,
colors,
rank,
name,
} = props;
return (
<g>
<rect
x={x}
y={y}
width={width}
height={height}
style={{
fill:
depth < 2
? colors[Math.floor((index / root.children.length) * 6)]
: "#ffffff00",
stroke: "#fff",
strokeWidth: 2 / (depth + 1e-10),
strokeOpacity: 1 / (depth + 1e-10),
}}
/>
{depth === 1 ? (
<text
x={x + width / 2}
y={y + height / 2 + 7}
textAnchor="middle"
fill="#fff"
fontSize={14}
>
{name}
</text>
) : null}
{depth === 1 ? (
<text x={x + 4} y={y + 18} fill="#fff" fontSize={16} fillOpacity={0.9}>
{index + 1}
</text>
) : null}
</g>
);
}

View File

@ -0,0 +1,89 @@
import { Tooltip as RechartsTooltip } from "recharts";
// Given a hex, convert to the opposite highest-contrast color
// and if `bw` is enabled, force it to be black/white to normalize
// interface.
function invertColor(hex, bw) {
if (hex.indexOf("#") === 0) {
hex = hex.slice(1);
}
// convert 3-digit hex to 6-digits.
if (hex.length === 3) {
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
}
if (hex.length !== 6) {
throw new Error("Invalid HEX color.");
}
var r = parseInt(hex.slice(0, 2), 16),
g = parseInt(hex.slice(2, 4), 16),
b = parseInt(hex.slice(4, 6), 16);
if (bw) {
// https://stackoverflow.com/a/3943023/112731
return r * 0.299 + g * 0.587 + b * 0.114 > 186 ? "#FFFFFF" : "#000000";
// : '#FFFFFF';
}
// invert color components
r = (255 - r).toString(16);
g = (255 - g).toString(16);
b = (255 - b).toString(16);
// pad each with zeros and return
return "#" + padZero(r) + padZero(g) + padZero(b);
}
function padZero(str, len) {
len = len || 2;
var zeros = new Array(len).join("0");
return (zeros + str).slice(-len);
}
export default function Tooltip({ legendColor, ...props }) {
return (
<RechartsTooltip
wrapperStyle={{ outline: "none" }}
isAnimationActive={false}
cursor={{ fill: "#d1d5db", opacity: "0.15" }}
position={{ y: 0 }}
{...props}
content={({ active, payload, label }) => {
return active && payload ? (
<div className="bg-white text-sm rounded-md border shadow-lg">
<div className="border-b py-2 px-4">
<p className="text-elem text-gray-700 font-medium">{label}</p>
</div>
<div className="space-y-1 py-2 px-4">
{payload.map(({ value, name }, idx) => (
<div
key={`id-${idx}`}
className="flex items-center justify-between space-x-8"
>
<div className="flex items-center space-x-2">
<span
className="shrink-0 h-3 w-3 border-white rounded-md rounded-full border-2 shadow-md"
style={{ backgroundColor: legendColor }}
/>
<p
style={{
color: invertColor(legendColor, true),
}}
className="font-medium tabular-nums text-right whitespace-nowrap"
>
{value}
</p>
</div>
<p
style={{
color: invertColor(legendColor, true),
}}
className="whitespace-nowrap font-normal"
>
{name}
</p>
</div>
))}
</div>
</div>
) : null;
}}
/>
);
}

View File

@ -0,0 +1,98 @@
export const Colors = {
blue: "#3b82f6",
sky: "#0ea5e9",
cyan: "#06b6d4",
teal: "#14b8a6",
emerald: "#10b981",
green: "#22c55e",
lime: "#84cc16",
yellow: "#eab308",
amber: "#f59e0b",
orange: "#f97316",
red: "#ef4444",
rose: "#f43f5e",
pink: "#ec4899",
fuchsia: "#d946ef",
purple: "#a855f7",
violet: "#8b5cf6",
indigo: "#6366f1",
neutral: "#737373",
stone: "#78716c",
gray: "#6b7280",
slate: "#64748b",
zinc: "#71717a",
};
export function getTremorColor(color) {
switch (color) {
case "blue":
return Colors.blue;
case "sky":
return Colors.sky;
case "cyan":
return Colors.cyan;
case "teal":
return Colors.teal;
case "emerald":
return Colors.emerald;
case "green":
return Colors.green;
case "lime":
return Colors.lime;
case "yellow":
return Colors.yellow;
case "amber":
return Colors.amber;
case "orange":
return Colors.orange;
case "red":
return Colors.red;
case "rose":
return Colors.rose;
case "pink":
return Colors.pink;
case "fuchsia":
return Colors.fuchsia;
case "purple":
return Colors.purple;
case "violet":
return Colors.violet;
case "indigo":
return Colors.indigo;
case "neutral":
return Colors.neutral;
case "stone":
return Colors.stone;
case "gray":
return Colors.gray;
case "slate":
return Colors.slate;
case "zinc":
return Colors.zinc;
}
}
export const themeColorRange = [
"slate",
"gray",
"zinc",
"neutral",
"stone",
"red",
"orange",
"amber",
"yellow",
"lime",
"green",
"emerald",
"teal",
"cyan",
"sky",
"blue",
"indigo",
"violet",
"purple",
"fuchsia",
"pink",
"rose",
];

View File

@ -0,0 +1,467 @@
import { v4 } from "uuid";
import {
AreaChart,
BarChart,
DonutChart,
Legend,
LineChart,
} from "@tremor/react";
import {
Bar,
CartesianGrid,
ComposedChart,
Funnel,
FunnelChart,
Line,
PolarAngleAxis,
PolarGrid,
PolarRadiusAxis,
Radar,
RadarChart,
RadialBar,
RadialBarChart,
Scatter,
ScatterChart,
Treemap,
XAxis,
YAxis,
} from "recharts";
import { Colors, getTremorColor } from "./chart-utils.js";
import CustomCell from "./CustomCell.jsx";
import Tooltip from "./CustomTooltip.jsx";
import { safeJsonParse } from "@/utils/request.js";
import renderMarkdown from "@/utils/chat/markdown.js";
import { WorkspaceProfileImage } from "../PromptReply/index.jsx";
import { memo, useCallback, useState } from "react";
import { saveAs } from "file-saver";
import { useGenerateImage } from "recharts-to-png";
import { CircleNotch, DownloadSimple } from "@phosphor-icons/react";
const dataFormatter = (number) => {
return Intl.NumberFormat("us").format(number).toString();
};
export function Chartable({ props, workspace }) {
const [getDivJpeg, { ref }] = useGenerateImage({
quality: 1,
type: "image/jpeg",
options: {
backgroundColor: "#393d43",
padding: 20,
},
});
const handleDownload = useCallback(async () => {
const jpeg = await getDivJpeg();
if (jpeg) saveAs(jpeg, `chart-${v4().split("-")[0]}.jpg`);
}, []);
const color = null;
const showLegend = true;
const content =
typeof props.content === "string"
? safeJsonParse(props.content, null)
: props.content;
if (content === null) return null;
const chartType = content?.type?.toLowerCase();
const data =
typeof content.dataset === "string"
? safeJsonParse(content.dataset, null)
: content.dataset;
const value = data.length > 0 ? Object.keys(data[0])[1] : "value";
const title = content?.title;
const renderChart = () => {
switch (chartType) {
case "area":
return (
<div className="bg-zinc-900 p-8 rounded-xl text-white">
<h3 className="text-lg font-medium">{title}</h3>
<AreaChart
className="h-[350px]"
data={data}
index="name"
categories={[value]}
colors={[color || "blue", "cyan"]}
showLegend={showLegend}
valueFormatter={dataFormatter}
/>
</div>
);
case "bar":
return (
<div className="bg-zinc-900 p-8 rounded-xl text-white">
<h3 className="text-lg font-medium">{title}</h3>
<BarChart
className="h-[350px]"
data={data}
index="name"
categories={[value]}
colors={[color || "blue"]}
showLegend={showLegend}
valueFormatter={dataFormatter}
layout={"vertical"}
yAxisWidth={100}
/>
</div>
);
case "line":
return (
<div className="bg-zinc-900 p-8 pb-12 rounded-xl text-white h-[500px]">
<h3 className="text-lg font-medium">{title}</h3>
<LineChart
className="h-[400px]"
data={data}
index="name"
categories={[value]}
colors={[color || "blue"]}
showLegend={showLegend}
valueFormatter={dataFormatter}
/>
</div>
);
case "composed":
return (
<div className="bg-zinc-900 p-8 rounded-xl text-white">
<h3 className="text-lg font-medium">{title}</h3>
{showLegend && (
<Legend
categories={[value]}
colors={[color || "blue", color || "blue"]}
className="mb-5 justify-end"
/>
)}
<ComposedChart width={500} height={260} data={data}>
<CartesianGrid
strokeDasharray="3 3"
horizontal
vertical={false}
/>
<XAxis
dataKey="name"
tickLine={false}
axisLine={false}
interval="preserveStartEnd"
tick={{ transform: "translate(0, 6)", fill: "white" }}
style={{
fontSize: "12px",
fontFamily: "Inter; Helvetica",
}}
padding={{ left: 10, right: 10 }}
/>
<YAxis
tickLine={false}
axisLine={false}
type="number"
tick={{ transform: "translate(-3, 0)", fill: "white" }}
style={{
fontSize: "12px",
fontFamily: "Inter; Helvetica",
}}
/>
<Tooltip legendColor={getTremorColor(color || "blue")} />
<Line
type="linear"
dataKey={value}
stroke={getTremorColor(color || "blue")}
dot={false}
strokeWidth={2}
/>
<Bar
dataKey="value"
name="value"
type="linear"
fill={getTremorColor(color || "blue")}
/>
</ComposedChart>
</div>
);
case "scatter":
return (
<div className="bg-zinc-900 p-8 rounded-xl text-white">
<h3 className="text-lg font-medium">{title}</h3>
{showLegend && (
<div className="flex justify-end">
<Legend
categories={[value]}
colors={[color || "blue", color || "blue"]}
className="mb-5"
/>
</div>
)}
<ScatterChart width={500} height={260} data={data}>
<CartesianGrid
strokeDasharray="3 3"
horizontal
vertical={false}
/>
<XAxis
dataKey="name"
tickLine={false}
axisLine={false}
interval="preserveStartEnd"
tick={{ transform: "translate(0, 6)", fill: "white" }}
style={{
fontSize: "12px",
fontFamily: "Inter; Helvetica",
}}
padding={{ left: 10, right: 10 }}
/>
<YAxis
tickLine={false}
axisLine={false}
type="number"
tick={{ transform: "translate(-3, 0)", fill: "white" }}
style={{
fontSize: "12px",
fontFamily: "Inter; Helvetica",
}}
/>
<Tooltip legendColor={getTremorColor(color || "blue")} />
<Scatter dataKey={value} fill={getTremorColor(color || "blue")} />
</ScatterChart>
</div>
);
case "pie":
return (
<div className="bg-zinc-900 p-8 rounded-xl text-white">
<h3 className="text-lg font-medium">{title}</h3>
<DonutChart
data={data}
category={value}
index="name"
colors={[
color || "cyan",
"violet",
"rose",
"amber",
"emerald",
"teal",
"fuchsia",
]}
// No actual legend for pie chart, but this will toggle the central text
showLabel={showLegend}
valueFormatter={dataFormatter}
customTooltip={customTooltip}
/>
</div>
);
case "radar":
return (
<div className="bg-zinc-900 p-8 rounded-xl text-white">
<h3 className="text-lg font-medium">{title}</h3>
{showLegend && (
<div className="flex justify-end">
<Legend
categories={[value]}
colors={[color || "blue", color || "blue"]}
className="mb-5"
/>
</div>
)}
<RadarChart
cx={300}
cy={250}
outerRadius={150}
width={600}
height={500}
data={data}
>
<PolarGrid />
<PolarAngleAxis dataKey="name" tick={{ fill: "white" }} />
<PolarRadiusAxis tick={{ fill: "white" }} />
<Tooltip legendColor={getTremorColor(color || "blue")} />
<Radar
dataKey="value"
stroke={getTremorColor(color || "blue")}
fill={getTremorColor(color || "blue")}
fillOpacity={0.6}
/>
</RadarChart>
</div>
);
case "radialbar":
return (
<div className="bg-zinc-900 p-8 rounded-xl text-white">
<h3 className="text-lg font-medium">{title}</h3>
{showLegend && (
<div className="flex justify-end">
<Legend
categories={[value]}
colors={[color || "blue", color || "blue"]}
className="mb-5"
/>
</div>
)}
<RadialBarChart
width={500}
height={300}
cx={150}
cy={150}
innerRadius={20}
outerRadius={140}
barSize={10}
data={data}
>
<RadialBar
angleAxisId={15}
label={{
position: "insideStart",
fill: getTremorColor(color || "blue"),
}}
dataKey="value"
/>
<Tooltip legendColor={getTremorColor(color || "blue")} />
</RadialBarChart>
</div>
);
case "treemap":
return (
<div className="bg-zinc-900 p-8 rounded-xl text-white">
<h3 className="text-lg font-medium">{title}</h3>
{showLegend && (
<div className="flex justify-end">
<Legend
categories={[value]}
colors={[color || "blue", color || "blue"]}
className="mb-5"
/>
</div>
)}
<Treemap
width={500}
height={260}
data={data}
dataKey="value"
stroke="#fff"
fill={getTremorColor(color || "blue")}
content={<CustomCell colors={Object.values(Colors)} />}
>
<Tooltip legendColor={getTremorColor(color || "blue")} />
</Treemap>
</div>
);
case "funnel":
return (
<div className="bg-zinc-900 p-8 rounded-xl text-white">
<h3 className="text-lg font-medium">{title}</h3>
{showLegend && (
<div className="flex justify-end">
<Legend
categories={[value]}
colors={[color || "blue", color || "blue"]}
className="mb-5"
/>
</div>
)}
<FunnelChart width={500} height={300} data={data}>
<Tooltip legendColor={getTremorColor(color || "blue")} />
<Funnel dataKey="value" color={getTremorColor(color || "blue")} />
</FunnelChart>
</div>
);
default:
return <p>Unsupported chart type.</p>;
}
};
if (!!props.chatId) {
return (
<div className="flex justify-center items-end w-full">
<div className="py-2 px-4 w-full flex gap-x-5 md:max-w-[800px] flex-col">
<div className="flex gap-x-5">
<WorkspaceProfileImage workspace={workspace} />
<div className="relative">
<DownloadGraph onClick={handleDownload} />
<div ref={ref}>{renderChart()}</div>
<span
className={`flex flex-col gap-y-1 mt-2`}
dangerouslySetInnerHTML={{
__html: renderMarkdown(content.caption),
}}
/>
</div>
</div>
</div>
</div>
);
}
return (
<div className="flex justify-center items-end w-full">
<div className="py-2 px-4 w-full flex gap-x-5 md:max-w-[800px] flex-col">
<div className="relative">
<DownloadGraph onClick={handleDownload} />
<div ref={ref}>{renderChart()}</div>
</div>
<div className="flex gap-x-5">
<span
className={`flex flex-col gap-y-1 mt-2`}
dangerouslySetInnerHTML={{
__html: renderMarkdown(content.caption),
}}
/>
</div>
</div>
</div>
);
}
const customTooltip = (props) => {
const { payload, active } = props;
if (!active || !payload) return null;
const categoryPayload = payload?.[0];
if (!categoryPayload) return null;
return (
<div className="w-56 bg-zinc-400 rounded-lg border p-2 text-white">
<div className="flex flex-1 space-x-2.5">
<div
className={`flex w-1.5 flex-col bg-${categoryPayload?.color}-500 rounded`}
/>
<div className="w-full">
<div className="flex items-center justify-between space-x-8">
<p className="whitespace-nowrap text-right text-tremor-content">
{categoryPayload.name}
</p>
<p className="whitespace-nowrap text-right font-medium text-tremor-content-emphasis">
{categoryPayload.value}
</p>
</div>
</div>
</div>
</div>
);
};
function DownloadGraph({ onClick }) {
const [loading, setLoading] = useState(false);
const handleClick = async () => {
setLoading(true);
await onClick?.();
setLoading(false);
};
return (
<div className="absolute top-3 right-3 z-50 cursor-pointer">
<div className="flex flex-col items-center">
<div className="p-1 rounded-full border-none">
{loading ? (
<CircleNotch
className="text-white/50 w-5 h-5 animate-spin"
aria-label="Downloading image..."
/>
) : (
<DownloadSimple
weight="bold"
className="text-white/50 w-5 h-5 hover:text-white"
onClick={handleClick}
aria-label="Download graph image"
/>
)}
</div>
</div>
</div>
);
}
export default memo(Chartable);

View File

@ -71,7 +71,7 @@ const PromptReply = ({
);
};
function WorkspaceProfileImage({ workspace }) {
export function WorkspaceProfileImage({ workspace }) {
if (!!workspace.pfpUrl) {
return (
<div className="relative w-[35px] h-[35px] rounded-full flex-shrink-0 overflow-hidden">

View File

@ -6,6 +6,7 @@ import ManageWorkspace from "../../../Modals/MangeWorkspace";
import { ArrowDown } from "@phosphor-icons/react";
import debounce from "lodash.debounce";
import useUser from "@/hooks/useUser";
import Chartable from "./Chartable";
export default function ChatHistory({ history = [], workspace, sendCommand }) {
const { user } = useUser();
@ -133,6 +134,12 @@ export default function ChatHistory({ history = [], workspace, sendCommand }) {
return <StatusResponse key={props.uuid} props={props} />;
}
if (props.type === "rechartVisualize" && !!props.content) {
return (
<Chartable key={props.uuid} workspace={workspace} props={props} />
);
}
if (isLastBotReply && props.animate) {
return (
<PromptReply

View File

@ -89,6 +89,7 @@ export function AvailableAgents({
<AbilityTag text="save-file-to-browser" />
<AbilityTag text="list-documents" />
<AbilityTag text="summarize-document" />
<AbilityTag text="chart-generation" />
</div>
</div>
</button>

View File

@ -679,3 +679,66 @@ does not extend the close button beyond the viewport. */
.white-scrollbar::-webkit-scrollbar-thumb:hover {
background-color: #cccccc;
}
/* Recharts rendering styles */
.recharts-text > * {
fill: #fff;
}
.recharts-legend-wrapper {
margin-bottom: 10px;
}
.text-tremor-content {
padding-bottom: 10px;
}
.file-upload {
-webkit-animation: fadein 0.3s linear forwards;
animation: fadein 0.3s linear forwards;
}
.file-upload-fadeout {
-webkit-animation: fadeout 0.3s linear forwards;
animation: fadeout 0.3s linear forwards;
}
@-webkit-keyframes fadein {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes fadein {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@-webkit-keyframes fadeout {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
@keyframes fadeout {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}

View File

@ -170,7 +170,7 @@ function AvailableAgentSkills({ skills, settings, toggleAgentSkill }) {
disabled={true}
/>
<GenericSkill
title="View and summarize documents"
title="View & summarize documents"
description="Allow the agent to list and summarize the content of workspace files currently embedded."
settings={settings}
enabled={true}
@ -183,6 +183,14 @@ function AvailableAgentSkills({ skills, settings, toggleAgentSkill }) {
enabled={true}
disabled={true}
/>
<GenericSkill
title="Generate charts"
description="Enable the default agent to generate various types of charts from data provided or given in chat."
skill="create-chart"
settings={settings}
toggleSkill={toggleAgentSkill}
enabled={skills.includes("create-chart")}
/>
<GenericSkill
title="Generate & save files to browser"
description="Enable the default agent to generate and write to files that save and can be downloaded in your browser."

View File

@ -11,6 +11,7 @@ const handledEvents = [
"fileDownload",
"awaitingFeedback",
"wssFailure",
"rechartVisualize",
];
export function websocketURI() {
@ -50,6 +51,25 @@ export default function handleSocketResponse(event, setChatHistory) {
return;
}
if (data.type === "rechartVisualize") {
return setChatHistory((prev) => {
return [
...prev.filter((msg) => !!msg.content),
{
type: "rechartVisualize",
uuid: v4(),
content: data.content,
role: "assistant",
sources: [],
closed: true,
error: null,
animate: false,
pending: false,
},
];
});
}
if (data.type === "wssFailure") {
return setChatHistory((prev) => {
return [

View File

@ -1,5 +1,6 @@
/** @type {import('tailwindcss').Config} */
export default {
darkMode: 'false',
content: {
relative: true,
files: [
@ -9,7 +10,8 @@ export default {
"./src/pages/**/*.{js,jsx}",
"./src/utils/**/*.js",
"./src/*.jsx",
"./index.html"
"./index.html",
'./node_modules/@tremor/**/*.{js,ts,jsx,tsx}'
]
},
theme: {
@ -86,5 +88,35 @@ export default {
}
}
},
// Required for rechart styles to show since they can be rendered dynamically and will be tree-shaken if not safe-listed.
safelist: [
{
pattern:
/^(bg-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(?:50|100|200|300|400|500|600|700|800|900|950))$/,
variants: ['hover', 'ui-selected'],
},
{
pattern:
/^(text-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(?:50|100|200|300|400|500|600|700|800|900|950))$/,
variants: ['hover', 'ui-selected'],
},
{
pattern:
/^(border-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(?:50|100|200|300|400|500|600|700|800|900|950))$/,
variants: ['hover', 'ui-selected'],
},
{
pattern:
/^(ring-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(?:50|100|200|300|400|500|600|700|800|900|950))$/,
},
{
pattern:
/^(stroke-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(?:50|100|200|300|400|500|600|700|800|900|950))$/,
},
{
pattern:
/^(fill-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(?:50|100|200|300|400|500|600|700|800|900|950))$/,
},
],
plugins: []
}

View File

@ -51,7 +51,7 @@ export default defineConfig({
rollupOptions: {
external: [
// Reduces transformation time by 50% and we don't even use this variant, so we can ignore.
/@phosphor-icons\/react\/dist\/ssr/,
/@phosphor-icons\/react\/dist\/ssr/
]
},
commonjsOptions: {

View File

@ -184,6 +184,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.22.5"
"@babel/runtime@^7.21.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7":
version "7.24.4"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.4.tgz#de795accd698007a66ba44add6cc86542aff1edd"
integrity sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==
dependencies:
regenerator-runtime "^0.14.0"
"@babel/template@^7.22.15":
version "7.22.15"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38"
@ -365,6 +372,13 @@
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.53.0.tgz#bea56f2ed2b5baea164348ff4d5a879f6f81f20d"
integrity sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==
"@floating-ui/core@^1.0.0":
version "1.6.0"
resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.0.tgz#fa41b87812a16bf123122bf945946bae3fdf7fc1"
integrity sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==
dependencies:
"@floating-ui/utils" "^0.2.1"
"@floating-ui/core@^1.5.3":
version "1.5.3"
resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.5.3.tgz#b6aa0827708d70971c8679a16cf680a515b8a52a"
@ -380,11 +394,48 @@
"@floating-ui/core" "^1.5.3"
"@floating-ui/utils" "^0.2.0"
"@floating-ui/utils@^0.2.0":
"@floating-ui/dom@^1.2.1":
version "1.6.3"
resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.3.tgz#954e46c1dd3ad48e49db9ada7218b0985cee75ef"
integrity sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==
dependencies:
"@floating-ui/core" "^1.0.0"
"@floating-ui/utils" "^0.2.0"
"@floating-ui/react-dom@^1.3.0":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-1.3.0.tgz#4d35d416eb19811c2b0e9271100a6aa18c1579b3"
integrity sha512-htwHm67Ji5E/pROEAr7f8IKFShuiCKHwUC/UY4vC3I5jiSvGFAYnSYiZO5MlGmads+QqvUkR9ANHEguGrDv72g==
dependencies:
"@floating-ui/dom" "^1.2.1"
"@floating-ui/react@^0.19.2":
version "0.19.2"
resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.19.2.tgz#c6e4d2097ed0dca665a7c042ddf9cdecc95e9412"
integrity sha512-JyNk4A0Ezirq8FlXECvRtQOX/iBe5Ize0W/pLkrZjfHW9GUV7Xnq6zm6fyZuQzaHHqEnVizmvlA96e1/CkZv+w==
dependencies:
"@floating-ui/react-dom" "^1.3.0"
aria-hidden "^1.1.3"
tabbable "^6.0.1"
"@floating-ui/utils@^0.2.0", "@floating-ui/utils@^0.2.1":
version "0.2.1"
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.1.tgz#16308cea045f0fc777b6ff20a9f25474dd8293d2"
integrity sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==
"@headlessui/react@^1.7.18":
version "1.7.18"
resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.7.18.tgz#30af4634d2215b2ca1aa29d07f33d02bea82d9d7"
integrity sha512-4i5DOrzwN4qSgNsL4Si61VMkUcWbcSKueUV7sFhpHzQcSShdlHENE5+QBntMSRvHt8NyoFO2AGG8si9lq+w4zQ==
dependencies:
"@tanstack/react-virtual" "^3.0.0-beta.60"
client-only "^0.0.1"
"@headlessui/tailwindcss@^0.2.0":
version "0.2.0"
resolved "https://registry.yarnpkg.com/@headlessui/tailwindcss/-/tailwindcss-0.2.0.tgz#2c55c98fd8eee4b4f21ec6eb35a014b840059eec"
integrity sha512-fpL830Fln1SykOCboExsWr3JIVeQKieLJ3XytLe/tt1A0XzqUthOftDmjcCYLW62w7mQI7wXcoPXr3tZ9QfGxw==
"@humanwhocodes/config-array@^0.11.13":
version "0.11.13"
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.13.tgz#075dc9684f40a531d9b26b0822153c1e832ee297"
@ -492,6 +543,32 @@
resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.14.1.tgz#6d2dd03d52e604279c38911afc1079d58c50a755"
integrity sha512-Qg4DMQsfPNAs88rb2xkdk03N3bjK4jgX5fR24eHCTR9q6PrhZQZ4UJBPzCHJkIpTRN1UKxx2DzjZmnC+7Lj0Ow==
"@tanstack/react-virtual@^3.0.0-beta.60":
version "3.2.1"
resolved "https://registry.yarnpkg.com/@tanstack/react-virtual/-/react-virtual-3.2.1.tgz#58ac9af23ff08b5f05a6dfe6a59deac2f9451508"
integrity sha512-i9Nt0ssIh2bSjomJZlr6Iq5usT/9+ewo2/fKHRNk6kjVKS8jrhXbnO8NEawarCuBx/efv0xpoUUKKGxa0cQb4Q==
dependencies:
"@tanstack/virtual-core" "3.2.1"
"@tanstack/virtual-core@3.2.1":
version "3.2.1"
resolved "https://registry.yarnpkg.com/@tanstack/virtual-core/-/virtual-core-3.2.1.tgz#b3e4214b8f462054501d80e8777068faa139bd06"
integrity sha512-nO0d4vRzsmpBQCJYyClNHPPoUMI4nXNfrm6IcCRL33ncWMoNVpURh9YebEHPw8KrtsP2VSJIHE4gf4XFGk1OGg==
"@tremor/react@^3.15.1":
version "3.15.1"
resolved "https://registry.yarnpkg.com/@tremor/react/-/react-3.15.1.tgz#a9c10887bd067ffe0e18ca763e425db057f3722f"
integrity sha512-vCUqgYo993VePn6yOs4102ibY2XYcDDp7I1ZV/+i5hdfp+XgsHyQvYeixQcETBMpcajwM8E8NOOO7k9ANLkrrw==
dependencies:
"@floating-ui/react" "^0.19.2"
"@headlessui/react" "^1.7.18"
"@headlessui/tailwindcss" "^0.2.0"
date-fns "^2.30.0"
react-day-picker "^8.9.1"
react-transition-state "^2.1.1"
recharts "^2.10.3"
tailwind-merge "^1.14.0"
"@types/babel__core@^7.20.3":
version "7.20.3"
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.3.tgz#d5625a50b6f18244425a1359a858c73d70340778"
@ -525,6 +602,57 @@
dependencies:
"@babel/types" "^7.20.7"
"@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"
integrity sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==
"@types/d3-color@*":
version "3.1.3"
resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-3.1.3.tgz#368c961a18de721da8200e80bf3943fb53136af2"
integrity sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==
"@types/d3-ease@^3.0.0":
version "3.0.2"
resolved "https://registry.yarnpkg.com/@types/d3-ease/-/d3-ease-3.0.2.tgz#e28db1bfbfa617076f7770dd1d9a48eaa3b6c51b"
integrity sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==
"@types/d3-interpolate@^3.0.1":
version "3.0.4"
resolved "https://registry.yarnpkg.com/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz#412b90e84870285f2ff8a846c6eb60344f12a41c"
integrity sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==
dependencies:
"@types/d3-color" "*"
"@types/d3-path@*":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-3.1.0.tgz#2b907adce762a78e98828f0b438eaca339ae410a"
integrity sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==
"@types/d3-scale@^4.0.2":
version "4.0.8"
resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-4.0.8.tgz#d409b5f9dcf63074464bf8ddfb8ee5a1f95945bb"
integrity sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==
dependencies:
"@types/d3-time" "*"
"@types/d3-shape@^3.1.0":
version "3.1.6"
resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-3.1.6.tgz#65d40d5a548f0a023821773e39012805e6e31a72"
integrity sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==
dependencies:
"@types/d3-path" "*"
"@types/d3-time@*", "@types/d3-time@^3.0.0":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-3.0.3.tgz#3c186bbd9d12b9d84253b6be6487ca56b54f88be"
integrity sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==
"@types/d3-timer@^3.0.0":
version "3.0.2"
resolved "https://registry.yarnpkg.com/@types/d3-timer/-/d3-timer-3.0.2.tgz#70bbda77dc23aa727413e22e214afa3f0e852f70"
integrity sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==
"@types/history@^4.7.11":
version "4.7.11"
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64"
@ -651,6 +779,13 @@ argparse@^2.0.1:
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
aria-hidden@^1.1.3:
version "1.2.4"
resolved "https://registry.yarnpkg.com/aria-hidden/-/aria-hidden-1.2.4.tgz#b78e383fdbc04d05762c78b4a25a501e736c4522"
integrity sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==
dependencies:
tslib "^2.0.0"
array-buffer-byte-length@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz#fabe8bc193fea865f317fe7807085ee0dee5aead"
@ -748,6 +883,11 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
base64-arraybuffer@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz#1c37589a7c4b0746e34bd1feb951da2df01c1bdc"
integrity sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==
base64-js@^1.3.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
@ -871,6 +1011,11 @@ classnames@^2.3.0:
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b"
integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==
client-only@^0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1"
integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==
cliui@^8.0.1:
version "8.0.1"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa"
@ -890,6 +1035,11 @@ clsx@^1.1.1:
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12"
integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==
clsx@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.0.tgz#e851283bcb5c80ee7608db18487433f7b23f77cb"
integrity sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==
color-convert@^1.3.0, color-convert@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
@ -954,6 +1104,13 @@ cross-spawn@^7.0.2, cross-spawn@^7.0.3:
shebang-command "^2.0.0"
which "^2.0.1"
css-line-break@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/css-line-break/-/css-line-break-2.1.0.tgz#bfef660dfa6f5397ea54116bb3cb4873edbc4fa0"
integrity sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==
dependencies:
utrie "^1.0.2"
cssesc@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
@ -964,6 +1121,84 @@ csstype@^3.0.2:
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b"
integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==
"d3-array@2 - 3", "d3-array@2.10.0 - 3", d3-array@^3.1.6:
version "3.2.4"
resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.4.tgz#15fec33b237f97ac5d7c986dc77da273a8ed0bb5"
integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==
dependencies:
internmap "1 - 2"
"d3-color@1 - 3":
version "3.1.0"
resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2"
integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==
d3-ease@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4"
integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==
"d3-format@1 - 3":
version "3.1.0"
resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641"
integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==
"d3-interpolate@1.2.0 - 3", d3-interpolate@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d"
integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==
dependencies:
d3-color "1 - 3"
d3-path@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.1.0.tgz#22df939032fb5a71ae8b1800d61ddb7851c42526"
integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==
d3-scale@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396"
integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==
dependencies:
d3-array "2.10.0 - 3"
d3-format "1 - 3"
d3-interpolate "1.2.0 - 3"
d3-time "2.1.1 - 3"
d3-time-format "2 - 4"
d3-shape@^3.1.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-3.2.0.tgz#a1a839cbd9ba45f28674c69d7f855bcf91dfc6a5"
integrity sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==
dependencies:
d3-path "^3.1.0"
"d3-time-format@2 - 4":
version "4.1.0"
resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a"
integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==
dependencies:
d3-time "1 - 3"
"d3-time@1 - 3", "d3-time@2.1.1 - 3", d3-time@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.1.0.tgz#9310db56e992e3c0175e1ef385e545e48a9bb5c7"
integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==
dependencies:
d3-array "2 - 3"
d3-timer@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0"
integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==
date-fns@^2.30.0:
version "2.30.0"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0"
integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==
dependencies:
"@babel/runtime" "^7.21.0"
debug@^4.1.0, debug@^4.1.1, debug@^4.3.2:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
@ -971,6 +1206,11 @@ debug@^4.1.0, debug@^4.1.1, debug@^4.3.2:
dependencies:
ms "2.1.2"
decimal.js-light@^2.4.1:
version "2.5.1"
resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz#134fd32508f19e208f4fb2f8dac0d2626a867934"
integrity sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==
deep-is@^0.1.3:
version "0.1.4"
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
@ -1046,6 +1286,14 @@ doctrine@^3.0.0:
dependencies:
esutils "^2.0.2"
dom-helpers@^5.0.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902"
integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==
dependencies:
"@babel/runtime" "^7.8.7"
csstype "^3.0.2"
dompurify@^3.0.8:
version "3.0.8"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.0.8.tgz#e0021ab1b09184bc8af7e35c7dd9063f43a8a437"
@ -1342,6 +1590,11 @@ esutils@^2.0.2:
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
eventemitter3@^4.0.1:
version "4.0.7"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
execa@^5.0.0:
version "5.1.1"
resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd"
@ -1382,6 +1635,11 @@ fast-diff@^1.1.2:
resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0"
integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==
fast-equals@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/fast-equals/-/fast-equals-5.0.1.tgz#a4eefe3c5d1c0d021aeed0bc10ba5e0c12ee405d"
integrity sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==
fast-glob@^3.3.0:
version "3.3.1"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4"
@ -1698,6 +1956,14 @@ highlight.js@^11.9.0:
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.9.0.tgz#04ab9ee43b52a41a047432c8103e2158a1b8b5b0"
integrity sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==
html2canvas@^1.2.0:
version "1.4.1"
resolved "https://registry.yarnpkg.com/html2canvas/-/html2canvas-1.4.1.tgz#7cef1888311b5011d507794a066041b14669a543"
integrity sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==
dependencies:
css-line-break "^2.1.0"
text-segmentation "^1.0.3"
human-signals@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
@ -1753,6 +2019,11 @@ internal-slot@^1.0.5:
hasown "^2.0.0"
side-channel "^1.0.4"
"internmap@1 - 2":
version "2.0.3"
resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009"
integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==
is-array-buffer@^3.0.1, is-array-buffer@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz#f2653ced8412081638ecb0ebbd0c41c6e0aecbbe"
@ -2483,7 +2754,7 @@ prettier@^3.0.3:
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.0.3.tgz#432a51f7ba422d1469096c0fdc28e235db8f9643"
integrity sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==
prop-types@^15.8.1:
prop-types@^15.6.2, prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@ -2502,6 +2773,11 @@ queue-microtask@^1.2.2:
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
react-day-picker@^8.9.1:
version "8.10.0"
resolved "https://registry.yarnpkg.com/react-day-picker/-/react-day-picker-8.10.0.tgz#729c5b9564967a924213978fb9c0751884a60595"
integrity sha512-mz+qeyrOM7++1NCb1ARXmkjMkzWVh2GL9YiPbRjKe0zHccvekk4HE+0MPOZOrosn8r8zTHIIeOUXTmXRqmkRmg==
react-device-detect@^2.2.2:
version "2.2.3"
resolved "https://registry.yarnpkg.com/react-device-detect/-/react-device-detect-2.2.3.tgz#97a7ae767cdd004e7c3578260f48cf70c036e7ca"
@ -2526,7 +2802,7 @@ react-dropzone@^14.2.3:
file-selector "^0.6.0"
prop-types "^15.8.1"
react-is@^16.13.1:
react-is@^16.10.2, react-is@^16.13.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@ -2556,6 +2832,15 @@ react-router@6.21.1:
dependencies:
"@remix-run/router" "1.14.1"
react-smooth@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/react-smooth/-/react-smooth-4.0.1.tgz#6200d8699bfe051ae40ba187988323b1449eab1a"
integrity sha512-OE4hm7XqR0jNOq3Qmk9mFLyd6p2+j6bvbPJ7qlB7+oo0eNcL2l7WQzG6MBnT3EXY6xzkLMUBec3AfewJdA0J8w==
dependencies:
fast-equals "^5.0.1"
prop-types "^15.8.1"
react-transition-group "^4.4.5"
react-tag-input-component@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/react-tag-input-component/-/react-tag-input-component-2.0.2.tgz#f62f013c6a535141dd1c6c3a88858223170150f1"
@ -2576,6 +2861,21 @@ react-tooltip@^5.25.2:
"@floating-ui/dom" "^1.0.0"
classnames "^2.3.0"
react-transition-group@^4.4.5:
version "4.4.5"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1"
integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==
dependencies:
"@babel/runtime" "^7.5.5"
dom-helpers "^5.0.1"
loose-envify "^1.4.0"
prop-types "^15.6.2"
react-transition-state@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/react-transition-state/-/react-transition-state-2.1.1.tgz#1601a6177926b647041b7d598bf124321ab8d25b"
integrity sha512-kQx5g1FVu9knoz1T1WkapjUgFz08qQ/g1OmuWGi3/AoEFfS0kStxrPlZx81urjCXdz2d+1DqLpU6TyLW/Ro04Q==
react@^18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
@ -2597,6 +2897,34 @@ readdirp@~3.6.0:
dependencies:
picomatch "^2.2.1"
recharts-scale@^0.4.4:
version "0.4.5"
resolved "https://registry.yarnpkg.com/recharts-scale/-/recharts-scale-0.4.5.tgz#0969271f14e732e642fcc5bd4ab270d6e87dd1d9"
integrity sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==
dependencies:
decimal.js-light "^2.4.1"
recharts-to-png@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/recharts-to-png/-/recharts-to-png-2.3.1.tgz#94d4edb8461ba4b16318edea77a34c421c16d7c1"
integrity sha512-a+OaAi03oFJMa+Burf3vyH060iFTrb35W8bBYUatNjZVrrMKUcFM3VOI1ym078WIH7XfgYQb17K9p2spVA2FzQ==
dependencies:
html2canvas "^1.2.0"
recharts@^2.10.3, recharts@^2.12.5:
version "2.12.5"
resolved "https://registry.yarnpkg.com/recharts/-/recharts-2.12.5.tgz#b335eb66173317dccb3e126fce1d7ac5b3cee1e9"
integrity sha512-Cy+BkqrFIYTHJCyKHJEPvbHE2kVQEP6PKbOHJ8ztRGTAhvHuUnCwDaKVb13OwRFZ0QNUk1QvGTDdgWSMbuMtKw==
dependencies:
clsx "^2.0.0"
eventemitter3 "^4.0.1"
lodash "^4.17.21"
react-is "^16.10.2"
react-smooth "^4.0.0"
recharts-scale "^0.4.4"
tiny-invariant "^1.3.1"
victory-vendor "^36.6.8"
reflect.getprototypeof@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz#aaccbf41aca3821b87bb71d9dcbc7ad0ba50a3f3"
@ -2609,6 +2937,11 @@ reflect.getprototypeof@^1.0.4:
globalthis "^1.0.3"
which-builtin-type "^1.1.3"
regenerator-runtime@^0.14.0:
version "0.14.1"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f"
integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==
regexp.prototype.flags@^1.5.0, regexp.prototype.flags@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz#90ce989138db209f81492edd734183ce99f9677e"
@ -2893,6 +3226,16 @@ synckit@^0.8.5:
"@pkgr/utils" "^2.3.1"
tslib "^2.5.0"
tabbable@^6.0.1:
version "6.2.0"
resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.2.0.tgz#732fb62bc0175cfcec257330be187dcfba1f3b97"
integrity sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==
tailwind-merge@^1.14.0:
version "1.14.0"
resolved "https://registry.yarnpkg.com/tailwind-merge/-/tailwind-merge-1.14.0.tgz#e677f55d864edc6794562c63f5001f45093cdb8b"
integrity sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==
tailwindcss@^3.3.1:
version "3.3.5"
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.3.5.tgz#22a59e2fbe0ecb6660809d9cc5f3976b077be3b8"
@ -3031,6 +3374,13 @@ text-path-case@^1.0.2:
dependencies:
text-dot-case "^1.0.2"
text-segmentation@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/text-segmentation/-/text-segmentation-1.0.3.tgz#52a388159efffe746b24a63ba311b6ac9f2d7943"
integrity sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==
dependencies:
utrie "^1.0.2"
text-sentence-case@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/text-sentence-case/-/text-sentence-case-1.0.2.tgz#e692a9aea3c8dcb1fb12242838e0ca3e9a22a90f"
@ -3085,6 +3435,11 @@ thenify-all@^1.0.0:
dependencies:
any-promise "^1.0.0"
tiny-invariant@^1.3.1:
version "1.3.3"
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127"
integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==
titleize@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/titleize/-/titleize-3.0.0.tgz#71c12eb7fdd2558aa8a44b0be83b8a76694acd53"
@ -3112,7 +3467,7 @@ ts-interface-checker@^0.1.9:
resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699"
integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==
tslib@^2.4.0, tslib@^2.5.0, tslib@^2.6.0:
tslib@^2.0.0, tslib@^2.4.0, tslib@^2.5.0, tslib@^2.6.0:
version "2.6.2"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
@ -3213,11 +3568,38 @@ util-deprecate@^1.0.2:
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
utrie@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/utrie/-/utrie-1.0.2.tgz#d42fe44de9bc0119c25de7f564a6ed1b2c87a645"
integrity sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==
dependencies:
base64-arraybuffer "^1.0.2"
uuid@^9.0.0:
version "9.0.1"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30"
integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==
victory-vendor@^36.6.8:
version "36.9.2"
resolved "https://registry.yarnpkg.com/victory-vendor/-/victory-vendor-36.9.2.tgz#668b02a448fa4ea0f788dbf4228b7e64669ff801"
integrity sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==
dependencies:
"@types/d3-array" "^3.0.3"
"@types/d3-ease" "^3.0.0"
"@types/d3-interpolate" "^3.0.1"
"@types/d3-scale" "^4.0.2"
"@types/d3-shape" "^3.1.0"
"@types/d3-time" "^3.0.0"
"@types/d3-timer" "^3.0.0"
d3-array "^3.1.6"
d3-ease "^3.0.1"
d3-interpolate "^3.0.1"
d3-scale "^4.0.2"
d3-shape "^3.1.0"
d3-time "^3.0.0"
d3-timer "^3.0.1"
vite@^4.3.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/vite/-/vite-4.5.0.tgz#ec406295b4167ac3bc23e26f9c8ff559287cff26"

View File

@ -11,7 +11,7 @@
},
"scripts": {
"lint": "cd server && yarn lint && cd ../frontend && yarn lint && cd ../embed && yarn lint && cd ../collector && yarn lint",
"setup": "cd server && yarn && cd ../collector && yarn && cd ../frontend && yarn && cd .. && yarn setup:envs && yarn prisma:setup && echo \"Please run yarn dev:server, yarn dev:collector, and yarn dev:frontend in separate terminal tabs.\"",
"setup": "cd server && yarn && cd ../collector && yarn && cd ../frontend && yarn && cd ../embed && yarn && cd .. && yarn setup:envs && yarn prisma:setup && echo \"Please run yarn dev:server, yarn dev:collector, and yarn dev:frontend in separate terminal tabs.\"",
"setup:envs": "cp -n ./frontend/.env.example ./frontend/.env && cp -n ./server/.env.example ./server/.env.development && cp -n ./collector/.env.example ./collector/.env && cp -n ./docker/.env.example ./docker/.env && echo \"All ENV files copied!\n\"",
"dev:server": "cd server && yarn dev",
"dev:collector": "cd collector && yarn dev",
@ -27,4 +27,4 @@
"generate::gcp_deployment": "node cloud-deployments/gcp/deployment/generate.mjs"
},
"private": false
}
}

View File

@ -2,6 +2,23 @@ const prisma = require("../utils/prisma");
const { EventLogs } = require("./eventLogs");
const User = {
writable: [
// Used for generic updates so we can validate keys in request body
"username",
"password",
"pfpFilename",
"role",
"suspended",
],
// validations for the above writable fields.
castColumnValue: function (key, value) {
switch (key) {
case "suspended":
return Number(Boolean(value));
default:
return String(value);
}
},
create: async function ({ username, password, role = "default" }) {
const passwordCheck = this.checkPasswordComplexity(password);
if (!passwordCheck.checkedOK) {
@ -42,13 +59,26 @@ const User = {
update: async function (userId, updates = {}) {
try {
if (!userId) throw new Error("No user id provided for update");
const currentUser = await prisma.users.findUnique({
where: { id: parseInt(userId) },
});
if (!currentUser) {
return { success: false, error: "User not found" };
}
if (!currentUser) return { success: false, error: "User not found" };
// Removes non-writable fields for generic updates
// and force-casts to the proper type;
Object.entries(updates).forEach(([key, value]) => {
if (this.writable.includes(key)) {
updates[key] = this.castColumnValue(key, value);
return;
}
delete updates[key];
});
if (Object.keys(updates).length === 0)
return { success: false, error: "No valid updates applied." };
// Handle password specific updates
if (updates.hasOwnProperty("password")) {
const passwordCheck = this.checkPasswordComplexity(updates.password);
if (!passwordCheck.checkedOK) {
@ -78,6 +108,24 @@ const User = {
}
},
// Explicit direct update of user object.
// Only use this method when directly setting a key value
// that takes no user input for the keys being modified.
_update: async function (id = null, data = {}) {
if (!id) throw new Error("No user id provided for update");
try {
const user = await prisma.users.update({
where: { id },
data,
});
return { user, message: null };
} catch (error) {
console.error(error.message);
return { user: null, message: error.message };
}
},
get: async function (clause = {}) {
try {
const user = await prisma.users.findFirst({ where: clause });

View File

@ -12,7 +12,7 @@
"scripts": {
"dev": "NODE_ENV=development nodemon --ignore documents --ignore vector-cache --ignore storage --ignore swagger --trace-warnings index.js",
"start": "NODE_ENV=production node index.js",
"lint": "yarn prettier --write ./endpoints ./models ./utils index.js",
"lint": "yarn prettier --ignore-path ../.prettierignore --write ./endpoints ./models ./utils index.js",
"swagger": "node ./swagger/init.js",
"sqlite:migrate": "cd ./utils/prisma && node migrateFromSqlite.js"
},

View File

@ -8,6 +8,11 @@ const {
const fs = require("fs");
const path = require("path");
const { safeJsonParse } = require("../../http");
const cacheFolder = path.resolve(
process.env.STORAGE_DIR
? path.resolve(process.env.STORAGE_DIR, "models", "openrouter")
: path.resolve(__dirname, `../../../storage/models/openrouter`)
);
class OpenRouterLLM {
constructor(embedder = null, modelPreference = null) {
@ -38,12 +43,8 @@ class OpenRouterLLM {
this.embedder = !embedder ? new NativeEmbedder() : embedder;
this.defaultTemp = 0.7;
const cacheFolder = path.resolve(
process.env.STORAGE_DIR
? path.resolve(process.env.STORAGE_DIR, "models", "openrouter")
: path.resolve(__dirname, `../../../storage/models/openrouter`)
);
fs.mkdirSync(cacheFolder, { recursive: true });
if (!fs.existsSync(cacheFolder))
fs.mkdirSync(cacheFolder, { recursive: true });
this.cacheModelPath = path.resolve(cacheFolder, "models.json");
this.cacheAtPath = path.resolve(cacheFolder, ".cached_at");
}
@ -52,11 +53,6 @@ class OpenRouterLLM {
console.log(`\x1b[36m[${this.constructor.name}]\x1b[0m ${text}`, ...args);
}
async init() {
await this.#syncModels();
return this;
}
// This checks if the .cached_at file has a timestamp that is more than 1Week (in millis)
// from the current date. If it is, then we will refetch the API so that all the models are up
// to date.
@ -80,37 +76,7 @@ class OpenRouterLLM {
this.log(
"Model cache is not present or stale. Fetching from OpenRouter API."
);
await fetch(`${this.basePath}/models`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then((res) => res.json())
.then(({ data = [] }) => {
const models = {};
data.forEach((model) => {
models[model.id] = {
id: model.id,
name: model.name,
organization:
model.id.split("/")[0].charAt(0).toUpperCase() +
model.id.split("/")[0].slice(1),
maxLength: model.context_length,
};
});
fs.writeFileSync(this.cacheModelPath, JSON.stringify(models), {
encoding: "utf-8",
});
fs.writeFileSync(this.cacheAtPath, String(Number(new Date())), {
encoding: "utf-8",
});
return models;
})
.catch((e) => {
console.error(e);
return {};
});
await fetchOpenRouterModels();
return;
}
@ -420,6 +386,54 @@ class OpenRouterLLM {
}
}
async function fetchOpenRouterModels() {
return await fetch(`https://openrouter.ai/api/v1/models`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then((res) => res.json())
.then(({ data = [] }) => {
const models = {};
data.forEach((model) => {
models[model.id] = {
id: model.id,
name: model.name,
organization:
model.id.split("/")[0].charAt(0).toUpperCase() +
model.id.split("/")[0].slice(1),
maxLength: model.context_length,
};
});
// Cache all response information
if (!fs.existsSync(cacheFolder))
fs.mkdirSync(cacheFolder, { recursive: true });
fs.writeFileSync(
path.resolve(cacheFolder, "models.json"),
JSON.stringify(models),
{
encoding: "utf-8",
}
);
fs.writeFileSync(
path.resolve(cacheFolder, ".cached_at"),
String(Number(new Date())),
{
encoding: "utf-8",
}
);
return models;
})
.catch((e) => {
console.error(e);
return {};
});
}
module.exports = {
OpenRouterLLM,
fetchOpenRouterModels,
};

View File

@ -22,7 +22,7 @@ async function generateRecoveryCodes(userId) {
const { error } = await RecoveryCode.createMany(newRecoveryCodes);
if (!!error) throw new Error(error);
const { success } = await User.update(userId, {
const { user: success } = await User._update(userId, {
seen_recovery_codes: true,
});
if (!success) throw new Error("Failed to generate user recovery codes!");
@ -80,6 +80,11 @@ async function resetPassword(token, _newPassword = "", confirmPassword = "") {
// JOI password rules will be enforced inside .update.
const { error } = await User.update(resetToken.user_id, {
password: newPassword,
});
// seen_recovery_codes is not publicly writable
// so we have to do direct update here
await User._update(resetToken.user_id, {
seen_recovery_codes: false,
});

View File

@ -21,6 +21,19 @@ const chatHistory = {
// We need a full conversation reply with prev being from
// the USER and the last being from anyone other than the user.
if (prev.from !== "USER" || last.from === "USER") return;
// If we have a post-reply flow we should save the chat using this special flow
// so that post save cleanup and other unique properties can be run as opposed to regular chat.
if (aibitat.hasOwnProperty("_replySpecialAttributes")) {
await this._storeSpecial(aibitat, {
prompt: prev.content,
response: last.content,
options: aibitat._replySpecialAttributes,
});
delete aibitat._replySpecialAttributes;
return;
}
await this._store(aibitat, {
prompt: prev.content,
response: last.content,
@ -42,6 +55,28 @@ const chatHistory = {
threadId: invocation?.thread_id || null,
});
},
_storeSpecial: async function (
aibitat,
{ prompt, response, options = {} } = {}
) {
const invocation = aibitat.handlerProps.invocation;
await WorkspaceChats.new({
workspaceId: Number(invocation.workspace_id),
prompt,
response: {
sources: options?.sources ?? [],
// when we have a _storeSpecial called the options param can include a storedResponse() function
// that will override the text property to store extra information in, depending on the special type of chat.
text: options.hasOwnProperty("storedResponse")
? options.storedResponse(response)
: response,
type: options?.saveAsType ?? "chat",
},
user: { id: invocation?.user_id || null },
threadId: invocation?.thread_id || null,
});
options?.postSave();
},
};
},
};

View File

@ -5,6 +5,7 @@ const { docSummarizer } = require("./summarize.js");
const { saveFileInBrowser } = require("./save-file-browser.js");
const { chatHistory } = require("./chat-history.js");
const { memory } = require("./memory.js");
const { rechart } = require("./rechart.js");
module.exports = {
webScraping,
@ -14,6 +15,7 @@ module.exports = {
saveFileInBrowser,
chatHistory,
memory,
rechart,
// Plugin name aliases so they can be pulled by slug as well.
[webScraping.name]: webScraping,
@ -23,4 +25,5 @@ module.exports = {
[saveFileInBrowser.name]: saveFileInBrowser,
[chatHistory.name]: chatHistory,
[memory.name]: memory,
[rechart.name]: rechart,
};

View File

@ -0,0 +1,109 @@
const { safeJsonParse } = require("../../../http");
const { Deduplicator } = require("../utils/dedupe");
const rechart = {
name: "create-chart",
startupConfig: {
params: {},
},
plugin: function () {
return {
name: this.name,
setup(aibitat) {
// Scrape a website and summarize the content based on objective if the content is too large.',
aibitat.function({
super: aibitat,
name: this.name,
tracker: new Deduplicator(),
description:
"Generates the JSON data required to generate a RechartJS chart to the user based on their prompt and available data.",
parameters: {
$schema: "http://json-schema.org/draft-07/schema#",
type: "object",
properties: {
type: {
type: "string",
enum: [
"area",
"bar",
"line",
"composed",
"scatter",
"pie",
"radar",
"radialBar",
"treemap",
"funnel",
],
description: "The type of chart to be generated.",
},
title: {
type: "string",
description:
"Title of the chart. There MUST always be a title. Do not leave it blank.",
},
dataset: {
type: "string",
description: `Valid JSON in which each element is an object for Recharts API for the 'type' of chart defined WITHOUT new line characters. Strictly using this FORMAT and naming:
{ "name": "a", "value": 12 }].
Make sure field "name" always stays named "name". Instead of naming value field value in JSON, name it based on user metric and make it the same across every item.
Make sure the format use double quotes and property names are string literals. Provide JSON data only.`,
},
},
additionalProperties: false,
},
required: ["type", "title", "dataset"],
handler: async function ({ type, dataset, title }) {
try {
if (!this.tracker.isUnique(this.name)) {
this.super.handlerProps.log(
`${this.name} has been run for this chat response already. It can only be called once per chat.`
);
return "The chart was generated and returned to the user. This function completed successfully. Do not call this function again.";
}
const data = safeJsonParse(dataset, null);
if (data === null) {
this.super.introspect(
`${this.caller}: ${this.name} provided invalid JSON data - so we cant make a ${type} chart.`
);
return "Invalid JSON provided. Please only provide valid RechartJS JSON to generate a chart.";
}
this.super.introspect(`${this.caller}: Rendering ${type} chart.`);
this.super.socket.send("rechartVisualize", {
type,
dataset,
title,
});
this.super._replySpecialAttributes = {
saveAsType: "rechartVisualize",
storedResponse: (additionalText = "") =>
JSON.stringify({
type,
dataset,
title,
caption: additionalText,
}),
postSave: () => this.tracker.removeUniqueConstraint(this.name),
};
this.tracker.markUnique(this.name);
return "The chart was generated and returned to the user. This function completed successfully. Do not make another chart.";
} catch (error) {
this.super.handlerProps.log(
`create-chart raised an error. ${error.message}`
);
return `Let the user know this action was not successful. An error was raised while generating the chart. ${error.message}`;
}
},
});
},
};
},
};
module.exports = {
rechart,
};

View File

@ -9,10 +9,17 @@
// ... do random # of times.
// We want to block all the reruns of a plugin, so we can add this to prevent that behavior from
// spamming the user (or other costly function) that have the exact same signatures.
// Track Run/isDuplicate prevents _exact_ data re-runs based on the SHA of their inputs
// StartCooldown/isOnCooldown does prevention of _near-duplicate_ runs based on only the function name that is running.
// isUnique/markUnique/removeUniqueConstraint prevents one-time functions from re-running. EG: charting.
const crypto = require("crypto");
const DEFAULT_COOLDOWN_MS = 5 * 1000;
class Deduplicator {
#hashes = {};
#cooldowns = {};
#uniques = {};
constructor() {}
trackRun(key, params = {}) {
@ -30,6 +37,32 @@ class Deduplicator {
.digest("hex");
return this.#hashes.hasOwnProperty(newSig);
}
startCooldown(
key,
parameters = {
cooldownInMs: DEFAULT_COOLDOWN_MS,
}
) {
this.#cooldowns[key] = Number(new Date()) + Number(parameters.cooldownInMs);
}
isOnCooldown(key) {
if (!this.#cooldowns.hasOwnProperty(key)) return false;
return Number(new Date()) <= this.#cooldowns[key];
}
isUnique(key) {
return !this.#uniques.hasOwnProperty(key);
}
removeUniqueConstraint(key) {
delete this.#uniques[key];
}
markUnique(key) {
this.#uniques[key] = Number(new Date());
}
}
module.exports.Deduplicator = Deduplicator;

View File

@ -123,6 +123,7 @@ function convertToChatHistory(history = []) {
sentAt: moment(createdAt).unix(),
},
{
type: data?.type || "chart",
role: "assistant",
content: data.text,
sources: data.sources || [],

View File

@ -1,4 +1,7 @@
const { OpenRouterLLM } = require("../AiProviders/openRouter");
const {
OpenRouterLLM,
fetchOpenRouterModels,
} = require("../AiProviders/openRouter");
const { perplexityModels } = require("../AiProviders/perplexity");
const { togetherAiModels } = require("../AiProviders/togetherAi");
const SUPPORT_CUSTOM_MODELS = [
@ -232,8 +235,7 @@ async function getPerplexityModels() {
}
async function getOpenRouterModels() {
const openrouter = await new OpenRouterLLM().init();
const knownModels = openrouter.models();
const knownModels = await fetchOpenRouterModels();
if (!Object.keys(knownModels).length === 0)
return { models: [], error: null };