From dee915ffd6dcdc1191458cd5324d97b457dee3ca Mon Sep 17 00:00:00 2001 From: Dmitrii Anfimov Date: Tue, 22 Jul 2025 21:49:54 +0200 Subject: [PATCH] feat: more llm provider options (google, mistral) (#28) * feat: add google provider * fix: default for google model * feat: multiple providers * fix: defaults from env for login form * fix: add mistral to env files * chore: delete unused code * chore: revert database url to original * fix: render default value for api key from env on server * fix: type errors during compilation --------- Co-authored-by: Vasily Zubarev --- .env.example | 6 + README.md | 8 +- ai/analyze.ts | 63 +- ai/providers/llmProvider.ts | 115 ++++ app/(app)/unsorted/actions.ts | 25 +- app/(app)/unsorted/page.tsx | 6 +- app/(auth)/actions.ts | 15 +- app/(auth)/self-hosted/page.tsx | 46 +- app/(auth)/self-hosted/setup-form-client.tsx | 76 +++ components/forms/simple.tsx | 13 +- components/settings/llm-settings-form.tsx | 188 +++++- docker-compose.build.yml | 1 + forms/settings.ts | 6 + lib/config.ts | 10 +- lib/llm-providers.ts | 47 ++ models/settings.ts | 39 ++ package-lock.json | 661 ++++++++++++++++++- package.json | 7 +- public/logo/google.svg | 1 + public/logo/mistral.svg | 1 + public/logo/openai.svg | 1 + 21 files changed, 1185 insertions(+), 150 deletions(-) create mode 100644 ai/providers/llmProvider.ts create mode 100644 app/(auth)/self-hosted/setup-form-client.tsx create mode 100644 lib/llm-providers.ts create mode 100644 public/logo/google.svg create mode 100644 public/logo/mistral.svg create mode 100644 public/logo/openai.svg diff --git a/.env.example b/.env.example index 1f77079..14dc456 100644 --- a/.env.example +++ b/.env.example @@ -9,6 +9,12 @@ DATABASE_URL="postgresql://user@localhost:5432/taxhacker" OPENAI_MODEL_NAME="gpt-4o-mini" OPENAI_API_KEY="" # "sk-..." +GOOGLE_MODEL_NAME="gemini-2.5-flash" +GOOGLE_API_KEY="" + +MISTRAL_MODEL_NAME="mistral-medium-latest" +MISTRAL_API_KEY="" + # Auth Config BETTER_AUTH_SECRET="random-secret-key" # please use any long random string here diff --git a/README.md b/README.md index 29bfe6b..75fc820 100644 --- a/README.md +++ b/README.md @@ -152,8 +152,12 @@ Configure TaxHacker to suit your needs with these environment variables: | `SELF_HOSTED_MODE` | No | Set it to "true" if you're self-hosting the app: it enables auto-login, custom API keys, and more | `true` | | `DISABLE_SIGNUP` | No | Disable new user registration on your instance | `false` | | `BETTER_AUTH_SECRET` | Yes | Secret key for authentication (min 16 characters) | `random-secret-key` | -| `OPENAI_MODEL_NAME` | No | OpenAI model to use for AI features | `gpt-4o-mini` | -| `OPENAI_API_KEY` | No | OpenAI API key for AI features. In self-hosted mode you can set it up in settings too. | `sk-...` | + +You can also specify LLM provider options in settings or with environment variables: + +- For OpenAI: `OPENAI_MODEL_NAME` and `OPENAI_API_KEY` +- For Google: `GOOGLE_MODEL_NAME` and `GOOGLE_API_KEY` +- For Mistral: `MISTRAL_MODEL_NAME` and `MISTRAL_API_KEY` ## ⌨️ Local Development diff --git a/ai/analyze.ts b/ai/analyze.ts index 7850080..6919912 100644 --- a/ai/analyze.ts +++ b/ai/analyze.ts @@ -1,10 +1,10 @@ "use server" import { ActionState } from "@/lib/actions" -import config from "@/lib/config" -import OpenAI from "openai" import { AnalyzeAttachment } from "./attachments" import { updateFile } from "@/models/files" +import { getSettings, getLLMSettings } from "@/models/settings" +import { requestLLM } from "./providers/llmProvider" export type AnalysisResult = { output: Record @@ -15,52 +15,39 @@ export async function analyzeTransaction( prompt: string, schema: Record, attachments: AnalyzeAttachment[], - apiKey: string, fileId: string, userId: string ): Promise> { - const openai = new OpenAI({ - apiKey, - }) - console.log("RUNNING AI ANALYSIS") - console.log("PROMPT:", prompt) - console.log("SCHEMA:", schema) + + const settings = await getSettings(userId) + const llmSettings = getLLMSettings(settings) try { - const response = await openai.responses.create({ - model: config.ai.modelName, - input: [ - { - role: "user", - content: prompt, - }, - { - role: "user", - content: attachments.map((attachment) => ({ - type: "input_image", - detail: "auto", - image_url: `data:${attachment.contentType};base64,${attachment.base64}`, - })), - }, - ], - text: { - format: { - type: "json_schema", - name: "transaction", - schema: schema, - strict: true, - }, - }, + const response = await requestLLM(llmSettings, { + prompt, + schema, + attachments, }) - console.log("ChatGPT response:", response.output_text) - console.log("ChatGPT tokens used:", response.usage) + if (response.error) { + throw new Error(response.error) + } + + const result = response.output + const tokensUsed = response.tokensUsed || 0 + + console.log("LLM response:", result) + console.log("LLM tokens used:", tokensUsed) - const result = JSON.parse(response.output_text) - await updateFile(fileId, userId, { cachedParseResult: result }) - return { success: true, data: { output: result, tokensUsed: response.usage?.total_tokens || 0 } } + return { + success: true, + data: { + output: result, + tokensUsed: tokensUsed + }, + } } catch (error) { console.error("AI Analysis error:", error) return { diff --git a/ai/providers/llmProvider.ts b/ai/providers/llmProvider.ts new file mode 100644 index 0000000..c996ed2 --- /dev/null +++ b/ai/providers/llmProvider.ts @@ -0,0 +1,115 @@ +import { ChatOpenAI } from "@langchain/openai" +import { ChatGoogleGenerativeAI } from "@langchain/google-genai" +import { ChatMistralAI } from "@langchain/mistralai" +import { BaseMessage, HumanMessage } from "@langchain/core/messages" + +export type LLMProvider = "openai" | "google" | "mistral" + +export interface LLMConfig { + provider: LLMProvider + apiKey: string + model: string +} + +export interface LLMSettings { + providers: LLMConfig[] +} + +export interface LLMRequest { + prompt: string + schema?: Record + attachments?: any[] +} + +export interface LLMResponse { + output: Record + tokensUsed?: number + provider: LLMProvider + error?: string +} + +async function requestLLMUnified(config: LLMConfig, req: LLMRequest): Promise { + try { + const temperature = 0; + let model: any; + if (config.provider === "openai") { + model = new ChatOpenAI({ + apiKey: config.apiKey, + model: config.model, + temperature: temperature, + }); + } else if (config.provider === "google") { + model = new ChatGoogleGenerativeAI({ + apiKey: config.apiKey, + model: config.model, + temperature: temperature, + }); + } else if (config.provider === "mistral") { + model = new ChatMistralAI({ + apiKey: config.apiKey, + model: config.model, + temperature: temperature, + }); + } else { + return { + output: {}, + provider: config.provider, + error: "Unknown provider", + }; + } + + const structuredModel = model.withStructuredOutput(req.schema, { 'name': 'transaction'}); + + let message_content: any = [{ type: "text", text: req.prompt }]; + if (req.attachments && req.attachments.length > 0) { + const images = req.attachments.map(att => ({ + type: "image_url", + image_url: { + url: `data:${att.contentType};base64,${att.base64}` + }, + })); + message_content.push(...images); + } + const messages: BaseMessage[] = [ + new HumanMessage({ content: message_content }) + ]; + + const response = await structuredModel.invoke(messages); + + return { + output: response, + provider: config.provider, + }; + } catch (error: any) { + return { + output: {}, + provider: config.provider, + error: error instanceof Error ? error.message : `${config.provider} request failed`, + }; + } +} + +export async function requestLLM(settings: LLMSettings, req: LLMRequest): Promise { + for (const config of settings.providers) { + if (!config.apiKey || !config.model) { + console.info('Skipping provider:', config.provider); + continue; + } + console.info('Use provider:', config.provider); + + const response = await requestLLMUnified(config, req); + + if (!response.error) { + return response; + } + else { + console.error(response.error) + } + } + + return { + output: {}, + provider: settings.providers[0]?.provider || "openai", + error: "All LLM providers failed or are not configured", + }; +} diff --git a/app/(app)/unsorted/actions.ts b/app/(app)/unsorted/actions.ts index 6fe1a00..7d5c4e3 100644 --- a/app/(app)/unsorted/actions.ts +++ b/app/(app)/unsorted/actions.ts @@ -31,20 +31,15 @@ export async function analyzeFileAction( const user = await getCurrentUser() if (!file || file.userId !== user.id) { - return { success: false, error: "File not found or does not belong to the user" } - } - - const apiKey = settings.openai_api_key || config.ai.openaiApiKey || "" - if (!apiKey) { - return { success: false, error: "OpenAI API key is not set" } - } - - if (isAiBalanceExhausted(user)) { - return { - success: false, - error: "You used all of your pre-paid AI scans, please upgrade your account or buy new subscription plan", + return { success: false, error: "File not found or does not belong to the user" } + } + + if (isAiBalanceExhausted(user)) { + return { + success: false, + error: "You used all of your pre-paid AI scans, please upgrade your account or buy new subscription plan", + } } - } if (isSubscriptionExpired(user)) { return { @@ -70,7 +65,7 @@ export async function analyzeFileAction( const schema = fieldsToJsonSchema(fields) - const results = await analyzeTransaction(prompt, schema, attachments, apiKey, file.id, user.id) + const results = await analyzeTransaction(prompt, schema, attachments, file.id, user.id) console.log("Analysis results:", results) @@ -98,7 +93,7 @@ export async function saveFileAsTransactionAction( const file = await getFileById(fileId, user.id) if (!file) throw new Error("File not found") - // Create transaction + // Create transaction const transaction = await createTransaction(user.id, validatedForm.data) // Move file to processed location diff --git a/app/(app)/unsorted/page.tsx b/app/(app)/unsorted/page.tsx index b72bc13..4f4a080 100644 --- a/app/(app)/unsorted/page.tsx +++ b/app/(app)/unsorted/page.tsx @@ -38,14 +38,14 @@ export default async function UnsortedPage() { {files.length > 1 && } - {config.selfHosted.isEnabled && !settings.openai_api_key && ( + {config.selfHosted.isEnabled && !settings.openai_api_key && !settings.google_api_key && !settings.mistral_api_key && (
- ChatGPT API Key is required for analyzing files + LLM provider API Key is required for analyzing files - Please set your OpenAI API key in the settings to use the analyze form. + Please set your LLM provider API key in the settings to use the analyze form.
diff --git a/app/(auth)/actions.ts b/app/(auth)/actions.ts index 6b49dfe..033f3c5 100644 --- a/app/(auth)/actions.ts +++ b/app/(auth)/actions.ts @@ -13,11 +13,20 @@ export async function selfHostedGetStartedAction(formData: FormData) { await createUserDefaults(user.id) } - const openaiApiKey = formData.get("openai_api_key") - if (openaiApiKey) { - await updateSettings(user.id, "openai_api_key", openaiApiKey as string) + const apiKeys = [ + "openai_api_key", + "google_api_key", + "mistral_api_key" + ] + + for (const key of apiKeys) { + const value = formData.get(key) + if (value) { + await updateSettings(user.id, key, value as string) + } } + const defaultCurrency = formData.get("default_currency") if (defaultCurrency) { await updateSettings(user.id, "default_currency", defaultCurrency as string) diff --git a/app/(auth)/self-hosted/page.tsx b/app/(auth)/self-hosted/page.tsx index 5527565..6159af0 100644 --- a/app/(auth)/self-hosted/page.tsx +++ b/app/(auth)/self-hosted/page.tsx @@ -1,15 +1,12 @@ -import { FormSelectCurrency } from "@/components/forms/select-currency" -import { FormInput } from "@/components/forms/simple" -import { Button } from "@/components/ui/button" import { Card, CardDescription, CardTitle } from "@/components/ui/card" import { ColoredText } from "@/components/ui/colored-text" import config from "@/lib/config" -import { DEFAULT_CURRENCIES, DEFAULT_SETTINGS } from "@/models/defaults" import { getSelfHostedUser } from "@/models/users" import { ShieldAlert } from "lucide-react" import Image from "next/image" import { redirect } from "next/navigation" -import { selfHostedGetStartedAction } from "../actions" +import { PROVIDERS } from "@/lib/llm-providers" +import SelfHostedSetupFormClient from "./setup-form-client" export default async function SelfHostedWelcomePage() { if (!config.selfHosted.isEnabled) { @@ -35,6 +32,14 @@ export default async function SelfHostedWelcomePage() { redirect(config.selfHosted.redirectUrl) } + // Собираем дефолтные ключи для всех провайдеров + const defaultProvider = PROVIDERS[0].key + const defaultApiKeys: Record = { + openai: config.ai.openaiApiKey ?? "", + google: config.ai.googleApiKey ?? "", + mistral: config.ai.mistralApiKey ?? "", + } + return ( Logo @@ -43,36 +48,7 @@ export default async function SelfHostedWelcomePage() {

Welcome to your own instance of TaxHacker. Let's set up a couple of settings to get started.

- -
-
- - - - Get your API key from{" "} - - OpenAI Platform Console - - -
- -
- s.code === "default_currency")?.value ?? "EUR"} - currencies={DEFAULT_CURRENCIES} - /> -
- - -
+
) diff --git a/app/(auth)/self-hosted/setup-form-client.tsx b/app/(auth)/self-hosted/setup-form-client.tsx new file mode 100644 index 0000000..d3aef62 --- /dev/null +++ b/app/(auth)/self-hosted/setup-form-client.tsx @@ -0,0 +1,76 @@ +"use client" +import { useState, useRef, useEffect, useCallback } from "react" +import { FormSelectCurrency } from "@/components/forms/select-currency" +import { FormInput } from "@/components/forms/simple" +import { Button } from "@/components/ui/button" +import { DEFAULT_CURRENCIES, DEFAULT_SETTINGS } from "@/models/defaults" +import { selfHostedGetStartedAction } from "../actions" +import { FormSelect } from "@/components/forms/simple" +import { PROVIDERS } from "@/lib/llm-providers" + +type Props = { + defaultProvider: string + defaultApiKeys: Record +} + +export default function SelfHostedSetupFormClient({ defaultProvider, defaultApiKeys }: Props) { + const [provider, setProvider] = useState(defaultProvider) + const selected = PROVIDERS.find(p => p.key === provider)! + const getDefaultApiKey = useCallback((providerKey: string) => defaultApiKeys[providerKey] ?? "", [defaultApiKeys]) + + const [apiKey, setApiKey] = useState(getDefaultApiKey(provider)) + const userTyped = useRef(false) + + useEffect(() => { + if (!userTyped.current) { + setApiKey(getDefaultApiKey(provider)) + } + userTyped.current = false + }, [provider, getDefaultApiKey]) + + return ( +
+
+ ({ + code: p.key, + name: p.label, + logo: p.logo + }))} + /> + s.code === "default_currency")?.value ?? "EUR"} + currencies={DEFAULT_CURRENCIES} + /> +
+
+ { + setApiKey(e.target.value) + userTyped.current = true + }} + placeholder={selected.placeholder} + /> + + Get key from + {"\u00A0"} + + {selected.help.label} + + +
+ +
+ ) +} \ No newline at end of file diff --git a/components/forms/simple.tsx b/components/forms/simple.tsx index b6a402b..c2e9a39 100644 --- a/components/forms/simple.tsx +++ b/components/forms/simple.tsx @@ -1,4 +1,5 @@ "use client" +import Image from "next/image" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" @@ -87,9 +88,10 @@ export const FormSelect = ({ placeholder, hideIfEmpty = false, isRequired = false, + onValueChange, ...props }: { - items: Array<{ code: string; name: string; color?: string; badge?: string }> + items: Array<{ code: string; name: string; color?: string; badge?: string; logo?: string }> title?: string emptyValue?: string placeholder?: string @@ -105,7 +107,11 @@ export const FormSelect = ({ return ( {title && {title}} - @@ -114,6 +120,9 @@ export const FormSelect = ({ {items.map((item) => (
+ {item.logo && ( + {item.name} + )} {item.badge && {item.badge}} {!item.badge && item.color && (
diff --git a/components/settings/llm-settings-form.tsx b/components/settings/llm-settings-form.tsx index da48707..c4ab6b0 100644 --- a/components/settings/llm-settings-form.tsx +++ b/components/settings/llm-settings-form.tsx @@ -3,44 +3,91 @@ import { fieldsToJsonSchema } from "@/ai/schema" import { saveSettingsAction } from "@/app/(app)/settings/actions" import { FormError } from "@/components/forms/error" -import { FormInput, FormTextarea } from "@/components/forms/simple" +import { FormTextarea } from "@/components/forms/simple" import { Button } from "@/components/ui/button" import { Card, CardTitle } from "@/components/ui/card" import { Field } from "@/prisma/client" -import { CircleCheckBig, Edit } from "lucide-react" +import { CircleCheckBig, Edit, GripVertical } from "lucide-react" import Link from "next/link" -import { useActionState } from "react" +import { useState, useActionState } from "react" +import { + DndContext, + closestCenter, + PointerSensor, + useSensor, + useSensors +} from "@dnd-kit/core" +import type { DragEndEvent } from "@dnd-kit/core"; +import { + arrayMove, + SortableContext, + useSortable, + verticalListSortingStrategy +} from "@dnd-kit/sortable" +import { PROVIDERS } from "@/lib/llm-providers"; + + +function getInitialProviderOrder(settings: Record) { + let order: string[] = [] + if (!settings.llm_providers) { + order = ['openai', 'google', 'mistral'] + } else { + order = settings.llm_providers.split(",").map(p => p.trim()) + } + // Remove duplicates and keep only valid providers + return order.filter((key, idx) => PROVIDERS.some(p => p.key === key) && order.indexOf(key) === idx) +} export default function LLMSettingsForm({ settings, fields, - showApiKey = true, }: { settings: Record fields: Field[] showApiKey?: boolean }) { const [saveState, saveAction, pending] = useActionState(saveSettingsAction, null) + const [providerOrder, setProviderOrder] = useState(getInitialProviderOrder(settings)) + + // Controlled values for each provider + const [providerValues, setProviderValues] = useState(() => { + const values: Record = {} + PROVIDERS.forEach((provider) => { + values[provider.key] = { + apiKey: settings[provider.apiKeyName], + model: settings[provider.modelName] || provider.defaultModelName, + } + }) + return values + }) + + function handleProviderValueChange(providerKey: string, field: "apiKey" | "model", value: string) { + setProviderValues((prev) => ({ + ...prev, + [providerKey]: { + ...prev[providerKey], + [field]: value, + }, + })) + } return ( <>
- {showApiKey && ( - <> - - - Get your API key from{" "} - - OpenAI Platform Console - - - - )} +
+ + + + Drag provider blocks to reorder. First is highest priority. + +
+ ) } + +type DndProviderBlocksProps = { + providerOrder: string[]; + setProviderOrder: React.Dispatch>; + providerValues: Record; + handleProviderValueChange: (providerKey: string, field: "apiKey" | "model", value: string) => void; +}; + +function DndProviderBlocks({ providerOrder, setProviderOrder, providerValues, handleProviderValueChange }: DndProviderBlocksProps) { + const sensors = useSensors(useSensor(PointerSensor)) + function handleDragEnd(event: DragEndEvent) { + const { active, over } = event + if (!over || active.id === over.id) return + const oldIndex = providerOrder.indexOf(active.id as string) + const newIndex = providerOrder.indexOf(over.id as string) + setProviderOrder(arrayMove(providerOrder, oldIndex, newIndex)) + } + return ( + + + {providerOrder.map((providerKey, idx) => ( + + ))} + + + ) +} + +type SortableProviderBlockProps = { + id: string; + idx: number; + providerKey: string; + value: { apiKey: string; model: string }; + handleValueChange: (providerKey: string, field: "apiKey" | "model", value: string) => void; +}; + +function SortableProviderBlock({ id, idx, providerKey, value, handleValueChange }: SortableProviderBlockProps) { + const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id }) + + const provider = PROVIDERS.find(p => p.key === providerKey) + if (!provider) return null + return ( +
+
+ {/* Drag handle */} + + + + {provider.label} +
+
+ handleValueChange(provider.key, "apiKey", e.target.value)} + className="flex-1 border rounded px-2 py-1" + placeholder="API key" + /> + handleValueChange(provider.key, "model", e.target.value)} + className="flex-1 border rounded px-2 py-1" + placeholder="Model name" + /> +
+ {provider.apiDoc && ( + + Get your API key from{" "} + + {provider.apiDocLabel} + + + )} +
+ ) +} diff --git a/docker-compose.build.yml b/docker-compose.build.yml index 357617e..04b31b8 100644 --- a/docker-compose.build.yml +++ b/docker-compose.build.yml @@ -21,6 +21,7 @@ services: postgres: image: postgres:17-alpine + container_name: taxhacker-postgres environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres diff --git a/forms/settings.ts b/forms/settings.ts index ae3e305..08ab599 100644 --- a/forms/settings.ts +++ b/forms/settings.ts @@ -7,6 +7,12 @@ export const settingsFormSchema = z.object({ default_category: z.string().optional(), default_project: z.string().optional(), openai_api_key: z.string().optional(), + openai_model_name: z.string().default('gpt-4o-mini'), + google_api_key: z.string().optional(), + google_model_name: z.string().default("gemini-2.5-flash"), + mistral_api_key: z.string().optional(), + mistral_model_name: z.string().default("mistral-medium-latest"), + llm_providers: z.string().default('openai,google,mistral'), prompt_analyse_new_file: z.string().optional(), is_welcome_message_hidden: z.string().optional(), }) diff --git a/lib/config.ts b/lib/config.ts index 92d9cd0..e28579e 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -6,6 +6,10 @@ const envSchema = z.object({ SELF_HOSTED_MODE: z.enum(["true", "false"]).default("true"), OPENAI_API_KEY: z.string().optional(), OPENAI_MODEL_NAME: z.string().default("gpt-4o-mini"), + GOOGLE_API_KEY: z.string().optional(), + GOOGLE_MODEL_NAME: z.string().default("gemini-2.5-flash"), + MISTRAL_API_KEY: z.string().optional(), + MISTRAL_MODEL_NAME: z.string().default("mistral-medium-latest"), BETTER_AUTH_SECRET: z .string() .min(16, "Auth secret must be at least 16 characters") @@ -50,7 +54,11 @@ const config = { }, ai: { openaiApiKey: env.OPENAI_API_KEY, - modelName: env.OPENAI_MODEL_NAME, + openaiModelName: env.OPENAI_MODEL_NAME, + googleApiKey: env.GOOGLE_API_KEY, + googleModelName: env.GOOGLE_MODEL_NAME, + mistralApiKey: env.MISTRAL_API_KEY, + mistralModelName: env.MISTRAL_MODEL_NAME, }, auth: { secret: env.BETTER_AUTH_SECRET, diff --git a/lib/llm-providers.ts b/lib/llm-providers.ts new file mode 100644 index 0000000..8ffa670 --- /dev/null +++ b/lib/llm-providers.ts @@ -0,0 +1,47 @@ +export const PROVIDERS = [ + { + key: "openai", + label: "OpenAI", + apiKeyName: "openai_api_key", + modelName: "openai_model_name", + defaultModelName: "gpt-4o-mini", + apiDoc: "https://platform.openai.com/settings/organization/api-keys", + apiDocLabel: "OpenAI Platform Console", + placeholder: "sk-...", + help: { + url: "https://platform.openai.com/settings/organization/api-keys", + label: "OpenAI Platform Console" + }, + logo: "/logo/openai.svg" + }, + { + key: "google", + label: "Google", + apiKeyName: "google_api_key", + modelName: "google_model_name", + defaultModelName: "gemini-2.5-flash", + apiDoc: "https://aistudio.google.com/apikey", + apiDocLabel: "Google AI Studio", + placeholder: "...", + help: { + url: "https://aistudio.google.com/apikey", + label: "Google AI Studio" + }, + logo: "/logo/google.svg" + }, + { + key: "mistral", + label: "Mistral", + apiKeyName: "mistral_api_key", + modelName: "mistral_model_name", + defaultModelName: "mistral-medium-latest", + apiDoc: "https://admin.mistral.ai/organization/api-keys", + apiDocLabel: "Mistral Admin Console", + placeholder: "...", + help: { + url: "https://admin.mistral.ai/organization/api-keys", + label: "Mistral Admin Console" + }, + logo: "/logo/mistral.svg" + }, +] diff --git a/models/settings.ts b/models/settings.ts index cd62c9f..e4cd71f 100644 --- a/models/settings.ts +++ b/models/settings.ts @@ -1,12 +1,51 @@ import { prisma } from "@/lib/db" +import { PROVIDERS } from "@/lib/llm-providers" import { cache } from "react" +import { LLMProvider } from "@/ai/providers/llmProvider" export type SettingsMap = Record +/** + * Helper to extract LLM provider settings from SettingsMap. + */ +export function getLLMSettings(settings: SettingsMap) { + const priorities = (settings.llm_providers || "openai,google,mistral").split(",").map(p => p.trim()).filter(Boolean) + + const providers = priorities.map((provider) => { + if (provider === "openai") { + return { + provider: provider as LLMProvider, + apiKey: settings.openai_api_key || "", + model: settings.openai_model_name || PROVIDERS[0]['defaultModelName'], + } + } + if (provider === "google") { + return { + provider: provider as LLMProvider, + apiKey: settings.google_api_key || "", + model: settings.google_model_name || PROVIDERS[1]['defaultModelName'], + } + } + if (provider === "mistral") { + return { + provider: provider as LLMProvider, + apiKey: settings.mistral_api_key || "", + model: settings.mistral_model_name || PROVIDERS[2]['defaultModelName'], + } + } + return null + }).filter((provider): provider is NonNullable => provider !== null) + + return { + providers, + } +} + export const getSettings = cache(async (userId: string): Promise => { const settings = await prisma.setting.findMany({ where: { userId }, }) + return settings.reduce((acc, setting) => { acc[setting.code] = setting.value || "" return acc diff --git a/package-lock.json b/package-lock.json index 7d97f71..f9689a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,13 @@ "name": "taxhacker", "version": "0.5.5", "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", "@fast-csv/format": "^5.0.2", "@fast-csv/parse": "^5.0.2", + "@langchain/google-genai": "^0.2.14", + "@langchain/mistralai": "^0.2.1", + "@langchain/openai": "^0.6.1", "@prisma/client": "^6.6.0", "@radix-ui/react-avatar": "^1.1.3", "@radix-ui/react-checkbox": "^1.1.4", @@ -31,11 +36,11 @@ "clsx": "^2.1.1", "date-fns": "^3.6.0", "jszip": "^3.10.1", + "langchain": "^0.3.30", "lucide-react": "^0.475.0", "mime-types": "^3.0.1", "next": "^15.2.4", "next-themes": "^0.4.4", - "openai": "^4.85.4", "pdf2pic": "^3.1.4", "react": "^19.0.0", "react-day-picker": "^8.10.1", @@ -364,6 +369,7 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/@better-auth/utils/-/utils-0.2.5.tgz", "integrity": "sha512-uI2+/8h/zVsH8RrYdG8eUErbuGBk16rZKQfz8CjxQOyCE6v7BqFYEbFwvOkvl1KbUdxhqOnXp78+uE5h8qVEgQ==", + "license": "MIT", "dependencies": { "typescript": "^5.8.2", "uncrypto": "^0.1.3" @@ -374,6 +380,66 @@ "resolved": "https://registry.npmjs.org/@better-fetch/fetch/-/fetch-1.1.18.tgz", "integrity": "sha512-rEFOE1MYIsBmoMJtQbl32PGHHXuG2hDxvEd7rUHE0vCBoFQVSDqaVs9hkZEtHCxRoY+CljXKFCOuJ8uxqw1LcA==" }, + "node_modules/@cfworker/json-schema": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@cfworker/json-schema/-/json-schema-4.1.1.tgz", + "integrity": "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==", + "license": "MIT", + "peer": true + }, + "node_modules/@dnd-kit/accessibility": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", + "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/core": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", + "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", + "license": "MIT", + "dependencies": { + "@dnd-kit/accessibility": "^3.1.1", + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/sortable": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz", + "integrity": "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==", + "license": "MIT", + "dependencies": { + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.3.0", + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/utilities": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", + "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/@emnapi/core": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.3.1.tgz", @@ -1035,6 +1101,15 @@ "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", "license": "MIT" }, + "node_modules/@google/generative-ai": { + "version": "0.24.1", + "resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.24.1.tgz", + "integrity": "sha512-MqO+MLfM6kjxcKoy0p1wRzG3b4ZZXtPI+z2IE26UogS2Cm/XHO+7gGRBh6gcJsOiIVoH93UwKvW4HdgiOZCy9Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@hexagon/base64": { "version": "1.1.28", "resolved": "https://registry.npmjs.org/@hexagon/base64/-/base64-1.1.28.tgz", @@ -1544,12 +1619,185 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@langchain/core": { + "version": "0.3.63", + "resolved": "https://registry.npmjs.org/@langchain/core/-/core-0.3.63.tgz", + "integrity": "sha512-CQfyu4WgwizUhSc1YsDDzzHga6WVhLqeuAyCD4VpGAPa3k3QI+H0b3ECFr/WjJMw0amMtHtfgPWMa1tS7P7qVg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@cfworker/json-schema": "^4.0.2", + "ansi-styles": "^5.0.0", + "camelcase": "6", + "decamelize": "1.2.0", + "js-tiktoken": "^1.0.12", + "langsmith": "^0.3.33", + "mustache": "^4.2.0", + "p-queue": "^6.6.2", + "p-retry": "4", + "uuid": "^10.0.0", + "zod": "^3.25.32", + "zod-to-json-schema": "^3.22.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@langchain/core/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@langchain/core/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "peer": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@langchain/google-genai": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/@langchain/google-genai/-/google-genai-0.2.14.tgz", + "integrity": "sha512-gKe/T2LNh8wSSMJOaFmYd8cwQnDSXKtVtC6a7CFoq5nWuh0bKzhItM/7bue1aMN8mlKfB2G1HCwxhaZoSpS/DA==", + "license": "MIT", + "dependencies": { + "@google/generative-ai": "^0.24.0", + "uuid": "^11.1.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": ">=0.3.58 <0.4.0" + } + }, + "node_modules/@langchain/google-genai/node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/@langchain/mistralai": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@langchain/mistralai/-/mistralai-0.2.1.tgz", + "integrity": "sha512-s91BlNcuxaaZGnVukyl81nwGrWpeE0EYiAdEFoBmZwlT4yLpx+QpPhRsGKrTg/Vm7Nscy6Wd8Xy2PJ93wftMdw==", + "license": "MIT", + "dependencies": { + "@mistralai/mistralai": "^1.3.1", + "uuid": "^10.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": ">=0.3.58 <0.4.0" + } + }, + "node_modules/@langchain/mistralai/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@langchain/openai": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@langchain/openai/-/openai-0.6.1.tgz", + "integrity": "sha512-jm8MzMEjAKPReYma4Lewb9vGnocKbhoClqPuRTxtKPDgqQ5yJWSisNy4iZO/a1d6ag/7MnxwKMjVsJdy1cBsxw==", + "license": "MIT", + "dependencies": { + "js-tiktoken": "^1.0.12", + "openai": "^5.3.0", + "zod": "^3.25.32" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": ">=0.3.58 <0.4.0" + } + }, + "node_modules/@langchain/openai/node_modules/openai": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/openai/-/openai-5.9.2.tgz", + "integrity": "sha512-d7t/lRkwZpSwIk7GW3EHRSGAlsuoi1WL6VhCO02raEzZO2ahEVAbWn3WmOcpeh9zF6xF9weJXoVeDkWJRz+SHA==", + "license": "Apache-2.0", + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/@langchain/textsplitters": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@langchain/textsplitters/-/textsplitters-0.1.0.tgz", + "integrity": "sha512-djI4uw9rlkAb5iMhtLED+xJebDdAG935AdP4eRTB02R7OB/act55Bj9wsskhZsvuyQRpO4O1wQOp85s6T6GWmw==", + "license": "MIT", + "dependencies": { + "js-tiktoken": "^1.0.12" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": ">=0.2.21 <0.4.0" + } + }, "node_modules/@levischuck/tiny-cbor": { "version": "0.2.11", "resolved": "https://registry.npmjs.org/@levischuck/tiny-cbor/-/tiny-cbor-0.2.11.tgz", "integrity": "sha512-llBRm4dT4Z89aRsm6u2oEZ8tfwL/2l6BwpZ7JcyieouniDECM5AqNgr/y08zalEIvW3RSK4upYyybDcmjXqAow==", "license": "MIT" }, + "node_modules/@mistralai/mistralai": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-1.7.4.tgz", + "integrity": "sha512-wty9hHEvIJ5RS8+75NY+a1zXtCCqYQgI26e8R2N7O9ZvD16ep2kF6ciJD2EiWOgS/K+iycRGsrI3nqgkPLG/Xw==", + "dependencies": { + "zod-to-json-schema": "^3.24.1" + }, + "peerDependencies": { + "zod": ">= 3" + } + }, "node_modules/@napi-rs/wasm-runtime": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.7.tgz", @@ -4124,9 +4372,9 @@ } }, "node_modules/@sentry/bundler-plugin-core/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -4638,6 +4886,8 @@ "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@types/node": "*", "form-data": "^4.0.0" @@ -4683,6 +4933,12 @@ "@types/react": "^19.0.0" } }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "license": "MIT" + }, "node_modules/@types/sharp": { "version": "0.31.1", "resolved": "https://registry.npmjs.org/@types/sharp/-/sharp-0.31.1.tgz", @@ -4707,6 +4963,12 @@ "@types/node": "*" } }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.27.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.27.0.tgz", @@ -4846,9 +5108,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5280,6 +5542,8 @@ "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "event-target-shim": "^5.0.0" }, @@ -5341,6 +5605,8 @@ "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "humanize-ms": "^1.2.1" }, @@ -5463,7 +5729,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, "license": "Python-2.0" }, "node_modules/aria-hidden": { @@ -5693,7 +5958,9 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/available-typed-arrays": { "version": "1.0.7", @@ -5758,9 +6025,10 @@ "license": "MIT" }, "node_modules/better-auth": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/better-auth/-/better-auth-1.2.10.tgz", - "integrity": "sha512-nEj1RG4DdLUuJiV5CR93ORyPCptGRBwksaPPCkUtGo9ka+UIlTpaiKoTaTqVLLYlqwX4bOj9tJ32oBNdf2G3Kg==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/better-auth/-/better-auth-1.2.12.tgz", + "integrity": "sha512-YicCyjQ+lxb7YnnaCewrVOjj3nPVa0xcfrOJK7k5MLMX9Mt9UnJ8GYaVQNHOHLyVxl92qc3C758X1ihqAUzm4w==", + "license": "MIT", "dependencies": { "@better-auth/utils": "0.2.5", "@better-fetch/fetch": "^1.1.18", @@ -5770,16 +6038,16 @@ "@simplewebauthn/server": "^13.0.0", "better-call": "^1.0.8", "defu": "^6.1.4", - "jose": "^5.9.6", + "jose": "^6.0.11", "kysely": "^0.28.2", "nanostores": "^0.11.3", "zod": "^3.24.1" } }, "node_modules/better-call": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/better-call/-/better-call-1.0.11.tgz", - "integrity": "sha512-MOM01EMZFMzApWq9+WfqAnl2+DzFoMNp4H+lTFE1p7WF4evMeaQAAcOhI1WwMjITV4PGIWJ3Vn5GciQ5VHXbIA==", + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/better-call/-/better-call-1.0.12.tgz", + "integrity": "sha512-ssq5OfB9Ungv2M1WVrRnMBomB0qz1VKuhkY2WxjHaLtlsHoSe9EPolj1xf7xf8LY9o3vfk3Rx6rCWI4oVHeBRg==", "dependencies": { "@better-fetch/fetch": "^1.1.4", "rou3": "^0.5.1", @@ -5809,9 +6077,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -5957,6 +6225,19 @@ "node": ">=6" } }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/camelcase-css": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", @@ -5990,7 +6271,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -6137,6 +6417,8 @@ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -6166,6 +6448,15 @@ "dev": true, "license": "MIT" }, + "node_modules/console-table-printer": { + "version": "2.14.6", + "resolved": "https://registry.npmjs.org/console-table-printer/-/console-table-printer-2.14.6.tgz", + "integrity": "sha512-MCBl5HNVaFuuHW6FGbL/4fB7N/ormCy+tQ+sxTrF6QtSbSNETvPuOVbkJBhzDgYhvjWGrTma4eYJa37ZuoQsPw==", + "license": "MIT", + "dependencies": { + "simple-wcswidth": "^1.0.1" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -6305,6 +6596,16 @@ } } }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -6368,6 +6669,8 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=0.4.0" } @@ -6678,6 +6981,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "devOptional": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -7231,10 +7535,18 @@ "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=6" } }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -7436,6 +7748,8 @@ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -7450,13 +7764,17 @@ "version": "1.7.2", "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/form-data/node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">= 0.6" } @@ -7466,6 +7784,8 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "mime-db": "1.52.0" }, @@ -7478,6 +7798,8 @@ "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "node-domexception": "1.0.0", "web-streams-polyfill": "4.0.0-beta.3" @@ -7678,9 +8000,9 @@ "peer": true }, "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -7849,6 +8171,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "devOptional": true, "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -7949,6 +8272,8 @@ "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "ms": "^2.0.0" } @@ -8556,14 +8881,23 @@ } }, "node_modules/jose": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz", - "integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==", + "version": "6.0.12", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.0.12.tgz", + "integrity": "sha512-T8xypXs8CpmiIi78k0E+Lk7T2zlK4zDyg+o1CZ4AkOHgDg98ogdP2BeZ61lTFKFyoEwJ9RgAgN+SdM3iPgNonQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" } }, + "node_modules/js-tiktoken": { + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/js-tiktoken/-/js-tiktoken-1.0.20.tgz", + "integrity": "sha512-Xlaqhhs8VfCd6Sh7a1cFkZHQbYTLCwVJJWiHVxBYzLPxW0XsoxBy1hitmjkdIjD3Aon5BXLHFwU5O8WUx6HH+A==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.5.1" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -8574,7 +8908,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -8636,6 +8969,15 @@ "json5": "lib/cli.js" } }, + "node_modules/jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -8678,10 +9020,167 @@ "version": "0.28.2", "resolved": "https://registry.npmjs.org/kysely/-/kysely-0.28.2.tgz", "integrity": "sha512-4YAVLoF0Sf0UTqlhgQMFU9iQECdah7n+13ANkiuVfRvlK+uI0Etbgd7bVP36dKlG+NXWbhGua8vnGt+sdhvT7A==", + "license": "MIT", "engines": { "node": ">=18.0.0" } }, + "node_modules/langchain": { + "version": "0.3.30", + "resolved": "https://registry.npmjs.org/langchain/-/langchain-0.3.30.tgz", + "integrity": "sha512-UyVsfwHDpHbrnWrjWuhJHqi8Non+Zcsf2kdpDTqyJF8NXrHBOpjdHT5LvPuW9fnE7miDTWf5mLcrWAGZgcrznQ==", + "license": "MIT", + "dependencies": { + "@langchain/openai": ">=0.1.0 <0.7.0", + "@langchain/textsplitters": ">=0.0.0 <0.2.0", + "js-tiktoken": "^1.0.12", + "js-yaml": "^4.1.0", + "jsonpointer": "^5.0.1", + "langsmith": "^0.3.33", + "openapi-types": "^12.1.3", + "p-retry": "4", + "uuid": "^10.0.0", + "yaml": "^2.2.1", + "zod": "^3.25.32" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/anthropic": "*", + "@langchain/aws": "*", + "@langchain/cerebras": "*", + "@langchain/cohere": "*", + "@langchain/core": ">=0.3.58 <0.4.0", + "@langchain/deepseek": "*", + "@langchain/google-genai": "*", + "@langchain/google-vertexai": "*", + "@langchain/google-vertexai-web": "*", + "@langchain/groq": "*", + "@langchain/mistralai": "*", + "@langchain/ollama": "*", + "@langchain/xai": "*", + "axios": "*", + "cheerio": "*", + "handlebars": "^4.7.8", + "peggy": "^3.0.2", + "typeorm": "*" + }, + "peerDependenciesMeta": { + "@langchain/anthropic": { + "optional": true + }, + "@langchain/aws": { + "optional": true + }, + "@langchain/cerebras": { + "optional": true + }, + "@langchain/cohere": { + "optional": true + }, + "@langchain/deepseek": { + "optional": true + }, + "@langchain/google-genai": { + "optional": true + }, + "@langchain/google-vertexai": { + "optional": true + }, + "@langchain/google-vertexai-web": { + "optional": true + }, + "@langchain/groq": { + "optional": true + }, + "@langchain/mistralai": { + "optional": true + }, + "@langchain/ollama": { + "optional": true + }, + "@langchain/xai": { + "optional": true + }, + "axios": { + "optional": true + }, + "cheerio": { + "optional": true + }, + "handlebars": { + "optional": true + }, + "peggy": { + "optional": true + }, + "typeorm": { + "optional": true + } + } + }, + "node_modules/langchain/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/langsmith": { + "version": "0.3.46", + "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.3.46.tgz", + "integrity": "sha512-Hhi4/cMjhWIGpu0DW5eQrXBbeeKQWPYYQyJCYzhFjod+xinMry4i8QR0gxrrgjGOgfMuU6nyK79YqjGTEPVbDA==", + "license": "MIT", + "dependencies": { + "@types/uuid": "^10.0.0", + "chalk": "^4.1.2", + "console-table-printer": "^2.12.1", + "p-queue": "^6.6.2", + "p-retry": "4", + "semver": "^7.6.3", + "uuid": "^10.0.0" + }, + "peerDependencies": { + "@opentelemetry/api": "*", + "@opentelemetry/exporter-trace-otlp-proto": "*", + "@opentelemetry/sdk-trace-base": "*", + "openai": "*" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@opentelemetry/exporter-trace-otlp-proto": { + "optional": true + }, + "@opentelemetry/sdk-trace-base": { + "optional": true + }, + "openai": { + "optional": true + } + } + }, + "node_modules/langsmith/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/language-subtag-registry": { "version": "0.3.23", "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", @@ -8997,6 +9496,16 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "license": "MIT", + "peer": true, + "bin": { + "mustache": "bin/mustache" + } + }, "node_modules/mz": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", @@ -9162,6 +9671,8 @@ } ], "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=10.5.0" } @@ -9345,6 +9856,8 @@ "resolved": "https://registry.npmjs.org/openai/-/openai-4.89.0.tgz", "integrity": "sha512-XNI0q2l8/Os6jmojxaID5EhyQjxZgzR2gWcpEjYWK5hGKwE7AcifxEY7UNwFDDHJQXqeiosQ0CJwQN+rvnwdjA==", "license": "Apache-2.0", + "optional": true, + "peer": true, "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", @@ -9375,6 +9888,8 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.81.tgz", "integrity": "sha512-7KO9oZ2//ivtSsryp0LQUqq79zyGXzwq1WqfywpC9ucjY7YyltMMmxWgtRFRKCxwa7VPxVBVy4kHf5UC1E8Lug==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "undici-types": "~5.26.4" } @@ -9383,6 +9898,14 @@ "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", "license": "MIT" }, "node_modules/optionator": { @@ -9421,6 +9944,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -9451,6 +9983,47 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "license": "MIT", + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -10283,6 +10856,15 @@ "integrity": "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==", "license": "MIT" }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -10773,6 +11355,12 @@ "is-arrayish": "^0.3.1" } }, + "node_modules/simple-wcswidth": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/simple-wcswidth/-/simple-wcswidth-1.1.2.tgz", + "integrity": "sha512-j7piyCjAeTDSjzTSQ7DokZtMNwNlEAyxqSZeCS+CXH7fJ4jx3FuJ/mTW3mE+6JLs4VJBbcll0Kjn+KXI5t21Iw==", + "license": "MIT" + }, "node_modules/slugify": { "version": "1.6.6", "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz", @@ -11777,6 +12365,8 @@ "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">= 14" } @@ -12161,13 +12751,22 @@ "license": "MIT" }, "node_modules/zod": { - "version": "3.24.2", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz", - "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==", + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } + }, + "node_modules/zod-to-json-schema": { + "version": "3.24.6", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", + "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.24.1" + } } } } diff --git a/package.json b/package.json index d2dcbd8..a0cb8c2 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,13 @@ "lint": "next lint" }, "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", "@fast-csv/format": "^5.0.2", "@fast-csv/parse": "^5.0.2", + "@langchain/google-genai": "^0.2.14", + "@langchain/mistralai": "^0.2.1", + "@langchain/openai": "^0.6.1", "@prisma/client": "^6.6.0", "@radix-ui/react-avatar": "^1.1.3", "@radix-ui/react-checkbox": "^1.1.4", @@ -33,11 +38,11 @@ "clsx": "^2.1.1", "date-fns": "^3.6.0", "jszip": "^3.10.1", + "langchain": "^0.3.30", "lucide-react": "^0.475.0", "mime-types": "^3.0.1", "next": "^15.2.4", "next-themes": "^0.4.4", - "openai": "^4.85.4", "pdf2pic": "^3.1.4", "react": "^19.0.0", "react-day-picker": "^8.10.1", diff --git a/public/logo/google.svg b/public/logo/google.svg new file mode 100644 index 0000000..3a26ead --- /dev/null +++ b/public/logo/google.svg @@ -0,0 +1 @@ +Gemini diff --git a/public/logo/mistral.svg b/public/logo/mistral.svg new file mode 100644 index 0000000..a48f560 --- /dev/null +++ b/public/logo/mistral.svg @@ -0,0 +1 @@ + diff --git a/public/logo/openai.svg b/public/logo/openai.svg new file mode 100644 index 0000000..0b36b8a --- /dev/null +++ b/public/logo/openai.svg @@ -0,0 +1 @@ +