diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml
index 0ad72084..24e868a0 100644
--- a/.gitea/workflows/deploy.yml
+++ b/.gitea/workflows/deploy.yml
@@ -83,7 +83,7 @@ jobs:
SLUG=$(echo "$REF" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//')
IMAGE_TAG="branch-${SLUG}-${SHORT_SHA}"
ENV_FILE=".env.branch-${SLUG}"
- TRAEFIK_HOST="${SLUG}.branch.mintel.me"
+ TRAEFIK_HOST="${SLUG}.branch.${DOMAIN}"
fi
# Standardize Traefik Rule (escaped backticks for Traefik v3)
diff --git a/app/api/ai-search/route.ts b/app/api/ai-search/route.ts
index d434ea8a..f5ed898f 100644
--- a/app/api/ai-search/route.ts
+++ b/app/api/ai-search/route.ts
@@ -1,138 +1,157 @@
-import { NextResponse } from 'next/server';
+import { NextResponse, NextRequest } from 'next/server'; // Added NextRequest
import { searchProducts } from '../../../src/lib/qdrant';
import redis from '../../../src/lib/redis';
import { z } from 'zod';
-
+import * as Sentry from '@sentry/nextjs';
// Config and constants
const RATE_LIMIT_POINTS = 5; // 5 requests
const RATE_LIMIT_DURATION = 60 * 1; // per 1 minute
-const requestSchema = z.object({
- query: z.string().min(1).max(500),
- _honeypot: z.string().max(0).optional(), // Honeypot trap: must be empty
-});
+// Removed requestSchema as it's replaced by direct parsing
-export async function POST(req: Request) {
+export async function POST(req: NextRequest) {
+ // Changed req type to NextRequest
+ try {
+ const { messages, visitorId, honeypot } = await req.json();
+
+ // 1. Basic Validation
+ if (!messages || !Array.isArray(messages) || messages.length === 0) {
+ return NextResponse.json({ error: 'Valid messages array is required' }, { status: 400 });
+ }
+
+ const latestMessage = messages[messages.length - 1].content;
+ const isBot = honeypot && honeypot.length > 0;
+
+ // Check if the input itself is obviously spam/too long
+ if (latestMessage.length > 500) {
+ return NextResponse.json({ error: 'Message too long' }, { status: 400 });
+ }
+
+ // 2. Honeypot check
+ if (isBot) {
+ console.warn('Honeypot triggered in AI search');
+ // Tarpit the bot
+ await new Promise((resolve) => setTimeout(resolve, 3000));
+ return NextResponse.json({
+ answerText: 'Vielen Dank für Ihre Anfrage.',
+ products: [],
+ });
+ }
+
+ // 3. Rate Limiting via Redis
try {
- // 1. IP extraction for Rate Limiting
- const forwardedFor = req.headers.get('x-forwarded-for');
- const realIp = req.headers.get('x-real-ip');
- const ip = forwardedFor?.split(',')[0] || realIp || 'anon';
- const rateLimitKey = `rate_limit:ai_search:${ip}`;
-
- // Redis Rate Limiting
- try {
- const current = await redis.incr(rateLimitKey);
- if (current === 1) {
- await redis.expire(rateLimitKey, RATE_LIMIT_DURATION);
- }
- if (current > RATE_LIMIT_POINTS) {
- return NextResponse.json({ error: 'Rate limit exceeded. Try again later.' }, { status: 429 });
- }
- } catch (redisError) {
- console.warn('Redis error during rate limiting:', redisError);
- // Fallback: proceed if Redis is down, to maintain availability
+ if (visitorId) {
+ const requestCount = await redis.incr(`ai_search_rate_limit:${visitorId}`);
+ if (requestCount === 1) {
+ await redis.expire(`ai_search_rate_limit:${visitorId}`, RATE_LIMIT_DURATION); // Use constant
}
- // 2. Validate request
- const json = await req.json().catch(() => ({}));
- const parseResult = requestSchema.safeParse(json);
-
- if (!parseResult.success) {
- return NextResponse.json({ error: 'Invalid request' }, { status: 400 });
+ if (requestCount > RATE_LIMIT_POINTS) {
+ // Use constant
+ return NextResponse.json(
+ {
+ error: 'Rate limit exceeded. Please try again later.',
+ },
+ { status: 429 },
+ );
}
+ }
+ } catch (redisError) {
+ // Renamed variable for clarity
+ console.error('Redis Rate Limiting Error:', redisError); // Changed to error for consistency
+ Sentry.captureException(redisError, { tags: { context: 'ai-search-rate-limit' } });
+ // Fail open if Redis is down
+ }
- const { query, _honeypot } = parseResult.data;
+ // 4. Fetch Context from Qdrant based on the latest message
+ let contextStr = '';
+ let foundProducts: any[] = [];
- // 3. Honeypot check
- // If the honeypot field has any content, this is a bot.
- if (_honeypot && _honeypot.length > 0) {
- // Return a fake success mask
- return NextResponse.json({ answer: 'Searching...' }, { status: 200 });
- }
+ try {
+ const searchResults = await searchProducts(latestMessage, 5);
- // 4. Qdrant Context Retrieval
- const searchResults = await searchProducts(query, 5);
+ if (searchResults && searchResults.length > 0) {
+ const productDescriptions = searchResults
+ .filter((p) => p.payload?.type === 'product' || !p.payload?.type)
+ .map((p: any) => p.payload?.content)
+ .join('\n\n');
- // Build context block
- const contextText = searchResults.map((res: any) => {
- const payload = res.payload;
- return `Product ID: ${payload?.id}
-Name: ${payload?.title}
-SKU: ${payload?.sku}
-Description: ${payload?.description}
-Slug: ${payload?.slug}
----`;
- }).join('\n');
+ const knowledgeDescriptions = searchResults
+ .filter((p) => p.payload?.type === 'knowledge')
+ .map((p: any) => p.payload?.content)
+ .join('\n\n');
- // 5. OpenRouter Integration (gemini-3-flash-preview)
- const openRouterKey = process.env.OPENROUTER_API_KEY;
- if (!openRouterKey) {
- return NextResponse.json({ error: 'Server configuration error' }, { status: 500 });
- }
+ contextStr = `KATALOG & PRODUKTE:\n${productDescriptions}\n\nKABELWISSEN (Handbuch):\n${knowledgeDescriptions}`;
- const systemPrompt = `You are the KLZ Cables AI Search Assistant, an intelligent, helpful, and highly specialized assistant strictly for the KLZ Cables website.
-Your primary goal is to help users find the correct industrial cables and products based ONLY on the context provided.
-Follow these strict rules:
-1. ONLY answer questions related to products, search queries, cables, or industrial electronics.
-2. If the user asks a question entirely unrelated to products or the company (e.g., "What is the capital of France?", "Write a poem", "What is 2+2?"), REFUSE to answer it. Instead, reply with a funny, sarcastic, or humorous comment about how you only know about cables and wires.
-3. Base your product answers strictly on the CONTEXT provided below. Do not hallucinate products.
-4. Output your response as a valid JSON object matching this schema exactly, do not use Markdown codeblocks, output RAW JSON:
-{
- "answerText": "A friendly description or answer based on the search.",
- "products": [
- { "id": "Context Product ID", "title": "Product Title", "sku": "Product SKU", "slug": "slug" }
- ]
-}
+ foundProducts = searchResults
+ .filter((p) => (p.payload?.type === 'product' || !p.payload?.type) && p.payload?.data)
+ .map((p: any) => p.payload?.data);
+ }
+ } catch (e) {
+ console.error('Qdrant Search Error:', e);
+ Sentry.captureException(e, { tags: { context: 'ai-search-qdrant' } });
+ // We can still proceed without context if Qdrant fails
+ }
-If you find relevant products in the context, add them to the "products" array. If no products match, use an empty array.
+ // 5. Generate AI Response via OpenRouter (Mistral for DSGVO)
+ const systemPrompt = `Du bist ein professioneller und extrem kompetenter Sales-Engineer / Consultant der Firma "KLZ Cables".
+Deine Aufgabe ist es, Kunden und Interessenten bei der Auswahl von Mittelspannungskabeln, Starkstromkabeln und Infrastrukturausrüstung beratend zur Seite zu stehen.
-CONTEXT:
-${contextText}
+WICHTIGE REGELN:
+1. ANTWORTE IMMER IN DER SPRACHE DES BENUTZERS. Wenn der Benutzer Deutsch spricht, antworte auf Deutsch.
+2. Wenn der Kunde vage ist (z.B. "Ich will einen Windpark bauen"), würge ihn NICHT ab. Stelle stattdessen gezielte, professionelle Rückfragen als Berater (z.B. "Für einen Windpark benötigen wir einige Rahmendaten: Reden wir über die Parkverkabelung (Mittelspannung, z.B. 20kV oder 33kV) oder die Netzanbindung? Welche Querschnitte oder Ströme erwarten Sie?").
+3. Nutze das bereitgestellte KABELWISSEN und KATALOG-Gedächtnis unten, um deine Antworten zu fundieren.
+4. Bleibe stets professionell, lösungsorientiert und leicht technisch (Industrial Aesthetic). Du kannst humorvoll sein, wenn der Nutzer offensichtlich Quatsch fragt, aber lenke es immer elegant zurück zu Kabeln oder Energieinfrastruktur.
+5. Antworte in reinem Text (kein Markdown für die Antwort, es sei denn es sind einfache Absätze oder Listen).
+6. Wenn genügend Informationen vorhanden sind, präsentiere passende Kabel aus dem Katalog.
+7. Oute dich als Berater von KLZ Cables.
+
+VERFÜGBARER KONTEXT:
+${contextStr ? contextStr : 'Keine spezifischen Katalogdaten für diese Anfrage gefunden.'}
`;
- const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {
- method: 'POST',
- headers: {
- 'Authorization': `Bearer ${openRouterKey}`,
- 'Content-Type': 'application/json',
- 'HTTP-Referer': process.env.NEXT_PUBLIC_BASE_URL || 'https://klz-cables.com',
- 'X-Title': 'KLZ Cables Search AI',
- },
- body: JSON.stringify({
- model: 'google/gemini-3-flash-preview',
- messages: [
- { role: 'system', content: systemPrompt },
- { role: 'user', content: query }
- ],
- response_format: { type: "json_object" }
- }),
- });
-
- if (!response.ok) {
- const errorBody = await response.text();
- throw new Error(`OpenRouter error: ${response.status} ${errorBody}`);
- }
-
- const completion = await response.json();
- const rawContent = completion.choices?.[0]?.message?.content;
-
- let answerJson;
- try {
- // Remove any potential markdown json block markers
- const sanitizedObjStr = rawContent.replace(/^```json\s*/, '').replace(/\s*```$/, '');
- answerJson = JSON.parse(sanitizedObjStr);
- } catch (parseError) {
- console.error('Failed to parse AI response:', rawContent);
- answerJson = {
- answerText: rawContent || "Sorry, I had trouble thinking about cables right now.",
- products: []
- };
- }
-
- return NextResponse.json(answerJson);
- } catch (error) {
- console.error('AI Search API Error:', error);
- return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
+ const openRouterKey = process.env.OPENROUTER_API_KEY;
+ if (!openRouterKey) {
+ throw new Error('OPENROUTER_API_KEY is not set');
}
+
+ const fetchRes = await fetch('https://openrouter.ai/api/v1/chat/completions', {
+ method: 'POST',
+ headers: {
+ Authorization: `Bearer ${openRouterKey}`,
+ 'Content-Type': 'application/json',
+ 'HTTP-Referer': process.env.NEXT_PUBLIC_BASE_URL || 'https://klz-cables.com',
+ 'X-Title': 'KLZ Cables Search AI',
+ },
+ body: JSON.stringify({
+ model: 'mistralai/mistral-large-2407',
+ temperature: 0.3,
+ messages: [
+ { role: 'system', content: systemPrompt },
+ ...messages.map((m: any) => ({
+ role: m.role,
+ content: typeof m.content === 'string' ? m.content : JSON.stringify(m.content),
+ })),
+ ],
+ }),
+ });
+
+ if (!fetchRes.ok) {
+ const errBody = await fetchRes.text();
+ throw new Error(`OpenRouter API Error: ${errBody}`);
+ }
+
+ const data = await fetchRes.json();
+ const text = data.choices[0].message.content;
+
+ // Return the AI's answer along with any found products
+ return NextResponse.json({
+ answerText: text,
+ products: foundProducts,
+ });
+ } catch (error) {
+ console.error('AI Search API Error:', error);
+ Sentry.captureException(error, { tags: { context: 'ai-search-api' } });
+ return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
+ }
}
diff --git a/components/home/Hero.tsx b/components/home/Hero.tsx
index 07324fd5..998c4a94 100644
--- a/components/home/Hero.tsx
+++ b/components/home/Hero.tsx
@@ -6,8 +6,9 @@ import { useTranslations, useLocale } from 'next-intl';
import dynamic from 'next/dynamic';
import { useAnalytics } from '../analytics/useAnalytics';
import { AnalyticsEvents } from '../analytics/analytics-events';
+import AIOrb from '../search/AIOrb';
import { useState } from 'react';
-import { Search, Sparkles } from 'lucide-react';
+import { ChevronRight } from 'lucide-react';
import { AISearchResults } from '../search/AISearchResults';
const HeroIllustration = dynamic(() => import('./HeroIllustration'), { ssr: false });
@@ -76,24 +77,26 @@ export default function Hero({ data }: { data?: any }) {
@@ -103,7 +106,7 @@ export default function Hero({ data }: { data?: any }) {
href="/contact"
variant="white"
size="lg"
- className="group w-full sm:w-auto h-14 md:h-16 px-8 md:px-10 text-base md:text-lg hover:scale-105 transition-transform"
+ className="group w-full sm:w-auto h-14 md:h-16 px-8 md:px-10 text-base md:text-lg hover:scale-105 transition-all outline-none"
onClick={() =>
trackEvent(AnalyticsEvents.BUTTON_CLICK, {
label: data?.ctaLabel || t('cta'),
diff --git a/components/search/AIOrb.tsx b/components/search/AIOrb.tsx
new file mode 100644
index 00000000..b5708b79
--- /dev/null
+++ b/components/search/AIOrb.tsx
@@ -0,0 +1,88 @@
+/* eslint-disable react/no-unknown-property */
+'use client';
+
+import React, { useRef } from 'react';
+import { Canvas, useFrame } from '@react-three/fiber';
+import { Sphere, MeshDistortMaterial, Environment, Float } from '@react-three/drei';
+import * as THREE from 'three';
+
+interface AIOrbProps {
+ isThinking: boolean;
+}
+
+function Orb({ isThinking }: AIOrbProps) {
+ const meshRef = useRef(null);
+ const materialRef = useRef(null);
+
+ // Dynamic properties based on state
+ const targetDistort = isThinking ? 0.6 : 0.3;
+ const targetSpeed = isThinking ? 5 : 2;
+ const color = isThinking ? '#00FF88' : '#00A3FF'; // Green/Blue based on thinking state
+
+ useFrame((state) => {
+ if (!materialRef.current) return;
+
+ // Smoothly interpolate material properties
+ materialRef.current.distort = THREE.MathUtils.lerp(
+ materialRef.current.distort,
+ targetDistort,
+ 0.1,
+ );
+ materialRef.current.speed = THREE.MathUtils.lerp(materialRef.current.speed, targetSpeed, 0.1);
+
+ // Smooth color transition
+ const currentColor = materialRef.current.color;
+ const targetColorObj = new THREE.Color(color);
+ currentColor.lerp(targetColorObj, 0.05);
+
+ // Slow rotation
+ if (meshRef.current) {
+ meshRef.current.rotation.x = state.clock.getElapsedTime() * 0.2;
+ meshRef.current.rotation.y = state.clock.getElapsedTime() * 0.3;
+ }
+ });
+
+ return (
+
+
+
+
+
+ );
+}
+
+export default function AIOrb({ isThinking = false }: AIOrbProps) {
+ return (
+
+ {/* Ambient glow effect behind the orb */}
+
+
+
+
+ );
+}
diff --git a/components/search/AISearchResults.tsx b/components/search/AISearchResults.tsx
index 023a2d86..cbb1680b 100644
--- a/components/search/AISearchResults.tsx
+++ b/components/search/AISearchResults.tsx
@@ -1,230 +1,323 @@
'use client';
import { useState, useRef, useEffect, KeyboardEvent } from 'react';
-import { useTranslations } from 'next-intl';
-import { Search, Loader2, X, Sparkles, ChevronRight, MessageSquareWarning } from 'lucide-react';
-import { Button, cn } from '@/components/ui';
+import { Search, X, Sparkles, ChevronRight, MessageSquareWarning } from 'lucide-react';
import Link from 'next/link';
import { useAnalytics } from '../analytics/useAnalytics';
import { AnalyticsEvents } from '../analytics/analytics-events';
-import Image from 'next/image';
+import ReactMarkdown from 'react-markdown';
+import remarkGfm from 'remark-gfm';
+import AIOrb from './AIOrb';
interface ProductMatch {
- id: string;
- title: string;
- sku: string;
- slug: string;
+ id: string;
+ title: string;
+ sku: string;
+ slug: string;
}
-interface AIResponse {
- answerText: string;
- products: ProductMatch[];
+interface Message {
+ role: 'user' | 'assistant';
+ content: string;
+ products?: ProductMatch[];
}
-
interface ComponentProps {
- isOpen: boolean;
- onClose: () => void;
- initialQuery?: string;
- triggerSearch?: boolean; // If true, immediately searches on mount with initialQuery
+ isOpen: boolean;
+ onClose: () => void;
+ initialQuery?: string;
+ triggerSearch?: boolean; // If true, immediately searches on mount with initialQuery
}
-export function AISearchResults({ isOpen, onClose, initialQuery = '', triggerSearch = false }: ComponentProps) {
- const t = useTranslations('Search');
- const { trackEvent } = useAnalytics();
+export function AISearchResults({
+ isOpen,
+ onClose,
+ initialQuery = '',
+ triggerSearch = false,
+}: ComponentProps) {
+ const { trackEvent } = useAnalytics();
- const [query, setQuery] = useState(initialQuery);
- const [honeypot, setHoneypot] = useState('');
- const [isLoading, setIsLoading] = useState(false);
- const [response, setResponse] = useState(null);
- const [error, setError] = useState(null);
- const inputRef = useRef(null);
- const modalRef = useRef(null);
+ const [query, setQuery] = useState('');
+ const [messages, setMessages] = useState([]);
+ const [honeypot, setHoneypot] = useState('');
+ const [isLoading, setIsLoading] = useState(false);
+ const [error, setError] = useState(null);
+ const inputRef = useRef(null);
+ const modalRef = useRef(null);
+ const messagesEndRef = useRef(null);
- useEffect(() => {
- if (isOpen) {
- document.body.style.overflow = 'hidden';
- // Slight delay to allow animation to start before focus
- setTimeout(() => inputRef.current?.focus(), 100);
+ useEffect(() => {
+ if (isOpen) {
+ document.body.style.overflow = 'hidden';
+ setTimeout(() => inputRef.current?.focus(), 100);
- if (triggerSearch && initialQuery && !response) {
- handleSearch(initialQuery);
- }
- } else {
- document.body.style.overflow = 'unset';
- }
- return () => { document.body.style.overflow = 'unset'; };
- }, [isOpen, triggerSearch]);
-
- useEffect(() => {
+ if (triggerSearch && initialQuery && messages.length === 0) {
setQuery(initialQuery);
- }, [initialQuery]);
-
- const handleSearch = async (searchQuery: string = query) => {
- if (!searchQuery.trim()) return;
-
- setIsLoading(true);
- setError(null);
- setResponse(null);
-
- trackEvent(AnalyticsEvents.FORM_SUBMIT, {
- type: 'ai_search',
- query: searchQuery
- });
-
- try {
- const res = await fetch('/api/ai-search', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ query: searchQuery, _honeypot: honeypot })
- });
-
- const data = await res.json();
-
- if (!res.ok) {
- throw new Error(data.error || 'Failed to fetch search results');
- }
-
- setResponse(data);
- } catch (err: any) {
- console.error(err);
- setError(err.message || 'An error occurred while searching. Please try again.');
- } finally {
- setIsLoading(false);
- }
+ handleSearch(initialQuery);
+ } else if (!triggerSearch) {
+ setQuery('');
+ }
+ } else {
+ document.body.style.overflow = 'unset';
+ setQuery('');
+ setMessages([]);
+ setError(null);
+ setIsLoading(false);
+ }
+ return () => {
+ document.body.style.overflow = 'unset';
};
+ }, [isOpen, triggerSearch]);
- const onKeyDown = (e: KeyboardEvent) => {
- if (e.key === 'Enter') {
- e.preventDefault();
- handleSearch();
- }
- if (e.key === 'Escape') {
- onClose();
- }
- };
+ useEffect(() => {
+ if (isOpen && initialQuery && messages.length === 0) {
+ setQuery(initialQuery);
+ }
+ }, [initialQuery, isOpen]);
- if (!isOpen) return null;
+ useEffect(() => {
+ // Auto-scroll to bottom of chat
+ messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
+ }, [messages, isLoading]);
- return (
-
-
+ const handleSearch = async (searchQuery: string = query) => {
+ if (!searchQuery.trim() || isLoading) return;
-
- {/* Header - Search Bar */}
-
-
-
setQuery(e.target.value)}
- onKeyDown={onKeyDown}
- placeholder={"What are you looking for?"}
- className="w-full bg-transparent border-none text-white text-xl md:text-3xl font-extrabold focus:outline-none placeholder:text-white/30"
- />
-
setHoneypot(e.target.value)}
- tabIndex={-1}
- autoComplete="off"
- aria-hidden="true"
- />
- {isLoading ? (
-
- ) : query ? (
-
- ) : null}
-
-
-
+ const newUserMessage: Message = { role: 'user', content: searchQuery };
+ const newMessagesContext = [...messages, newUserMessage];
- {/* Content Area */}
-
- {!response && !isLoading && !error && (
-
-
-
Describe what you need, and our AI will find it.
-
- )}
+ setMessages(newMessagesContext);
+ setQuery('');
+ setIsLoading(true);
+ setError(null);
- {error && (
-
-
-
-
Encountered an error
-
{error}
-
-
- )}
+ trackEvent(AnalyticsEvents.FORM_SUBMIT, {
+ type: 'ai_search',
+ query: searchQuery,
+ });
- {response && (
-
- {/* AI Answer */}
-
-
-
-
AI Assistant
-
- {response.answerText}
-
-
+ try {
+ const res = await fetch('/api/ai-search', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ messages: newMessagesContext,
+ _honeypot: honeypot,
+ }),
+ });
- {/* Product Matches */}
- {response.products && response.products.length > 0 && (
-
-
Matching Products
-
- {response.products.map((product, idx) => (
-
{
- onClose();
- trackEvent(AnalyticsEvents.BUTTON_CLICK, {
- target: product.slug,
- location: 'ai_search_results'
- });
- }}
- className="group flex flex-col justify-between bg-white text-primary rounded-xl p-6 hover:shadow-2xl hover:-translate-y-1 transition-all duration-300"
- >
-
-
{product.sku}
-
{product.title}
-
-
- Details
-
-
-
- ))}
-
-
- )}
-
- )}
-
-
+ const data = await res.json();
+
+ if (!res.ok) {
+ throw new Error(data.error || 'Failed to fetch search results');
+ }
+
+ setMessages((prev) => [
+ ...prev,
+ {
+ role: 'assistant',
+ content: data.answerText,
+ products: data.products,
+ },
+ ]);
+
+ // Re-focus input after response so user can continue typing easily
+ setTimeout(() => inputRef.current?.focus(), 100);
+ } catch (err: any) {
+ console.error(err);
+ setError(err.message || 'An error occurred while chatting. Please try again.');
+ trackEvent(AnalyticsEvents.ERROR, {
+ location: 'ai_search_results',
+ message: err.message,
+ query: searchQuery,
+ });
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const onKeyDown = (e: KeyboardEvent
) => {
+ if (e.key === 'Enter') {
+ e.preventDefault();
+ handleSearch();
+ }
+ if (e.key === 'Escape') {
+ onClose();
+ }
+ };
+
+ if (!isOpen) return null;
+
+ // Handle clicking outside to close
+ const handleBackdropClick = (e: React.MouseEvent) => {
+ if (e.target === e.currentTarget) {
+ onClose();
+ }
+ };
+
+ return (
+
+
+ {/* Header */}
+
+
+
+
+ KLZ AI Consultant
+
+
+
- );
+
+ {/* Chat History Area */}
+
+ {messages.length === 0 && !isLoading && !error && (
+
+
+
I am your technical consultant.
+
+ Describe your project, ask for specific cables, or tell me your requirements.
+
+
+ )}
+
+ {messages.map((msg, index) => (
+
+
+ {msg.role === 'assistant' && (
+
+
+ AI Assistant
+
+ )}
+
+ {msg.role === 'assistant' ? (
+
{msg.content}
+ ) : (
+
{msg.content}
+ )}
+
+
+ {/* Product Matches inside Assistant Message */}
+ {msg.role === 'assistant' && msg.products && msg.products.length > 0 && (
+
+
+ Empfohlene Produkte
+
+
+ {msg.products.map((product, idx) => (
+
{
+ onClose();
+ trackEvent(AnalyticsEvents.BUTTON_CLICK, {
+ target: product.slug,
+ location: 'ai_search_results',
+ });
+ }}
+ className="group flex flex-col justify-between bg-white text-primary rounded-lg p-4 hover:shadow-lg hover:-translate-y-1 transition-all duration-300"
+ >
+
+
+ {product.sku}
+
+
+ {product.title}
+
+
+
+
+ Details
+
+
+
+
+ ))}
+
+
+ )}
+
+
+ ))}
+
+ {isLoading && (
+
+ )}
+
+ {error && (
+
+
+
+
System Error
+
{error}
+
+
+ )}
+
+
+
+
+ {/* Input Area */}
+
+
+
+ );
}
diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml
index b5ffc484..8fa87877 100644
--- a/docker-compose.dev.yml
+++ b/docker-compose.dev.yml
@@ -48,7 +48,7 @@ services:
cpus: '4'
memory: 8G
command: >
- sh -c "pnpm install && pnpm next dev --webpack --hostname 0.0.0.0"
+ sh -c "pnpm install --no-frozen-lockfile && pnpm next dev --webpack --hostname 0.0.0.0"
labels:
- "traefik.enable=true"
- "traefik.http.services.${PROJECT_NAME:-klz}-app-svc.loadbalancer.server.port=3000"
diff --git a/docker-compose.yml b/docker-compose.yml
index 9a6a3c5f..939f9cb8 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -109,6 +109,8 @@ services:
klz-qdrant:
image: qdrant/qdrant:v1.13.2
restart: unless-stopped
+ ports:
+ - "6333:6333"
environment:
QDRANT__SERVICE__HTTP_PORT: 6333
QDRANT__SERVICE__GRPC_PORT: 6334
diff --git a/package.json b/package.json
index f2c017c1..582288f9 100644
--- a/package.json
+++ b/package.json
@@ -5,10 +5,11 @@
"packageManager": "pnpm@10.18.3",
"dependencies": {
"@ai-sdk/google": "^3.0.31",
- "@mintel/mail": "^1.8.21",
- "@mintel/next-config": "^1.8.21",
- "@mintel/next-feedback": "^1.8.21",
- "@mintel/next-utils": "^1.8.21",
+ "@ai-sdk/openai": "^3.0.36",
+ "@mintel/mail": "^1.9.0",
+ "@mintel/next-config": "^1.9.0",
+ "@mintel/next-feedback": "^1.9.0",
+ "@mintel/next-utils": "^1.9.0",
"@payloadcms/db-postgres": "^3.77.0",
"@payloadcms/email-nodemailer": "^3.77.0",
"@payloadcms/next": "^3.77.0",
@@ -17,6 +18,8 @@
"@qdrant/js-client-rest": "^1.17.0",
"@react-email/components": "^1.0.7",
"@react-pdf/renderer": "^4.3.2",
+ "@react-three/drei": "^10.7.7",
+ "@react-three/fiber": "^9.5.0",
"@sentry/nextjs": "^10.39.0",
"@types/recharts": "^2.0.1",
"ai": "^6.0.101",
@@ -42,13 +45,17 @@
"react-dom": "^19.2.4",
"react-email": "^5.2.5",
"react-leaflet": "^4.2.1",
+ "react-markdown": "^10.1.0",
"recharts": "^3.7.0",
+ "rehype-raw": "^7.0.0",
+ "remark-gfm": "^4.0.1",
"require-in-the-middle": "^8.0.1",
"resend": "^3.5.0",
"schema-dts": "^1.1.5",
"sharp": "^0.34.5",
"svg-to-pdfkit": "^0.1.8",
"tailwind-merge": "^3.4.0",
+ "three": "^0.183.1",
"xlsx": "npm:@e965/xlsx@^0.20.3",
"zod": "3.25.76"
},
@@ -57,8 +64,8 @@
"@commitlint/config-conventional": "^20.4.0",
"@cspell/dict-de-de": "^4.1.2",
"@lhci/cli": "^0.15.1",
- "@mintel/eslint-config": "1.8.21",
- "@mintel/tsconfig": "^1.8.21",
+ "@mintel/eslint-config": "^1.9.0",
+ "@mintel/tsconfig": "^1.9.0",
"@next/bundle-analyzer": "^16.1.6",
"@tailwindcss/cli": "^4.1.18",
"@tailwindcss/postcss": "^4.1.18",
@@ -84,6 +91,7 @@
"lint-staged": "^16.2.7",
"lucide-react": "^0.563.0",
"pa11y-ci": "^4.0.1",
+ "pdf-parse": "^2.4.5",
"postcss": "^8.5.6",
"prettier": "^3.8.1",
"puppeteer": "^24.37.3",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 9a1d1511..0f1a6f4e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -15,18 +15,21 @@ importers:
'@ai-sdk/google':
specifier: ^3.0.31
version: 3.0.31(zod@3.25.76)
+ '@ai-sdk/openai':
+ specifier: ^3.0.36
+ version: 3.0.36(zod@3.25.76)
'@mintel/mail':
- specifier: ^1.8.21
- version: 1.8.21(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ specifier: ^1.9.0
+ version: 1.9.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@mintel/next-config':
- specifier: ^1.8.21
- version: 1.8.21(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.1(@opentelemetry/api@1.9.0))(@swc/helpers@0.5.18)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3)(typescript@5.9.3)(webpack@5.105.0(esbuild@0.25.12))
+ specifier: ^1.9.0
+ version: 1.9.5(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.1(@opentelemetry/api@1.9.0))(@swc/helpers@0.5.18)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3)(typescript@5.9.3)(webpack@5.105.0(esbuild@0.25.12))
'@mintel/next-feedback':
- specifier: ^1.8.21
- version: 1.8.21(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3)
+ specifier: ^1.9.0
+ version: 1.9.5(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3)
'@mintel/next-utils':
- specifier: ^1.8.21
- version: 1.8.21(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@swc/helpers@0.5.18)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3)(typescript@5.9.3)
+ specifier: ^1.9.0
+ version: 1.9.5(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@swc/helpers@0.5.18)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3)(typescript@5.9.3)
'@payloadcms/db-postgres':
specifier: ^3.77.0
version: 3.77.0(@opentelemetry/api@1.9.0)(payload@3.77.0(graphql@16.12.0)(typescript@5.9.3))
@@ -51,6 +54,12 @@ importers:
'@react-pdf/renderer':
specifier: ^4.3.2
version: 4.3.2(react@19.2.4)
+ '@react-three/drei':
+ specifier: ^10.7.7
+ version: 10.7.7(@react-three/fiber@9.5.0(@types/react@19.2.13)(immer@11.1.4)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(three@0.183.1))(@types/react@19.2.13)(@types/three@0.183.1)(immer@11.1.4)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(three@0.183.1)
+ '@react-three/fiber':
+ specifier: ^9.5.0
+ version: 9.5.0(@types/react@19.2.13)(immer@11.1.4)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(three@0.183.1)
'@sentry/nextjs':
specifier: ^10.39.0
version: 10.39.0(@opentelemetry/context-async-hooks@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.1(@opentelemetry/api@1.9.0))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3))(react@19.2.4)(webpack@5.105.0(esbuild@0.25.12))
@@ -126,9 +135,18 @@ importers:
react-leaflet:
specifier: ^4.2.1
version: 4.2.1(leaflet@1.9.4)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ react-markdown:
+ specifier: ^10.1.0
+ version: 10.1.0(@types/react@19.2.13)(react@19.2.4)
recharts:
specifier: ^3.7.0
version: 3.7.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react-is@16.13.1)(react@19.2.4)(redux@5.0.1)
+ rehype-raw:
+ specifier: ^7.0.0
+ version: 7.0.0
+ remark-gfm:
+ specifier: ^4.0.1
+ version: 4.0.1
require-in-the-middle:
specifier: ^8.0.1
version: 8.0.1
@@ -147,6 +165,9 @@ importers:
tailwind-merge:
specifier: ^3.4.0
version: 3.4.0
+ three:
+ specifier: ^0.183.1
+ version: 0.183.1
xlsx:
specifier: npm:@e965/xlsx@^0.20.3
version: '@e965/xlsx@0.20.3'
@@ -167,11 +188,11 @@ importers:
specifier: ^0.15.1
version: 0.15.1
'@mintel/eslint-config':
- specifier: 1.8.21
- version: 1.8.21(@typescript-eslint/parser@8.56.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
+ specifier: ^1.9.0
+ version: 1.9.5(@typescript-eslint/parser@8.56.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
'@mintel/tsconfig':
- specifier: ^1.8.21
- version: 1.8.21
+ specifier: ^1.9.0
+ version: 1.9.5
'@next/bundle-analyzer':
specifier: ^16.1.6
version: 16.1.6
@@ -247,6 +268,9 @@ importers:
pa11y-ci:
specifier: ^4.0.1
version: 4.0.1(typescript@5.9.3)
+ pdf-parse:
+ specifier: ^2.4.5
+ version: 2.4.5
postcss:
specifier: ^8.5.6
version: 8.5.6
@@ -295,6 +319,12 @@ packages:
peerDependencies:
zod: ^3.25.76 || ^4.1.8
+ '@ai-sdk/openai@3.0.36':
+ resolution: {integrity: sha512-foY3onGY8l3q9niMw0Cwe9xrYnm46keIWL57NRw6F3DKzSW9TYTfx0cQJs/j8lXJ8lPzqNxpMO/zXOkqCUt3IQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ zod: ^3.25.76 || ^4.1.8
+
'@ai-sdk/provider-utils@4.0.15':
resolution: {integrity: sha512-8XiKWbemmCbvNN0CLR9u3PQiet4gtEVIrX4zzLxnCj06AwsEDJwJVBbKrEI4t6qE8XRSIvU2irka0dcpziKW6w==}
engines: {node: '>=18'}
@@ -761,10 +791,6 @@ packages:
'@dimforge/rapier3d-compat@0.12.0':
resolution: {integrity: sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==}
- '@directus/sdk@21.1.0':
- resolution: {integrity: sha512-Ig8zZAQDbc7QMIM54N+x71C04lni9MN9yalNAezjDjFdNknTJzupDY7V5cb+kOJL8GsqDE9Bg8xq8xCmkDVs5A==}
- engines: {node: '>=22'}
-
'@discoveryjs/json-ext@0.5.7':
resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==}
engines: {node: '>=10.0.0'}
@@ -1725,32 +1751,35 @@ packages:
'@lhci/utils@0.15.1':
resolution: {integrity: sha512-WclJnUQJeOMY271JSuaOjCv/aA0pgvuHZS29NFNdIeI14id8eiFsjith85EGKYhljgoQhJ2SiW4PsVfFiakNNw==}
+ '@mediapipe/tasks-vision@0.10.17':
+ resolution: {integrity: sha512-CZWV/q6TTe8ta61cZXjfnnHsfWIdFhms03M9T7Cnd5y2mdpylJM0rF1qRq+wsQVRMLz1OYPVEBU9ph2Bx8cxrg==}
+
'@medv/finder@4.0.2':
resolution: {integrity: sha512-RraNY9SCcx4KZV0Dh6BEW6XEW2swkqYca74pkFFRw6hHItSHiy+O/xMnpbofjYbzXj0tSpBGthUF1hHTsr3vIQ==}
- '@mintel/eslint-config@1.8.21':
- resolution: {integrity: sha512-GH5tm1y89AhD+Lxf95BGCOdy7Nv1OPNLWrUpaTR6jsuKfH2dm9fU66LF7sDH5THmrkfAZ8zSzHJsKPjintv3IA==}
+ '@mintel/eslint-config@1.9.5':
+ resolution: {integrity: sha512-nZylW/99gnzkU/oCQNR5Muj6/gGYsf1EJSM8LSRAU3sU6wR3kUu4cba7LVQPb6uTrfW8CzD2JsL8pzanDqSZzA==, tarball: https://git.infra.mintel.me/api/packages/mmintel/npm/%40mintel%2Feslint-config/-/1.9.5/eslint-config-1.9.5.tgz}
- '@mintel/mail@1.8.21':
- resolution: {integrity: sha512-leZV9gINmxD4eVJ3Ij9KdrQoyib67NVHgL/93J7KcWSUWKbr2HVuKUBpiWImeeEZn3JO0f7JwRbVUzXPBRVeQA==}
+ '@mintel/mail@1.9.5':
+ resolution: {integrity: sha512-g7+pL2/NFrmjAgMUHHj4GTpWestabdIZBR0UkMiJBsEFpxu7TFJXorFw418w/z+G21JQDKLyQrT/NpNLaCFW+Q==, tarball: https://git.infra.mintel.me/api/packages/mmintel/npm/%40mintel%2Fmail/-/1.9.5/mail-1.9.5.tgz}
peerDependencies:
react: ^19.0.0
react-dom: ^19.0.0
- '@mintel/next-config@1.8.21':
- resolution: {integrity: sha512-K4jb9Glf84a212BRZ/zmOUueBphmsikvStFCuDc5lxyFT+Hkj4w8ChmtI7gaUxHMrftooduGPXJ1+NFpKkvc/Q==}
+ '@mintel/next-config@1.9.5':
+ resolution: {integrity: sha512-gnEtpoGXHjFwPIccU5GUoqqkAL1vni1uNtxSJ1XkG4z8HVQ9C3dH4IxxmUqMJaLL0fTSDOZpZKZIV0C1C28iyg==, tarball: https://git.infra.mintel.me/api/packages/mmintel/npm/%40mintel%2Fnext-config/-/1.9.5/next-config-1.9.5.tgz}
- '@mintel/next-feedback@1.8.21':
- resolution: {integrity: sha512-n2KzGDbOvAskuzjbt8h5EOMSEnISxHrsXxJwDdMxCXEgmzfJSvWpP2mAqb684dimOwo1UWHE6DMSAFc1FXeYwg==}
+ '@mintel/next-feedback@1.9.5':
+ resolution: {integrity: sha512-7h9ClhTiEA86u+8hrdJohoPJUkVGy6txSo9P9/7//wS/CL81c5azV7GrGDS/pdGmAYdkVSp14i3/8Uq9lqTlZA==, tarball: https://git.infra.mintel.me/api/packages/mmintel/npm/%40mintel%2Fnext-feedback/-/1.9.5/next-feedback-1.9.5.tgz}
peerDependencies:
react: ^19.0.0
react-dom: ^19.0.0
- '@mintel/next-utils@1.8.21':
- resolution: {integrity: sha512-sr0yDtySGou+3DNvrqY6HWSHCiVIc8nnoRbckyPMSE21AGxk2aJineXGy9BO9tulSBdhStm2SgdC7McMFTszug==}
+ '@mintel/next-utils@1.9.5':
+ resolution: {integrity: sha512-Ndcf2AONTccw8zMbsyFudubBsnoXfYuYoRPGq3d5OLcKfzlz+2g0ee8xcsNFwa9fpnAAMlvauYu0mIRGA/ydVA==, tarball: https://git.infra.mintel.me/api/packages/mmintel/npm/%40mintel%2Fnext-utils/-/1.9.5/next-utils-1.9.5.tgz}
- '@mintel/tsconfig@1.8.21':
- resolution: {integrity: sha512-ePBfBZiijyXKOS6nLIyxkg7QDZEEC1TugzNhmvwwpc0Yh7BmVHyNpvjg6zKsoGj2rok+9Kc8mLH1WihQIs8SKg==}
+ '@mintel/tsconfig@1.9.5':
+ resolution: {integrity: sha512-dxYJoGAE+9vwaPIpQL3NCzyEKMFYuJyDwWmCuUeP0lFo91brgSpHfl4cw98sK8UsrncIP6r4YvAgncooVnovaQ==, tarball: https://git.infra.mintel.me/api/packages/mmintel/npm/%40mintel%2Ftsconfig/-/1.9.5/tsconfig-1.9.5.tgz}
'@monaco-editor/loader@1.7.0':
resolution: {integrity: sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA==}
@@ -1762,6 +1791,75 @@ packages:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ '@monogrid/gainmap-js@3.4.0':
+ resolution: {integrity: sha512-2Z0FATFHaoYJ8b+Y4y4Hgfn3FRFwuU5zRrk+9dFWp4uGAdHGqVEdP7HP+gLA3X469KXHmfupJaUbKo1b/aDKIg==}
+ peerDependencies:
+ three: '>= 0.159.0'
+
+ '@napi-rs/canvas-android-arm64@0.1.80':
+ resolution: {integrity: sha512-sk7xhN/MoXeuExlggf91pNziBxLPVUqF2CAVnB57KLG/pz7+U5TKG8eXdc3pm0d7Od0WreB6ZKLj37sX9muGOQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [android]
+
+ '@napi-rs/canvas-darwin-arm64@0.1.80':
+ resolution: {integrity: sha512-O64APRTXRUiAz0P8gErkfEr3lipLJgM6pjATwavZ22ebhjYl/SUbpgM0xcWPQBNMP1n29afAC/Us5PX1vg+JNQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@napi-rs/canvas-darwin-x64@0.1.80':
+ resolution: {integrity: sha512-FqqSU7qFce0Cp3pwnTjVkKjjOtxMqRe6lmINxpIZYaZNnVI0H5FtsaraZJ36SiTHNjZlUB69/HhxNDT1Aaa9vA==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@napi-rs/canvas-linux-arm-gnueabihf@0.1.80':
+ resolution: {integrity: sha512-eyWz0ddBDQc7/JbAtY4OtZ5SpK8tR4JsCYEZjCE3dI8pqoWUC8oMwYSBGCYfsx2w47cQgQCgMVRVTFiiO38hHQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm]
+ os: [linux]
+
+ '@napi-rs/canvas-linux-arm64-gnu@0.1.80':
+ resolution: {integrity: sha512-qwA63t8A86bnxhuA/GwOkK3jvb+XTQaTiVML0vAWoHyoZYTjNs7BzoOONDgTnNtr8/yHrq64XXzUoLqDzU+Uuw==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@napi-rs/canvas-linux-arm64-musl@0.1.80':
+ resolution: {integrity: sha512-1XbCOz/ymhj24lFaIXtWnwv/6eFHXDrjP0jYkc6iHQ9q8oXKzUX1Lc6bu+wuGiLhGh2GS/2JlfORC5ZcXimRcg==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@napi-rs/canvas-linux-riscv64-gnu@0.1.80':
+ resolution: {integrity: sha512-XTzR125w5ZMs0lJcxRlS1K3P5RaZ9RmUsPtd1uGt+EfDyYMu4c6SEROYsxyatbbu/2+lPe7MPHOO/0a0x7L/gw==}
+ engines: {node: '>= 10'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@napi-rs/canvas-linux-x64-gnu@0.1.80':
+ resolution: {integrity: sha512-BeXAmhKg1kX3UCrJsYbdQd3hIMDH/K6HnP/pG2LuITaXhXBiNdh//TVVVVCBbJzVQaV5gK/4ZOCMrQW9mvuTqA==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@napi-rs/canvas-linux-x64-musl@0.1.80':
+ resolution: {integrity: sha512-x0XvZWdHbkgdgucJsRxprX/4o4sEed7qo9rCQA9ugiS9qE2QvP0RIiEugtZhfLH3cyI+jIRFJHV4Fuz+1BHHMg==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@napi-rs/canvas-win32-x64-msvc@0.1.80':
+ resolution: {integrity: sha512-Z8jPsM6df5V8B1HrCHB05+bDiCxjE9QA//3YrkKIdVDEwn5RKaqOxCJDRJkl48cJbylcrJbW4HxZbTte8juuPg==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [win32]
+
+ '@napi-rs/canvas@0.1.80':
+ resolution: {integrity: sha512-DxuT1ClnIPts1kQx8FBmkk4BQDTfI5kIzywAaMjQSXfNnra5UFU9PwurXrl+Je3bJ6BGsp/zmshVVFbCmyI+ww==}
+ engines: {node: '>= 10'}
+
'@napi-rs/wasm-runtime@0.2.12':
resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==}
@@ -2569,6 +2667,42 @@ packages:
'@react-pdf/types@2.9.2':
resolution: {integrity: sha512-dufvpKId9OajLLbgn9q7VLUmyo1Jf+iyGk2ZHmCL8nIDtL8N1Ejh9TH7+pXXrR0tdie1nmnEb5Bz9U7g4hI4/g==}
+ '@react-three/drei@10.7.7':
+ resolution: {integrity: sha512-ff+J5iloR0k4tC++QtD/j9u3w5fzfgFAWDtAGQah9pF2B1YgOq/5JxqY0/aVoQG5r3xSZz0cv5tk2YuBob4xEQ==}
+ peerDependencies:
+ '@react-three/fiber': ^9.0.0
+ react: ^19
+ react-dom: ^19
+ three: '>=0.159'
+ peerDependenciesMeta:
+ react-dom:
+ optional: true
+
+ '@react-three/fiber@9.5.0':
+ resolution: {integrity: sha512-FiUzfYW4wB1+PpmsE47UM+mCads7j2+giRBltfwH7SNhah95rqJs3ltEs9V3pP8rYdS0QlNne+9Aj8dS/SiaIA==}
+ peerDependencies:
+ expo: '>=43.0'
+ expo-asset: '>=8.4'
+ expo-file-system: '>=11.0'
+ expo-gl: '>=11.0'
+ react: '>=19 <19.3'
+ react-dom: '>=19 <19.3'
+ react-native: '>=0.78'
+ three: '>=0.156'
+ peerDependenciesMeta:
+ expo:
+ optional: true
+ expo-asset:
+ optional: true
+ expo-file-system:
+ optional: true
+ expo-gl:
+ optional: true
+ react-dom:
+ optional: true
+ react-native:
+ optional: true
+
'@reduxjs/toolkit@2.11.2':
resolution: {integrity: sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==}
peerDependencies:
@@ -3166,6 +3300,9 @@ packages:
'@types/deep-eql@4.0.2':
resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
+ '@types/draco3d@1.4.10':
+ resolution: {integrity: sha512-AX22jp8Y7wwaBgAixaSvkoG4M/+PlAcm3Qs4OW8yT9DM4xUpWKeFhLueTAyZF39pviAdcDdeJoACapiAceqNcw==}
+
'@types/eslint-scope@3.7.7':
resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==}
@@ -3222,6 +3359,9 @@ packages:
'@types/nodemailer@7.0.9':
resolution: {integrity: sha512-vI8oF1M+8JvQhsId0Pc38BdUP2evenIIys7c7p+9OZXSPOH5c1dyINP1jT8xQ2xPuBUXmIC87s+91IZMDjH8Ow==}
+ '@types/offscreencanvas@2019.7.3':
+ resolution: {integrity: sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==}
+
'@types/parse-json@4.0.2':
resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
@@ -3239,6 +3379,11 @@ packages:
peerDependencies:
'@types/react': ^19.2.0
+ '@types/react-reconciler@0.28.9':
+ resolution: {integrity: sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==}
+ peerDependencies:
+ '@types/react': '*'
+
'@types/react-transition-group@4.4.12':
resolution: {integrity: sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==}
peerDependencies:
@@ -3349,6 +3494,9 @@ packages:
resolution: {integrity: sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ '@ungap/structured-clone@1.3.0':
+ resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
+
'@unrs/resolver-binding-android-arm-eabi@1.11.1':
resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==}
cpu: [arm]
@@ -3444,6 +3592,14 @@ packages:
cpu: [x64]
os: [win32]
+ '@use-gesture/core@10.3.1':
+ resolution: {integrity: sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw==}
+
+ '@use-gesture/react@10.3.1':
+ resolution: {integrity: sha512-Yy19y6O2GJq8f7CHf7L0nxL8bf4PZCPaVOCgJrusOeFHY1LvHgYXnmnXg6N5iwAnbgbZCDjo60SiM6IPJi9C5g==}
+ peerDependencies:
+ react: '>= 16.8.0'
+
'@vercel/oidc@3.1.0':
resolution: {integrity: sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w==}
engines: {node: '>= 20'}
@@ -3803,6 +3959,9 @@ packages:
resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==}
engines: {node: '>=10', npm: '>=6'}
+ bail@2.0.2:
+ resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==}
+
balanced-match@4.0.3:
resolution: {integrity: sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==}
engines: {node: 20 || >=22}
@@ -3928,6 +4087,9 @@ packages:
buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
+ buffer@6.0.3:
+ resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
+
busboy@1.6.0:
resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
engines: {node: '>=10.16.0'}
@@ -3956,6 +4118,12 @@ packages:
resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}
engines: {node: '>=6'}
+ camera-controls@3.1.2:
+ resolution: {integrity: sha512-xkxfpG2ECZ6Ww5/9+kf4mfg1VEYAoe9aDSY+IwF0UEs7qEzwy0aVRfs2grImIECs/PoBtWFrh7RXsQkwG922JA==}
+ engines: {node: '>=22.0.0', npm: '>=10.5.1'}
+ peerDependencies:
+ three: '>=0.126.1'
+
caniuse-lite@1.0.30001769:
resolution: {integrity: sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==}
@@ -4124,6 +4292,9 @@ packages:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
+ comma-separated-tokens@2.0.3:
+ resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
+
commander@10.0.1:
resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==}
engines: {node: '>=14'}
@@ -4255,6 +4426,11 @@ packages:
resolution: {integrity: sha512-p9nwwR4qyT5W996vBZhdvBCnMhicY5ytZkR4D1Xj0wuTDEiMnjwR57Q3RXYY/s0EpX6Ay3vgIcfaR+ewGHsi+g==}
engines: {node: '>=18.0'}
+ cross-env@7.0.3:
+ resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==}
+ engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'}
+ hasBin: true
+
cross-spawn@7.0.6:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
@@ -4515,6 +4691,9 @@ packages:
resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
+ detect-gpu@5.0.70:
+ resolution: {integrity: sha512-bqerEP1Ese6nt3rFkwPnGbsUF9a4q+gMmpTVVOEzoCyeCc+y7/RvJnQZJx1JwhgQI5Ntg0Kgat8Uu7XpBqnz1w==}
+
detect-libc@2.1.2:
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
engines: {node: '>=8'}
@@ -4570,6 +4749,9 @@ packages:
resolution: {integrity: sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==}
engines: {node: '>=12'}
+ draco3d@1.5.7:
+ resolution: {integrity: sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ==}
+
drizzle-kit@0.31.7:
resolution: {integrity: sha512-hOzRGSdyKIU4FcTSFYGKdXEjFsncVwHZ43gY3WU5Bz9j5Iadp6Rh6hxLSQ1IWXpKLBKt/d5y1cpSPcV+FcoQ1A==}
hasBin: true
@@ -4837,6 +5019,10 @@ packages:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
engines: {node: '>=10'}
+ escape-string-regexp@5.0.0:
+ resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
+ engines: {node: '>=12'}
+
escodegen@2.1.0:
resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==}
engines: {node: '>=6.0'}
@@ -5027,6 +5213,9 @@ packages:
resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==}
engines: {node: '>=0.10.0'}
+ extend@3.0.2:
+ resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
+
external-editor@3.1.0:
resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==}
engines: {node: '>=4'}
@@ -5086,6 +5275,9 @@ packages:
picomatch:
optional: true
+ fflate@0.6.10:
+ resolution: {integrity: sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==}
+
fflate@0.8.2:
resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
@@ -5326,6 +5518,9 @@ packages:
resolution: {integrity: sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==}
engines: {node: '>=0.10.0'}
+ glsl-noise@0.0.0:
+ resolution: {integrity: sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w==}
+
gopd@1.2.0:
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
engines: {node: '>= 0.4'}
@@ -5395,6 +5590,27 @@ packages:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
+ hast-util-from-parse5@8.0.3:
+ resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==}
+
+ hast-util-parse-selector@4.0.0:
+ resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==}
+
+ hast-util-raw@9.1.0:
+ resolution: {integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==}
+
+ hast-util-to-jsx-runtime@2.3.6:
+ resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==}
+
+ hast-util-to-parse5@8.0.1:
+ resolution: {integrity: sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==}
+
+ hast-util-whitespace@3.0.0:
+ resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==}
+
+ hastscript@9.0.1:
+ resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==}
+
help-me@5.0.0:
resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==}
@@ -5404,6 +5620,9 @@ packages:
hermes-parser@0.25.1:
resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==}
+ hls.js@1.6.15:
+ resolution: {integrity: sha512-E3a5VwgXimGHwpRGV+WxRTKeSp2DW5DI5MWv34ulL3t5UNmyJWCQ1KmLEHbYzcfThfXG8amBL+fCYPneGHC4VA==}
+
hoist-non-react-statics@3.3.2:
resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
@@ -5431,6 +5650,9 @@ packages:
resolution: {integrity: sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==}
engines: {node: '>=14'}
+ html-url-attributes@3.0.1:
+ resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==}
+
html-validate@10.8.0:
resolution: {integrity: sha512-SJzCC9XzYOvyJWSHWYwXwqi8WIg6LcO3Pz+MGyxOSkMYiNerkUm/pv4uJsOM2pfHObX4r8EcPyhSjQ7jCdajnw==}
engines: {node: ^20.19.0 || >= 22.12.0}
@@ -5450,6 +5672,9 @@ packages:
vitest:
optional: true
+ html-void-elements@3.0.0:
+ resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}
+
html2canvas@1.4.1:
resolution: {integrity: sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==}
engines: {node: '>=8.0.0'}
@@ -5586,6 +5811,9 @@ packages:
resolution: {integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
+ inline-style-parser@0.2.7:
+ resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==}
+
inquirer@6.5.2:
resolution: {integrity: sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==}
engines: {node: '>=6.0.0'}
@@ -5752,6 +5980,9 @@ packages:
is-potential-custom-element-name@1.0.1:
resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
+ is-promise@2.2.2:
+ resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==}
+
is-reference@1.2.1:
resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==}
@@ -5837,6 +6068,11 @@ packages:
resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==}
engines: {node: '>= 0.4'}
+ its-fine@2.0.0:
+ resolution: {integrity: sha512-KLViCmWx94zOvpLwSlsx6yOCeMhZYaxrJV87Po5k/FoZzcPSahvK5qJ7fYhS61sZi5ikmh2S3Hz55A2l3U69ng==}
+ peerDependencies:
+ react: ^19.0.0
+
jackspeak@3.4.3:
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
@@ -6201,6 +6437,12 @@ packages:
peerDependencies:
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ maath@0.10.8:
+ resolution: {integrity: sha512-tRvbDF0Pgqz+9XUa4jjfgAQ8/aPKmQdWXilFu2tMy4GWj4NOsx99HlULO4IeREfbO3a0sA145DZYyvXPkybm0g==}
+ peerDependencies:
+ '@types/three': '>=0.134.0'
+ three: '>=0.134.0'
+
magic-string@0.30.21:
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
@@ -6215,6 +6457,9 @@ packages:
map-stream@0.1.0:
resolution: {integrity: sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==}
+ markdown-table@3.0.4:
+ resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==}
+
marked@14.0.0:
resolution: {integrity: sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==}
engines: {node: '>= 18'}
@@ -6245,15 +6490,45 @@ packages:
md5@2.3.0:
resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==}
+ mdast-util-find-and-replace@3.0.2:
+ resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==}
+
mdast-util-from-markdown@2.0.2:
resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==}
+ mdast-util-gfm-autolink-literal@2.0.1:
+ resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==}
+
+ mdast-util-gfm-footnote@2.1.0:
+ resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==}
+
+ mdast-util-gfm-strikethrough@2.0.0:
+ resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==}
+
+ mdast-util-gfm-table@2.0.0:
+ resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==}
+
+ mdast-util-gfm-task-list-item@2.0.0:
+ resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==}
+
+ mdast-util-gfm@3.1.0:
+ resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==}
+
+ mdast-util-mdx-expression@2.0.1:
+ resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==}
+
mdast-util-mdx-jsx@3.1.3:
resolution: {integrity: sha512-bfOjvNt+1AcbPLTFMFWY149nJz0OjmewJs3LQQ5pIyVGxP4CdOqNVJL6kTaM5c68p8q82Xv3nCyFfUnuEcH3UQ==}
+ mdast-util-mdxjs-esm@2.0.1:
+ resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==}
+
mdast-util-phrasing@4.1.0:
resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==}
+ mdast-util-to-hast@13.2.1:
+ resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==}
+
mdast-util-to-markdown@2.1.2:
resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==}
@@ -6291,6 +6566,11 @@ packages:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'}
+ meshline@3.3.1:
+ resolution: {integrity: sha512-/TQj+JdZkeSUOl5Mk2J7eLcYTLiQm2IDzmlSvYm7ov15anEcDJ92GHqqazxTSreeNgfnYu24kiEvvv0WlbCdFQ==}
+ peerDependencies:
+ three: '>=0.137'
+
meshoptimizer@1.0.1:
resolution: {integrity: sha512-Vix+QlA1YYT3FwmBBZ+49cE5y/b+pRrcXKqGpS5ouh33d3lSp2PoTpCw19E0cKDFWalembrHnIaZetf27a+W2g==}
@@ -6304,6 +6584,27 @@ packages:
micromark-core-commonmark@2.0.3:
resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==}
+ micromark-extension-gfm-autolink-literal@2.1.0:
+ resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==}
+
+ micromark-extension-gfm-footnote@2.1.0:
+ resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==}
+
+ micromark-extension-gfm-strikethrough@2.1.0:
+ resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==}
+
+ micromark-extension-gfm-table@2.1.1:
+ resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==}
+
+ micromark-extension-gfm-tagfilter@2.0.0:
+ resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==}
+
+ micromark-extension-gfm-task-list-item@2.1.0:
+ resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==}
+
+ micromark-extension-gfm@3.0.0:
+ resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==}
+
micromark-extension-mdx-jsx@3.0.1:
resolution: {integrity: sha512-vNuFb9czP8QCtAQcEJn0UJQJZA8Dk6DXKBqx+bg/w0WGuSxDxNr7hErW89tHUY31dUW4NqEOWwmEUNhjTFmHkg==}
@@ -6845,6 +7146,15 @@ packages:
pdf-lib@1.17.1:
resolution: {integrity: sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==}
+ pdf-parse@2.4.5:
+ resolution: {integrity: sha512-mHU89HGh7v+4u2ubfnevJ03lmPgQ5WU4CxAVmTSh/sxVTEDYd1er/dKS/A6vg77NX47KTEoihq8jZBLr8Cxuwg==}
+ engines: {node: '>=20.16.0 <21 || >=22.3.0'}
+ hasBin: true
+
+ pdfjs-dist@5.4.296:
+ resolution: {integrity: sha512-DlOzet0HO7OEnmUmB6wWGJrrdvbyJKftI1bhMitK7O2N8W2gc757yyYBbINy9IDafXAV9wmKr9t7xsTaNKRG5Q==}
+ engines: {node: '>=20.16.0 || >=22.3.0'}
+
pdfkit@0.17.2:
resolution: {integrity: sha512-UnwF5fXy08f0dnp4jchFYAROKMNTaPqb/xgR8GtCzIcqoTnbOqtp3bwKvO4688oHI6vzEEs8Q6vqqEnC5IUELw==}
@@ -7019,6 +7329,9 @@ packages:
postgres-range@1.1.4:
resolution: {integrity: sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==}
+ potpack@1.0.2:
+ resolution: {integrity: sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==}
+
prelude-ls@1.2.1:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'}
@@ -7052,6 +7365,9 @@ packages:
resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==}
engines: {node: '>=0.4.0'}
+ promise-worker-transferable@1.0.4:
+ resolution: {integrity: sha512-bN+0ehEnrXfxV2ZQvU2PetO0n4gqBD4ulq3MI1WOPLgr7/Mg9yRQkX5+0v1vagr74ZTsl7XtzlaYDo2EuCeYJw==}
+
prompts@2.4.2:
resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
engines: {node: '>= 6'}
@@ -7059,6 +7375,9 @@ packages:
prop-types@15.8.1:
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
+ property-information@7.1.0:
+ resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==}
+
proto-list@1.2.4:
resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==}
@@ -7188,6 +7507,12 @@ packages:
react: ^18.0.0
react-dom: ^18.0.0
+ react-markdown@10.1.0:
+ resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==}
+ peerDependencies:
+ '@types/react': '>=18'
+ react: '>=18'
+
react-promise-suspense@0.3.4:
resolution: {integrity: sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ==}
@@ -7219,6 +7544,15 @@ packages:
react: '>=16.6.0'
react-dom: '>=16.6.0'
+ react-use-measure@2.1.7:
+ resolution: {integrity: sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==}
+ peerDependencies:
+ react: '>=16.13'
+ react-dom: '>=16.13'
+ peerDependenciesMeta:
+ react-dom:
+ optional: true
+
react@19.2.4:
resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==}
engines: {node: '>=0.10.0'}
@@ -7267,6 +7601,21 @@ packages:
resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
engines: {node: '>= 0.4'}
+ rehype-raw@7.0.0:
+ resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==}
+
+ remark-gfm@4.0.1:
+ resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==}
+
+ remark-parse@11.0.0:
+ resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==}
+
+ remark-rehype@11.1.2:
+ resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==}
+
+ remark-stringify@11.0.0:
+ resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==}
+
require-directory@2.1.1:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
@@ -7578,6 +7927,9 @@ packages:
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
engines: {node: '>=0.10.0'}
+ space-separated-tokens@2.0.2:
+ resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
+
speedline-core@1.4.3:
resolution: {integrity: sha512-DI7/OuAUD+GMpR6dmu8lliO2Wg5zfeh+/xsdyJZCzd8o5JgFUjCeLsBDuZjIQJdwXS3J0L/uZYrELKYqx+PXog==}
engines: {node: '>=8.0'}
@@ -7613,6 +7965,15 @@ packages:
state-local@1.0.7:
resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==}
+ stats-gl@2.4.2:
+ resolution: {integrity: sha512-g5O9B0hm9CvnM36+v7SFl39T7hmAlv541tU81ME8YeSb3i1CIP5/QdDeSB3A0la0bKNHpxpwxOVRo2wFTYEosQ==}
+ peerDependencies:
+ '@types/three': '*'
+ three: '*'
+
+ stats.js@0.17.0:
+ resolution: {integrity: sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw==}
+
statuses@2.0.2:
resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
engines: {node: '>= 0.8'}
@@ -7737,6 +8098,12 @@ packages:
stubborn-utils@1.0.2:
resolution: {integrity: sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg==}
+ style-to-js@1.1.21:
+ resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==}
+
+ style-to-object@1.0.14:
+ resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==}
+
styled-jsx@5.1.6:
resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==}
engines: {node: '>= 12.0.0'}
@@ -7769,6 +8136,11 @@ packages:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
+ suspend-react@0.1.3:
+ resolution: {integrity: sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==}
+ peerDependencies:
+ react: '>=17.0'
+
svg-arc-to-cubic-bezier@3.2.0:
resolution: {integrity: sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g==}
@@ -7844,6 +8216,19 @@ packages:
resolution: {integrity: sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==}
engines: {node: '>=20'}
+ three-mesh-bvh@0.8.3:
+ resolution: {integrity: sha512-4G5lBaF+g2auKX3P0yqx+MJC6oVt6sB5k+CchS6Ob0qvH0YIhuUk1eYr7ktsIpY+albCqE80/FVQGV190PmiAg==}
+ peerDependencies:
+ three: '>= 0.159.0'
+
+ three-stdlib@2.36.1:
+ resolution: {integrity: sha512-XyGQrFmNQ5O/IoKm556ftwKsBg11TIb301MB5dWNicziQBEs2g3gtOYIf7pFiLa0zI2gUwhtCjv9fmjnxKZ1Cg==}
+ peerDependencies:
+ three: '>=0.128.0'
+
+ three@0.183.1:
+ resolution: {integrity: sha512-Psv6bbd3d/M/01MT2zZ+VmD0Vj2dbWTNhfe4CuSg7w5TuW96M3NOyCVuh9SZQ05CpGmD7NEcJhZw4GVjhCYxfQ==}
+
through@2.3.8:
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
@@ -7929,6 +8314,25 @@ packages:
resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
hasBin: true
+ trim-lines@3.0.1:
+ resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
+
+ troika-three-text@0.52.4:
+ resolution: {integrity: sha512-V50EwcYGruV5rUZ9F4aNsrytGdKcXKALjEtQXIOBfhVoZU9VAqZNIoGQ3TMiooVqFAbR1w15T+f+8gkzoFzawg==}
+ peerDependencies:
+ three: '>=0.125.0'
+
+ troika-three-utils@0.52.4:
+ resolution: {integrity: sha512-NORAStSVa/BDiG52Mfudk4j1FG4jC4ILutB3foPnfGbOeIs9+G5vZLa0pnmnaftZUGm4UwSoqEpWdqvC7zms3A==}
+ peerDependencies:
+ three: '>=0.125.0'
+
+ troika-worker-utils@0.52.0:
+ resolution: {integrity: sha512-W1CpvTHykaPH5brv5VHLfQo9D1OYuo0cSBEUQFFT/nBUzM8iD6Lq2/tgG/f1OelbAS1WtaTPQzE5uM49egnngw==}
+
+ trough@2.2.0:
+ resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==}
+
truncate-utf8-bytes@1.0.2:
resolution: {integrity: sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==}
@@ -7967,6 +8371,9 @@ packages:
engines: {node: '>=18.0.0'}
hasBin: true
+ tunnel-rat@0.1.2:
+ resolution: {integrity: sha512-lR5VHmkPhzdhrM092lI2nACsLO4QubF0/yoOhzX7c+wIpbN1GjHNzCc91QlpxBi+cnx8vVJ+Ur6vL5cEoQPFpQ==}
+
turbo-darwin-64@2.8.10:
resolution: {integrity: sha512-A03fXh+B7S8mL3PbdhTd+0UsaGrhfyPkODvzBDpKRY7bbeac4MDFpJ7I+Slf2oSkCEeSvHKR7Z4U71uKRUfX7g==}
cpu: [x64]
@@ -8080,6 +8487,9 @@ packages:
unicode-trie@2.0.0:
resolution: {integrity: sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==}
+ unified@11.0.5:
+ resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==}
+
unique-string@2.0.0:
resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==}
engines: {node: '>=8'}
@@ -8090,6 +8500,9 @@ packages:
unist-util-position-from-estree@2.0.0:
resolution: {integrity: sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==}
+ unist-util-position@5.0.0:
+ resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==}
+
unist-util-stringify-position@4.0.0:
resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==}
@@ -8149,6 +8562,10 @@ packages:
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+ utility-types@3.11.0:
+ resolution: {integrity: sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==}
+ engines: {node: '>= 4'}
+
utils-merge@1.0.1:
resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
engines: {node: '>= 0.4.0'}
@@ -8176,9 +8593,15 @@ packages:
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
engines: {node: '>= 0.8'}
+ vfile-location@5.0.3:
+ resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==}
+
vfile-message@4.0.3:
resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==}
+ vfile@6.0.3:
+ resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==}
+
victory-vendor@37.3.6:
resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==}
@@ -8283,9 +8706,18 @@ packages:
resolution: {integrity: sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==}
engines: {node: '>=10.13.0'}
+ web-namespaces@2.0.1:
+ resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==}
+
webdriver-bidi-protocol@0.4.1:
resolution: {integrity: sha512-ARrjNjtWRRs2w4Tk7nqrf2gBI0QXWuOmMCx2hU+1jUt6d00MjMxURrhxhGbrsoiZKJrhTSTzbIrc554iKI10qw==}
+ webgl-constants@1.1.1:
+ resolution: {integrity: sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg==}
+
+ webgl-sdf-generator@1.1.1:
+ resolution: {integrity: sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==}
+
webidl-conversions@3.0.1:
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
@@ -8528,6 +8960,39 @@ packages:
zod@3.25.76:
resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
+ zustand@4.5.7:
+ resolution: {integrity: sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==}
+ engines: {node: '>=12.7.0'}
+ peerDependencies:
+ '@types/react': '>=16.8'
+ immer: '>=9.0.6'
+ react: '>=16.8'
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ immer:
+ optional: true
+ react:
+ optional: true
+
+ zustand@5.0.11:
+ resolution: {integrity: sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg==}
+ engines: {node: '>=12.20.0'}
+ peerDependencies:
+ '@types/react': '>=18.0.0'
+ immer: '>=9.0.6'
+ react: '>=18.0.0'
+ use-sync-external-store: '>=1.2.0'
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ immer:
+ optional: true
+ react:
+ optional: true
+ use-sync-external-store:
+ optional: true
+
zwitch@2.0.4:
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
@@ -8548,6 +9013,12 @@ snapshots:
'@ai-sdk/provider-utils': 4.0.15(zod@3.25.76)
zod: 3.25.76
+ '@ai-sdk/openai@3.0.36(zod@3.25.76)':
+ dependencies:
+ '@ai-sdk/provider': 3.0.8
+ '@ai-sdk/provider-utils': 4.0.15(zod@3.25.76)
+ zod: 3.25.76
+
'@ai-sdk/provider-utils@4.0.15(zod@3.25.76)':
dependencies:
'@ai-sdk/provider': 3.0.8
@@ -9070,8 +9541,6 @@ snapshots:
'@dimforge/rapier3d-compat@0.12.0': {}
- '@directus/sdk@21.1.0': {}
-
'@discoveryjs/json-ext@0.5.7': {}
'@dnd-kit/accessibility@3.1.1(react@19.2.4)':
@@ -9944,9 +10413,11 @@ snapshots:
- supports-color
- utf-8-validate
+ '@mediapipe/tasks-vision@0.10.17': {}
+
'@medv/finder@4.0.2': {}
- '@mintel/eslint-config@1.8.21(@typescript-eslint/parser@8.56.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
+ '@mintel/eslint-config@1.9.5(@typescript-eslint/parser@8.56.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
dependencies:
'@eslint/eslintrc': 3.3.4
'@eslint/js': 9.39.3
@@ -9963,13 +10434,13 @@ snapshots:
- supports-color
- typescript
- '@mintel/mail@1.8.21(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ '@mintel/mail@1.9.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies:
'@react-email/components': 0.0.33(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
react: 19.2.4
react-dom: 19.2.4(react@19.2.4)
- '@mintel/next-config@1.8.21(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.1(@opentelemetry/api@1.9.0))(@swc/helpers@0.5.18)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3)(typescript@5.9.3)(webpack@5.105.0(esbuild@0.25.12))':
+ '@mintel/next-config@1.9.5(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.1(@opentelemetry/api@1.9.0))(@swc/helpers@0.5.18)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3)(typescript@5.9.3)(webpack@5.105.0(esbuild@0.25.12))':
dependencies:
'@sentry/nextjs': 10.39.0(@opentelemetry/context-async-hooks@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.1(@opentelemetry/api@1.9.0))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3))(react@19.2.4)(webpack@5.105.0(esbuild@0.25.12))
next: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3)
@@ -9992,9 +10463,8 @@ snapshots:
- typescript
- webpack
- '@mintel/next-feedback@1.8.21(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3)':
+ '@mintel/next-feedback@1.9.5(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3)':
dependencies:
- '@directus/sdk': 21.1.0
'@medv/finder': 4.0.2
clsx: 2.1.1
framer-motion: 11.18.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
@@ -10013,9 +10483,8 @@ snapshots:
- babel-plugin-react-compiler
- sass
- '@mintel/next-utils@1.8.21(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@swc/helpers@0.5.18)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3)(typescript@5.9.3)':
+ '@mintel/next-utils@1.9.5(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@swc/helpers@0.5.18)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3)(typescript@5.9.3)':
dependencies:
- '@directus/sdk': 21.1.0
next: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3)
next-intl: 4.8.2(@swc/helpers@0.5.18)(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3))(react@19.2.4)(typescript@5.9.3)
zod: 3.25.76
@@ -10031,7 +10500,7 @@ snapshots:
- sass
- typescript
- '@mintel/tsconfig@1.8.21': {}
+ '@mintel/tsconfig@1.9.5': {}
'@monaco-editor/loader@1.7.0':
dependencies:
@@ -10044,6 +10513,54 @@ snapshots:
react: 19.2.4
react-dom: 19.2.4(react@19.2.4)
+ '@monogrid/gainmap-js@3.4.0(three@0.183.1)':
+ dependencies:
+ promise-worker-transferable: 1.0.4
+ three: 0.183.1
+
+ '@napi-rs/canvas-android-arm64@0.1.80':
+ optional: true
+
+ '@napi-rs/canvas-darwin-arm64@0.1.80':
+ optional: true
+
+ '@napi-rs/canvas-darwin-x64@0.1.80':
+ optional: true
+
+ '@napi-rs/canvas-linux-arm-gnueabihf@0.1.80':
+ optional: true
+
+ '@napi-rs/canvas-linux-arm64-gnu@0.1.80':
+ optional: true
+
+ '@napi-rs/canvas-linux-arm64-musl@0.1.80':
+ optional: true
+
+ '@napi-rs/canvas-linux-riscv64-gnu@0.1.80':
+ optional: true
+
+ '@napi-rs/canvas-linux-x64-gnu@0.1.80':
+ optional: true
+
+ '@napi-rs/canvas-linux-x64-musl@0.1.80':
+ optional: true
+
+ '@napi-rs/canvas-win32-x64-msvc@0.1.80':
+ optional: true
+
+ '@napi-rs/canvas@0.1.80':
+ optionalDependencies:
+ '@napi-rs/canvas-android-arm64': 0.1.80
+ '@napi-rs/canvas-darwin-arm64': 0.1.80
+ '@napi-rs/canvas-darwin-x64': 0.1.80
+ '@napi-rs/canvas-linux-arm-gnueabihf': 0.1.80
+ '@napi-rs/canvas-linux-arm64-gnu': 0.1.80
+ '@napi-rs/canvas-linux-arm64-musl': 0.1.80
+ '@napi-rs/canvas-linux-riscv64-gnu': 0.1.80
+ '@napi-rs/canvas-linux-x64-gnu': 0.1.80
+ '@napi-rs/canvas-linux-x64-musl': 0.1.80
+ '@napi-rs/canvas-win32-x64-msvc': 0.1.80
+
'@napi-rs/wasm-runtime@0.2.12':
dependencies:
'@emnapi/core': 1.8.1
@@ -11050,6 +11567,59 @@ snapshots:
'@react-pdf/primitives': 4.1.1
'@react-pdf/stylesheet': 6.1.2
+ '@react-three/drei@10.7.7(@react-three/fiber@9.5.0(@types/react@19.2.13)(immer@11.1.4)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(three@0.183.1))(@types/react@19.2.13)(@types/three@0.183.1)(immer@11.1.4)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(three@0.183.1)':
+ dependencies:
+ '@babel/runtime': 7.28.6
+ '@mediapipe/tasks-vision': 0.10.17
+ '@monogrid/gainmap-js': 3.4.0(three@0.183.1)
+ '@react-three/fiber': 9.5.0(@types/react@19.2.13)(immer@11.1.4)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(three@0.183.1)
+ '@use-gesture/react': 10.3.1(react@19.2.4)
+ camera-controls: 3.1.2(three@0.183.1)
+ cross-env: 7.0.3
+ detect-gpu: 5.0.70
+ glsl-noise: 0.0.0
+ hls.js: 1.6.15
+ maath: 0.10.8(@types/three@0.183.1)(three@0.183.1)
+ meshline: 3.3.1(three@0.183.1)
+ react: 19.2.4
+ stats-gl: 2.4.2(@types/three@0.183.1)(three@0.183.1)
+ stats.js: 0.17.0
+ suspend-react: 0.1.3(react@19.2.4)
+ three: 0.183.1
+ three-mesh-bvh: 0.8.3(three@0.183.1)
+ three-stdlib: 2.36.1(three@0.183.1)
+ troika-three-text: 0.52.4(three@0.183.1)
+ tunnel-rat: 0.1.2(@types/react@19.2.13)(immer@11.1.4)(react@19.2.4)
+ use-sync-external-store: 1.6.0(react@19.2.4)
+ utility-types: 3.11.0
+ zustand: 5.0.11(@types/react@19.2.13)(immer@11.1.4)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4))
+ optionalDependencies:
+ react-dom: 19.2.4(react@19.2.4)
+ transitivePeerDependencies:
+ - '@types/react'
+ - '@types/three'
+ - immer
+
+ '@react-three/fiber@9.5.0(@types/react@19.2.13)(immer@11.1.4)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(three@0.183.1)':
+ dependencies:
+ '@babel/runtime': 7.28.6
+ '@types/webxr': 0.5.24
+ base64-js: 1.5.1
+ buffer: 6.0.3
+ its-fine: 2.0.0(@types/react@19.2.13)(react@19.2.4)
+ react: 19.2.4
+ react-use-measure: 2.1.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ scheduler: 0.27.0
+ suspend-react: 0.1.3(react@19.2.4)
+ three: 0.183.1
+ use-sync-external-store: 1.6.0(react@19.2.4)
+ zustand: 5.0.11(@types/react@19.2.13)(immer@11.1.4)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4))
+ optionalDependencies:
+ react-dom: 19.2.4(react@19.2.4)
+ transitivePeerDependencies:
+ - '@types/react'
+ - immer
+
'@reduxjs/toolkit@2.11.2(react-redux@9.2.0(@types/react@19.2.13)(react@19.2.4)(redux@5.0.1))(react@19.2.4)':
dependencies:
'@standard-schema/spec': 1.1.0
@@ -11635,6 +12205,8 @@ snapshots:
'@types/deep-eql@4.0.2': {}
+ '@types/draco3d@1.4.10': {}
+
'@types/eslint-scope@3.7.7':
dependencies:
'@types/eslint': 9.6.1
@@ -11698,6 +12270,8 @@ snapshots:
dependencies:
'@types/node': 22.19.10
+ '@types/offscreencanvas@2019.7.3': {}
+
'@types/parse-json@4.0.2': {}
'@types/pg-pool@2.0.7':
@@ -11720,6 +12294,10 @@ snapshots:
dependencies:
'@types/react': 19.2.13
+ '@types/react-reconciler@0.28.9(@types/react@19.2.13)':
+ dependencies:
+ '@types/react': 19.2.13
+
'@types/react-transition-group@4.4.12(@types/react@19.2.13)':
dependencies:
'@types/react': 19.2.13
@@ -11873,6 +12451,8 @@ snapshots:
'@typescript-eslint/types': 8.56.1
eslint-visitor-keys: 5.0.1
+ '@ungap/structured-clone@1.3.0': {}
+
'@unrs/resolver-binding-android-arm-eabi@1.11.1':
optional: true
@@ -11932,6 +12512,13 @@ snapshots:
'@unrs/resolver-binding-win32-x64-msvc@1.11.1':
optional: true
+ '@use-gesture/core@10.3.1': {}
+
+ '@use-gesture/react@10.3.1(react@19.2.4)':
+ dependencies:
+ '@use-gesture/core': 10.3.1
+ react: 19.2.4
+
'@vercel/oidc@3.1.0': {}
'@vitejs/plugin-react@5.1.4(vite@7.3.1(@types/node@22.19.10)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))':
@@ -12344,6 +12931,8 @@ snapshots:
cosmiconfig: 7.1.0
resolve: 1.22.11
+ bail@2.0.2: {}
+
balanced-match@4.0.3: {}
balanced-match@4.0.4: {}
@@ -12466,6 +13055,11 @@ snapshots:
buffer-from@1.1.2: {}
+ buffer@6.0.3:
+ dependencies:
+ base64-js: 1.5.1
+ ieee754: 1.2.1
+
busboy@1.6.0:
dependencies:
streamsearch: 1.1.0
@@ -12493,6 +13087,10 @@ snapshots:
camelcase@5.3.1: {}
+ camera-controls@3.1.2(three@0.183.1):
+ dependencies:
+ three: 0.183.1
+
caniuse-lite@1.0.30001769: {}
ccount@2.0.1: {}
@@ -12688,6 +13286,8 @@ snapshots:
dependencies:
delayed-stream: 1.0.0
+ comma-separated-tokens@2.0.3: {}
+
commander@10.0.1: {}
commander@13.1.0: {}
@@ -12832,6 +13432,10 @@ snapshots:
croner@9.1.0: {}
+ cross-env@7.0.3:
+ dependencies:
+ cross-spawn: 7.0.6
+
cross-spawn@7.0.6:
dependencies:
path-key: 3.1.1
@@ -13108,6 +13712,10 @@ snapshots:
destroy@1.2.0: {}
+ detect-gpu@5.0.70:
+ dependencies:
+ webgl-constants: 1.1.1
+
detect-libc@2.1.2: {}
devlop@1.1.0:
@@ -13163,6 +13771,8 @@ snapshots:
dotenv@17.3.1: {}
+ draco3d@1.5.7: {}
+
drizzle-kit@0.31.7:
dependencies:
'@drizzle-team/brocli': 0.10.2
@@ -13473,6 +14083,8 @@ snapshots:
escape-string-regexp@4.0.0: {}
+ escape-string-regexp@5.0.0: {}
+
escodegen@2.1.0:
dependencies:
esprima: 4.0.1
@@ -13788,6 +14400,8 @@ snapshots:
dependencies:
is-extendable: 0.1.1
+ extend@3.0.2: {}
+
external-editor@3.1.0:
dependencies:
chardet: 0.7.0
@@ -13844,6 +14458,8 @@ snapshots:
optionalDependencies:
picomatch: 4.0.3
+ fflate@0.6.10: {}
+
fflate@0.8.2: {}
figures@2.0.0:
@@ -14106,6 +14722,8 @@ snapshots:
pify: 2.3.0
pinkie-promise: 2.0.1
+ glsl-noise@0.0.0: {}
+
gopd@1.2.0: {}
graceful-fs@4.2.11: {}
@@ -14172,6 +14790,79 @@ snapshots:
dependencies:
function-bind: 1.1.2
+ hast-util-from-parse5@8.0.3:
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/unist': 3.0.3
+ devlop: 1.1.0
+ hastscript: 9.0.1
+ property-information: 7.1.0
+ vfile: 6.0.3
+ vfile-location: 5.0.3
+ web-namespaces: 2.0.1
+
+ hast-util-parse-selector@4.0.0:
+ dependencies:
+ '@types/hast': 3.0.4
+
+ hast-util-raw@9.1.0:
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/unist': 3.0.3
+ '@ungap/structured-clone': 1.3.0
+ hast-util-from-parse5: 8.0.3
+ hast-util-to-parse5: 8.0.1
+ html-void-elements: 3.0.0
+ mdast-util-to-hast: 13.2.1
+ parse5: 7.3.0
+ unist-util-position: 5.0.0
+ unist-util-visit: 5.1.0
+ vfile: 6.0.3
+ web-namespaces: 2.0.1
+ zwitch: 2.0.4
+
+ hast-util-to-jsx-runtime@2.3.6:
+ dependencies:
+ '@types/estree': 1.0.8
+ '@types/hast': 3.0.4
+ '@types/unist': 3.0.3
+ comma-separated-tokens: 2.0.3
+ devlop: 1.1.0
+ estree-util-is-identifier-name: 3.0.0
+ hast-util-whitespace: 3.0.0
+ mdast-util-mdx-expression: 2.0.1
+ mdast-util-mdx-jsx: 3.1.3
+ mdast-util-mdxjs-esm: 2.0.1
+ property-information: 7.1.0
+ space-separated-tokens: 2.0.2
+ style-to-js: 1.1.21
+ unist-util-position: 5.0.0
+ vfile-message: 4.0.3
+ transitivePeerDependencies:
+ - supports-color
+
+ hast-util-to-parse5@8.0.1:
+ dependencies:
+ '@types/hast': 3.0.4
+ comma-separated-tokens: 2.0.3
+ devlop: 1.1.0
+ property-information: 7.1.0
+ space-separated-tokens: 2.0.2
+ web-namespaces: 2.0.1
+ zwitch: 2.0.4
+
+ hast-util-whitespace@3.0.0:
+ dependencies:
+ '@types/hast': 3.0.4
+
+ hastscript@9.0.1:
+ dependencies:
+ '@types/hast': 3.0.4
+ comma-separated-tokens: 2.0.3
+ hast-util-parse-selector: 4.0.0
+ property-information: 7.1.0
+ space-separated-tokens: 2.0.2
+
help-me@5.0.0: {}
hermes-estree@0.25.1: {}
@@ -14180,6 +14871,8 @@ snapshots:
dependencies:
hermes-estree: 0.25.1
+ hls.js@1.6.15: {}
+
hoist-non-react-statics@3.3.2:
dependencies:
react-is: 16.13.1
@@ -14212,6 +14905,8 @@ snapshots:
htmlparser2: 8.0.2
selderee: 0.11.0
+ html-url-attributes@3.0.1: {}
+
html-validate@10.8.0(vitest@4.0.18):
dependencies:
'@html-validate/stylish': 4.3.0
@@ -14225,6 +14920,8 @@ snapshots:
optionalDependencies:
vitest: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@22.19.10)(@vitest/ui@4.0.18)(happy-dom@20.6.1)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)
+ html-void-elements@3.0.0: {}
+
html2canvas@1.4.1:
dependencies:
css-line-break: 2.1.0
@@ -14364,6 +15061,8 @@ snapshots:
ini@4.1.1: {}
+ inline-style-parser@0.2.7: {}
+
inquirer@6.5.2:
dependencies:
ansi-escapes: 3.2.0
@@ -14538,6 +15237,8 @@ snapshots:
is-potential-custom-element-name@1.0.1: {}
+ is-promise@2.2.2: {}
+
is-reference@1.2.1:
dependencies:
'@types/estree': 1.0.8
@@ -14621,6 +15322,13 @@ snapshots:
has-symbols: 1.1.0
set-function-name: 2.0.2
+ its-fine@2.0.0(@types/react@19.2.13)(react@19.2.4):
+ dependencies:
+ '@types/react-reconciler': 0.28.9(@types/react@19.2.13)
+ react: 19.2.4
+ transitivePeerDependencies:
+ - '@types/react'
+
jackspeak@3.4.3:
dependencies:
'@isaacs/cliui': 8.0.2
@@ -15005,6 +15713,11 @@ snapshots:
dependencies:
react: 19.2.4
+ maath@0.10.8(@types/three@0.183.1)(three@0.183.1):
+ dependencies:
+ '@types/three': 0.183.1
+ three: 0.183.1
+
magic-string@0.30.21:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
@@ -15019,6 +15732,8 @@ snapshots:
map-stream@0.1.0: {}
+ markdown-table@3.0.4: {}
+
marked@14.0.0: {}
marked@15.0.12: {}
@@ -15040,6 +15755,13 @@ snapshots:
crypt: 0.0.2
is-buffer: 1.1.6
+ mdast-util-find-and-replace@3.0.2:
+ dependencies:
+ '@types/mdast': 4.0.4
+ escape-string-regexp: 5.0.0
+ unist-util-is: 6.0.1
+ unist-util-visit-parents: 6.0.2
+
mdast-util-from-markdown@2.0.2:
dependencies:
'@types/mdast': 4.0.4
@@ -15057,6 +15779,74 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ mdast-util-gfm-autolink-literal@2.0.1:
+ dependencies:
+ '@types/mdast': 4.0.4
+ ccount: 2.0.1
+ devlop: 1.1.0
+ mdast-util-find-and-replace: 3.0.2
+ micromark-util-character: 2.1.1
+
+ mdast-util-gfm-footnote@2.1.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ devlop: 1.1.0
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-to-markdown: 2.1.2
+ micromark-util-normalize-identifier: 2.0.1
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-gfm-strikethrough@2.0.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-to-markdown: 2.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-gfm-table@2.0.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ devlop: 1.1.0
+ markdown-table: 3.0.4
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-to-markdown: 2.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-gfm-task-list-item@2.0.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ devlop: 1.1.0
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-to-markdown: 2.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-gfm@3.1.0:
+ dependencies:
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-gfm-autolink-literal: 2.0.1
+ mdast-util-gfm-footnote: 2.1.0
+ mdast-util-gfm-strikethrough: 2.0.0
+ mdast-util-gfm-table: 2.0.0
+ mdast-util-gfm-task-list-item: 2.0.0
+ mdast-util-to-markdown: 2.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-mdx-expression@2.0.1:
+ dependencies:
+ '@types/estree-jsx': 1.0.5
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ devlop: 1.1.0
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-to-markdown: 2.1.2
+ transitivePeerDependencies:
+ - supports-color
+
mdast-util-mdx-jsx@3.1.3:
dependencies:
'@types/estree-jsx': 1.0.5
@@ -15074,11 +15864,34 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ mdast-util-mdxjs-esm@2.0.1:
+ dependencies:
+ '@types/estree-jsx': 1.0.5
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ devlop: 1.1.0
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-to-markdown: 2.1.2
+ transitivePeerDependencies:
+ - supports-color
+
mdast-util-phrasing@4.1.0:
dependencies:
'@types/mdast': 4.0.4
unist-util-is: 6.0.1
+ mdast-util-to-hast@13.2.1:
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ '@ungap/structured-clone': 1.3.0
+ devlop: 1.1.0
+ micromark-util-sanitize-uri: 2.0.1
+ trim-lines: 3.0.1
+ unist-util-position: 5.0.0
+ unist-util-visit: 5.1.0
+ vfile: 6.0.3
+
mdast-util-to-markdown@2.1.2:
dependencies:
'@types/mdast': 4.0.4
@@ -15113,6 +15926,10 @@ snapshots:
merge2@1.4.1: {}
+ meshline@3.3.1(three@0.183.1):
+ dependencies:
+ three: 0.183.1
+
meshoptimizer@1.0.1: {}
metaviewport-parser@0.3.0: {}
@@ -15138,6 +15955,64 @@ snapshots:
micromark-util-symbol: 2.0.1
micromark-util-types: 2.0.2
+ micromark-extension-gfm-autolink-literal@2.1.0:
+ dependencies:
+ micromark-util-character: 2.1.1
+ micromark-util-sanitize-uri: 2.0.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-extension-gfm-footnote@2.1.0:
+ dependencies:
+ devlop: 1.1.0
+ micromark-core-commonmark: 2.0.3
+ micromark-factory-space: 2.0.1
+ micromark-util-character: 2.1.1
+ micromark-util-normalize-identifier: 2.0.1
+ micromark-util-sanitize-uri: 2.0.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-extension-gfm-strikethrough@2.1.0:
+ dependencies:
+ devlop: 1.1.0
+ micromark-util-chunked: 2.0.1
+ micromark-util-classify-character: 2.0.1
+ micromark-util-resolve-all: 2.0.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-extension-gfm-table@2.1.1:
+ dependencies:
+ devlop: 1.1.0
+ micromark-factory-space: 2.0.1
+ micromark-util-character: 2.1.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-extension-gfm-tagfilter@2.0.0:
+ dependencies:
+ micromark-util-types: 2.0.2
+
+ micromark-extension-gfm-task-list-item@2.1.0:
+ dependencies:
+ devlop: 1.1.0
+ micromark-factory-space: 2.0.1
+ micromark-util-character: 2.1.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-extension-gfm@3.0.0:
+ dependencies:
+ micromark-extension-gfm-autolink-literal: 2.1.0
+ micromark-extension-gfm-footnote: 2.1.0
+ micromark-extension-gfm-strikethrough: 2.1.0
+ micromark-extension-gfm-table: 2.1.1
+ micromark-extension-gfm-tagfilter: 2.0.0
+ micromark-extension-gfm-task-list-item: 2.1.0
+ micromark-util-combine-extensions: 2.0.1
+ micromark-util-types: 2.0.2
+
micromark-extension-mdx-jsx@3.0.1:
dependencies:
'@types/acorn': 4.0.6
@@ -15825,6 +16700,15 @@ snapshots:
pako: 1.0.11
tslib: 1.14.1
+ pdf-parse@2.4.5:
+ dependencies:
+ '@napi-rs/canvas': 0.1.80
+ pdfjs-dist: 5.4.296
+
+ pdfjs-dist@5.4.296:
+ optionalDependencies:
+ '@napi-rs/canvas': 0.1.80
+
pdfkit@0.17.2:
dependencies:
crypto-js: 4.2.0
@@ -16024,6 +16908,8 @@ snapshots:
postgres-range@1.1.4: {}
+ potpack@1.0.2: {}
+
prelude-ls@1.2.1: {}
prepend-http@3.0.1: {}
@@ -16040,6 +16926,11 @@ snapshots:
progress@2.0.3: {}
+ promise-worker-transferable@1.0.4:
+ dependencies:
+ is-promise: 2.2.2
+ lie: 3.1.1
+
prompts@2.4.2:
dependencies:
kleur: 3.0.3
@@ -16051,6 +16942,8 @@ snapshots:
object-assign: 4.1.1
react-is: 16.13.1
+ property-information@7.1.0: {}
+
proto-list@1.2.4: {}
protocolify@3.0.0:
@@ -16238,6 +17131,24 @@ snapshots:
react: 19.2.4
react-dom: 19.2.4(react@19.2.4)
+ react-markdown@10.1.0(@types/react@19.2.13)(react@19.2.4):
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ '@types/react': 19.2.13
+ devlop: 1.1.0
+ hast-util-to-jsx-runtime: 2.3.6
+ html-url-attributes: 3.0.1
+ mdast-util-to-hast: 13.2.1
+ react: 19.2.4
+ remark-parse: 11.0.0
+ remark-rehype: 11.1.2
+ unified: 11.0.5
+ unist-util-visit: 5.1.0
+ vfile: 6.0.3
+ transitivePeerDependencies:
+ - supports-color
+
react-promise-suspense@0.3.4:
dependencies:
fast-deep-equal: 2.0.1
@@ -16279,6 +17190,12 @@ snapshots:
react: 19.2.4
react-dom: 19.2.4(react@19.2.4)
+ react-use-measure@2.1.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
+ dependencies:
+ react: 19.2.4
+ optionalDependencies:
+ react-dom: 19.2.4(react@19.2.4)
+
react@19.2.4: {}
readdirp@3.6.0:
@@ -16341,6 +17258,46 @@ snapshots:
gopd: 1.2.0
set-function-name: 2.0.2
+ rehype-raw@7.0.0:
+ dependencies:
+ '@types/hast': 3.0.4
+ hast-util-raw: 9.1.0
+ vfile: 6.0.3
+
+ remark-gfm@4.0.1:
+ dependencies:
+ '@types/mdast': 4.0.4
+ mdast-util-gfm: 3.1.0
+ micromark-extension-gfm: 3.0.0
+ remark-parse: 11.0.0
+ remark-stringify: 11.0.0
+ unified: 11.0.5
+ transitivePeerDependencies:
+ - supports-color
+
+ remark-parse@11.0.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ mdast-util-from-markdown: 2.0.2
+ micromark-util-types: 2.0.2
+ unified: 11.0.5
+ transitivePeerDependencies:
+ - supports-color
+
+ remark-rehype@11.1.2:
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ mdast-util-to-hast: 13.2.1
+ unified: 11.0.5
+ vfile: 6.0.3
+
+ remark-stringify@11.0.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ mdast-util-to-markdown: 2.1.2
+ unified: 11.0.5
+
require-directory@2.1.1: {}
require-from-string@2.0.2: {}
@@ -16754,6 +17711,8 @@ snapshots:
source-map@0.6.1: {}
+ space-separated-tokens@2.0.2: {}
+
speedline-core@1.4.3:
dependencies:
'@types/node': 22.19.10
@@ -16793,6 +17752,13 @@ snapshots:
state-local@1.0.7: {}
+ stats-gl@2.4.2(@types/three@0.183.1)(three@0.183.1):
+ dependencies:
+ '@types/three': 0.183.1
+ three: 0.183.1
+
+ stats.js@0.17.0: {}
+
statuses@2.0.2: {}
std-env@3.10.0: {}
@@ -16945,6 +17911,14 @@ snapshots:
stubborn-utils@1.0.2: {}
+ style-to-js@1.1.21:
+ dependencies:
+ style-to-object: 1.0.14
+
+ style-to-object@1.0.14:
+ dependencies:
+ inline-style-parser: 0.2.7
+
styled-jsx@5.1.6(@babel/core@7.29.0)(react@19.2.4):
dependencies:
client-only: 0.0.1
@@ -16968,6 +17942,10 @@ snapshots:
supports-preserve-symlinks-flag@1.0.0: {}
+ suspend-react@0.1.3(react@19.2.4):
+ dependencies:
+ react: 19.2.4
+
svg-arc-to-cubic-bezier@3.2.0: {}
svg-to-pdfkit@0.1.8:
@@ -17049,6 +18027,22 @@ snapshots:
dependencies:
real-require: 0.2.0
+ three-mesh-bvh@0.8.3(three@0.183.1):
+ dependencies:
+ three: 0.183.1
+
+ three-stdlib@2.36.1(three@0.183.1):
+ dependencies:
+ '@types/draco3d': 1.4.10
+ '@types/offscreencanvas': 2019.7.3
+ '@types/webxr': 0.5.24
+ draco3d: 1.5.7
+ fflate: 0.6.10
+ potpack: 1.0.2
+ three: 0.183.1
+
+ three@0.183.1: {}
+
through@2.3.8: {}
tiny-inflate@1.0.3: {}
@@ -17122,6 +18116,24 @@ snapshots:
tree-kill@1.2.2: {}
+ trim-lines@3.0.1: {}
+
+ troika-three-text@0.52.4(three@0.183.1):
+ dependencies:
+ bidi-js: 1.0.3
+ three: 0.183.1
+ troika-three-utils: 0.52.4(three@0.183.1)
+ troika-worker-utils: 0.52.0
+ webgl-sdf-generator: 1.1.1
+
+ troika-three-utils@0.52.4(three@0.183.1):
+ dependencies:
+ three: 0.183.1
+
+ troika-worker-utils@0.52.0: {}
+
+ trough@2.2.0: {}
+
truncate-utf8-bytes@1.0.2:
dependencies:
utf8-byte-length: 1.0.5
@@ -17160,6 +18172,14 @@ snapshots:
optionalDependencies:
fsevents: 2.3.3
+ tunnel-rat@0.1.2(@types/react@19.2.13)(immer@11.1.4)(react@19.2.4):
+ dependencies:
+ zustand: 4.5.7(@types/react@19.2.13)(immer@11.1.4)(react@19.2.4)
+ transitivePeerDependencies:
+ - '@types/react'
+ - immer
+ - react
+
turbo-darwin-64@2.8.10:
optional: true
@@ -17281,6 +18301,16 @@ snapshots:
pako: 0.2.9
tiny-inflate: 1.0.3
+ unified@11.0.5:
+ dependencies:
+ '@types/unist': 3.0.3
+ bail: 2.0.2
+ devlop: 1.1.0
+ extend: 3.0.2
+ is-plain-obj: 4.1.0
+ trough: 2.2.0
+ vfile: 6.0.3
+
unique-string@2.0.0:
dependencies:
crypto-random-string: 2.0.0
@@ -17293,6 +18323,10 @@ snapshots:
dependencies:
'@types/unist': 3.0.3
+ unist-util-position@5.0.0:
+ dependencies:
+ '@types/unist': 3.0.3
+
unist-util-stringify-position@4.0.0:
dependencies:
'@types/unist': 3.0.3
@@ -17378,6 +18412,8 @@ snapshots:
util-deprecate@1.0.2: {}
+ utility-types@3.11.0: {}
+
utils-merge@1.0.1: {}
utrie@1.0.2:
@@ -17394,11 +18430,21 @@ snapshots:
vary@1.1.2: {}
+ vfile-location@5.0.3:
+ dependencies:
+ '@types/unist': 3.0.3
+ vfile: 6.0.3
+
vfile-message@4.0.3:
dependencies:
'@types/unist': 3.0.3
unist-util-stringify-position: 4.0.0
+ vfile@6.0.3:
+ dependencies:
+ '@types/unist': 3.0.3
+ vfile-message: 4.0.3
+
victory-vendor@37.3.6:
dependencies:
'@types/d3-array': 3.2.2
@@ -17506,8 +18552,14 @@ snapshots:
glob-to-regexp: 0.4.1
graceful-fs: 4.2.11
+ web-namespaces@2.0.1: {}
+
webdriver-bidi-protocol@0.4.1: {}
+ webgl-constants@1.1.1: {}
+
+ webgl-sdf-generator@1.1.1: {}
+
webidl-conversions@3.0.1: {}
webidl-conversions@8.0.1: {}
@@ -17768,4 +18820,19 @@ snapshots:
zod@3.25.76: {}
+ zustand@4.5.7(@types/react@19.2.13)(immer@11.1.4)(react@19.2.4):
+ dependencies:
+ use-sync-external-store: 1.6.0(react@19.2.4)
+ optionalDependencies:
+ '@types/react': 19.2.13
+ immer: 11.1.4
+ react: 19.2.4
+
+ zustand@5.0.11(@types/react@19.2.13)(immer@11.1.4)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4)):
+ optionalDependencies:
+ '@types/react': 19.2.13
+ immer: 11.1.4
+ react: 19.2.4
+ use-sync-external-store: 1.6.0(react@19.2.4)
+
zwitch@2.0.4: {}
diff --git a/src/lib/qdrant.ts b/src/lib/qdrant.ts
index f5ec584b..8762d82f 100644
--- a/src/lib/qdrant.ts
+++ b/src/lib/qdrant.ts
@@ -1,11 +1,15 @@
import { QdrantClient } from '@qdrant/js-client-rest';
-const qdrantUrl = process.env.QDRANT_URL || 'http://localhost:6333';
+const isDockerContainer =
+ process.env.IS_DOCKER === 'true' || process.env.HOSTNAME?.includes('klz-app');
+const qdrantUrl =
+ process.env.QDRANT_URL ||
+ (isDockerContainer ? 'http://klz-qdrant:6333' : 'http://localhost:6333');
const qdrantApiKey = process.env.QDRANT_API_KEY || '';
export const qdrant = new QdrantClient({
- url: qdrantUrl,
- apiKey: qdrantApiKey || undefined,
+ url: qdrantUrl,
+ apiKey: qdrantApiKey || undefined,
});
export const COLLECTION_NAME = 'klz_products';
@@ -15,110 +19,116 @@ export const VECTOR_SIZE = 1536; // OpenAI text-embedding-3-small
* Ensure the collection exists in Qdrant.
*/
export async function ensureCollection() {
- try {
- const collections = await qdrant.getCollections();
- const exists = collections.collections.some(c => c.name === COLLECTION_NAME);
- if (!exists) {
- await qdrant.createCollection(COLLECTION_NAME, {
- vectors: {
- size: VECTOR_SIZE,
- distance: 'Cosine',
- },
- });
- console.log(`Successfully created Qdrant collection: ${COLLECTION_NAME}`);
- }
- } catch (error) {
- console.error('Error ensuring Qdrant collection:', error);
+ try {
+ const collections = await qdrant.getCollections();
+ const exists = collections.collections.some((c) => c.name === COLLECTION_NAME);
+ if (!exists) {
+ await qdrant.createCollection(COLLECTION_NAME, {
+ vectors: {
+ size: VECTOR_SIZE,
+ distance: 'Cosine',
+ },
+ });
+ console.log(`Successfully created Qdrant collection: ${COLLECTION_NAME}`);
}
+ } catch (error) {
+ console.error('Error ensuring Qdrant collection:', error);
+ }
}
/**
* Generate an embedding for a given text using OpenRouter (OpenAI embedding proxy)
*/
export async function generateEmbedding(text: string): Promise {
- const openRouterKey = process.env.OPENROUTER_API_KEY;
- if (!openRouterKey) {
- throw new Error('OPENROUTER_API_KEY is not set');
- }
+ const openRouterKey = process.env.OPENROUTER_API_KEY;
+ if (!openRouterKey) {
+ throw new Error('OPENROUTER_API_KEY is not set');
+ }
- const response = await fetch('https://openrouter.ai/api/v1/embeddings', {
- method: 'POST',
- headers: {
- 'Authorization': `Bearer ${openRouterKey}`,
- 'Content-Type': 'application/json',
- 'HTTP-Referer': process.env.NEXT_PUBLIC_BASE_URL || 'https://klz-cables.com',
- 'X-Title': 'KLZ Cables Search AI',
- },
- body: JSON.stringify({
- model: 'openai/text-embedding-3-small',
- input: text,
- }),
- });
+ const response = await fetch('https://openrouter.ai/api/v1/embeddings', {
+ method: 'POST',
+ headers: {
+ Authorization: `Bearer ${openRouterKey}`,
+ 'Content-Type': 'application/json',
+ 'HTTP-Referer': process.env.NEXT_PUBLIC_BASE_URL || 'https://klz-cables.com',
+ 'X-Title': 'KLZ Cables Search AI',
+ },
+ body: JSON.stringify({
+ model: 'openai/text-embedding-3-small',
+ input: text,
+ }),
+ });
- if (!response.ok) {
- const errorBody = await response.text();
- throw new Error(`Failed to generate embedding: ${response.status} ${response.statusText} ${errorBody}`);
- }
+ if (!response.ok) {
+ const errorBody = await response.text();
+ throw new Error(
+ `Failed to generate embedding: ${response.status} ${response.statusText} ${errorBody}`,
+ );
+ }
- const data = await response.json();
- return data.data[0].embedding;
+ const data = await response.json();
+ return data.data[0].embedding;
}
/**
* Upsert a product into Qdrant
*/
-export async function upsertProductVector(id: string | number, text: string, payload: Record) {
- try {
- await ensureCollection();
- const vector = await generateEmbedding(text);
+export async function upsertProductVector(
+ id: string | number,
+ text: string,
+ payload: Record,
+) {
+ try {
+ await ensureCollection();
+ const vector = await generateEmbedding(text);
- await qdrant.upsert(COLLECTION_NAME, {
- wait: true,
- points: [
- {
- id: id,
- vector,
- payload,
- }
- ]
- });
- } catch (error) {
- console.error('Error writing to Qdrant:', error);
- }
+ await qdrant.upsert(COLLECTION_NAME, {
+ wait: true,
+ points: [
+ {
+ id: id,
+ vector,
+ payload,
+ },
+ ],
+ });
+ } catch (error) {
+ console.error('Error writing to Qdrant:', error);
+ }
}
/**
* Delete a product from Qdrant
*/
export async function deleteProductVector(id: string | number) {
- try {
- await ensureCollection();
- await qdrant.delete(COLLECTION_NAME, {
- wait: true,
- points: [id] as [string | number],
- });
- } catch (error) {
- console.error('Error deleting from Qdrant:', error);
- }
+ try {
+ await ensureCollection();
+ await qdrant.delete(COLLECTION_NAME, {
+ wait: true,
+ points: [id] as [string | number],
+ });
+ } catch (error) {
+ console.error('Error deleting from Qdrant:', error);
+ }
}
/**
* Search products in Qdrant
*/
export async function searchProducts(query: string, limit = 5) {
- try {
- await ensureCollection();
- const vector = await generateEmbedding(query);
+ try {
+ await ensureCollection();
+ const vector = await generateEmbedding(query);
- const results = await qdrant.search(COLLECTION_NAME, {
- vector,
- limit,
- with_payload: true,
- });
+ const results = await qdrant.search(COLLECTION_NAME, {
+ vector,
+ limit,
+ with_payload: true,
+ });
- return results;
- } catch (error) {
- console.error('Error searching in Qdrant:', error);
- return [];
- }
+ return results;
+ } catch (error) {
+ console.error('Error searching in Qdrant:', error);
+ return [];
+ }
}
diff --git a/src/lib/redis.ts b/src/lib/redis.ts
index 0f14c745..93b6cd0d 100644
--- a/src/lib/redis.ts
+++ b/src/lib/redis.ts
@@ -1,16 +1,22 @@
import Redis from 'ioredis';
-const redisUrl = process.env.REDIS_URL || 'redis://klz-redis:6379';
+const isDockerContainer =
+ process.env.IS_DOCKER === 'true' || process.env.HOSTNAME?.includes('klz-app');
+const redisUrl =
+ process.env.REDIS_URL ||
+ (isDockerContainer ? 'redis://klz-redis:6379' : 'redis://localhost:6379');
// Only create a single instance in Node.js
const globalForRedis = global as unknown as { redis: Redis };
-export const redis = globalForRedis.redis || new Redis(redisUrl, {
+export const redis =
+ globalForRedis.redis ||
+ new Redis(redisUrl, {
maxRetriesPerRequest: 3,
-});
+ });
if (process.env.NODE_ENV !== 'production') {
- globalForRedis.redis = redis;
+ globalForRedis.redis = redis;
}
export default redis;
diff --git a/src/scripts/ingest-pdf.ts b/src/scripts/ingest-pdf.ts
new file mode 100644
index 00000000..e78437cf
--- /dev/null
+++ b/src/scripts/ingest-pdf.ts
@@ -0,0 +1,63 @@
+import fs from 'fs';
+import path from 'path';
+import crypto from 'crypto';
+
+// Override Qdrant URL for local script execution outside docker
+process.env.QDRANT_URL = process.env.QDRANT_URL || 'http://localhost:6333';
+
+import { upsertProductVector } from '../lib/qdrant';
+
+// Ingests the extracted Kabelhandbuch text into Qdrant as distinct knowledge topics.
+async function ingestPDF(txtPath: string) {
+ if (!fs.existsSync(txtPath)) {
+ console.error(`File not found: ${txtPath}`);
+ process.exit(1);
+ }
+
+ try {
+ const text = fs.readFileSync(txtPath, 'utf8');
+
+ // Simple sentence/paragraph chunking
+ // We split by standard paragraph breaks (double newline) or large content blocks.
+ const chunks = text
+ .split(/\n\s*\n/)
+ .map((c) => c.trim())
+ .filter((c) => c.length > 50);
+
+ console.log(`Extracted ${text.length} characters from PDF.`);
+ console.log(`Generated ${chunks.length} chunks for vector ingestion.\n`);
+
+ for (let i = 0; i < chunks.length; i++) {
+ // We limit chuck sizes to ensure Openrouter embedding models don't timeout/fail,
+ // stringing multiple paragraphs if they are short, or cutting them if too long.
+ // For baseline, we'll index every chunk individually mapped as 'knowledge' with a unique ID
+
+ const chunkText = chunks[i];
+
+ // Generate a synthetic ID that won't collide with Payload Product IDs
+ // Qdrant strictly requires UUID or unsigned int.
+ const syntheticId = crypto.randomUUID();
+
+ const payloadData = {
+ type: 'knowledge', // Custom flag to differentiate from 'product'
+ title: `Kabelhandbuch Wissen - Bereich ${i + 1}`,
+ content: chunkText,
+ source: 'Kabelhandbuch KLZ.pdf',
+ };
+
+ // Use the existing upsert function since it just embeds the text and stores the payload
+ await upsertProductVector(syntheticId, chunkText, payloadData);
+ console.log(`✅ Upserted chunk ${i + 1}/${chunks.length}`);
+ }
+
+ console.log('🎉 PDF Ingestion Complete!');
+ process.exit(0);
+ } catch (err) {
+ console.error('Failed to parse PDF:', err);
+ process.exit(1);
+ }
+}
+
+// Run mapping
+const targetTxt = '/Users/marcmintel/Downloads/kabelhandbuch.txt';
+ingestPDF(targetTxt);
diff --git a/test-chat2.mjs b/test-chat2.mjs
new file mode 100644
index 00000000..c574cff9
--- /dev/null
+++ b/test-chat2.mjs
@@ -0,0 +1,20 @@
+import fetch from 'node-fetch';
+
+async function test() {
+ const messages = [
+ { role: 'user', content: 'Ich will einen Windpark bauen' }
+ ];
+
+ console.log('Sending message:', messages[0].content);
+
+ const res = await fetch('http://localhost:3000/api/ai-search', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ messages })
+ });
+
+ const data = await res.json();
+ console.log('\nAI Response:', data);
+}
+
+test().catch(console.error);
diff --git a/test-simple.mjs b/test-simple.mjs
new file mode 100644
index 00000000..20a28cf3
--- /dev/null
+++ b/test-simple.mjs
@@ -0,0 +1,16 @@
+import { generateText } from 'ai';
+import { createOpenAI } from '@ai-sdk/openai';
+
+const openrouter = createOpenAI({
+ baseURL: 'https://openrouter.ai/api/v1',
+ apiKey: process.env.OPENROUTER_API_KEY,
+});
+
+async function run() {
+ const { text } = await generateText({
+ model: openrouter('mistralai/mistral-large-2407'),
+ prompt: 'Hello world! Reply in one word.',
+ });
+ console.log('Result:', text);
+}
+run();