From bea9752d0c8c4c10e72364a635b7551e1780130b Mon Sep 17 00:00:00 2001 From: jrgleason Date: Sat, 7 Dec 2024 09:25:02 -0500 Subject: [PATCH 1/8] Pushing changes to work on laptop --- app/build.gradle | 11 +- .../example/advisors/CachedAnswerAdvisor.java | 184 ++++ app/src/main/resources/application.yml | 3 + docker-compose.yml | 6 +- infra-compose.yml | 5 + ui/package-lock.json | 880 +++++++++--------- ui/package.json | 5 +- ui/src/components/AddDocumentModal.jsx | 34 +- 8 files changed, 663 insertions(+), 465 deletions(-) create mode 100644 app/src/main/java/org/example/advisors/CachedAnswerAdvisor.java create mode 100644 infra-compose.yml diff --git a/app/build.gradle b/app/build.gradle index f276b55..1f0219c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -32,6 +32,10 @@ repositories { group = 'org.cbusha' version = '0.0.1-SNAPSHOT' +ext { + springAiVersion = '1.0.0-M3' +} + dependencies { implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'org.springframework.boot:spring-boot-starter-logging' @@ -43,9 +47,10 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-integration' implementation 'org.springframework.integration:spring-integration-mqtt:6.3.4' - implementation 'org.springframework.ai:spring-ai-openai-spring-boot-starter:1.0.0-M3' - implementation 'org.springframework.ai:spring-ai-anthropic-spring-boot-starter:1.0.0-M3' - implementation 'org.springframework.ai:spring-ai-pinecone-store-spring-boot-starter:1.0.0-M3' + implementation "org.springframework.ai:spring-ai-openai-spring-boot-starter:${springAiVersion}" + implementation "org.springframework.ai:spring-ai-anthropic-spring-boot-starter:${springAiVersion}" + implementation "org.springframework.ai:spring-ai-pinecone-store-spring-boot-starter:${springAiVersion}" + implementation "org.springframework.ai:spring-ai-redis-store-spring-boot-starter:${springAiVersion}" implementation 'org.springframework.boot:spring-boot-starter-websocket:3.3.5' diff --git a/app/src/main/java/org/example/advisors/CachedAnswerAdvisor.java b/app/src/main/java/org/example/advisors/CachedAnswerAdvisor.java new file mode 100644 index 0000000..f0f4ba1 --- /dev/null +++ b/app/src/main/java/org/example/advisors/CachedAnswerAdvisor.java @@ -0,0 +1,184 @@ +package org.example.advisors; + +import org.springframework.ai.chat.client.advisor.api.*; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.messages.AssistantMessage; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.model.Generation; +import org.springframework.ai.document.Document; +import org.springframework.ai.vectorstore.SearchRequest; +import org.springframework.ai.vectorstore.VectorStore; +import org.springframework.util.Assert; +import reactor.core.publisher.Flux; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Advisor that checks a vector store for similar previous questions and returns + * cached answers if similarity threshold is met, avoiding unnecessary AI calls. + */ +public class CachedAnswerAdvisor implements CallAroundAdvisor, StreamAroundAdvisor { + + private static final double DEFAULT_SIMILARITY_THRESHOLD = 0.95; + private static final int DEFAULT_ORDER = 0; + + private final VectorStore vectorStore; + private final double similarityThreshold; + private final int order; + + public static final String CACHE_HIT = "cache_hit"; + public static final String SIMILARITY_SCORE = "similarity_score"; + public static final String ORIGINAL_QUESTION = "original_question"; + + public CachedAnswerAdvisor(VectorStore vectorStore) { + this(vectorStore, DEFAULT_SIMILARITY_THRESHOLD, DEFAULT_ORDER); + } + + public CachedAnswerAdvisor(VectorStore vectorStore, double similarityThreshold, int order) { + Assert.notNull(vectorStore, "VectorStore must not be null!"); + Assert.isTrue(similarityThreshold > 0 && similarityThreshold <= 1.0, + "Similarity threshold must be between 0 and 1"); + + this.vectorStore = vectorStore; + this.similarityThreshold = similarityThreshold; + this.order = order; + } + + @Override + public String getName() { + return this.getClass().getSimpleName(); + } + + @Override + public int getOrder() { + return this.order; + } + + @Override + public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) { + // Check cache first + Document bestMatch = findBestMatch(advisedRequest.userText()); + + if (bestMatch != null && isSimilarEnough(bestMatch)) { + return createCachedResponse(bestMatch, advisedRequest); + } + + // If no good cache match, get response from chain and cache it + AdvisedResponse response = chain.nextAroundCall(advisedRequest); + Generation result = response.response().getResult(); + if (result != null) { + cacheResponse(advisedRequest.userText(), result.getOutput().getContent()); + } + return response; + } + + @Override + public Flux aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) { + // Check cache first + Document bestMatch = findBestMatch(advisedRequest.userText()); + + if (bestMatch != null && isSimilarEnough(bestMatch)) { + return Flux.just(createCachedResponse(bestMatch, advisedRequest)); + } + + // Collect the responses and build complete content + StringBuilder completeResponse = new StringBuilder(); + return chain.nextAroundStream(advisedRequest) + .doOnNext(response -> { + // Append each chunk of the response + Generation result = response.response().getResult(); + if (result != null) { + completeResponse.append(result.getOutput().getContent()); + } + }) + .doOnComplete(() -> { + // Cache the complete response + if (!completeResponse.isEmpty()) { + cacheResponse(advisedRequest.userText(), completeResponse.toString()); + } + }); + } + + private Document findBestMatch(String query) { + SearchRequest searchRequest = SearchRequest.query(query).withTopK(1); + List results = vectorStore.similaritySearch(searchRequest); + return results.isEmpty() ? null : results.getFirst(); + } + + private boolean isSimilarEnough(Document match) { + Object scoreObj = match.getMetadata().get("score"); + if (scoreObj == null) { + return false; + } + double score; + if (scoreObj instanceof Number) { + score = ((Number) scoreObj).doubleValue(); + } else { + try { + score = Double.parseDouble(scoreObj.toString()); + } catch (NumberFormatException e) { + return false; + } + } + return score >= similarityThreshold; + } + + private AdvisedResponse createCachedResponse(Document match, AdvisedRequest request) { + Map metadata = new HashMap<>(); + metadata.put(CACHE_HIT, true); + metadata.put(SIMILARITY_SCORE, match.getMetadata().get("score")); + metadata.put(ORIGINAL_QUESTION, match.getMetadata().get("original_question")); + + UserMessage responseMessage = new UserMessage(match.getContent()); + ChatResponse response = ChatResponse.builder() + .withGenerations(List.of(new Generation(new AssistantMessage(match.getContent())))) + .build(); + + return new AdvisedResponse(response, request.adviseContext()); + } + + /** + * Adds a new question-answer pair to the cache + */ + public void cacheResponse(String question, String answer) { + // Store both question and answer in content for embedding + String combinedContent = String.format("Question: %s\nAnswer: %s", question, answer); + + Document doc = new Document(combinedContent, Map.of( + "original_question", question, + "original_answer", answer, + "type", "cached_response" + )); + vectorStore.add(List.of(doc)); + } + + public static Builder builder(VectorStore vectorStore) { + return new Builder(vectorStore); + } + + public static class Builder { + private final VectorStore vectorStore; + private double similarityThreshold = DEFAULT_SIMILARITY_THRESHOLD; + private int order = DEFAULT_ORDER; + + private Builder(VectorStore vectorStore) { + this.vectorStore = vectorStore; + } + + public Builder withSimilarityThreshold(double threshold) { + this.similarityThreshold = threshold; + return this; + } + + public Builder withOrder(int order) { + this.order = order; + return this; + } + + public CachedAnswerAdvisor build() { + return new CachedAnswerAdvisor(this.vectorStore, this.similarityThreshold, this.order); + } + } +} \ No newline at end of file diff --git a/app/src/main/resources/application.yml b/app/src/main/resources/application.yml index 7db7b0e..11f024d 100644 --- a/app/src/main/resources/application.yml +++ b/app/src/main/resources/application.yml @@ -12,6 +12,9 @@ spring: name: "my-spring-ai" ai: vectorstore: + redis: + index: spring-ai-example + uri: redis://localhost:6379 pinecone: apiKey: ${PINECONE_API_KEY} index-name: spring-ai-example diff --git a/docker-compose.yml b/docker-compose.yml index 39f2225..27e9fbc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,4 +7,8 @@ services: - SPRING_PROFILES_ACTIVE=${SPRING_PROFILES_ACTIVE} - SPRING_AI_OPENAI_API_KEY=${OPENAI_API_KEY} - PINECONE_API_KEY=${PINECONE_API_KEY} - - SPRING_AI_ANTHROPIC_API_KEY=${ANTHROPIC_KEY} \ No newline at end of file + - SPRING_AI_ANTHROPIC_API_KEY=${ANTHROPIC_KEY} + redis: + image: redis/redis-stack:latest + ports: + - "6379:6379" diff --git a/infra-compose.yml b/infra-compose.yml new file mode 100644 index 0000000..8ca2260 --- /dev/null +++ b/infra-compose.yml @@ -0,0 +1,5 @@ +services: + redis: + image: redis/redis-stack:latest + ports: + - "6379:6379" \ No newline at end of file diff --git a/ui/package-lock.json b/ui/package-lock.json index 067d69b..549a2c7 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -11,13 +11,12 @@ "dependencies": { "@emotion/react": "latest", "@emotion/styled": "latest", - "@monaco-editor/react": "latest", "@mui/material": "latest", "@vitejs/plugin-react": "latest", "@xstate/react": "latest", "lucide-react": "latest", - "react": "latest", - "react-dom": "latest", + "react": "^19.0.0", + "react-dom": "^19.0.0", "uuid": "latest", "vite": "latest", "xstate": "latest" @@ -70,9 +69,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.2.tgz", - "integrity": "sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.3.tgz", + "integrity": "sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -115,13 +114,13 @@ "license": "MIT" }, "node_modules/@babel/generator": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz", - "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.3.tgz", + "integrity": "sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.26.2", - "@babel/types": "^7.26.0", + "@babel/parser": "^7.26.3", + "@babel/types": "^7.26.3", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" @@ -226,12 +225,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", - "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.3.tgz", + "integrity": "sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==", "license": "MIT", "dependencies": { - "@babel/types": "^7.26.0" + "@babel/types": "^7.26.3" }, "bin": { "parser": "bin/babel-parser.js" @@ -297,16 +296,16 @@ } }, "node_modules/@babel/traverse": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz", - "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==", + "version": "7.26.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.4.tgz", + "integrity": "sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/generator": "^7.25.9", - "@babel/parser": "^7.25.9", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.3", + "@babel/parser": "^7.26.3", "@babel/template": "^7.25.9", - "@babel/types": "^7.25.9", + "@babel/types": "^7.26.3", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -315,9 +314,9 @@ } }, "node_modules/@babel/types": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", - "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.3.tgz", + "integrity": "sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.25.9", @@ -474,9 +473,9 @@ "license": "MIT" }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz", + "integrity": "sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==", "cpu": [ "ppc64" ], @@ -486,13 +485,13 @@ "aix" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.0.tgz", + "integrity": "sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==", "cpu": [ "arm" ], @@ -502,13 +501,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz", + "integrity": "sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==", "cpu": [ "arm64" ], @@ -518,13 +517,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.0.tgz", + "integrity": "sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==", "cpu": [ "x64" ], @@ -534,13 +533,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz", + "integrity": "sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==", "cpu": [ "arm64" ], @@ -550,13 +549,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz", + "integrity": "sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==", "cpu": [ "x64" ], @@ -566,13 +565,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz", + "integrity": "sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==", "cpu": [ "arm64" ], @@ -582,13 +581,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz", + "integrity": "sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==", "cpu": [ "x64" ], @@ -598,13 +597,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz", + "integrity": "sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==", "cpu": [ "arm" ], @@ -614,13 +613,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz", + "integrity": "sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==", "cpu": [ "arm64" ], @@ -630,13 +629,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz", + "integrity": "sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==", "cpu": [ "ia32" ], @@ -646,13 +645,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz", + "integrity": "sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==", "cpu": [ "loong64" ], @@ -662,13 +661,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz", + "integrity": "sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==", "cpu": [ "mips64el" ], @@ -678,13 +677,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz", + "integrity": "sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==", "cpu": [ "ppc64" ], @@ -694,13 +693,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz", + "integrity": "sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==", "cpu": [ "riscv64" ], @@ -710,13 +709,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz", + "integrity": "sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==", "cpu": [ "s390x" ], @@ -726,13 +725,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz", + "integrity": "sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==", "cpu": [ "x64" ], @@ -742,13 +741,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz", + "integrity": "sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==", "cpu": [ "x64" ], @@ -758,13 +757,29 @@ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz", + "integrity": "sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz", + "integrity": "sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==", "cpu": [ "x64" ], @@ -774,13 +789,13 @@ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz", + "integrity": "sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==", "cpu": [ "x64" ], @@ -790,13 +805,13 @@ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz", + "integrity": "sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==", "cpu": [ "arm64" ], @@ -806,13 +821,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz", + "integrity": "sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==", "cpu": [ "ia32" ], @@ -822,13 +837,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz", + "integrity": "sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==", "cpu": [ "x64" ], @@ -838,7 +853,7 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@isaacs/cliui": { @@ -907,36 +922,10 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@monaco-editor/loader": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.4.0.tgz", - "integrity": "sha512-00ioBig0x642hytVspPl7DbQyaSWRaolYie/UFNjoTdvoKPzo6xrXLhTk9ixgIKcLH5b5vDOjVNiGyY+uDCUlg==", - "license": "MIT", - "dependencies": { - "state-local": "^1.0.6" - }, - "peerDependencies": { - "monaco-editor": ">= 0.21.0 < 1" - } - }, - "node_modules/@monaco-editor/react": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.6.0.tgz", - "integrity": "sha512-RFkU9/i7cN2bsq/iTkurMWOEErmYcY6JiQI3Jn+WeR/FGISH8JbHERjpS9oRuSOPvDMJI0Z8nJeKkbOs9sBYQw==", - "license": "MIT", - "dependencies": { - "@monaco-editor/loader": "^1.4.0" - }, - "peerDependencies": { - "monaco-editor": ">= 0.25.0 < 1", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, "node_modules/@mui/core-downloads-tracker": { - "version": "6.1.8", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.1.8.tgz", - "integrity": "sha512-TGAvzwUg9hybDacwfIGFjI2bXYXrIqky+vMfaeay8rvT56/PNAlvIDUJ54kpT5KRc9AWAihOvtDI7/LJOThOmQ==", + "version": "6.1.10", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.1.10.tgz", + "integrity": "sha512-LY5wdiLCBDY7u+Od8UmFINZFGN/5ZU90fhAslf/ZtfP+5RhuY45f679pqYIxe0y54l6Gkv9PFOc8Cs10LDTBYg==", "license": "MIT", "funding": { "type": "opencollective", @@ -944,16 +933,16 @@ } }, "node_modules/@mui/material": { - "version": "6.1.8", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.1.8.tgz", - "integrity": "sha512-QZdQFnXct+7NXIzHgT3qt+sQiO7HYGZU2vymP9Xl9tUMXEOA/S1mZMMb7+WGZrk5TzNlU/kP/85K0da5V1jXoQ==", + "version": "6.1.10", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.1.10.tgz", + "integrity": "sha512-txnwYObY4N9ugv5T2n5h1KcbISegZ6l65w1/7tpSU5OB6MQCU94YkP8n/3slDw2KcEfRk4+4D8EUGfhSPMODEQ==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/core-downloads-tracker": "^6.1.8", - "@mui/system": "^6.1.8", + "@mui/core-downloads-tracker": "^6.1.10", + "@mui/system": "^6.1.10", "@mui/types": "^7.2.19", - "@mui/utils": "^6.1.8", + "@mui/utils": "^6.1.10", "@popperjs/core": "^2.11.8", "@types/react-transition-group": "^4.4.11", "clsx": "^2.1.1", @@ -972,7 +961,7 @@ "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", - "@mui/material-pigment-css": "^6.1.8", + "@mui/material-pigment-css": "^6.1.10", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" @@ -993,13 +982,13 @@ } }, "node_modules/@mui/private-theming": { - "version": "6.1.8", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.1.8.tgz", - "integrity": "sha512-TuKl7msynCNCVvhX3c0ef1sF0Qb3VHcPs8XOGB/8bdOGBr/ynmIG1yTMjZeiFQXk8yN9fzK/FDEKMFxILNn3wg==", + "version": "6.1.10", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.1.10.tgz", + "integrity": "sha512-DqgsH0XFEweeG3rQfVkqTkeXcj/E76PGYWag8flbPdV8IYdMo+DfVdFlZK8JEjsaIVD2Eu1kJg972XnH5pfnBQ==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/utils": "^6.1.8", + "@mui/utils": "^6.1.10", "prop-types": "^15.8.1" }, "engines": { @@ -1020,14 +1009,14 @@ } }, "node_modules/@mui/styled-engine": { - "version": "6.1.8", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.1.8.tgz", - "integrity": "sha512-ZvEoT0U2nPLSLI+B4by4cVjaZnPT2f20f4JUPkyHdwLv65ZzuoHiTlwyhqX1Ch63p8bcJzKTHQVGisEoMK6PGA==", + "version": "6.1.10", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.1.10.tgz", + "integrity": "sha512-+NV9adKZYhslJ270iPjf2yzdVJwav7CIaXcMlPSi1Xy1S/zRe5xFgZ6BEoMdmGRpr34lIahE8H1acXP2myrvRw==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@emotion/cache": "^11.13.1", - "@emotion/serialize": "^1.3.2", + "@emotion/cache": "^11.13.5", + "@emotion/serialize": "^1.3.3", "@emotion/sheet": "^1.4.0", "csstype": "^3.1.3", "prop-types": "^15.8.1" @@ -1054,16 +1043,16 @@ } }, "node_modules/@mui/system": { - "version": "6.1.8", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.1.8.tgz", - "integrity": "sha512-i1kLfQoWxzFpXTBQIuPoA3xKnAnP3en4I2T8xIolovSolGQX5k8vGjw1JaydQS40td++cFsgCdEU458HDNTGUA==", + "version": "6.1.10", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.1.10.tgz", + "integrity": "sha512-5YNIqxETR23SIkyP7MY2fFnXmplX/M4wNi2R+10AVRd3Ub+NLctWY/Vs5vq1oAMF0eSDLhRTGUjaUe+IGSfWqg==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/private-theming": "^6.1.8", - "@mui/styled-engine": "^6.1.8", + "@mui/private-theming": "^6.1.10", + "@mui/styled-engine": "^6.1.10", "@mui/types": "^7.2.19", - "@mui/utils": "^6.1.8", + "@mui/utils": "^6.1.10", "clsx": "^2.1.1", "csstype": "^3.1.3", "prop-types": "^15.8.1" @@ -1108,9 +1097,9 @@ } }, "node_modules/@mui/utils": { - "version": "6.1.8", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.8.tgz", - "integrity": "sha512-O2DWb1kz8hiANVcR7Z4gOB3SvPPsSQGUmStpyBDzde6dJIfBzgV9PbEQOBZd3EBsd1pB+Uv1z5LAJAbymmawrA==", + "version": "6.1.10", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.10.tgz", + "integrity": "sha512-1ETuwswGjUiAf2dP9TkBy8p49qrw2wXa+RuAjNTRE5+91vtXJ1HKrs7H9s8CZd1zDlQVzUcUAPm9lpQwF5ogTw==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", @@ -1201,6 +1190,7 @@ "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.1.tgz", "integrity": "sha512-+tNWdlWKbpB3WgBN7ijjYkq9X5uhjmcvyjEght4NmH5fAU++zfQzAJ6wumLS+dNcvwEZhKx2Z+skY8m7v0wGSA==", "dev": true, + "license": "MIT", "dependencies": { "@rollup/pluginutils": "^5.0.1", "commondir": "^1.0.1", @@ -1222,37 +1212,12 @@ } } }, - "node_modules/@rollup/plugin-commonjs/node_modules/fdir": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.2.tgz", - "integrity": "sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==", - "dev": true, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-commonjs/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/@rollup/pluginutils": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.3.tgz", "integrity": "sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==", "dev": true, + "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", @@ -1270,22 +1235,10 @@ } } }, - "node_modules/@rollup/pluginutils/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.27.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.27.3.tgz", - "integrity": "sha512-EzxVSkIvCFxUd4Mgm4xR9YXrcp976qVaHnqom/Tgm+vU79k4vV4eYTjmRvGfeoW8m9LVcsAy/lGjcgVegKEhLQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.28.1.tgz", + "integrity": "sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ==", "cpu": [ "arm" ], @@ -1296,9 +1249,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.27.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.27.3.tgz", - "integrity": "sha512-LJc5pDf1wjlt9o/Giaw9Ofl+k/vLUaYsE2zeQGH85giX2F+wn/Cg8b3c5CDP3qmVmeO5NzwVUzQQxwZvC2eQKw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.28.1.tgz", + "integrity": "sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA==", "cpu": [ "arm64" ], @@ -1309,9 +1262,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.27.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.27.3.tgz", - "integrity": "sha512-OuRysZ1Mt7wpWJ+aYKblVbJWtVn3Cy52h8nLuNSzTqSesYw1EuN6wKp5NW/4eSre3mp12gqFRXOKTcN3AI3LqA==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.28.1.tgz", + "integrity": "sha512-prduvrMKU6NzMq6nxzQw445zXgaDBbMQvmKSJaxpaZ5R1QDM8w+eGxo6Y/jhT/cLoCvnZI42oEqf9KQNYz1fqQ==", "cpu": [ "arm64" ], @@ -1322,9 +1275,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.27.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.27.3.tgz", - "integrity": "sha512-xW//zjJMlJs2sOrCmXdB4d0uiilZsOdlGQIC/jjmMWT47lkLLoB1nsNhPUcnoqyi5YR6I4h+FjBpILxbEy8JRg==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.28.1.tgz", + "integrity": "sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ==", "cpu": [ "x64" ], @@ -1335,9 +1288,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.27.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.27.3.tgz", - "integrity": "sha512-58E0tIcwZ+12nK1WiLzHOD8I0d0kdrY/+o7yFVPRHuVGY3twBwzwDdTIBGRxLmyjciMYl1B/U515GJy+yn46qw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.28.1.tgz", + "integrity": "sha512-HTDPdY1caUcU4qK23FeeGxCdJF64cKkqajU0iBnTVxS8F7H/7BewvYoG+va1KPSL63kQ1PGNyiwKOfReavzvNA==", "cpu": [ "arm64" ], @@ -1348,9 +1301,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.27.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.27.3.tgz", - "integrity": "sha512-78fohrpcVwTLxg1ZzBMlwEimoAJmY6B+5TsyAZ3Vok7YabRBUvjYTsRXPTjGEvv/mfgVBepbW28OlMEz4w8wGA==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.28.1.tgz", + "integrity": "sha512-m/uYasxkUevcFTeRSM9TeLyPe2QDuqtjkeoTpP9SW0XxUWfcYrGDMkO/m2tTw+4NMAF9P2fU3Mw4ahNvo7QmsQ==", "cpu": [ "x64" ], @@ -1361,9 +1314,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.27.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.27.3.tgz", - "integrity": "sha512-h2Ay79YFXyQi+QZKo3ISZDyKaVD7uUvukEHTOft7kh00WF9mxAaxZsNs3o/eukbeKuH35jBvQqrT61fzKfAB/Q==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.28.1.tgz", + "integrity": "sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA==", "cpu": [ "arm" ], @@ -1374,9 +1327,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.27.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.27.3.tgz", - "integrity": "sha512-Sv2GWmrJfRY57urktVLQ0VKZjNZGogVtASAgosDZ1aUB+ykPxSi3X1nWORL5Jk0sTIIwQiPH7iE3BMi9zGWfkg==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.28.1.tgz", + "integrity": "sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg==", "cpu": [ "arm" ], @@ -1387,9 +1340,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.27.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.27.3.tgz", - "integrity": "sha512-FPoJBLsPW2bDNWjSrwNuTPUt30VnfM8GPGRoLCYKZpPx0xiIEdFip3dH6CqgoT0RnoGXptaNziM0WlKgBc+OWQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.28.1.tgz", + "integrity": "sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA==", "cpu": [ "arm64" ], @@ -1400,9 +1353,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.27.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.27.3.tgz", - "integrity": "sha512-TKxiOvBorYq4sUpA0JT+Fkh+l+G9DScnG5Dqx7wiiqVMiRSkzTclP35pE6eQQYjP4Gc8yEkJGea6rz4qyWhp3g==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.28.1.tgz", + "integrity": "sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A==", "cpu": [ "arm64" ], @@ -1412,10 +1365,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.28.1.tgz", + "integrity": "sha512-vPul4uodvWvLhRco2w0GcyZcdyBfpfDRgNKU+p35AWEbJ/HPs1tOUrkSueVbBS0RQHAf/A+nNtDpvw95PeVKOA==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.27.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.27.3.tgz", - "integrity": "sha512-v2M/mPvVUKVOKITa0oCFksnQQ/TqGrT+yD0184/cWHIu0LoIuYHwox0Pm3ccXEz8cEQDLk6FPKd1CCm+PlsISw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.28.1.tgz", + "integrity": "sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A==", "cpu": [ "ppc64" ], @@ -1426,9 +1392,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.27.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.27.3.tgz", - "integrity": "sha512-LdrI4Yocb1a/tFVkzmOE5WyYRgEBOyEhWYJe4gsDWDiwnjYKjNs7PS6SGlTDB7maOHF4kxevsuNBl2iOcj3b4A==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.28.1.tgz", + "integrity": "sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA==", "cpu": [ "riscv64" ], @@ -1439,9 +1405,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.27.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.27.3.tgz", - "integrity": "sha512-d4wVu6SXij/jyiwPvI6C4KxdGzuZOvJ6y9VfrcleHTwo68fl8vZC5ZYHsCVPUi4tndCfMlFniWgwonQ5CUpQcA==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.28.1.tgz", + "integrity": "sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg==", "cpu": [ "s390x" ], @@ -1452,9 +1418,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.27.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.27.3.tgz", - "integrity": "sha512-/6bn6pp1fsCGEY5n3yajmzZQAh+mW4QPItbiWxs69zskBzJuheb3tNynEjL+mKOsUSFK11X4LYF2BwwXnzWleA==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.28.1.tgz", + "integrity": "sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw==", "cpu": [ "x64" ], @@ -1465,9 +1431,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.27.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.27.3.tgz", - "integrity": "sha512-nBXOfJds8OzUT1qUreT/en3eyOXd2EH5b0wr2bVB5999qHdGKkzGzIyKYaKj02lXk6wpN71ltLIaQpu58YFBoQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.28.1.tgz", + "integrity": "sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g==", "cpu": [ "x64" ], @@ -1478,9 +1444,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.27.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.27.3.tgz", - "integrity": "sha512-ogfbEVQgIZOz5WPWXF2HVb6En+kWzScuxJo/WdQTqEgeyGkaa2ui5sQav9Zkr7bnNCLK48uxmmK0TySm22eiuw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.28.1.tgz", + "integrity": "sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A==", "cpu": [ "arm64" ], @@ -1491,9 +1457,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.27.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.27.3.tgz", - "integrity": "sha512-ecE36ZBMLINqiTtSNQ1vzWc5pXLQHlf/oqGp/bSbi7iedcjcNb6QbCBNG73Euyy2C+l/fn8qKWEwxr+0SSfs3w==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.28.1.tgz", + "integrity": "sha512-ZkyTJ/9vkgrE/Rk9vhMXhf8l9D+eAhbAVbsGsXKy2ohmJaWg0LPQLnIxRdRp/bKyr8tXuPlXhIoGlEB5XpJnGA==", "cpu": [ "ia32" ], @@ -1504,9 +1470,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.27.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.27.3.tgz", - "integrity": "sha512-vliZLrDmYKyaUoMzEbMTg2JkerfBjn03KmAw9CykO0Zzkzoyd7o3iZNam/TpyWNjNT+Cz2iO3P9Smv2wgrR+Eg==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.28.1.tgz", + "integrity": "sha512-ZvK2jBafvttJjoIdKm/Q/Bh7IJ1Ose9IBOwpOXcOvW3ikGTQGmKDgxTC6oCAzW6PynbkKP8+um1du81XJHZ0JA==", "cpu": [ "x64" ], @@ -1570,18 +1536,17 @@ "license": "MIT" }, "node_modules/@types/prop-types": { - "version": "15.7.13", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", - "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", + "version": "15.7.14", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", + "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", "license": "MIT" }, "node_modules/@types/react": { - "version": "18.3.12", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz", - "integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==", + "version": "19.0.1", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.1.tgz", + "integrity": "sha512-YW6614BDhqbpR5KtUYzTA+zlA7nayzJRA9ljz9CQoxthR0sDisYZLuvSMsil36t4EH/uAt8T52Xb4sVw17G+SQ==", "license": "MIT", "dependencies": { - "@types/prop-types": "*", "csstype": "^3.0.2" } }, @@ -1595,14 +1560,14 @@ } }, "node_modules/@vitejs/plugin-react": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.3.tgz", - "integrity": "sha512-NooDe9GpHGqNns1i8XDERg0Vsg5SSYRhRxxyTGogUdkdNt47jal+fbuYi+Yfq6pzRCKXyoPcWisfxE6RIM3GKA==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz", + "integrity": "sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==", "license": "MIT", "dependencies": { - "@babel/core": "^7.25.2", - "@babel/plugin-transform-react-jsx-self": "^7.24.7", - "@babel/plugin-transform-react-jsx-source": "^7.24.7", + "@babel/core": "^7.26.0", + "@babel/plugin-transform-react-jsx-self": "^7.25.9", + "@babel/plugin-transform-react-jsx-source": "^7.25.9", "@types/babel__core": "^7.20.5", "react-refresh": "^0.14.2" }, @@ -1610,13 +1575,14 @@ "node": "^14.18.0 || >=16.0.0" }, "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0" + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" } }, "node_modules/@xstate/react": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@xstate/react/-/react-5.0.0.tgz", "integrity": "sha512-MkYMpmqqCdK43wSl/V/jSpsvumzV4RSG2ZOUEAIrg/w36BJpyufMrsR0rz7POX5ICF5s3xzP9q7Hd5TyM5SSyQ==", + "license": "MIT", "dependencies": { "use-isomorphic-layout-effect": "^1.1.2", "use-sync-external-store": "^1.2.0" @@ -1678,6 +1644,19 @@ "node": ">= 8" } }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -1833,9 +1812,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001683", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001683.tgz", - "integrity": "sha512-iqmNnThZ0n70mNwvxpEC2nBJ037ZHZUoBI5Gorh1Mw6IlEAZujEoU1tXA628iZfzm7R9FvFzxbfdgml82a3k8Q==", + "version": "1.0.30001687", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001687.tgz", + "integrity": "sha512-0S/FDhf4ZiqrTUiQ39dKeUjYRjkv7lOZU1Dgif2rIqrTzX/1wV2hfKu9TOm1IHkdSijfLswxTFzl/cvir+SLSQ==", "funding": [ { "type": "opencollective", @@ -1933,7 +1912,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/convert-source-map": { "version": "1.9.0", @@ -1957,6 +1937,15 @@ "node": ">=10" } }, + "node_modules/cosmiconfig/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1992,9 +1981,9 @@ "license": "MIT" }, "node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2040,9 +2029,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.64", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.64.tgz", - "integrity": "sha512-IXEuxU+5ClW2IGEYFC2T7szbyVgehupCWQe5GNh+H065CD6U6IFN0s4KeAMFGNmQolRU4IV7zGBWSYMmZ8uuqQ==", + "version": "1.5.71", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.71.tgz", + "integrity": "sha512-dB68l59BI75W1BUGVTAEJy45CEVuEGy9qPVVQ8pnHyHMn36PLPPoE1mjLH+lo9rKulO3HC2OhbACI/8tCqJBcA==", "license": "ISC" }, "node_modules/emoji-regex": { @@ -2062,41 +2051,42 @@ } }, "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.0.tgz", + "integrity": "sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==", "hasInstallScript": true, "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" + "@esbuild/aix-ppc64": "0.24.0", + "@esbuild/android-arm": "0.24.0", + "@esbuild/android-arm64": "0.24.0", + "@esbuild/android-x64": "0.24.0", + "@esbuild/darwin-arm64": "0.24.0", + "@esbuild/darwin-x64": "0.24.0", + "@esbuild/freebsd-arm64": "0.24.0", + "@esbuild/freebsd-x64": "0.24.0", + "@esbuild/linux-arm": "0.24.0", + "@esbuild/linux-arm64": "0.24.0", + "@esbuild/linux-ia32": "0.24.0", + "@esbuild/linux-loong64": "0.24.0", + "@esbuild/linux-mips64el": "0.24.0", + "@esbuild/linux-ppc64": "0.24.0", + "@esbuild/linux-riscv64": "0.24.0", + "@esbuild/linux-s390x": "0.24.0", + "@esbuild/linux-x64": "0.24.0", + "@esbuild/netbsd-x64": "0.24.0", + "@esbuild/openbsd-arm64": "0.24.0", + "@esbuild/openbsd-x64": "0.24.0", + "@esbuild/sunos-x64": "0.24.0", + "@esbuild/win32-arm64": "0.24.0", + "@esbuild/win32-ia32": "0.24.0", + "@esbuild/win32-x64": "0.24.0" } }, "node_modules/escalade": { @@ -2124,7 +2114,8 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-glob": { "version": "3.3.2", @@ -2166,6 +2157,21 @@ "reusify": "^1.0.4" } }, + "node_modules/fdir": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.2.tgz", + "integrity": "sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -2416,6 +2422,7 @@ "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/estree": "*" } @@ -2447,7 +2454,7 @@ "version": "1.21.6", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", - "dev": true, + "devOptional": true, "license": "MIT", "bin": { "jiti": "bin/jiti.js" @@ -2490,13 +2497,16 @@ } }, "node_modules/lilconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", "dev": true, "license": "MIT", "engines": { - "node": ">=10" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" } }, "node_modules/lines-and-columns": { @@ -2527,9 +2537,9 @@ } }, "node_modules/lucide-react": { - "version": "0.460.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.460.0.tgz", - "integrity": "sha512-BVtq/DykVeIvRTJvRAgCsOwaGL8Un3Bxh8MbDxMhEWlZay3T4IpEKDEpwt5KZ0KJMHzgm6jrltxlT5eXOWXDHg==", + "version": "0.468.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.468.0.tgz", + "integrity": "sha512-6koYRhnM2N0GGZIdXzSeiNwguv1gt/FAjZOiPl76roBi3xKEXa4WmfpxgQwTTL4KipXjefrnf3oV4IsYhi4JFA==", "license": "ISC", "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" @@ -2540,6 +2550,7 @@ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.14.tgz", "integrity": "sha512-5c99P1WKTed11ZC0HMJOj6CDIue6F8ySu+bJL+85q1zBEIY8IklrJ1eiKC2NDRh3Ct3FcvmJPyQHb9erXMTJNw==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } @@ -2568,6 +2579,19 @@ "node": ">=8.6" } }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -2594,13 +2618,6 @@ "node": ">=16 || 14 >=14.17" } }, - "node_modules/monaco-editor": { - "version": "0.52.0", - "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.0.tgz", - "integrity": "sha512-OeWhNpABLCeTqubfqLMXGsqf6OmPU6pHM85kF3dhy6kq5hnhuVS1p3VrEW/XhWHc71P2tHyS5JFySD8mgs1crw==", - "license": "MIT", - "peer": true - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -2620,9 +2637,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "funding": [ { "type": "github", @@ -2775,13 +2792,13 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "dev": true, "license": "MIT", "engines": { - "node": ">=8.6" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" @@ -2909,32 +2926,6 @@ } } }, - "node_modules/postcss-load-config/node_modules/lilconfig": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", - "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, - "node_modules/postcss-load-config/node_modules/yaml": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz", - "integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==", - "dev": true, - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/postcss-nested": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", @@ -3021,28 +3012,24 @@ "license": "MIT" }, "node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", + "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - }, "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", + "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", "license": "MIT", "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" + "scheduler": "^0.25.0" }, "peerDependencies": { - "react": "^18.3.1" + "react": "^19.0.0" } }, "node_modules/react-is": { @@ -3099,6 +3086,19 @@ "node": ">=8.10.0" } }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", @@ -3143,9 +3143,9 @@ } }, "node_modules/rollup": { - "version": "4.27.3", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.27.3.tgz", - "integrity": "sha512-SLsCOnlmGt9VoZ9Ek8yBK8tAdmPHeppkw+Xa7yDlCEhDTvwYei03JlWo1fdc7YTfLZ4tD8riJCUyAgTbszk1fQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.28.1.tgz", + "integrity": "sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg==", "license": "MIT", "dependencies": { "@types/estree": "1.0.6" @@ -3158,24 +3158,25 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.27.3", - "@rollup/rollup-android-arm64": "4.27.3", - "@rollup/rollup-darwin-arm64": "4.27.3", - "@rollup/rollup-darwin-x64": "4.27.3", - "@rollup/rollup-freebsd-arm64": "4.27.3", - "@rollup/rollup-freebsd-x64": "4.27.3", - "@rollup/rollup-linux-arm-gnueabihf": "4.27.3", - "@rollup/rollup-linux-arm-musleabihf": "4.27.3", - "@rollup/rollup-linux-arm64-gnu": "4.27.3", - "@rollup/rollup-linux-arm64-musl": "4.27.3", - "@rollup/rollup-linux-powerpc64le-gnu": "4.27.3", - "@rollup/rollup-linux-riscv64-gnu": "4.27.3", - "@rollup/rollup-linux-s390x-gnu": "4.27.3", - "@rollup/rollup-linux-x64-gnu": "4.27.3", - "@rollup/rollup-linux-x64-musl": "4.27.3", - "@rollup/rollup-win32-arm64-msvc": "4.27.3", - "@rollup/rollup-win32-ia32-msvc": "4.27.3", - "@rollup/rollup-win32-x64-msvc": "4.27.3", + "@rollup/rollup-android-arm-eabi": "4.28.1", + "@rollup/rollup-android-arm64": "4.28.1", + "@rollup/rollup-darwin-arm64": "4.28.1", + "@rollup/rollup-darwin-x64": "4.28.1", + "@rollup/rollup-freebsd-arm64": "4.28.1", + "@rollup/rollup-freebsd-x64": "4.28.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.28.1", + "@rollup/rollup-linux-arm-musleabihf": "4.28.1", + "@rollup/rollup-linux-arm64-gnu": "4.28.1", + "@rollup/rollup-linux-arm64-musl": "4.28.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.28.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.28.1", + "@rollup/rollup-linux-riscv64-gnu": "4.28.1", + "@rollup/rollup-linux-s390x-gnu": "4.28.1", + "@rollup/rollup-linux-x64-gnu": "4.28.1", + "@rollup/rollup-linux-x64-musl": "4.28.1", + "@rollup/rollup-win32-arm64-msvc": "4.28.1", + "@rollup/rollup-win32-ia32-msvc": "4.28.1", + "@rollup/rollup-win32-x64-msvc": "4.28.1", "fsevents": "~2.3.2" } }, @@ -3204,13 +3205,10 @@ } }, "node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - } + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", + "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", + "license": "MIT" }, "node_modules/semver": { "version": "6.3.1", @@ -3275,12 +3273,6 @@ "node": ">=0.10.0" } }, - "node_modules/state-local": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz", - "integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==", - "license": "MIT" - }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -3427,9 +3419,9 @@ } }, "node_modules/tailwindcss": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.15.tgz", - "integrity": "sha512-r4MeXnfBmSOuKUWmXe6h2CcyfzJCEk4F0pptO5jlnYSIViUkVmsawj80N5h2lO3gwcmSb4n3PuN+e+GC1Guylw==", + "version": "3.4.16", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.16.tgz", + "integrity": "sha512-TI4Cyx7gDiZ6r44ewaJmt0o6BrMCT5aK5e0rmJ/G9Xq3w7CX/5VXl/zIPEJZFUK5VEqwByyhqNPycPlvcK4ZNw==", "dev": true, "license": "MIT", "dependencies": { @@ -3442,7 +3434,7 @@ "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.21.6", - "lilconfig": "^2.1.0", + "lilconfig": "^3.1.3", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", @@ -3538,11 +3530,12 @@ } }, "node_modules/use-isomorphic-layout-effect": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz", - "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.0.tgz", + "integrity": "sha512-q6ayo8DWoPZT0VdG4u3D3uxcgONP3Mevx2i2b0434cwWBoL+aelL1DzkXI6w3PhTZzUeR2kaVlZn70iCiseP6w==", + "license": "MIT", "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -3551,11 +3544,12 @@ } }, "node_modules/use-sync-external-store": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz", - "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz", + "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==", + "license": "MIT", "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/util-deprecate": { @@ -3573,25 +3567,26 @@ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" ], + "license": "MIT", "bin": { "uuid": "dist/esm/bin/uuid" } }, "node_modules/vite": { - "version": "5.4.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", - "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.3.tgz", + "integrity": "sha512-Cmuo5P0ENTN6HxLSo6IHsjCLn/81Vgrp81oaiFFMRa8gGDj5xEjIcEpf2ZymZtZR8oU0P2JX5WuUp/rlXcHkAw==", "license": "MIT", "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" + "esbuild": "^0.24.0", + "postcss": "^8.4.49", + "rollup": "^4.23.0" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -3600,19 +3595,25 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", - "terser": "^5.4.0" + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, + "jiti": { + "optional": true + }, "less": { "optional": true }, @@ -3633,6 +3634,12 @@ }, "terser": { "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true } } }, @@ -3754,6 +3761,7 @@ "version": "5.19.0", "resolved": "https://registry.npmjs.org/xstate/-/xstate-5.19.0.tgz", "integrity": "sha512-Juh1MjeRaVWr1IRxXYvQMMRFMrei6vq6+AfP6Zk9D9YV0ZuvubN0aM6s2ITwUrq+uWtP1NTO8kOZmsM/IqeOiQ==", + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/xstate" @@ -3766,12 +3774,16 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz", + "integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==", + "devOptional": true, "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, "engines": { - "node": ">= 6" + "node": ">= 14" } } } diff --git a/ui/package.json b/ui/package.json index 9c31c78..ae4bd17 100644 --- a/ui/package.json +++ b/ui/package.json @@ -14,13 +14,12 @@ "dependencies": { "@emotion/react": "latest", "@emotion/styled": "latest", - "@monaco-editor/react": "latest", "@mui/material": "latest", "@vitejs/plugin-react": "latest", "@xstate/react": "latest", "lucide-react": "latest", - "react": "latest", - "react-dom": "latest", + "react": "^19.0.0", + "react-dom": "^19.0.0", "uuid": "latest", "vite": "latest", "xstate": "latest" diff --git a/ui/src/components/AddDocumentModal.jsx b/ui/src/components/AddDocumentModal.jsx index dc04dba..3e0eddc 100644 --- a/ui/src/components/AddDocumentModal.jsx +++ b/ui/src/components/AddDocumentModal.jsx @@ -1,16 +1,13 @@ -// components/AddDocumentModal.jsx -import React, {useState} from 'react'; -import {X} from 'lucide-react'; -import Editor from "@monaco-editor/react"; +import React, { useState } from 'react'; +import { X } from 'lucide-react'; -export const AddDocumentModal = ({isOpen, onClose}) => { +export const AddDocumentModal = ({ isOpen, onClose }) => { const [content, setContent] = useState('{\n \n}'); const [isSubmitting, setIsSubmitting] = useState(false); const [error, setError] = useState(null); - const handleEditorChange = (value) => { - setContent(value); - // Clear any previous error when user starts typing + const handleEditorChange = (e) => { + setContent(e.target.value); if (error) setError(null); }; @@ -19,7 +16,6 @@ export const AddDocumentModal = ({isOpen, onClose}) => { setIsSubmitting(true); setError(null); - // Validate JSON before submitting try { JSON.parse(content); } catch (e) { @@ -34,7 +30,7 @@ export const AddDocumentModal = ({isOpen, onClose}) => { headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify({content}), + body: JSON.stringify({ content }), }); if (!response.ok) { @@ -80,7 +76,7 @@ export const AddDocumentModal = ({isOpen, onClose}) => { onClick={onClose} className="text-gray-500 hover:text-gray-700" > - + @@ -88,21 +84,11 @@ export const AddDocumentModal = ({isOpen, onClose}) => {
-
{error && ( From 8a82ff1eb0c54d931c7e80a3158e6a537a032b94 Mon Sep 17 00:00:00 2001 From: jrgleason Date: Sat, 7 Dec 2024 12:07:37 -0500 Subject: [PATCH 2/8] trying to get the proper response returned --- app/build.gradle | 3 +- .../example/advisors/CachedAnswerAdvisor.java | 139 ++++++++++++------ .../config/clients/AnthropicClientConfig.java | 20 +-- .../config/clients/BaseClientConfig.java | 7 - .../config/clients/OpenAIClientConfig.java | 29 ++-- .../CustomRedisVectorStoreConfig.java | 50 +++++++ 6 files changed, 166 insertions(+), 82 deletions(-) create mode 100644 app/src/main/java/org/example/vectorstore/CustomRedisVectorStoreConfig.java diff --git a/app/build.gradle b/app/build.gradle index 1f0219c..708374e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -37,13 +37,13 @@ ext { } dependencies { + implementation platform("org.springframework.ai:spring-ai-bom:${springAiVersion}") implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'org.springframework.boot:spring-boot-starter-logging' implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-webflux' implementation 'org.springframework.boot:spring-boot-starter-actuator' - implementation 'org.springframework.boot:spring-boot-starter-integration' implementation 'org.springframework.integration:spring-integration-mqtt:6.3.4' @@ -51,7 +51,6 @@ dependencies { implementation "org.springframework.ai:spring-ai-anthropic-spring-boot-starter:${springAiVersion}" implementation "org.springframework.ai:spring-ai-pinecone-store-spring-boot-starter:${springAiVersion}" implementation "org.springframework.ai:spring-ai-redis-store-spring-boot-starter:${springAiVersion}" - implementation 'org.springframework.boot:spring-boot-starter-websocket:3.3.5' implementation 'com.github.ben-manes.caffeine:caffeine' diff --git a/app/src/main/java/org/example/advisors/CachedAnswerAdvisor.java b/app/src/main/java/org/example/advisors/CachedAnswerAdvisor.java index f0f4ba1..5890494 100644 --- a/app/src/main/java/org/example/advisors/CachedAnswerAdvisor.java +++ b/app/src/main/java/org/example/advisors/CachedAnswerAdvisor.java @@ -1,7 +1,8 @@ package org.example.advisors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.ai.chat.client.advisor.api.*; -import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.messages.AssistantMessage; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.model.Generation; @@ -18,20 +19,20 @@ /** * Advisor that checks a vector store for similar previous questions and returns * cached answers if similarity threshold is met, avoiding unnecessary AI calls. + * The similarity search is performed only on the question part, not the answer. */ public class CachedAnswerAdvisor implements CallAroundAdvisor, StreamAroundAdvisor { + public static final String CACHE_HIT = "cache_hit"; + public static final String SIMILARITY_SCORE = "similarity_score"; + public static final String ORIGINAL_QUESTION = "original_question"; private static final double DEFAULT_SIMILARITY_THRESHOLD = 0.95; private static final int DEFAULT_ORDER = 0; - + private final Logger logger = LoggerFactory.getLogger(CachedAnswerAdvisor.class); private final VectorStore vectorStore; private final double similarityThreshold; private final int order; - public static final String CACHE_HIT = "cache_hit"; - public static final String SIMILARITY_SCORE = "similarity_score"; - public static final String ORIGINAL_QUESTION = "original_question"; - public CachedAnswerAdvisor(VectorStore vectorStore) { this(vectorStore, DEFAULT_SIMILARITY_THRESHOLD, DEFAULT_ORDER); } @@ -46,6 +47,10 @@ public CachedAnswerAdvisor(VectorStore vectorStore, double similarityThreshold, this.order = order; } + public static Builder builder(VectorStore vectorStore) { + return new Builder(vectorStore); + } + @Override public String getName() { return this.getClass().getSimpleName(); @@ -86,76 +91,128 @@ public Flux aroundStream(AdvisedRequest advisedRequest, StreamA // Collect the responses and build complete content StringBuilder completeResponse = new StringBuilder(); return chain.nextAroundStream(advisedRequest) - .doOnNext(response -> { - // Append each chunk of the response - Generation result = response.response().getResult(); - if (result != null) { - completeResponse.append(result.getOutput().getContent()); - } - }) - .doOnComplete(() -> { - // Cache the complete response - if (!completeResponse.isEmpty()) { - cacheResponse(advisedRequest.userText(), completeResponse.toString()); - } - }); + .doOnNext(response -> { + // Append each chunk of the response + Generation result = response.response().getResult(); + if (result != null) { + completeResponse.append(result.getOutput().getContent()); + } + }) + .doOnComplete(() -> { + // Cache the complete response + if (!completeResponse.isEmpty()) { + cacheResponse(advisedRequest.userText(), completeResponse.toString()); + } + }); } private Document findBestMatch(String query) { SearchRequest searchRequest = SearchRequest.query(query).withTopK(1); List results = vectorStore.similaritySearch(searchRequest); + + if (!results.isEmpty()) { + Document match = results.getFirst(); + logger.debug("Query: {}", query); + logger.debug("Best match content: {}", match.getContent()); + logger.debug("Best match metadata: {}", match.getMetadata()); + logger.debug("Vector score: {}", match.getMetadata().get("vector_score")); + } + return results.isEmpty() ? null : results.getFirst(); } private boolean isSimilarEnough(Document match) { - Object scoreObj = match.getMetadata().get("score"); + Object scoreObj = match.getMetadata().get("vector_score"); if (scoreObj == null) { return false; } - double score; + double distance; if (scoreObj instanceof Number) { - score = ((Number) scoreObj).doubleValue(); + distance = ((Number) scoreObj).doubleValue(); } else { try { - score = Double.parseDouble(scoreObj.toString()); + distance = Double.parseDouble(scoreObj.toString()); } catch (NumberFormatException e) { return false; } } - return score >= similarityThreshold; + // Redis returns cosine distance where: + // 0.0 means identical vectors (most similar) + // 2.0 means opposite vectors (least similar) + // We need to convert this to a similarity score between 0 and 1 + double similarity = 1.0 - (distance / 2.0); + return similarity >= similarityThreshold; } private AdvisedResponse createCachedResponse(Document match, AdvisedRequest request) { Map metadata = new HashMap<>(); metadata.put(CACHE_HIT, true); - metadata.put(SIMILARITY_SCORE, match.getMetadata().get("score")); + metadata.put(SIMILARITY_SCORE, match.getMetadata().get("vector_score")); metadata.put(ORIGINAL_QUESTION, match.getMetadata().get("original_question")); - UserMessage responseMessage = new UserMessage(match.getContent()); + // Extract answer from content if metadata is not available + String originalAnswer; + if (match.getMetadata().containsKey("original_answer")) { + originalAnswer = (String) match.getMetadata().get("original_answer"); + } else { + // Parse from content assuming old format + String content = match.getContent(); + int answerStart = content.indexOf("Answer: "); + if (answerStart != -1) { + originalAnswer = content.substring(answerStart + 8); + } else { + originalAnswer = content; + } + } ChatResponse response = ChatResponse.builder() - .withGenerations(List.of(new Generation(new AssistantMessage(match.getContent())))) - .build(); + .withGenerations(List.of(new Generation(new AssistantMessage(originalAnswer)))) + .build(); return new AdvisedResponse(response, request.adviseContext()); } /** - * Adds a new question-answer pair to the cache + * Cleans up old format entries in the cache. + * Call this method if you have old format entries in your Redis store. */ - public void cacheResponse(String question, String answer) { - // Store both question and answer in content for embedding - String combinedContent = String.format("Question: %s\nAnswer: %s", question, answer); - - Document doc = new Document(combinedContent, Map.of( - "original_question", question, - "original_answer", answer, - "type", "cached_response" - )); - vectorStore.add(List.of(doc)); + public void cleanupOldFormatEntries() { + SearchRequest searchRequest = SearchRequest.query("Question:").withTopK(100); + List oldDocs = vectorStore.similaritySearch(searchRequest); + + for (Document doc : oldDocs) { + String content = doc.getContent(); + if (content.startsWith("Question: ")) { + int answerStart = content.indexOf("Answer: "); + if (answerStart != -1) { + String question = content.substring(9, answerStart).trim(); + String answer = content.substring(answerStart + 8).trim(); + // Re-cache in new format + cacheResponse(question, answer); + // TODO: Add method to delete old document if your VectorStore implementation supports it + } + } + } } - public static Builder builder(VectorStore vectorStore) { - return new Builder(vectorStore); + /** + * Adds a new question-answer pair to the cache. + * Only the question is used for embedding and similarity search, + * while the answer is stored in metadata. + */ + public void cacheResponse(String question, String answer) { + // Create metadata map with all fields we want to store + Map metadata = new HashMap<>(); + metadata.put("$.original_question", question); // Using JSON path notation + metadata.put("$.original_answer", answer); + metadata.put("$.type", "cached_response"); + + logger.debug("Creating document with metadata: {}", metadata); + + // Create document with question as content and metadata + Document doc = new Document(question, metadata); + logger.debug("Created document: {}", doc); + + vectorStore.add(List.of(doc)); } public static class Builder { diff --git a/app/src/main/java/org/example/config/clients/AnthropicClientConfig.java b/app/src/main/java/org/example/config/clients/AnthropicClientConfig.java index f2286b2..0f8d2ec 100644 --- a/app/src/main/java/org/example/config/clients/AnthropicClientConfig.java +++ b/app/src/main/java/org/example/config/clients/AnthropicClientConfig.java @@ -9,22 +9,12 @@ @Configuration public class AnthropicClientConfig extends BaseClientConfig { - - private final ChatClient.Builder anthropicBuilder; - - public AnthropicClientConfig(@Qualifier("anthropicChatClientBuilder") ChatClient.Builder anthropicBuilder) { - this.anthropicBuilder = anthropicBuilder; - } - - @Override - protected ChatClient.Builder getBuilder() { - return anthropicBuilder; - } - @Bean(name = "anthropicBuildClient") - @Override - public ChatClient buildClient(MessageChatMemoryAdvisor messageChatMemoryAdvisor) { - return getBuilder() + public ChatClient buildClient( + @Qualifier("anthropicChatClientBuilder") ChatClient.Builder anthropicBuilder, + MessageChatMemoryAdvisor messageChatMemoryAdvisor + ) { + return anthropicBuilder .defaultAdvisors(messageChatMemoryAdvisor) .defaultSystem(instructions) .defaultOptions(new AnthropicChatOptions()) diff --git a/app/src/main/java/org/example/config/clients/BaseClientConfig.java b/app/src/main/java/org/example/config/clients/BaseClientConfig.java index 73f71ac..75f4d5d 100644 --- a/app/src/main/java/org/example/config/clients/BaseClientConfig.java +++ b/app/src/main/java/org/example/config/clients/BaseClientConfig.java @@ -2,19 +2,12 @@ import lombok.Data; import org.example.config.SpringAIConfig; -import org.springframework.ai.chat.client.ChatClient; -import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Import; @Data @Import({SpringAIConfig.class}) public abstract class BaseClientConfig { - @Value("${app.bot.instructions}") protected String instructions; - - protected abstract ChatClient.Builder getBuilder(); - - protected abstract ChatClient buildClient(MessageChatMemoryAdvisor messageChatMemoryAdvisor); } \ No newline at end of file diff --git a/app/src/main/java/org/example/config/clients/OpenAIClientConfig.java b/app/src/main/java/org/example/config/clients/OpenAIClientConfig.java index c043616..5da7cae 100644 --- a/app/src/main/java/org/example/config/clients/OpenAIClientConfig.java +++ b/app/src/main/java/org/example/config/clients/OpenAIClientConfig.java @@ -1,5 +1,6 @@ package org.example.config.clients; +import org.example.advisors.CachedAnswerAdvisor; import org.example.advisors.SimpleLoggingAdvisor; import org.example.service.DeviceStateService; import org.example.service.ToggleFunction; @@ -19,33 +20,27 @@ @Configuration public class OpenAIClientConfig extends BaseClientConfig { - private final ChatClient.Builder openAiBuilder; - private final VectorStore vectorStore; private final DeviceStateService deviceStateService; public OpenAIClientConfig( - DeviceStateService deviceStateService, - @Qualifier("openAiChatClientBuilder") ChatClient.Builder openAiBuilder, - VectorStore vectorStore + DeviceStateService deviceStateService ) { this.deviceStateService = deviceStateService; - this.openAiBuilder = openAiBuilder; - this.vectorStore = vectorStore; - } - - @Override - protected ChatClient.Builder getBuilder() { - return openAiBuilder; } @Bean(name = "openAiBuildClient") - @Override - public ChatClient buildClient(MessageChatMemoryAdvisor messageChatMemoryAdvisor) { - return getBuilder() + public ChatClient buildClient( + @Qualifier("openAiChatClientBuilder") ChatClient.Builder openAiBuilder, + MessageChatMemoryAdvisor messageChatMemoryAdvisor, + @Qualifier("customRedisVectorStore") VectorStore redisVectorStore, + @Qualifier("vectorStore") VectorStore pineconeVectorStore + ) { + return openAiBuilder .defaultAdvisors( messageChatMemoryAdvisor, - new QuestionAnswerAdvisor(vectorStore, SearchRequest.defaults()), - new SimpleLoggingAdvisor() + new QuestionAnswerAdvisor(pineconeVectorStore, SearchRequest.defaults()), + new SimpleLoggingAdvisor(), + new CachedAnswerAdvisor(redisVectorStore) ) .defaultFunctions("toggleDevice") .defaultSystem(instructions) diff --git a/app/src/main/java/org/example/vectorstore/CustomRedisVectorStoreConfig.java b/app/src/main/java/org/example/vectorstore/CustomRedisVectorStoreConfig.java new file mode 100644 index 0000000..2afd420 --- /dev/null +++ b/app/src/main/java/org/example/vectorstore/CustomRedisVectorStoreConfig.java @@ -0,0 +1,50 @@ +package org.example.vectorstore; + +import io.micrometer.observation.ObservationRegistry; +import org.springframework.ai.embedding.BatchingStrategy; +import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.ai.vectorstore.RedisVectorStore; +import org.springframework.ai.vectorstore.observation.VectorStoreObservationConvention; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.Message; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.StringRedisSerializer; +import redis.clients.jedis.JedisPooled; + +// TODO: add conditional +@Configuration +public class CustomRedisVectorStoreConfig { + @Bean + public JedisConnectionFactory redisConnectionFactory() { + return new JedisConnectionFactory(); + } + + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(connectionFactory); + template.setKeySerializer(new StringRedisSerializer()); + template.setValueSerializer(new StringRedisSerializer()); + return template; + } + + @Bean(name = "customRedisVectorStore") + public RedisVectorStore redisVectorStore(EmbeddingModel embeddingModel, JedisConnectionFactory jedisConnectionFactory, + ObjectProvider observationRegistry, + ObjectProvider customObservationConvention, + BatchingStrategy batchingStrategy) { + var config = RedisVectorStore.RedisVectorStoreConfig.builder() + .withIndexName("spring-ai-example") + .withPrefix("prefix") + .build(); + + return new RedisVectorStore(config, embeddingModel, + new JedisPooled(jedisConnectionFactory.getHostName(), jedisConnectionFactory.getPort()), + true, observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP), + customObservationConvention.getIfAvailable(() -> null), batchingStrategy); + } +} From eef78f49f826e978d7449b70ae75152609eba1e2 Mon Sep 17 00:00:00 2001 From: jrgleason Date: Sat, 7 Dec 2024 12:52:05 -0500 Subject: [PATCH 3/8] Cache is working but audio is a bit funky right now --- .../example/advisors/CachedAnswerAdvisor.java | 106 +++++++++--------- .../CustomRedisVectorStoreConfig.java | 20 ++++ ui/src/components/ChatInterface.jsx | 10 +- ui/src/hooks/useChat.js | 89 ++++++++++++--- ui/src/state/StateMachine.js | 50 +++++++-- 5 files changed, 196 insertions(+), 79 deletions(-) diff --git a/app/src/main/java/org/example/advisors/CachedAnswerAdvisor.java b/app/src/main/java/org/example/advisors/CachedAnswerAdvisor.java index 5890494..cf61ae6 100644 --- a/app/src/main/java/org/example/advisors/CachedAnswerAdvisor.java +++ b/app/src/main/java/org/example/advisors/CachedAnswerAdvisor.java @@ -22,13 +22,12 @@ * The similarity search is performed only on the question part, not the answer. */ public class CachedAnswerAdvisor implements CallAroundAdvisor, StreamAroundAdvisor { - public static final String CACHE_HIT = "cache_hit"; public static final String SIMILARITY_SCORE = "similarity_score"; public static final String ORIGINAL_QUESTION = "original_question"; + private static final Logger logger = LoggerFactory.getLogger(CachedAnswerAdvisor.class); private static final double DEFAULT_SIMILARITY_THRESHOLD = 0.95; private static final int DEFAULT_ORDER = 0; - private final Logger logger = LoggerFactory.getLogger(CachedAnswerAdvisor.class); private final VectorStore vectorStore; private final double similarityThreshold; private final int order; @@ -107,15 +106,25 @@ public Flux aroundStream(AdvisedRequest advisedRequest, StreamA } private Document findBestMatch(String query) { + logger.debug("Searching for query: {}", query); SearchRequest searchRequest = SearchRequest.query(query).withTopK(1); List results = vectorStore.similaritySearch(searchRequest); if (!results.isEmpty()) { Document match = results.getFirst(); - logger.debug("Query: {}", query); - logger.debug("Best match content: {}", match.getContent()); - logger.debug("Best match metadata: {}", match.getMetadata()); - logger.debug("Vector score: {}", match.getMetadata().get("vector_score")); + logger.debug("Found match with ID: {}", match.getId()); + logger.debug("Match content: {}", match.getContent()); + logger.debug("Match metadata: {}", match.getMetadata()); + logger.debug("Match vector score: {}", match.getMetadata().get("vector_score")); + + // Try to parse and log raw JSON content + try { + logger.debug("Raw document: {}", match); + } catch (Exception e) { + logger.warn("Failed to parse document as JSON", e); + } + } else { + logger.debug("No matches found for query: {}", query); } return results.isEmpty() ? null : results.getFirst(); @@ -145,66 +154,29 @@ private boolean isSimilarEnough(Document match) { } private AdvisedResponse createCachedResponse(Document match, AdvisedRequest request) { + logger.debug("Creating cached response from match: {}", match); Map metadata = new HashMap<>(); metadata.put(CACHE_HIT, true); metadata.put(SIMILARITY_SCORE, match.getMetadata().get("vector_score")); - metadata.put(ORIGINAL_QUESTION, match.getMetadata().get("original_question")); - // Extract answer from content if metadata is not available - String originalAnswer; - if (match.getMetadata().containsKey("original_answer")) { - originalAnswer = (String) match.getMetadata().get("original_answer"); - } else { - // Parse from content assuming old format - String content = match.getContent(); - int answerStart = content.indexOf("Answer: "); - if (answerStart != -1) { - originalAnswer = content.substring(answerStart + 8); - } else { - originalAnswer = content; - } - } + String originalAnswer = extractAnswer(match); + + // Create response with metadata included ChatResponse response = ChatResponse.builder() + .withMetadata(CACHE_HIT, true) + .withMetadata(SIMILARITY_SCORE, match.getMetadata().get("vector_score")) .withGenerations(List.of(new Generation(new AssistantMessage(originalAnswer)))) .build(); return new AdvisedResponse(response, request.adviseContext()); } - /** - * Cleans up old format entries in the cache. - * Call this method if you have old format entries in your Redis store. - */ - public void cleanupOldFormatEntries() { - SearchRequest searchRequest = SearchRequest.query("Question:").withTopK(100); - List oldDocs = vectorStore.similaritySearch(searchRequest); - - for (Document doc : oldDocs) { - String content = doc.getContent(); - if (content.startsWith("Question: ")) { - int answerStart = content.indexOf("Answer: "); - if (answerStart != -1) { - String question = content.substring(9, answerStart).trim(); - String answer = content.substring(answerStart + 8).trim(); - // Re-cache in new format - cacheResponse(question, answer); - // TODO: Add method to delete old document if your VectorStore implementation supports it - } - } - } - } - - /** - * Adds a new question-answer pair to the cache. - * Only the question is used for embedding and similarity search, - * while the answer is stored in metadata. - */ public void cacheResponse(String question, String answer) { - // Create metadata map with all fields we want to store + // Create metadata map with plain field names Map metadata = new HashMap<>(); - metadata.put("$.original_question", question); // Using JSON path notation - metadata.put("$.original_answer", answer); - metadata.put("$.type", "cached_response"); + metadata.put("original_question", question); + metadata.put("original_answer", answer); + metadata.put("type", "cached_response"); logger.debug("Creating document with metadata: {}", metadata); @@ -213,6 +185,34 @@ public void cacheResponse(String question, String answer) { logger.debug("Created document: {}", doc); vectorStore.add(List.of(doc)); + logger.debug("Document added to vector store"); + } + + private String extractAnswer(Document match) { + logger.debug("Extracting answer from document: {}", match); + + // Try to get answer from metadata first + Map docMetadata = match.getMetadata(); + if (docMetadata.containsKey("original_answer")) { + String answer = (String) docMetadata.get("original_answer"); + logger.debug("Found answer in metadata: {}", answer); + return answer; + } + + // Fallback to parsing from content if not in metadata + String content = match.getContent(); + logger.debug("No answer in metadata, checking content: {}", content); + + int answerStart = content.indexOf("Answer: "); + if (answerStart != -1) { + String answer = content.substring(answerStart + 8); + logger.debug("Found answer in content: {}", answer); + return answer; + } + + // If no "Answer: " prefix found, use entire content + logger.warn("No answer prefix found in content, using entire content as answer"); + return content; } public static class Builder { diff --git a/app/src/main/java/org/example/vectorstore/CustomRedisVectorStoreConfig.java b/app/src/main/java/org/example/vectorstore/CustomRedisVectorStoreConfig.java index 2afd420..b677d47 100644 --- a/app/src/main/java/org/example/vectorstore/CustomRedisVectorStoreConfig.java +++ b/app/src/main/java/org/example/vectorstore/CustomRedisVectorStoreConfig.java @@ -1,6 +1,8 @@ package org.example.vectorstore; import io.micrometer.observation.ObservationRegistry; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.ai.embedding.BatchingStrategy; import org.springframework.ai.embedding.EmbeddingModel; import org.springframework.ai.vectorstore.RedisVectorStore; @@ -15,9 +17,14 @@ import org.springframework.data.redis.serializer.StringRedisSerializer; import redis.clients.jedis.JedisPooled; +import java.util.List; + + // TODO: add conditional @Configuration public class CustomRedisVectorStoreConfig { + private static final Logger logger = LoggerFactory.getLogger(CustomRedisVectorStoreConfig.class); + @Bean public JedisConnectionFactory redisConnectionFactory() { return new JedisConnectionFactory(); @@ -37,11 +44,24 @@ public RedisVectorStore redisVectorStore(EmbeddingModel embeddingModel, JedisCon ObjectProvider observationRegistry, ObjectProvider customObservationConvention, BatchingStrategy batchingStrategy) { + // Define the metadata fields we want Redis to index and return + // Use plain names because RedisVectorStore will add the $. prefix internally + List metadataFields = List.of( + RedisVectorStore.MetadataField.text("original_answer"), + RedisVectorStore.MetadataField.text("original_question"), + RedisVectorStore.MetadataField.tag("type") + ); + + logger.debug("Configuring Redis vector store with metadata fields: {}", metadataFields); + var config = RedisVectorStore.RedisVectorStoreConfig.builder() .withIndexName("spring-ai-example") .withPrefix("prefix") + .withMetadataFields(metadataFields) .build(); + logger.debug("Created Redis vector store config: {}", config); + return new RedisVectorStore(config, embeddingModel, new JedisPooled(jedisConnectionFactory.getHostName(), jedisConnectionFactory.getPort()), true, observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP), diff --git a/ui/src/components/ChatInterface.jsx b/ui/src/components/ChatInterface.jsx index 5840bca..d8718b8 100644 --- a/ui/src/components/ChatInterface.jsx +++ b/ui/src/components/ChatInterface.jsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { LoadingSpinner } from './LoadingSpinner'; import { MessageBubble } from './MessageBubble'; import { ChatInput } from './ChatInput'; @@ -23,6 +23,12 @@ const ChatInterface = () => { const [isStreaming, setIsStreaming] = useState(false); const { isLoading, sendMessage, streamMessage } = useChat(); + // Add debug logging + useEffect(() => { + console.log('Current state:', state); + console.log('Send function:', send); + }, [state, send]); + const handleSubmit = (e) => { e.preventDefault(); if (isStreaming) { @@ -105,7 +111,7 @@ const ChatInterface = () => { onSubmit={handleSubmit} mode={mode} /> - + {state.context.audioSupported && } diff --git a/ui/src/hooks/useChat.js b/ui/src/hooks/useChat.js index 4589ec3..124b123 100644 --- a/ui/src/hooks/useChat.js +++ b/ui/src/hooks/useChat.js @@ -51,10 +51,23 @@ export const useChat = () => { }); const responseText = await textResponse.text(); - await send({type: 'STREAM', chunk: responseText, responseId: messageId}); + // Check if it's a cached response by trying to parse as JSON + let isCached = false; + try { + const jsonResponse = JSON.parse(responseText); + isCached = jsonResponse.metadata && jsonResponse.metadata.cache_hit; + } catch (e) { + // Not JSON, so not a cached response + isCached = false; + } + + // Handle both cached and non-cached responses the same way for UI + await send({type: 'STREAM', chunk: isCached ? JSON.parse(responseText).generation.content : responseText, responseId: messageId}); await send({type: 'COMPLETE'}); - await generateAudioForMessage(messageId, responseText, send); + // Generate audio if needed + const textForAudio = isCached ? JSON.parse(responseText).generation.content : responseText; + await generateAudioForMessage(messageId, textForAudio, send); return true; } catch (error) { @@ -72,36 +85,86 @@ export const useChat = () => { const messageId = uuidv4(); let completeMessage = ''; - await send({type: 'ASK', message, speaker: "user", responder: "ai", responseId: messageId}); + await send({ + type: 'ASK', + message, + speaker: "user", + responder: "ai", + responseId: messageId + }); try { const response = await fetch(`/openai/stream?message=${encodeURIComponent(message)}`, { method: 'GET', headers: {'Content-Type': 'application/json'} }); - const reader = response.body.getReader(); - const decoder = new TextDecoder("utf-8"); - - while (true) { - const {done, value} = await reader.read(); - if (done) break; - const chunk = decoder.decode(value, {stream: true}); - completeMessage += chunk; + + // First check if this is a cached response by looking at the first chunk + const firstChunk = await response.text(); + let isCached = false; + let cachedContent = ''; + + try { + const jsonResponse = JSON.parse(firstChunk); + if (jsonResponse.metadata && jsonResponse.metadata.cache_hit) { + isCached = true; + cachedContent = jsonResponse.generation.content; + } + } catch (e) { + // Not JSON, so not a cached response + isCached = false; + } + + if (isCached) { + // Handle cached response similar to non-streaming + completeMessage = cachedContent; await send({ type: 'STREAM', - chunk, + chunk: cachedContent, responseId: messageId }); + } else { + // Handle streaming response + const reader = response.body.getReader(); + const decoder = new TextDecoder("utf-8"); + + // If we already got the first chunk (non-cached case), process it + if (firstChunk) { + completeMessage += firstChunk; + await send({ + type: 'STREAM', + chunk: firstChunk, + responseId: messageId + }); + } + + while (true) { + const {done, value} = await reader.read(); + if (done) break; + const chunk = decoder.decode(value, {stream: true}); + completeMessage += chunk; + await send({ + type: 'STREAM', + chunk, + responseId: messageId + }); + } } await send({type: 'COMPLETE'}); + if(message.mode !== 'openai-image'){ await generateAudioForMessage(messageId, completeMessage, send); } return true; } catch (error) { - await send({type: 'STREAM_ERROR'}); + console.error('Error in streamMessage:', error); + await send({ + type: 'STREAM_ERROR', + error: 'Failed to get response', + responseId: messageId + }); return false; } finally { setIsLoading(false); diff --git a/ui/src/state/StateMachine.js b/ui/src/state/StateMachine.js index 3fb59fd..66b563e 100644 --- a/ui/src/state/StateMachine.js +++ b/ui/src/state/StateMachine.js @@ -2,6 +2,12 @@ import {assign, createMachine, fromPromise} from "xstate"; import {v4 as uuidv4} from 'uuid'; const handleAudioPlayback = fromPromise(async ({ input, context }) => { + // First check if audio is supported + if (!window.Audio || !window.MediaSource || !MediaSource.isTypeSupported) { + console.warn('Audio playback not supported in this browser'); + return { audioSupported: false }; + } + const audio = new Audio(); const mediaSource = new MediaSource(); @@ -28,7 +34,7 @@ const handleAudioPlayback = fromPromise(async ({ input, context }) => { 'audio/aac', 'audio/webm', 'audio/webm; codecs=opus' - ].filter(Boolean); // Remove null/undefined entries + ].filter(Boolean); // Try each MIME type until we find one that works for (const mimeType of mimeTypes) { @@ -43,7 +49,7 @@ const handleAudioPlayback = fromPromise(async ({ input, context }) => { } if (!sourceBuffer) { - throw new Error('No supported audio format found'); + return resolve({ audioSupported: false, error: 'No supported audio format found' }); } // Function to safely append buffer @@ -70,7 +76,6 @@ const handleAudioPlayback = fromPromise(async ({ input, context }) => { await appendBuffer(value); } catch (e) { console.error('Error appending buffer:', e); - // If we hit a quota exceeded error, try to remove some data if (e.name === 'QuotaExceededError') { const removeAmount = value.length; await new Promise(resolveRemove => { @@ -79,7 +84,7 @@ const handleAudioPlayback = fromPromise(async ({ input, context }) => { }); await appendBuffer(value); } else { - throw e; + return resolve({ audioSupported: false, error: e.message }); } } } @@ -91,24 +96,24 @@ const handleAudioPlayback = fromPromise(async ({ input, context }) => { // Set up audio element event handlers audio.addEventListener('canplay', () => { - resolve(audio); + resolve({ audio, audioSupported: true }); }, { once: true }); audio.addEventListener('error', (e) => { - reject(new Error('Audio element error: ' + e.error)); + resolve({ audioSupported: false, error: 'Audio element error: ' + e.error }); }, { once: true }); } catch (error) { - reject(error); + resolve({ audioSupported: false, error: error.message }); } }, { once: true }); mediaSource.addEventListener('sourceclosed', () => { - reject(new Error('MediaSource was closed')); + resolve({ audioSupported: false, error: 'MediaSource was closed' }); }, { once: true }); mediaSource.addEventListener('sourceerror', (error) => { - reject(new Error('MediaSource error: ' + error)); + resolve({ audioSupported: false, error: 'MediaSource error: ' + error }); }, { once: true }); }); }); @@ -163,13 +168,36 @@ export const simpleMachine = createMachine({ onDone: { target: 'idle', actions: assign(({event, context}) => { - context.audio = event.output; + // Check the response from handleAudioPlayback + if (event.output && event.output.audioSupported === false) { + context.audioSupported = false; + console.warn('Audio playback not supported or failed'); + } + if (event.output && event.output.audio) { + context.audio = event.output.audio; + } }) }, onError: { target: 'idle', actions: assign({ - errorMessage: ({event}) => event.data + audioSupported: false, + errorMessage: ({event}) => { + console.warn('Audio playback error:', event.data); + return 'Audio playback not supported in this browser'; + } + }) + } + }, + on: { + ASK: { + target: 'ask', + // Cancel the audio playback when transitioning to ask + actions: assign(({context}) => { + if (context.audio) { + context.audio.pause(); + context.audio = null; + } }) } } From f3454e96c573a7e0683b2d4dbc419e33891335b2 Mon Sep 17 00:00:00 2001 From: jrgleason Date: Sat, 7 Dec 2024 13:39:43 -0500 Subject: [PATCH 4/8] It is at least reading back --- ui/src/components/AudioController.jsx | 76 ++++++++++++++++++++++----- ui/src/components/AudioDebugger.jsx | 47 +++++++++++++++++ ui/src/components/ChatInterface.jsx | 10 ++-- ui/src/hooks/useChat.js | 11 +++- ui/src/state/StateMachine.js | 71 +++++++++---------------- 5 files changed, 148 insertions(+), 67 deletions(-) create mode 100644 ui/src/components/AudioDebugger.jsx diff --git a/ui/src/components/AudioController.jsx b/ui/src/components/AudioController.jsx index 2f19f76..2a7a44a 100644 --- a/ui/src/components/AudioController.jsx +++ b/ui/src/components/AudioController.jsx @@ -1,6 +1,6 @@ -import React, { useEffect, useRef, useState } from 'react'; -import { Pause, Play, Volume2, VolumeX } from 'lucide-react'; -import { useStateContext } from "../state/StateProvider.jsx"; +import React, {useEffect, useRef, useState} from 'react'; +import {Pause, Play, Volume2, VolumeX} from 'lucide-react'; +import {useStateContext} from "../state/StateProvider.jsx"; const AudioController = () => { const [isPlaying, setIsPlaying] = useState(false); @@ -8,16 +8,29 @@ const AudioController = () => { const [progress, setProgress] = useState(0); const progressRef = useRef(null); - const { state, send } = useStateContext(); + const {state, send} = useStateContext(); // Watch for audio becoming available useEffect(() => { console.log("Audio state changed:", state.context.audio); - if (state.context.audio) { - state.context.audio.play().catch(error => { - console.error('Error auto-playing audio:', error); - setIsPlaying(false); - }); + const audio = state.context.audio; + + // Check if audio is a proper Audio element + if (!audio || typeof audio.play !== 'function') { + return; + } + + // Wait for browser interaction before trying to play + const playPromise = audio.play(); + if (playPromise !== undefined) { + playPromise + .then(() => { + setIsPlaying(true); + }) + .catch(error => { + console.error('Error playing audio:', error); + setIsPlaying(false); + }); } }, [state.context.audio]); @@ -50,19 +63,53 @@ const AudioController = () => { }, [state.context.audio]); const togglePlay = () => { + console.log('=== Toggle Play triggered ==='); const audio = state.context.audio; - if (isPlaying) audio.pause(); - else audio.play().catch(console.error); + + // Check if audio is a proper Audio element by checking for play method + if (!audio || typeof audio.play !== 'function') { + console.warn('No valid audio element available'); + return; + } + + try { + if (isPlaying) { + console.log('Attempting to pause'); + audio.pause(); + } else { + console.log('Attempting to play'); + const playPromise = audio.play(); + if (playPromise !== undefined) { + playPromise + .then(() => { + console.log('Play started successfully'); + setIsPlaying(true); + }) + .catch(error => { + console.error('Error playing audio:', error); + setIsPlaying(false); + }); + } + } + } catch (error) { + console.error('Error in togglePlay:', error); + } }; const toggleMute = () => { const audio = state.context.audio; + if (!audio || typeof audio.play !== 'function') { + return; + } audio.muted = !audio.muted; setIsMuted(!isMuted); }; const handleProgressClick = (e) => { const audio = state.context.audio; + if (!audio || typeof audio.play !== 'function') { + return; + } const rect = progressRef.current.getBoundingClientRect(); const newTime = ((e.clientX - rect.left) / rect.width) * audio.duration; audio.currentTime = newTime; @@ -71,10 +118,11 @@ const AudioController = () => { const resetAudio = () => { const audio = state.context.audio; - if (audio) { - audio.currentTime = 0; - audio.pause(); + if (!audio || typeof audio.play !== 'function') { + return; } + audio.currentTime = 0; + audio.pause(); }; return ( diff --git a/ui/src/components/AudioDebugger.jsx b/ui/src/components/AudioDebugger.jsx new file mode 100644 index 0000000..5465668 --- /dev/null +++ b/ui/src/components/AudioDebugger.jsx @@ -0,0 +1,47 @@ +import React, { useEffect } from 'react'; +import { useStateContext } from "../state/StateProvider.jsx"; + +const AudioDebugger = () => { + const { state } = useStateContext(); + + useEffect(() => { + console.log("=== Audio Debug Info ==="); + console.log("Current state:", state.value); + console.log("Audio in context:", state.context.audio); + + if (state.context.audio) { + // Only try to access audio properties if we have an audio element + const audio = state.context.audio; + try { + console.log("Audio properties:", { + src: audio.src || 'no src', + readyState: audio.readyState, + paused: audio.paused, + duration: audio.duration || 'unknown', + error: audio.error ? 'yes' : 'no' + }); + } catch (e) { + console.log("Error accessing audio properties:", e); + } + } + }, [state.context.audio, state.value]); + + return ( +
+

Audio Debug Info

+
+

Current State: {state.value || 'unknown'}

+

Audio Available: {state.context.audio ? 'Yes' : 'No'}

+ {state.context.audio && ( + <> +

Ready State: {String(state.context.audio.readyState || 'unknown')}

+

Paused: {String(state.context.audio.paused)}

+

Has Source: {state.context.audio.src ? 'Yes' : 'No'}

+ + )} +
+
+ ); +}; + +export default AudioDebugger; \ No newline at end of file diff --git a/ui/src/components/ChatInterface.jsx b/ui/src/components/ChatInterface.jsx index d8718b8..44f997d 100644 --- a/ui/src/components/ChatInterface.jsx +++ b/ui/src/components/ChatInterface.jsx @@ -15,6 +15,7 @@ import Typography from '@mui/material/Typography'; import Switch from '@mui/material/Switch'; import FormControlLabel from '@mui/material/FormControlLabel'; import Paper from '@mui/material/Paper'; +import AudioDebugger from "./AudioDebugger.jsx"; const ChatInterface = () => { const { state, send } = useStateContext(); @@ -23,12 +24,6 @@ const ChatInterface = () => { const [isStreaming, setIsStreaming] = useState(false); const { isLoading, sendMessage, streamMessage } = useChat(); - // Add debug logging - useEffect(() => { - console.log('Current state:', state); - console.log('Send function:', send); - }, [state, send]); - const handleSubmit = (e) => { e.preventDefault(); if (isStreaming) { @@ -111,7 +106,8 @@ const ChatInterface = () => { onSubmit={handleSubmit} mode={mode} /> - {state.context.audioSupported && } + + diff --git a/ui/src/hooks/useChat.js b/ui/src/hooks/useChat.js index 124b123..356e43f 100644 --- a/ui/src/hooks/useChat.js +++ b/ui/src/hooks/useChat.js @@ -14,11 +14,20 @@ export const useChat = () => { }; const generateAudioForMessage = async (messageId, text, send) => { + if (!text) { + console.warn('No text provided for audio generation'); + return; + } + try { + console.log('Generating audio for:', text.substring(0, 50) + '...'); const audioResponse = await fetch(`/openai/audio?message=${encodeURIComponent(text)}`, { method: 'GET' }); - if (!audioResponse.ok) throw new Error('Audio stream response was not ok'); + + if (!audioResponse.ok) { + throw new Error(`Audio stream response was not ok: ${audioResponse.status}`); + } await send({ type: 'PLAYBACK', diff --git a/ui/src/state/StateMachine.js b/ui/src/state/StateMachine.js index 66b563e..26f30a1 100644 --- a/ui/src/state/StateMachine.js +++ b/ui/src/state/StateMachine.js @@ -2,12 +2,6 @@ import {assign, createMachine, fromPromise} from "xstate"; import {v4 as uuidv4} from 'uuid'; const handleAudioPlayback = fromPromise(async ({ input, context }) => { - // First check if audio is supported - if (!window.Audio || !window.MediaSource || !MediaSource.isTypeSupported) { - console.warn('Audio playback not supported in this browser'); - return { audioSupported: false }; - } - const audio = new Audio(); const mediaSource = new MediaSource(); @@ -49,7 +43,7 @@ const handleAudioPlayback = fromPromise(async ({ input, context }) => { } if (!sourceBuffer) { - return resolve({ audioSupported: false, error: 'No supported audio format found' }); + throw new Error('No supported audio format found'); } // Function to safely append buffer @@ -84,7 +78,7 @@ const handleAudioPlayback = fromPromise(async ({ input, context }) => { }); await appendBuffer(value); } else { - return resolve({ audioSupported: false, error: e.message }); + throw e; } } } @@ -96,31 +90,31 @@ const handleAudioPlayback = fromPromise(async ({ input, context }) => { // Set up audio element event handlers audio.addEventListener('canplay', () => { - resolve({ audio, audioSupported: true }); + resolve(audio); }, { once: true }); audio.addEventListener('error', (e) => { - resolve({ audioSupported: false, error: 'Audio element error: ' + e.error }); + reject(new Error('Audio element error: ' + e.error)); }, { once: true }); } catch (error) { - resolve({ audioSupported: false, error: error.message }); + reject(error); } }, { once: true }); mediaSource.addEventListener('sourceclosed', () => { - resolve({ audioSupported: false, error: 'MediaSource was closed' }); + reject(new Error('MediaSource was closed')); }, { once: true }); mediaSource.addEventListener('sourceerror', (error) => { - resolve({ audioSupported: false, error: 'MediaSource error: ' + error }); + reject(new Error('MediaSource error: ' + error)); }, { once: true }); }); }); const askQuestion = fromPromise(async ({input}) => { try { - console.log(`Adding message ${input.message} by ${input.speaker}`); + // console.log(`Adding message ${input.message} by ${input.speaker}`); return { ...input.messages, [uuidv4()]: { @@ -146,7 +140,6 @@ export const simpleMachine = createMachine({ context: { messages: {}, isLoading: false, - audioElements: {}, errorMessage: "" }, states: { @@ -154,7 +147,7 @@ export const simpleMachine = createMachine({ on: { ASK: 'ask', PLAYBACK: { - target: 'playback', + target: 'playback' } } }, @@ -167,38 +160,29 @@ export const simpleMachine = createMachine({ }), onDone: { target: 'idle', - actions: assign(({event, context}) => { - // Check the response from handleAudioPlayback - if (event.output && event.output.audioSupported === false) { - context.audioSupported = false; - console.warn('Audio playback not supported or failed'); - } - if (event.output && event.output.audio) { - context.audio = event.output.audio; - } - }) + actions: [ + assign(({event, context}) => { + console.log('=== Assigning audio to context ==='); + context.audio = event.output; + return context; + }) + ] }, onError: { target: 'idle', actions: assign({ - audioSupported: false, - errorMessage: ({event}) => { - console.warn('Audio playback error:', event.data); - return 'Audio playback not supported in this browser'; - } + errorMessage: ({event}) => event.data }) } }, on: { ASK: { target: 'ask', - // Cancel the audio playback when transitioning to ask - actions: assign(({context}) => { - if (context.audio) { - context.audio.pause(); - context.audio = null; - } - }) + // actions: (context) => { + // if (context.audio) { + // context.audio.pause(); + // } + // } } } }, @@ -215,13 +199,13 @@ export const simpleMachine = createMachine({ }), onDone: { actions: assign(({event, context}) => { - context.messages = event.output + context.messages = event.output; }) }, onError: { target: 'idle', actions: assign(({event, context}) => { - context.errorMessage = event.data + context.errorMessage = event.data; }) } }, @@ -232,16 +216,13 @@ export const simpleMachine = createMachine({ context.messages[event.responseId] = { ...currentValue, content: currentValue.content + event.chunk - } + }; }) }, STREAM_ERROR: { target: 'idle', actions: assign({ - // TODO: Add error message to the responseId - errorMessage: ({event}) => { - return event.error - } + errorMessage: ({event}) => event.error }) }, COMPLETE: { From 84299d808dcdc9e690c32a2b4a752bc9d5354f20 Mon Sep 17 00:00:00 2001 From: jrgleason Date: Sat, 7 Dec 2024 13:46:18 -0500 Subject: [PATCH 5/8] Now seems to work and I have change the threshold to not catch everything --- .../main/java/org/example/advisors/CachedAnswerAdvisor.java | 3 +-- ui/src/components/ChatInterface.jsx | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/example/advisors/CachedAnswerAdvisor.java b/app/src/main/java/org/example/advisors/CachedAnswerAdvisor.java index cf61ae6..54283cc 100644 --- a/app/src/main/java/org/example/advisors/CachedAnswerAdvisor.java +++ b/app/src/main/java/org/example/advisors/CachedAnswerAdvisor.java @@ -24,9 +24,8 @@ public class CachedAnswerAdvisor implements CallAroundAdvisor, StreamAroundAdvisor { public static final String CACHE_HIT = "cache_hit"; public static final String SIMILARITY_SCORE = "similarity_score"; - public static final String ORIGINAL_QUESTION = "original_question"; private static final Logger logger = LoggerFactory.getLogger(CachedAnswerAdvisor.class); - private static final double DEFAULT_SIMILARITY_THRESHOLD = 0.95; + private static final double DEFAULT_SIMILARITY_THRESHOLD = 0.97; private static final int DEFAULT_ORDER = 0; private final VectorStore vectorStore; private final double similarityThreshold; diff --git a/ui/src/components/ChatInterface.jsx b/ui/src/components/ChatInterface.jsx index 44f997d..760ad61 100644 --- a/ui/src/components/ChatInterface.jsx +++ b/ui/src/components/ChatInterface.jsx @@ -106,7 +106,7 @@ const ChatInterface = () => { onSubmit={handleSubmit} mode={mode} /> - + {/**/} From 4a568eea7fe17c870316cff535c19cfb5c791eee Mon Sep 17 00:00:00 2001 From: jrgleason Date: Sat, 7 Dec 2024 13:50:55 -0500 Subject: [PATCH 6/8] Fixing the streaming --- ui/src/hooks/useChat.js | 57 ++++++++--------------------------------- 1 file changed, 10 insertions(+), 47 deletions(-) diff --git a/ui/src/hooks/useChat.js b/ui/src/hooks/useChat.js index 356e43f..6974ca3 100644 --- a/ui/src/hooks/useChat.js +++ b/ui/src/hooks/useChat.js @@ -108,61 +108,24 @@ export const useChat = () => { headers: {'Content-Type': 'application/json'} }); - // First check if this is a cached response by looking at the first chunk - const firstChunk = await response.text(); - let isCached = false; - let cachedContent = ''; - - try { - const jsonResponse = JSON.parse(firstChunk); - if (jsonResponse.metadata && jsonResponse.metadata.cache_hit) { - isCached = true; - cachedContent = jsonResponse.generation.content; - } - } catch (e) { - // Not JSON, so not a cached response - isCached = false; - } - - if (isCached) { - // Handle cached response similar to non-streaming - completeMessage = cachedContent; + const reader = response.body.getReader(); + const decoder = new TextDecoder("utf-8"); + + while (true) { + const {done, value} = await reader.read(); + if (done) break; + const chunk = decoder.decode(value, {stream: true}); + completeMessage += chunk; await send({ type: 'STREAM', - chunk: cachedContent, + chunk, responseId: messageId }); - } else { - // Handle streaming response - const reader = response.body.getReader(); - const decoder = new TextDecoder("utf-8"); - - // If we already got the first chunk (non-cached case), process it - if (firstChunk) { - completeMessage += firstChunk; - await send({ - type: 'STREAM', - chunk: firstChunk, - responseId: messageId - }); - } - - while (true) { - const {done, value} = await reader.read(); - if (done) break; - const chunk = decoder.decode(value, {stream: true}); - completeMessage += chunk; - await send({ - type: 'STREAM', - chunk, - responseId: messageId - }); - } } await send({type: 'COMPLETE'}); - if(message.mode !== 'openai-image'){ + if (message.mode !== 'openai-image') { await generateAudioForMessage(messageId, completeMessage, send); } return true; From 040848eed57bf92ccc977b4b5d9ccdf74adfae14 Mon Sep 17 00:00:00 2001 From: jrgleason Date: Mon, 9 Dec 2024 23:30:23 -0500 Subject: [PATCH 7/8] Final commit before the presentation --- app/build.gradle | 1 + .../example/advisors/CachedAnswerAdvisor.java | 31 ++-- .../config/clients/OpenAIClientConfig.java | 4 +- .../example/controller/PinconeController.java | 14 +- .../CustomRedisVectorStoreConfig.java | 5 +- out1 | 139 ++++++++++++++++++ out2 | 139 ++++++++++++++++++ ui/src/components/AddDocumentModal.jsx | 5 +- ui/src/components/AudioController.jsx | 47 +++--- ui/src/components/DocumentGrid.jsx | 85 +++++++++-- ui/src/state/StateMachine.js | 28 +++- 11 files changed, 436 insertions(+), 62 deletions(-) create mode 100644 out1 create mode 100644 out2 diff --git a/app/build.gradle b/app/build.gradle index 708374e..c96e05c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -53,6 +53,7 @@ dependencies { implementation "org.springframework.ai:spring-ai-redis-store-spring-boot-starter:${springAiVersion}" implementation 'org.springframework.boot:spring-boot-starter-websocket:3.3.5' + implementation 'com.google.code.gson:gson:2.11.0' implementation 'com.github.ben-manes.caffeine:caffeine' compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' diff --git a/app/src/main/java/org/example/advisors/CachedAnswerAdvisor.java b/app/src/main/java/org/example/advisors/CachedAnswerAdvisor.java index 54283cc..f0c1cfe 100644 --- a/app/src/main/java/org/example/advisors/CachedAnswerAdvisor.java +++ b/app/src/main/java/org/example/advisors/CachedAnswerAdvisor.java @@ -109,24 +109,25 @@ private Document findBestMatch(String query) { SearchRequest searchRequest = SearchRequest.query(query).withTopK(1); List results = vectorStore.similaritySearch(searchRequest); - if (!results.isEmpty()) { - Document match = results.getFirst(); - logger.debug("Found match with ID: {}", match.getId()); - logger.debug("Match content: {}", match.getContent()); - logger.debug("Match metadata: {}", match.getMetadata()); - logger.debug("Match vector score: {}", match.getMetadata().get("vector_score")); - - // Try to parse and log raw JSON content - try { - logger.debug("Raw document: {}", match); - } catch (Exception e) { - logger.warn("Failed to parse document as JSON", e); - } - } else { + if (results.isEmpty()) { logger.debug("No matches found for query: {}", query); + return null; + } + + Document match = results.get(0); + logger.debug("Found match with ID: {}", match.getId()); + logger.debug("Match content: {}", match.getContent()); + logger.debug("Match metadata: {}", match.getMetadata()); + logger.debug("Match vector score: {}", match.getMetadata().get("vector_score")); + + // Try to parse and log raw JSON content + try { + logger.debug("Raw document: {}", match); + } catch (Exception e) { + logger.warn("Failed to parse document as JSON", e); } - return results.isEmpty() ? null : results.getFirst(); + return match; } private boolean isSimilarEnough(Document match) { diff --git a/app/src/main/java/org/example/config/clients/OpenAIClientConfig.java b/app/src/main/java/org/example/config/clients/OpenAIClientConfig.java index 5da7cae..a35ef49 100644 --- a/app/src/main/java/org/example/config/clients/OpenAIClientConfig.java +++ b/app/src/main/java/org/example/config/clients/OpenAIClientConfig.java @@ -39,8 +39,8 @@ public ChatClient buildClient( .defaultAdvisors( messageChatMemoryAdvisor, new QuestionAnswerAdvisor(pineconeVectorStore, SearchRequest.defaults()), - new SimpleLoggingAdvisor(), - new CachedAnswerAdvisor(redisVectorStore) + new SimpleLoggingAdvisor() +// new CachedAnswerAdvisor(redisVectorStore) ) .defaultFunctions("toggleDevice") .defaultSystem(instructions) diff --git a/app/src/main/java/org/example/controller/PinconeController.java b/app/src/main/java/org/example/controller/PinconeController.java index 1cd45ab..a67ea38 100644 --- a/app/src/main/java/org/example/controller/PinconeController.java +++ b/app/src/main/java/org/example/controller/PinconeController.java @@ -1,11 +1,13 @@ package org.example.controller; +import com.google.gson.Gson; import org.springframework.ai.document.Document; import org.springframework.ai.vectorstore.SearchRequest; import org.springframework.ai.vectorstore.VectorStore; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -21,7 +23,8 @@ public PinconeController(VectorStore vectorStore) { @PostMapping("add") public ResponseEntity addDocument(@RequestBody Map payload) { - String content = payload.toString(); + Gson gson = new Gson(); + String content = gson.toJson(payload); Document doc = new Document(content); vectorStore.add(List.of(doc)); return ResponseEntity.ok().build(); @@ -29,13 +32,20 @@ public ResponseEntity addDocument(@RequestBody Map payload @GetMapping("search") public List> searchDocuments() { - // Retrieve documents similar to a query List results = this.vectorStore.similaritySearch(SearchRequest.query("Smart Home").withTopK(5)); return results.stream().map(doc -> Map.of( + "id", doc.getId(), // Include the id property "content", doc.getContent(), "metadata", doc.getMetadata() )).collect(Collectors.toList()); } + + @DeleteMapping("delete") + public ResponseEntity deleteDocument(@RequestParam String id) { + // Implement the logic to delete a document by its ID + vectorStore.delete(Collections.singletonList(id)); + return ResponseEntity.noContent().build(); + } } diff --git a/app/src/main/java/org/example/vectorstore/CustomRedisVectorStoreConfig.java b/app/src/main/java/org/example/vectorstore/CustomRedisVectorStoreConfig.java index b677d47..ec8e6af 100644 --- a/app/src/main/java/org/example/vectorstore/CustomRedisVectorStoreConfig.java +++ b/app/src/main/java/org/example/vectorstore/CustomRedisVectorStoreConfig.java @@ -44,8 +44,6 @@ public RedisVectorStore redisVectorStore(EmbeddingModel embeddingModel, JedisCon ObjectProvider observationRegistry, ObjectProvider customObservationConvention, BatchingStrategy batchingStrategy) { - // Define the metadata fields we want Redis to index and return - // Use plain names because RedisVectorStore will add the $. prefix internally List metadataFields = List.of( RedisVectorStore.MetadataField.text("original_answer"), RedisVectorStore.MetadataField.text("original_question"), @@ -64,7 +62,8 @@ public RedisVectorStore redisVectorStore(EmbeddingModel embeddingModel, JedisCon return new RedisVectorStore(config, embeddingModel, new JedisPooled(jedisConnectionFactory.getHostName(), jedisConnectionFactory.getPort()), - true, observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP), + true, // This is where initializeSchema is set to true + observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP), customObservationConvention.getIfAvailable(() -> null), batchingStrategy); } } diff --git a/out1 b/out1 new file mode 100644 index 0000000..5773521 --- /dev/null +++ b/out1 @@ -0,0 +1,139 @@ +index_name +spring-ai-example +index_options + +index_definition +key_type +HASH +prefixes + +default_score +1 +attributes +identifier +content +attribute +content +type +TEXT +WEIGHT +1 +identifier +vector_score +attribute +vector_score +type +NUMERIC +num_docs +0 +max_doc_id +0 +num_terms +0 +num_records +0 +inverted_sz_mb +0 +vector_index_sz_mb +0 +total_inverted_index_blocks +0 +offset_vectors_sz_mb +0 +doc_table_size_mb +0 +sortable_values_size_mb +0 +key_table_size_mb +0 +tag_overhead_sz_mb +0 +text_overhead_sz_mb +0 +total_index_memory_sz_mb +0 +geoshapes_sz_mb +0 +records_per_doc_avg +nan +bytes_per_record_avg +nan +offsets_per_term_avg +nan +offset_bits_per_record_avg +nan +hash_indexing_failures +0 +total_indexing_time +0 +indexing +0 +percent_indexed +1 +number_of_uses +4 +cleaning +0 +gc_stats +bytes_collected +0 +total_ms_run +0 +total_cycles +0 +average_cycle_time_ms +nan +last_run_time_ms +0 +gc_numeric_trees_missed +0 +gc_blocks_denied +0 +cursor_stats +global_idle +0 +global_total +0 +index_capacity +128 +index_total +0 +dialect_stats +dialect_1 +0 +dialect_2 +1 +dialect_3 +0 +dialect_4 +0 +Index Errors +indexing failures +0 +last indexing error +N/A +last indexing error key +N/A +field statistics +identifier +content +attribute +content +Index Errors +indexing failures +0 +last indexing error +N/A +last indexing error key +N/A +identifier +vector_score +attribute +vector_score +Index Errors +indexing failures +0 +last indexing error +N/A +last indexing error key +N/A diff --git a/out2 b/out2 new file mode 100644 index 0000000..120e29a --- /dev/null +++ b/out2 @@ -0,0 +1,139 @@ +index_name +spring-ai-example +index_options + +index_definition +key_type +HASH +prefixes + +default_score +1 +attributes +identifier +content +attribute +content +type +TEXT +WEIGHT +1 +identifier +vector_score +attribute +vector_score +type +NUMERIC +num_docs +0 +max_doc_id +0 +num_terms +0 +num_records +0 +inverted_sz_mb +0 +vector_index_sz_mb +0 +total_inverted_index_blocks +0 +offset_vectors_sz_mb +0 +doc_table_size_mb +0 +sortable_values_size_mb +0 +key_table_size_mb +0 +tag_overhead_sz_mb +0 +text_overhead_sz_mb +0 +total_index_memory_sz_mb +0 +geoshapes_sz_mb +0 +records_per_doc_avg +nan +bytes_per_record_avg +nan +offsets_per_term_avg +nan +offset_bits_per_record_avg +nan +hash_indexing_failures +0 +total_indexing_time +0 +indexing +0 +percent_indexed +1 +number_of_uses +5 +cleaning +0 +gc_stats +bytes_collected +0 +total_ms_run +0 +total_cycles +0 +average_cycle_time_ms +nan +last_run_time_ms +0 +gc_numeric_trees_missed +0 +gc_blocks_denied +0 +cursor_stats +global_idle +0 +global_total +0 +index_capacity +128 +index_total +0 +dialect_stats +dialect_1 +0 +dialect_2 +1 +dialect_3 +0 +dialect_4 +0 +Index Errors +indexing failures +0 +last indexing error +N/A +last indexing error key +N/A +field statistics +identifier +content +attribute +content +Index Errors +indexing failures +0 +last indexing error +N/A +last indexing error key +N/A +identifier +vector_score +attribute +vector_score +Index Errors +indexing failures +0 +last indexing error +N/A +last indexing error key +N/A diff --git a/ui/src/components/AddDocumentModal.jsx b/ui/src/components/AddDocumentModal.jsx index 3e0eddc..9eb116a 100644 --- a/ui/src/components/AddDocumentModal.jsx +++ b/ui/src/components/AddDocumentModal.jsx @@ -16,8 +16,9 @@ export const AddDocumentModal = ({ isOpen, onClose }) => { setIsSubmitting(true); setError(null); + let parsedContent; try { - JSON.parse(content); + parsedContent = JSON.parse(content); } catch (e) { setError("Invalid JSON format"); setIsSubmitting(false); @@ -30,7 +31,7 @@ export const AddDocumentModal = ({ isOpen, onClose }) => { headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify({ content }), + body: JSON.stringify({ content: parsedContent }), }); if (!response.ok) { diff --git a/ui/src/components/AudioController.jsx b/ui/src/components/AudioController.jsx index 2a7a44a..86be087 100644 --- a/ui/src/components/AudioController.jsx +++ b/ui/src/components/AudioController.jsx @@ -36,30 +36,31 @@ const AudioController = () => { // Handle audio event listeners useEffect(() => { + console.log("Audio state changed:", state.context.audio); const audio = state.context.audio; - if (!audio) return; - - const updateProgress = () => setProgress((audio.currentTime / audio.duration) * 100); - - const handlePlay = () => setIsPlaying(true); - const handlePause = () => setIsPlaying(false); - const handleEnded = () => { - setIsPlaying(false); - setProgress(0); - resetAudio(); - }; - - audio.addEventListener('play', handlePlay); - audio.addEventListener('pause', handlePause); - audio.addEventListener('ended', handleEnded); - audio.addEventListener('timeupdate', updateProgress); - - return () => { - audio.removeEventListener('play', handlePlay); - audio.removeEventListener('pause', handlePause); - audio.removeEventListener('ended', handleEnded); - audio.removeEventListener('timeupdate', updateProgress); - }; + + // Check if audio is a proper Audio element + if (!audio || typeof audio.play !== 'function') { + return; + } + + // Only attempt to play if the audio is not already playing + if (!audio.paused) { + return; + } + + // Wait for browser interaction before trying to play + const playPromise = audio.play(); + if (playPromise !== undefined) { + playPromise + .then(() => { + setIsPlaying(true); + }) + .catch(error => { + console.error('Error playing audio:', error); + setIsPlaying(false); + }); + } }, [state.context.audio]); const togglePlay = () => { diff --git a/ui/src/components/DocumentGrid.jsx b/ui/src/components/DocumentGrid.jsx index a37571a..1f22df0 100644 --- a/ui/src/components/DocumentGrid.jsx +++ b/ui/src/components/DocumentGrid.jsx @@ -1,20 +1,24 @@ -// components/DocumentGrid.jsx import React, {useEffect, useState} from 'react'; -import {RefreshCw} from 'lucide-react'; +import {RefreshCw, Trash2} from 'lucide-react'; export const DocumentGrid = () => { const [documents, setDocuments] = useState([]); const [isLoading, setIsLoading] = useState(false); + const [message, setMessage] = useState(''); const fetchDocuments = async () => { setIsLoading(true); + setDocuments([]); // Clear current documents try { const response = await fetch('/pinecone/search'); if (!response.ok) { throw new Error('Failed to fetch documents'); } const data = await response.json(); - setDocuments(data); + setDocuments(data.map(doc => ({ + ...doc, + content: JSON.parse(doc.content).content + }))); } catch (error) { console.error('Error fetching documents:', error); } finally { @@ -22,6 +26,40 @@ export const DocumentGrid = () => { } }; + const deleteDocument = async (id) => { + setIsLoading(true); + try { + const response = await fetch(`/pinecone/delete?id=${id}`, { method: 'DELETE' }); + if (!response.ok) { + throw new Error('Failed to delete document'); + } + setMessage('Document deleted successfully'); + setDocuments(documents.filter(doc => doc.id !== id)); + } catch (error) { + console.error('Error deleting document:', error); + setMessage('Failed to delete document'); + } finally { + setIsLoading(false); + } + }; + + const deleteCache = async () => { + setIsLoading(true); + try { + const response = await fetch('/api/cache/deleteAll', { method: 'DELETE' }); + if (!response.ok) { + throw new Error('Failed to delete cache'); + } + setMessage('Cache deleted successfully'); + fetchDocuments(); // Refetch documents after cache deletion + } catch (error) { + console.error('Error deleting cache:', error); + setMessage('Failed to delete cache'); + } finally { + setIsLoading(false); + } + }; + useEffect(() => { fetchDocuments(); }, []); @@ -30,22 +68,31 @@ export const DocumentGrid = () => {

Stored Documents

- +
+ +
+ {message && ( +
+ {message} +
+ )} +
+ @@ -59,9 +106,19 @@ export const DocumentGrid = () => { + ))} diff --git a/ui/src/state/StateMachine.js b/ui/src/state/StateMachine.js index 26f30a1..96e3a20 100644 --- a/ui/src/state/StateMachine.js +++ b/ui/src/state/StateMachine.js @@ -1,6 +1,14 @@ import {assign, createMachine, fromPromise} from "xstate"; import {v4 as uuidv4} from 'uuid'; +const deleteAllDocuments = fromPromise(async () => { + const response = await fetch('/api/cache/deleteAll', { method: 'DELETE' }); + if (!response.ok) { + throw new Error('Failed to delete all documents'); + } + return 'All documents were deleted successfully'; +}); + const handleAudioPlayback = fromPromise(async ({ input, context }) => { const audio = new Audio(); const mediaSource = new MediaSource(); @@ -148,7 +156,8 @@ export const simpleMachine = createMachine({ ASK: 'ask', PLAYBACK: { target: 'playback' - } + }, + DELETE_ALL: 'deleteAll' } }, playback: { @@ -229,6 +238,23 @@ export const simpleMachine = createMachine({ target: 'idle' } } + }, + deleteAll: { + invoke: { + src: deleteAllDocuments, + onDone: { + target: 'idle', + actions: assign({ + successMessage: ({event}) => event.data + }) + }, + onError: { + target: 'idle', + actions: assign({ + errorMessage: ({event}) => event.data + }) + } + } } } }); \ No newline at end of file From e2e549a47cbf81df37f26bf25bc508ececa7ce9d Mon Sep 17 00:00:00 2001 From: jrgleason Date: Mon, 9 Dec 2024 23:31:12 -0500 Subject: [PATCH 8/8] oops --- out1 | 139 ----------------------------------------------------------- out2 | 139 ----------------------------------------------------------- 2 files changed, 278 deletions(-) delete mode 100644 out1 delete mode 100644 out2 diff --git a/out1 b/out1 deleted file mode 100644 index 5773521..0000000 --- a/out1 +++ /dev/null @@ -1,139 +0,0 @@ -index_name -spring-ai-example -index_options - -index_definition -key_type -HASH -prefixes - -default_score -1 -attributes -identifier -content -attribute -content -type -TEXT -WEIGHT -1 -identifier -vector_score -attribute -vector_score -type -NUMERIC -num_docs -0 -max_doc_id -0 -num_terms -0 -num_records -0 -inverted_sz_mb -0 -vector_index_sz_mb -0 -total_inverted_index_blocks -0 -offset_vectors_sz_mb -0 -doc_table_size_mb -0 -sortable_values_size_mb -0 -key_table_size_mb -0 -tag_overhead_sz_mb -0 -text_overhead_sz_mb -0 -total_index_memory_sz_mb -0 -geoshapes_sz_mb -0 -records_per_doc_avg -nan -bytes_per_record_avg -nan -offsets_per_term_avg -nan -offset_bits_per_record_avg -nan -hash_indexing_failures -0 -total_indexing_time -0 -indexing -0 -percent_indexed -1 -number_of_uses -4 -cleaning -0 -gc_stats -bytes_collected -0 -total_ms_run -0 -total_cycles -0 -average_cycle_time_ms -nan -last_run_time_ms -0 -gc_numeric_trees_missed -0 -gc_blocks_denied -0 -cursor_stats -global_idle -0 -global_total -0 -index_capacity -128 -index_total -0 -dialect_stats -dialect_1 -0 -dialect_2 -1 -dialect_3 -0 -dialect_4 -0 -Index Errors -indexing failures -0 -last indexing error -N/A -last indexing error key -N/A -field statistics -identifier -content -attribute -content -Index Errors -indexing failures -0 -last indexing error -N/A -last indexing error key -N/A -identifier -vector_score -attribute -vector_score -Index Errors -indexing failures -0 -last indexing error -N/A -last indexing error key -N/A diff --git a/out2 b/out2 deleted file mode 100644 index 120e29a..0000000 --- a/out2 +++ /dev/null @@ -1,139 +0,0 @@ -index_name -spring-ai-example -index_options - -index_definition -key_type -HASH -prefixes - -default_score -1 -attributes -identifier -content -attribute -content -type -TEXT -WEIGHT -1 -identifier -vector_score -attribute -vector_score -type -NUMERIC -num_docs -0 -max_doc_id -0 -num_terms -0 -num_records -0 -inverted_sz_mb -0 -vector_index_sz_mb -0 -total_inverted_index_blocks -0 -offset_vectors_sz_mb -0 -doc_table_size_mb -0 -sortable_values_size_mb -0 -key_table_size_mb -0 -tag_overhead_sz_mb -0 -text_overhead_sz_mb -0 -total_index_memory_sz_mb -0 -geoshapes_sz_mb -0 -records_per_doc_avg -nan -bytes_per_record_avg -nan -offsets_per_term_avg -nan -offset_bits_per_record_avg -nan -hash_indexing_failures -0 -total_indexing_time -0 -indexing -0 -percent_indexed -1 -number_of_uses -5 -cleaning -0 -gc_stats -bytes_collected -0 -total_ms_run -0 -total_cycles -0 -average_cycle_time_ms -nan -last_run_time_ms -0 -gc_numeric_trees_missed -0 -gc_blocks_denied -0 -cursor_stats -global_idle -0 -global_total -0 -index_capacity -128 -index_total -0 -dialect_stats -dialect_1 -0 -dialect_2 -1 -dialect_3 -0 -dialect_4 -0 -Index Errors -indexing failures -0 -last indexing error -N/A -last indexing error key -N/A -field statistics -identifier -content -attribute -content -Index Errors -indexing failures -0 -last indexing error -N/A -last indexing error key -N/A -identifier -vector_score -attribute -vector_score -Index Errors -indexing failures -0 -last indexing error -N/A -last indexing error key -N/A
Content MetadataActions
-
-                                        {JSON.stringify(doc.metadata, null, 2)}
-                                    
+
+                                    {JSON.stringify(doc.metadata, null, 2)}
+                                
+
+