From 60875c25a68502bcd81d422ebd2dfc2d1d10b593 Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 1 Oct 2025 22:18:26 -0400 Subject: [PATCH] Update ai validation to use gpt-5 and the new responses api --- inventory-server/package-lock.json | 133 +---- inventory-server/package.json | 2 +- inventory-server/src/routes/ai-validation.js | 485 +++++++++++++---- inventory/package-lock.json | 493 +++++++++++------- .../components/AiValidationDialogs.tsx | 219 ++++++-- .../hooks/useAiValidation.tsx | 342 +++++++++++- .../hooks/useValidationState.tsx | 4 +- 7 files changed, 1184 insertions(+), 494 deletions(-) diff --git a/inventory-server/package-lock.json b/inventory-server/package-lock.json index e44b10b..f58f1e8 100755 --- a/inventory-server/package-lock.json +++ b/inventory-server/package-lock.json @@ -20,7 +20,7 @@ "express": "^4.18.2", "multer": "^1.4.5-lts.1", "mysql2": "^3.12.0", - "openai": "^4.85.3", + "openai": "^6.0.0", "pg": "^8.14.1", "pm2": "^5.3.0", "ssh2": "^1.16.0", @@ -399,43 +399,12 @@ "integrity": "sha512-R/BHQFripuhW6XPXy05hIvXJQdQ4540KnTvEFHSLjXfHYM41liOLKgIJEyYYiQe796xpaMHfe4Uj/p7Uvng2vA==", "license": "MIT" }, - "node_modules/@types/node": { - "version": "18.19.76", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.76.tgz", - "integrity": "sha512-yvR7Q9LdPz2vGpmpJX5LolrgRdWvB67MJKDPSgIIzpFbaf9a1j/f5DnLp5VDyHGMR0QZHlTr1afsD87QCXFHKw==", - "license": "MIT", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/node-fetch": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", - "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "form-data": "^4.0.0" - } - }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "license": "ISC" }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "license": "MIT", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -458,18 +427,6 @@ "node": ">= 14" } }, - "node_modules/agentkeepalive": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", - "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", - "license": "MIT", - "dependencies": { - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, "node_modules/amp": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/amp/-/amp-0.3.1.tgz", @@ -1327,15 +1284,6 @@ "node": ">= 0.6" } }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/eventemitter2": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-5.0.1.tgz", @@ -1474,25 +1422,6 @@ "node": ">= 6" } }, - "node_modules/form-data-encoder": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", - "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", - "license": "MIT" - }, - "node_modules/formdata-node": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", - "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", - "license": "MIT", - "dependencies": { - "node-domexception": "1.0.0", - "web-streams-polyfill": "4.0.0-beta.3" - }, - "engines": { - "node": ">= 12.20" - } - }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -1853,15 +1782,6 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.0.0" - } - }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -2407,25 +2327,6 @@ "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", "license": "MIT" }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "engines": { - "node": ">=10.5.0" - } - }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -2622,25 +2523,16 @@ } }, "node_modules/openai": { - "version": "4.85.3", - "resolved": "https://registry.npmjs.org/openai/-/openai-4.85.3.tgz", - "integrity": "sha512-KTMXAK6FPd2IvsPtglMt0J1GyVrjMxCYzu/mVbCPabzzquSJoZlYpHtE0p0ScZPyt11XTc757xSO4j39j5g+Xw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.0.0.tgz", + "integrity": "sha512-J7LEmTn3WLZnbyEmMYcMPyT5A0fGzhPwSvVUcNRKy6j2hJIbqSFrJERnUHYNkcoCCalRumypnj9AVoe5bVHd3Q==", "license": "Apache-2.0", - "dependencies": { - "@types/node": "^18.11.18", - "@types/node-fetch": "^2.6.4", - "abort-controller": "^3.0.0", - "agentkeepalive": "^4.2.1", - "form-data-encoder": "1.7.2", - "formdata-node": "^4.3.2", - "node-fetch": "^2.6.7" - }, "bin": { "openai": "bin/cli" }, "peerDependencies": { "ws": "^8.18.0", - "zod": "^3.23.8" + "zod": "^3.25 || ^4.0" }, "peerDependenciesMeta": { "ws": { @@ -3941,12 +3833,6 @@ "dev": true, "license": "MIT" }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "license": "MIT" - }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -4017,15 +3903,6 @@ "lodash": "^4.17.14" } }, - "node_modules/web-streams-polyfill": { - "version": "4.0.0-beta.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", - "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", diff --git a/inventory-server/package.json b/inventory-server/package.json index dc359e7..9b2b54e 100755 --- a/inventory-server/package.json +++ b/inventory-server/package.json @@ -29,7 +29,7 @@ "express": "^4.18.2", "multer": "^1.4.5-lts.1", "mysql2": "^3.12.0", - "openai": "^4.85.3", + "openai": "^6.0.0", "pg": "^8.14.1", "pm2": "^5.3.0", "ssh2": "^1.16.0", diff --git a/inventory-server/src/routes/ai-validation.js b/inventory-server/src/routes/ai-validation.js index b849b2b..019f2ea 100644 --- a/inventory-server/src/routes/ai-validation.js +++ b/inventory-server/src/routes/ai-validation.js @@ -6,7 +6,7 @@ const path = require("path"); const dotenv = require("dotenv"); const mysql = require('mysql2/promise'); const { Client } = require('ssh2'); -const { getDbConnection } = require('../utils/dbConnection'); // Import the optimized connection function +const { getDbConnection, closeAllConnections } = require('../utils/dbConnection'); // Import the optimized connection function // Ensure environment variables are loaded dotenv.config({ path: path.join(__dirname, "../../.env") }); @@ -19,6 +19,121 @@ if (!process.env.OPENAI_API_KEY) { console.error("Warning: OPENAI_API_KEY is not set in environment variables"); } +async function createResponsesCompletion(payload) { + if (!openai.responses?.create) { + throw new Error( + "OpenAI client does not expose responses.create; please verify the openai SDK version." + ); + } + + return openai.responses.create(payload); +} + +const AI_VALIDATION_SCHEMA_NAME = "ai_validation_response"; + +const FLEXIBLE_PRIMITIVE_SCHEMAS = [ + { type: "string" }, + { type: "number" }, + { type: "boolean" }, + { type: "null" }, +]; + +const FLEXIBLE_ARRAY_SCHEMA = { + type: "array", + items: { + anyOf: FLEXIBLE_PRIMITIVE_SCHEMAS, + }, +}; + +const FLEXIBLE_OBJECT_SCHEMA = { + type: "object", + properties: {}, + patternProperties: { + ".+": { + anyOf: [...FLEXIBLE_PRIMITIVE_SCHEMAS, FLEXIBLE_ARRAY_SCHEMA], + }, + }, + additionalProperties: false, +}; + +const FLEXIBLE_VALUE_SCHEMA = { + anyOf: [...FLEXIBLE_PRIMITIVE_SCHEMAS, FLEXIBLE_ARRAY_SCHEMA, FLEXIBLE_OBJECT_SCHEMA], +}; + +const AI_VALIDATION_JSON_SCHEMA = { + type: "object", + additionalProperties: false, + required: [ + "correctedData", + "changes", + "warnings", + "summary", + "qualityNotes", + "nextSteps", + "metadata" + ], + properties: { + correctedData: { + type: "array", + items: { + type: "object", + properties: {}, + patternProperties: { + ".+": FLEXIBLE_VALUE_SCHEMA, + }, + additionalProperties: false, + }, + }, + changes: { + type: "array", + items: { + type: "string", + }, + default: [], + }, + warnings: { + type: "array", + items: { + type: "string", + }, + default: [], + }, + summary: { + type: "string", + default: "", + }, + qualityNotes: { + type: "array", + items: { + type: "string", + }, + default: [], + }, + nextSteps: { + type: "array", + items: { + type: "string", + }, + default: [], + }, + metadata: { + type: "object", + properties: {}, + patternProperties: { + ".+": FLEXIBLE_VALUE_SCHEMA, + }, + additionalProperties: false, + }, + }, +}; + +const AI_VALIDATION_TEXT_FORMAT = { + type: "json_schema", + name: AI_VALIDATION_SCHEMA_NAME, + strict: true, + schema: AI_VALIDATION_JSON_SCHEMA, +}; + // Debug endpoint for viewing prompt router.post("/debug", async (req, res) => { try { @@ -139,6 +254,12 @@ router.post("/debug", async (req, res) => { code: error.code || null, name: error.name || null }); + } finally { + try { + await closeAllConnections(); + } catch (closeError) { + console.error("โš ๏ธ Failed to close DB connections after debug request:", closeError); + } } }); @@ -402,8 +523,9 @@ async function generateDebugResponse(productsToUse, res) { console.log("Sending response with taxonomy stats:", response.taxonomyStats); return response; - } finally { - if (promptConnection) await promptConnection.end(); + } catch (promptLoadError) { + console.error("Error loading prompt:", promptLoadError); + throw promptLoadError; } } catch (error) { console.error("Error generating debug response:", error); @@ -883,34 +1005,80 @@ router.post("/validate", async (req, res) => { console.log("๐Ÿ”„ Loading prompt with filtered taxonomy..."); const promptData = await loadPrompt(connection, products, req.app.locals.pool); const fullUserPrompt = promptData.userContent + "\n" + JSON.stringify(products); - const promptLength = promptData.systemInstructions.length + fullUserPrompt.length; // Store prompt length for performance metrics + promptLength = promptData.systemInstructions.length + fullUserPrompt.length; // Store prompt length for performance metrics console.log("๐Ÿ“ Generated prompt length:", promptLength); console.log("๐Ÿ“ System instructions length:", promptData.systemInstructions.length); console.log("๐Ÿ“ User content length:", fullUserPrompt.length); - console.log("๐Ÿค– Sending request to OpenAI..."); - const completion = await openai.chat.completions.create({ - model: "gpt-4o", - messages: [ + console.log("๐Ÿค– Sending request to OpenAI Responses API..."); + + // GPT-5 Responses API Configuration: + // - Using "gpt-5" (reasoning model) for complex product validation + // - reasoning.effort: "medium" balances quality and speed (minimal, low, medium, high) + // - text.verbosity: "medium" provides balanced output detail (low, medium, high) + // - max_output_tokens: 20000 ensures space for large product batches + // Note: Responses API is the recommended endpoint for GPT-5 models + const completion = await createResponsesCompletion({ + model: "gpt-5", + input: [ { - role: "system", - content: promptData.systemInstructions, + role: "developer", + content: `${promptData.systemInstructions}\n\nYou MUST respond with a single valid JSON object containing the following top-level keys: correctedData, changes, warnings, summary, qualityNotes, nextSteps, metadata.\n- correctedData: array of product objects reflecting the updated data.\n- changes: array of human-readable bullet points summarizing the nature of updates.\n- warnings: array of caveats or risks that still require review.\n- summary: a concise paragraph (<=75 words) describing overall data quality and improvements.\n- qualityNotes: array of short comments (<=40 words each) about validation quality or notable observations.\n- nextSteps: array of recommended manual follow-up actions (if none, provide an empty array).\n- metadata: object containing any supplemental machine-readable information (optional fields allowed).\nDo NOT include Markdown code fences or any text outside the JSON object.`, }, { role: "user", content: fullUserPrompt, }, ], - temperature: 0.2, - response_format: { type: "json_object" }, + reasoning: { + effort: "medium" + }, + text: { + verbosity: "medium", + format: AI_VALIDATION_TEXT_FORMAT, + }, + max_output_tokens: 20000, }); - console.log("โœ… Received response from OpenAI"); - const rawResponse = completion.choices[0].message.content; - console.log("๐Ÿ“„ Raw AI response length:", rawResponse.length); + console.log("โœ… Received response from OpenAI Responses API"); + + // Responses API structure: response has 'output' array with message objects + const rawResponse = extractResponseText(completion); + console.log("๐Ÿ“„ Raw AI response length:", rawResponse ? rawResponse.length : 0); + + if (!rawResponse) { + throw new Error("OpenAI response did not include any text output"); + } + + const responseModel = completion.model; + const usage = completion.usage || {}; + + // GPT-5 Responses API provides detailed token usage including reasoning tokens + const tokenUsageSummary = { + prompt: usage.input_tokens ?? usage.prompt_tokens ?? null, + completion: usage.output_tokens ?? usage.completion_tokens ?? null, + total: usage.total_tokens ?? null, + // GPT-5 reasoning tokens are in output_tokens_details + reasoning: usage.output_tokens_details?.reasoning_tokens ?? usage.completion_tokens_details?.reasoning_tokens ?? null, + // Also capture text generation tokens separately from reasoning + textGeneration: usage.output_tokens_details?.text_generation_tokens ?? usage.completion_tokens_details?.text_generation_tokens ?? null, + cachedPrompt: usage.input_tokens_details?.cached_tokens ?? usage.prompt_tokens_details?.cached_tokens ?? null, + // Capture audio tokens if present (future GPT-5 feature) + audioTokens: usage.output_tokens_details?.audio_tokens ?? usage.completion_tokens_details?.audio_tokens ?? null, + }; + + // Extract reasoning_effort and verbosity that were actually applied + const reasoningEffortApplied = completion.reasoning?.effort || "medium"; + const verbosityApplied = completion.text?.verbosity || "medium"; + + console.log("๐Ÿ“Š Token usage summary:", tokenUsageSummary); + console.log("๐Ÿค– Model dispatched:", responseModel); + console.log("๐Ÿง  Reasoning effort applied:", reasoningEffortApplied); + console.log("๐Ÿ“ Verbosity applied:", verbosityApplied); try { - const aiResponse = JSON.parse(rawResponse); + const normalizedResponse = normalizeJsonResponse(rawResponse); + const aiResponse = JSON.parse(normalizedResponse); console.log( "๐Ÿ”„ Parsed AI response with keys:", Object.keys(aiResponse) @@ -975,7 +1143,12 @@ router.post("/validate", async (req, res) => { const endTime = new Date(); let performanceMetrics = { promptLength, - productCount: products.length + productCount: products.length, + model: responseModel, + tokenUsage: tokenUsageSummary, + reasoningTokens: tokenUsageSummary.reasoning, + reasoningEffort: reasoningEffortApplied, + verbosity: verbosityApplied, }; try { @@ -1040,83 +1213,78 @@ router.post("/validate", async (req, res) => { let promptSources = null; try { - // Get system prompt - const systemPromptResult = await pool.query(` - SELECT * FROM ai_prompts WHERE prompt_type = 'system' - `); - - // Get general prompt - const generalPromptResult = await pool.query(` - SELECT * FROM ai_prompts WHERE prompt_type = 'general' - `); - - // Extract unique company IDs from products - const companyIds = new Set(); - products.forEach(product => { - if (product.company) { - companyIds.add(String(product.company)); - } - }); - - let companyPrompts = []; - if (companyIds.size > 0) { - // Fetch company-specific prompts - const companyPromptsResult = await pool.query(` - SELECT * FROM ai_prompts - WHERE prompt_type = 'company_specific' - AND company = ANY($1) - `, [Array.from(companyIds)]); - - companyPrompts = companyPromptsResult.rows; - } - - // Find company names from taxonomy for the validation endpoint - const companyPromptsWithNames = companyPrompts.map(prompt => { - let companyName = "Unknown Company"; - if (taxonomy.companies && Array.isArray(taxonomy.companies)) { - const companyData = taxonomy.companies.find(company => - String(company[0]) === String(prompt.company) - ); - if (companyData && companyData[1]) { - companyName = companyData[1]; + // Use the local PostgreSQL pool from the app + const pool = req.app.locals.pool; + if (!pool) { + console.warn("โš ๏ธ Local database pool not available for prompt sources"); + } else { + // Get system prompt + const systemPromptResult = await pool.query(` + SELECT * FROM ai_prompts WHERE prompt_type = 'system' + `); + + // Get general prompt + const generalPromptResult = await pool.query(` + SELECT * FROM ai_prompts WHERE prompt_type = 'general' + `); + + // Extract unique company IDs from products + const companyIds = new Set(); + products.forEach(product => { + if (product.company) { + companyIds.add(String(product.company)); } + }); + + let companyPrompts = []; + if (companyIds.size > 0) { + // Fetch company-specific prompts + const companyPromptsResult = await pool.query(` + SELECT * FROM ai_prompts + WHERE prompt_type = 'company_specific' + AND company = ANY($1) + `, [Array.from(companyIds)]); + + companyPrompts = companyPromptsResult.rows; } - - return { + + // Format company prompts for response + // Note: Company names would require re-fetching taxonomy data + // For now, we include company ID only + const companyPromptsWithNames = companyPrompts.map(prompt => ({ id: prompt.id, company: prompt.company, - companyName: companyName, prompt_text: prompt.prompt_text - }; - }); - - // Set prompt sources - if (generalPromptResult.rows.length > 0) { - const generalPrompt = generalPromptResult.rows[0]; - let systemPrompt = null; - - if (systemPromptResult.rows.length > 0) { - systemPrompt = systemPromptResult.rows[0]; + })); + + // Set prompt sources + if (generalPromptResult.rows.length > 0) { + const generalPrompt = generalPromptResult.rows[0]; + let systemPrompt = null; + + if (systemPromptResult.rows.length > 0) { + systemPrompt = systemPromptResult.rows[0]; + } + + promptSources = { + ...(systemPrompt ? { + systemPrompt: { + id: systemPrompt.id, + prompt_text: systemPrompt.prompt_text + } + } : { + systemPrompt: { + id: 0, + prompt_text: `You are a specialized e-commerce product data processor for a crafting supplies website tasked with providing complete, correct, appealing, and SEO-friendly product listings. You should write professionally, but in a friendly and engaging tone. You have meticulous attention to detail and are a master at your craft.` + } + }), + generalPrompt: { + id: generalPrompt.id, + prompt_text: generalPrompt.prompt_text + }, + companyPrompts: companyPromptsWithNames + }; } - - promptSources = { - ...(systemPrompt ? { - systemPrompt: { - id: systemPrompt.id, - prompt_text: systemPrompt.prompt_text - } - } : { - systemPrompt: { - id: 0, - prompt_text: `You are a specialized e-commerce product data processor for a crafting supplies website tasked with providing complete, correct, appealing, and SEO-friendly product listings. You should write professionally, but in a friendly and engaging tone. You have meticulous attention to detail and are a master at your craft.` - } - }), - generalPrompt: { - id: generalPrompt.id, - prompt_text: generalPrompt.prompt_text - }, - companyPrompts: companyPromptsWithNames - }; } } catch (promptSourceError) { console.error("โš ๏ธ Error getting prompt sources:", promptSourceError); @@ -1126,16 +1294,26 @@ router.post("/validate", async (req, res) => { // Include prompt sources in the response res.json({ success: true, - changeDetails: changeDetails, - performanceMetrics: performanceMetrics || { - // Fallback: calculate a simple estimate - promptLength: promptLength, - processingTimeSeconds: Math.max(15, Math.round(promptLength / 1000)), - isEstimate: true, - productCount: products.length - }, - promptSources: promptSources, ...aiResponse, + changeDetails, + performanceMetrics: + performanceMetrics || { + // Fallback: calculate a simple estimate + promptLength, + processingTimeSeconds: Math.max(15, Math.round(promptLength / 1000)), + isEstimate: true, + productCount: products.length, + model: responseModel, + tokenUsage: tokenUsageSummary, + reasoningTokens: tokenUsageSummary.reasoning, + reasoningEffort: reasoningEffortApplied, + verbosity: verbosityApplied, + }, + promptSources, + model: responseModel, + tokenUsage: tokenUsageSummary, + reasoningEffort: reasoningEffortApplied, + verbosity: verbosityApplied, }); } catch (parseError) { console.error("โŒ Error parsing AI response:", parseError); @@ -1151,10 +1329,6 @@ router.post("/validate", async (req, res) => { success: false, error: "OpenAI API Error: " + openaiError.message, }); - } finally { - // Clean up database connection and SSH tunnel - if (connection) await connection.end(); - if (ssh) ssh.end(); } } catch (error) { console.error("โŒ AI Validation Error:", error); @@ -1167,6 +1341,12 @@ router.post("/validate", async (req, res) => { success: false, error: error.message || "Error during AI validation", }); + } finally { + try { + await closeAllConnections(); + } catch (closeError) { + console.error("โš ๏ธ Failed to close DB connections after validation request:", closeError); + } } }); @@ -1249,8 +1429,11 @@ router.get("/test-taxonomy", async (req, res) => { timestamp: new Date().toISOString() }); } finally { - if (connection) await connection.end(); - if (ssh) ssh.end(); + try { + await closeAllConnections(); + } catch (closeError) { + console.error("โš ๏ธ Failed to close DB connections after test-taxonomy request:", closeError); + } } } catch (error) { console.error("Test taxonomy endpoint error:", error); @@ -1262,3 +1445,99 @@ router.get("/test-taxonomy", async (req, res) => { }); module.exports = router; + +function extractResponseText(response) { + if (!response) return ""; + + const outputs = []; + if (Array.isArray(response.output)) { + outputs.push(...response.output); + } + if (Array.isArray(response.outputs)) { + outputs.push(...response.outputs); + } + + const segments = outputs.flatMap((output) => collectTextSegments(output?.content ?? output)); + + if (segments.length === 0 && typeof response.output_text === "string") { + segments.push(response.output_text); + } + + if (segments.length === 0 && response.choices?.length) { + segments.push( + ...collectTextSegments(response.choices?.[0]?.message?.content) + ); + } + + const text = segments.join("").trim(); + return text; +} + +function collectTextSegments(node) { + if (node == null) return []; + + if (typeof node === "string" || typeof node === "number" || typeof node === "boolean") { + return [String(node)]; + } + + if (Array.isArray(node)) { + return node.flatMap(collectTextSegments); + } + + if (typeof node !== "object") { + return []; + } + + const segments = []; + + if (typeof node.text === "string") { + segments.push(node.text); + } else if (Array.isArray(node.text)) { + segments.push(...node.text.flatMap(collectTextSegments)); + } + + if (typeof node.content === "string") { + segments.push(node.content); + } else if (Array.isArray(node.content)) { + segments.push(...node.content.flatMap(collectTextSegments)); + } + + if (typeof node.output_text === "string") { + segments.push(node.output_text); + } else if (Array.isArray(node.output_text)) { + segments.push(...node.output_text.flatMap(collectTextSegments)); + } + + if (typeof node.value === "string") { + segments.push(node.value); + } + + if (typeof node.data === "string") { + segments.push(node.data); + } + + return segments; +} + +function normalizeJsonResponse(text) { + if (!text || typeof text !== 'string') return text; + let cleaned = text.trim(); + + if (cleaned.startsWith('```')) { + const firstLineBreak = cleaned.indexOf('\n'); + if (firstLineBreak !== -1) { + cleaned = cleaned.substring(firstLineBreak + 1); + } else { + cleaned = cleaned.replace(/^```/, ''); + } + + const closingFenceIndex = cleaned.lastIndexOf('```'); + if (closingFenceIndex !== -1) { + cleaned = cleaned.substring(0, closingFenceIndex); + } + + cleaned = cleaned.trim(); + } + + return cleaned; +} diff --git a/inventory/package-lock.json b/inventory/package-lock.json index 85d35d6..12504d2 100644 --- a/inventory/package-lock.json +++ b/inventory/package-lock.json @@ -1043,9 +1043,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", - "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", "dev": true, "license": "MIT", "dependencies": { @@ -1085,9 +1085,9 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz", - "integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1099,10 +1099,20 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/config-helpers": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", + "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/core": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.11.0.tgz", - "integrity": "sha512-DWUB2pksgNEb6Bz2fggIy1wh6fGgZP4Xyy/Mt0QZPiloKKXerbqq9D3SBQTlCRYOrcRPu4vuz+CGjwdfqxnoWA==", + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1113,9 +1123,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", - "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1150,13 +1160,16 @@ } }, "node_modules/@eslint/js": { - "version": "9.20.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.20.0.tgz", - "integrity": "sha512-iZA07H9io9Wn836aVTytRaNqh00Sad+EamwOVJT12GTLw1VGMFV/4JaME+JjLtr9fiGaoWgYnS54wrfWsSs4oQ==", + "version": "9.36.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.36.0.tgz", + "integrity": "sha512-uhCbYtYynH30iZErszX78U+nR3pJU3RHGQ57NXy5QupD4SBVwDeU8TNBy+MjMngc1UyIW9noKqsRqfjQTBU2dw==", "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" } }, "node_modules/@eslint/object-schema": { @@ -1170,32 +1183,19 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", - "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.10.0", + "@eslint/core": "^0.15.2", "levn": "^0.4.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz", - "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, "node_modules/@floating-ui/core": { "version": "1.6.9", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz", @@ -1296,9 +1296,9 @@ } }, "node_modules/@humanwhocodes/retry": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", - "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -2559,9 +2559,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.8.tgz", - "integrity": "sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw==", + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.3.tgz", + "integrity": "sha512-h6cqHGZ6VdnwliFG1NXvMPTy/9PS3h8oLh7ImwR+kl+oYnQizgjxsONmmPSb2C66RksfkfIxEVtDSEcJiO0tqw==", "cpu": [ "arm" ], @@ -2573,9 +2573,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.8.tgz", - "integrity": "sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q==", + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.3.tgz", + "integrity": "sha512-wd+u7SLT/u6knklV/ifG7gr5Qy4GUbH2hMWcDauPFJzmCZUAJ8L2bTkVXC2niOIxp8lk3iH/QX8kSrUxVZrOVw==", "cpu": [ "arm64" ], @@ -2587,9 +2587,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.8.tgz", - "integrity": "sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q==", + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.3.tgz", + "integrity": "sha512-lj9ViATR1SsqycwFkJCtYfQTheBdvlWJqzqxwc9f2qrcVrQaF/gCuBRTiTolkRWS6KvNxSk4KHZWG7tDktLgjg==", "cpu": [ "arm64" ], @@ -2601,9 +2601,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.8.tgz", - "integrity": "sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw==", + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.3.tgz", + "integrity": "sha512-+Dyo7O1KUmIsbzx1l+4V4tvEVnVQqMOIYtrxK7ncLSknl1xnMHLgn7gddJVrYPNZfEB8CIi3hK8gq8bDhb3h5A==", "cpu": [ "x64" ], @@ -2615,9 +2615,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.8.tgz", - "integrity": "sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA==", + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.3.tgz", + "integrity": "sha512-u9Xg2FavYbD30g3DSfNhxgNrxhi6xVG4Y6i9Ur1C7xUuGDW3banRbXj+qgnIrwRN4KeJ396jchwy9bCIzbyBEQ==", "cpu": [ "arm64" ], @@ -2629,9 +2629,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.8.tgz", - "integrity": "sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q==", + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.3.tgz", + "integrity": "sha512-5M8kyi/OX96wtD5qJR89a/3x5x8x5inXBZO04JWhkQb2JWavOWfjgkdvUqibGJeNNaz1/Z1PPza5/tAPXICI6A==", "cpu": [ "x64" ], @@ -2643,9 +2643,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.8.tgz", - "integrity": "sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g==", + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.3.tgz", + "integrity": "sha512-IoerZJ4l1wRMopEHRKOO16e04iXRDyZFZnNZKrWeNquh5d6bucjezgd+OxG03mOMTnS1x7hilzb3uURPkJ0OfA==", "cpu": [ "arm" ], @@ -2657,9 +2657,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.8.tgz", - "integrity": "sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA==", + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.3.tgz", + "integrity": "sha512-ZYdtqgHTDfvrJHSh3W22TvjWxwOgc3ThK/XjgcNGP2DIwFIPeAPNsQxrJO5XqleSlgDux2VAoWQ5iJrtaC1TbA==", "cpu": [ "arm" ], @@ -2671,9 +2671,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.8.tgz", - "integrity": "sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A==", + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.3.tgz", + "integrity": "sha512-NcViG7A0YtuFDA6xWSgmFb6iPFzHlf5vcqb2p0lGEbT+gjrEEz8nC/EeDHvx6mnGXnGCC1SeVV+8u+smj0CeGQ==", "cpu": [ "arm64" ], @@ -2685,9 +2685,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.8.tgz", - "integrity": "sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q==", + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.3.tgz", + "integrity": "sha512-d3pY7LWno6SYNXRm6Ebsq0DJGoiLXTb83AIPCXl9fmtIQs/rXoS8SJxxUNtFbJ5MiOvs+7y34np77+9l4nfFMw==", "cpu": [ "arm64" ], @@ -2698,10 +2698,10 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.8.tgz", - "integrity": "sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ==", + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.3.tgz", + "integrity": "sha512-3y5GA0JkBuirLqmjwAKwB0keDlI6JfGYduMlJD/Rl7fvb4Ni8iKdQs1eiunMZJhwDWdCvrcqXRY++VEBbvk6Eg==", "cpu": [ "loong64" ], @@ -2712,10 +2712,10 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.8.tgz", - "integrity": "sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw==", + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.3.tgz", + "integrity": "sha512-AUUH65a0p3Q0Yfm5oD2KVgzTKgwPyp9DSXc3UA7DtxhEb/WSPfbG4wqXeSN62OG5gSo18em4xv6dbfcUGXcagw==", "cpu": [ "ppc64" ], @@ -2727,9 +2727,23 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.8.tgz", - "integrity": "sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw==", + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.3.tgz", + "integrity": "sha512-1makPhFFVBqZE+XFg3Dkq+IkQ7JvmUrwwqaYBL2CE+ZpxPaqkGaiWFEWVGyvTwZace6WLJHwjVh/+CXbKDGPmg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.3.tgz", + "integrity": "sha512-OOFJa28dxfl8kLOPMUOQBCO6z3X2SAfzIE276fwT52uXDWUS178KWq0pL7d6p1kz7pkzA0yQwtqL0dEPoVcRWg==", "cpu": [ "riscv64" ], @@ -2741,9 +2755,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.8.tgz", - "integrity": "sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA==", + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.3.tgz", + "integrity": "sha512-jMdsML2VI5l+V7cKfZx3ak+SLlJ8fKvLJ0Eoa4b9/vCUrzXKgoKxvHqvJ/mkWhFiyp88nCkM5S2v6nIwRtPcgg==", "cpu": [ "s390x" ], @@ -2755,9 +2769,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.8.tgz", - "integrity": "sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA==", + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.3.tgz", + "integrity": "sha512-tPgGd6bY2M2LJTA1uGq8fkSPK8ZLYjDjY+ZLK9WHncCnfIz29LIXIqUgzCR0hIefzy6Hpbe8Th5WOSwTM8E7LA==", "cpu": [ "x64" ], @@ -2769,9 +2783,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.8.tgz", - "integrity": "sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ==", + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.3.tgz", + "integrity": "sha512-BCFkJjgk+WFzP+tcSMXq77ymAPIxsX9lFJWs+2JzuZTLtksJ2o5hvgTdIcZ5+oKzUDMwI0PfWzRBYAydAHF2Mw==", "cpu": [ "x64" ], @@ -2782,10 +2796,24 @@ "linux" ] }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.3.tgz", + "integrity": "sha512-KTD/EqjZF3yvRaWUJdD1cW+IQBk4fbQaHYJUmP8N4XoKFZilVL8cobFSTDnjTtxWJQ3JYaMgF4nObY/+nYkumA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.8.tgz", - "integrity": "sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ==", + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.3.tgz", + "integrity": "sha512-+zteHZdoUYLkyYKObGHieibUFLbttX2r+58l27XZauq0tcWYYuKUwY2wjeCN9oK1Um2YgH2ibd6cnX/wFD7DuA==", "cpu": [ "arm64" ], @@ -2797,9 +2825,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.8.tgz", - "integrity": "sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w==", + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.3.tgz", + "integrity": "sha512-of1iHkTQSo3kr6dTIRX6t81uj/c/b15HXVsPcEElN5sS859qHrOepM5p9G41Hah+CTqSh2r8Bm56dL2z9UQQ7g==", "cpu": [ "ia32" ], @@ -2810,10 +2838,24 @@ "win32" ] }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.3.tgz", + "integrity": "sha512-s0hybmlHb56mWVZQj8ra9048/WZTPLILKxcvcq+8awSZmyiSUZjjem1AhU3Tf4ZKpYhK4mg36HtHDOe8QJS5PQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.8.tgz", - "integrity": "sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g==", + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.3.tgz", + "integrity": "sha512-zGIbEVVXVtauFgl3MRwGWEN36P5ZGenHRMgNw88X5wEhEBpq0XrMEZwOn07+ICrwM17XO5xfMZqh0OldCH5VTA==", "cpu": [ "x64" ], @@ -3000,12 +3042,6 @@ "@babel/types": "^7.20.7" } }, - "node_modules/@types/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", - "license": "MIT" - }, "node_modules/@types/d3-array": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", @@ -3070,9 +3106,9 @@ "license": "MIT" }, "node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, "license": "MIT" }, @@ -3306,9 +3342,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3407,9 +3443,9 @@ } }, "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", "bin": { @@ -3577,13 +3613,13 @@ } }, "node_modules/axios": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.3.tgz", - "integrity": "sha512-iP4DebzoNlP/YN2dpwCgb8zoCmhtkajzS48JvwmkSkXvPI3DHc7m+XYL5tGnSlJtR6nImXZmdCuN5aP8dh1d8A==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", + "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", + "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, @@ -3652,9 +3688,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -4873,22 +4909,23 @@ } }, "node_modules/eslint": { - "version": "9.20.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.20.1.tgz", - "integrity": "sha512-m1mM33o6dBUjxl2qb6wv6nGNwCAsns1eKtaQ4l/NPHeTvhiUPbtdfMyktxN4B3fgHIgsYh1VT3V9txblpQHq+g==", + "version": "9.36.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.36.0.tgz", + "integrity": "sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.19.0", - "@eslint/core": "^0.11.0", - "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "9.20.0", - "@eslint/plugin-kit": "^0.2.5", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.1", + "@eslint/core": "^0.15.2", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.36.0", + "@eslint/plugin-kit": "^0.3.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.1", + "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", @@ -4896,9 +4933,9 @@ "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.2.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -4956,9 +4993,9 @@ } }, "node_modules/eslint-scope": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", - "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -4973,9 +5010,9 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -5019,15 +5056,15 @@ } }, "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.14.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5331,14 +5368,15 @@ } }, "node_modules/form-data": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", - "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { @@ -5545,9 +5583,9 @@ } }, "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -7080,15 +7118,13 @@ } }, "node_modules/react-router": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.1.5.tgz", - "integrity": "sha512-8BUF+hZEU4/z/JD201yK6S+UYhsf58bzYIDq2NS1iGpwxSXDu7F+DeGSkIXMFBuHZB21FSiCzEcUb18cQNdRkA==", + "version": "7.9.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.3.tgz", + "integrity": "sha512-4o2iWCFIwhI/eYAIL43+cjORXYn/aRQPgtFRRZb3VzoyQ5Uej0Bmqj7437L97N9NJW4wnicSwLOLS+yCXfAPgg==", "license": "MIT", "dependencies": { - "@types/cookie": "^0.6.0", "cookie": "^1.0.1", - "set-cookie-parser": "^2.6.0", - "turbo-stream": "2.4.0" + "set-cookie-parser": "^2.6.0" }, "engines": { "node": ">=20.0.0" @@ -7104,12 +7140,12 @@ } }, "node_modules/react-router-dom": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.1.5.tgz", - "integrity": "sha512-/4f9+up0Qv92D3bB8iN5P1s3oHAepSGa9h5k6tpTFlixTTskJZwKGhJ6vRJ277tLD1zuaZTt95hyGWV1Z37csQ==", + "version": "7.9.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.9.3.tgz", + "integrity": "sha512-1QSbA0TGGFKTAc/aWjpfW/zoEukYfU4dc1dLkT/vvf54JoGMkW+fNA+3oyo2gWVW1GM7BxjJVHz5GnPJv40rvg==", "license": "MIT", "dependencies": { - "react-router": "7.1.5" + "react-router": "7.9.3" }, "engines": { "node": ">=20.0.0" @@ -7331,13 +7367,13 @@ } }, "node_modules/rollup": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.8.tgz", - "integrity": "sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ==", + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.3.tgz", + "integrity": "sha512-RIDh866U8agLgiIcdpB+COKnlCreHJLfIhWC3LVflku5YHfpnsIKigRZeFfMfCc4dVcqNVfQQ5gO/afOck064A==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.6" + "@types/estree": "1.0.8" }, "bin": { "rollup": "dist/bin/rollup" @@ -7347,25 +7383,28 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.34.8", - "@rollup/rollup-android-arm64": "4.34.8", - "@rollup/rollup-darwin-arm64": "4.34.8", - "@rollup/rollup-darwin-x64": "4.34.8", - "@rollup/rollup-freebsd-arm64": "4.34.8", - "@rollup/rollup-freebsd-x64": "4.34.8", - "@rollup/rollup-linux-arm-gnueabihf": "4.34.8", - "@rollup/rollup-linux-arm-musleabihf": "4.34.8", - "@rollup/rollup-linux-arm64-gnu": "4.34.8", - "@rollup/rollup-linux-arm64-musl": "4.34.8", - "@rollup/rollup-linux-loongarch64-gnu": "4.34.8", - "@rollup/rollup-linux-powerpc64le-gnu": "4.34.8", - "@rollup/rollup-linux-riscv64-gnu": "4.34.8", - "@rollup/rollup-linux-s390x-gnu": "4.34.8", - "@rollup/rollup-linux-x64-gnu": "4.34.8", - "@rollup/rollup-linux-x64-musl": "4.34.8", - "@rollup/rollup-win32-arm64-msvc": "4.34.8", - "@rollup/rollup-win32-ia32-msvc": "4.34.8", - "@rollup/rollup-win32-x64-msvc": "4.34.8", + "@rollup/rollup-android-arm-eabi": "4.52.3", + "@rollup/rollup-android-arm64": "4.52.3", + "@rollup/rollup-darwin-arm64": "4.52.3", + "@rollup/rollup-darwin-x64": "4.52.3", + "@rollup/rollup-freebsd-arm64": "4.52.3", + "@rollup/rollup-freebsd-x64": "4.52.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.52.3", + "@rollup/rollup-linux-arm-musleabihf": "4.52.3", + "@rollup/rollup-linux-arm64-gnu": "4.52.3", + "@rollup/rollup-linux-arm64-musl": "4.52.3", + "@rollup/rollup-linux-loong64-gnu": "4.52.3", + "@rollup/rollup-linux-ppc64-gnu": "4.52.3", + "@rollup/rollup-linux-riscv64-gnu": "4.52.3", + "@rollup/rollup-linux-riscv64-musl": "4.52.3", + "@rollup/rollup-linux-s390x-gnu": "4.52.3", + "@rollup/rollup-linux-x64-gnu": "4.52.3", + "@rollup/rollup-linux-x64-musl": "4.52.3", + "@rollup/rollup-openharmony-arm64": "4.52.3", + "@rollup/rollup-win32-arm64-msvc": "4.52.3", + "@rollup/rollup-win32-ia32-msvc": "4.52.3", + "@rollup/rollup-win32-x64-gnu": "4.52.3", + "@rollup/rollup-win32-x64-msvc": "4.52.3", "fsevents": "~2.3.2" } }, @@ -7806,6 +7845,54 @@ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", "license": "MIT" }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -7843,12 +7930,6 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, - "node_modules/turbo-stream": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz", - "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==", - "license": "ISC" - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -8054,15 +8135,18 @@ } }, "node_modules/vite": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.2.tgz", - "integrity": "sha512-yW7PeMM+LkDzc7CgJuRLMW2Jz0FxMOsVJ8Lv3gpgW9WLcb9cTW+121UEr1hvmfR7w3SegR5ItvYyzVz1vxNJgQ==", + "version": "6.3.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.6.tgz", + "integrity": "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==", "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", "postcss": "^8.5.3", - "rollup": "^4.30.1" + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" }, "bin": { "vite": "bin/vite.js" @@ -8125,6 +8209,37 @@ } } }, + "node_modules/vite/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", diff --git a/inventory/src/components/product-import/steps/ValidationStepNew/components/AiValidationDialogs.tsx b/inventory/src/components/product-import/steps/ValidationStepNew/components/AiValidationDialogs.tsx index 38f0b55..6426327 100644 --- a/inventory/src/components/product-import/steps/ValidationStepNew/components/AiValidationDialogs.tsx +++ b/inventory/src/components/product-import/steps/ValidationStepNew/components/AiValidationDialogs.tsx @@ -25,6 +25,7 @@ import { } from "../hooks/useAiValidation"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; +import { Protected } from "@/components/auth/Protected"; interface TaxonomyStats { categories: number; @@ -96,7 +97,7 @@ export const AiValidationDialogs: React.FC = ({ getFieldDisplayValueWithHighlight, fields, }) => { - const [costPerMillionTokens, setCostPerMillionTokens] = useState(2.5); // Default cost + const [costPerMillionTokens, setCostPerMillionTokens] = useState(1.25); // Default cost // Create our own state to track changes const [localReversionState, setLocalReversionState] = useState< @@ -170,8 +171,20 @@ export const AiValidationDialogs: React.FC = ({ return (estimatedTokens / 1_000_000) * costPerMillionTokens * 100; // In cents }; + const formatNumber = (value: number | null | undefined): string => { + if (value === null || value === undefined) { + return "โ€”"; + } + return value.toLocaleString(); + }; + // Use the prompt length from the current prompt const promptLength = currentPrompt.prompt ? currentPrompt.prompt.length : 0; + const tokenUsage = aiValidationDetails.tokenUsage; + const formattedReasoningEffort = aiValidationDetails.reasoningEffort + ? aiValidationDetails.reasoningEffort.charAt(0).toUpperCase() + + aiValidationDetails.reasoningEffort.slice(1) + : null; return ( <> @@ -677,39 +690,39 @@ export const AiValidationDialogs: React.FC = ({ aiValidationProgress.estimatedSeconds && aiValidationProgress.elapsedSeconds !== undefined && aiValidationProgress.step > 0 && - aiValidationProgress.step < 5 && ( -
- {(() => { - // Calculate time remaining using the elapsed seconds - const elapsedSeconds = - aiValidationProgress.elapsedSeconds; - const totalEstimatedSeconds = - aiValidationProgress.estimatedSeconds; - const remainingSeconds = Math.max( - 0, - totalEstimatedSeconds - elapsedSeconds - ); + aiValidationProgress.step < 5 && (() => { + const elapsedSeconds = aiValidationProgress.elapsedSeconds; + const totalEstimatedSeconds = aiValidationProgress.estimatedSeconds; + const remainingSeconds = Math.max( + 0, + totalEstimatedSeconds - elapsedSeconds + ); - // Format time remaining - if (remainingSeconds < 60) { - return `Approximately ${Math.round( - remainingSeconds - )} seconds remaining`; - } else { + if (remainingSeconds <= 5) { + return null; + } + + const message = remainingSeconds < 60 + ? `Approximately ${Math.round(remainingSeconds)} seconds remaining` + : (() => { const minutes = Math.floor(remainingSeconds / 60); const seconds = Math.round(remainingSeconds % 60); return `Approximately ${minutes}m ${seconds}s remaining`; - } - })()} - {aiValidationProgress.promptLength && ( -

- Prompt length:{" "} - {aiValidationProgress.promptLength.toLocaleString()}{" "} - characters -

- )} -
- ) + })(); + + return ( +
+ {message} + {aiValidationProgress.promptLength && ( +

+ Prompt length:{" "} + {aiValidationProgress.promptLength.toLocaleString()}{" "} + characters +

+ )} +
+ ); + })() ); })()} @@ -723,13 +736,139 @@ export const AiValidationDialogs: React.FC = ({ setAiValidationDetails((prev) => ({ ...prev, isOpen: open })) } > - + AI Validation Results Review the changes and warnings suggested by the AI + + {(aiValidationDetails.model || tokenUsage || formattedReasoningEffort) && ( +
+
+ {aiValidationDetails.model && ( + + Model ยท {aiValidationDetails.model} + + )} + {formattedReasoningEffort && ( + + Reasoning {formattedReasoningEffort} + + )} +
+ {tokenUsage && ( +
+
+ + Prompt tokens + + + {formatNumber(tokenUsage.prompt)} + +
+
+ + Completion tokens + + + {formatNumber(tokenUsage.completion)} + +
+
+ + Total tokens + + + {formatNumber(tokenUsage.total)} + +
+
+ + Reasoning tokens + + + {formatNumber(tokenUsage.reasoning)} + +
+
+ + Cached prompt tokens + + + {formatNumber(tokenUsage.cachedPrompt)} + +
+
+ )} +
+ )} +
+ {(aiValidationDetails.summary || + (aiValidationDetails.changes && aiValidationDetails.changes.length > 0) || + (aiValidationDetails.warnings && aiValidationDetails.warnings.length > 0) || + (aiValidationDetails.qualityNotes && aiValidationDetails.qualityNotes.length > 0) || + (aiValidationDetails.nextSteps && aiValidationDetails.nextSteps.length > 0)) && ( + + + Overall Assessment + + + {aiValidationDetails.changes && + aiValidationDetails.changes.length > 0 && ( +
+

+ Key Changes +

+
    + {aiValidationDetails.changes.map((change, idx) => ( +
  • {change}
  • + ))} +
+
+ )} + {aiValidationDetails.warnings && + aiValidationDetails.warnings.length > 0 && ( +
+

+ Warnings +

+
    + {aiValidationDetails.warnings.map((warning, idx) => ( +
  • {warning}
  • + ))} +
+
+ )} + {aiValidationDetails.summary && ( +

{aiValidationDetails.summary}

+ )} + {aiValidationDetails.qualityNotes && + aiValidationDetails.qualityNotes.length > 0 && ( +
+

Quality Notes

+
    + {aiValidationDetails.qualityNotes.map((note, idx) => ( +
  • {note}
  • + ))} +
+
+ )} + {aiValidationDetails.nextSteps && + aiValidationDetails.nextSteps.length > 0 && ( +
+

Suggested Next Steps

+
    + {aiValidationDetails.nextSteps.map((step, idx) => ( +
  • {step}
  • + ))} +
+
+ )} +
+
+ )} {aiValidationDetails.changeDetails && aiValidationDetails.changeDetails.length > 0 ? ( @@ -855,23 +994,7 @@ export const AiValidationDialogs: React.FC = ({ ) : (
- {aiValidationDetails.warnings && - aiValidationDetails.warnings.length > 0 ? ( -
-

- No changes were made, but the AI provided some warnings: -

-
    - {aiValidationDetails.warnings.map((warning, i) => ( -
  • - {warning} -
  • - ))} -
-
- ) : ( -

No changes or warnings were suggested by the AI.

- )} +

No field-level changes were suggested by the AI.

)}
diff --git a/inventory/src/components/product-import/steps/ValidationStepNew/hooks/useAiValidation.tsx b/inventory/src/components/product-import/steps/ValidationStepNew/hooks/useAiValidation.tsx index 37e8679..cbb46c2 100644 --- a/inventory/src/components/product-import/steps/ValidationStepNew/hooks/useAiValidation.tsx +++ b/inventory/src/components/product-import/steps/ValidationStepNew/hooks/useAiValidation.tsx @@ -20,12 +20,28 @@ export interface ProductChangeDetail { changes: ChangeDetail[]; } +export type ReasoningEffortLevel = 'minimal' | 'low' | 'medium' | 'high'; + +export interface AiValidationTokenUsage { + prompt: number | null; + completion: number | null; + total: number | null; + reasoning?: number | null; + cachedPrompt?: number | null; +} + export interface AiValidationDetails { changes: string[]; warnings: string[]; changeDetails: ProductChangeDetail[]; isOpen: boolean; - originalData?: RowData[]; + originalData?: RowData[]; + model?: string; + reasoningEffort?: ReasoningEffortLevel; + tokenUsage?: AiValidationTokenUsage; + summary?: string; + qualityNotes?: string[]; + nextSteps?: string[]; } export interface AiValidationProgress { @@ -37,8 +53,119 @@ export interface AiValidationProgress { promptLength?: number; elapsedSeconds?: number; progressPercent?: number; + model?: string; + reasoningEffort?: ReasoningEffortLevel; + tokenUsage?: AiValidationTokenUsage; } +const toNumberOrNull = (value: unknown): number | null => { + if (typeof value === 'number' && Number.isFinite(value)) { + return value; + } + if (typeof value === 'string') { + const parsed = Number(value); + return Number.isFinite(parsed) ? parsed : null; + } + return null; +}; + +const normalizeReasoningEffort = ( + value: unknown +): ReasoningEffortLevel | undefined => { + if (typeof value !== 'string') return undefined; + const normalized = value.toLowerCase(); + if ( + normalized === 'minimal' || + normalized === 'low' || + normalized === 'medium' || + normalized === 'high' + ) { + return normalized as ReasoningEffortLevel; + } + return undefined; +}; + +const isRecord = (value: unknown): value is Record => { + return value !== null && typeof value === 'object' && !Array.isArray(value); +}; + +const valuesAreEqual = (a: unknown, b: unknown): boolean => { + if (a === b) { + // Handle NaN equality manually + return a === a || b === b; + } + + try { + return JSON.stringify(a) === JSON.stringify(b); + } catch { + return false; + } +}; + +const buildChangeDetailsFromData = ( + originalRows: RowData[], + correctedRows: unknown[] +): ProductChangeDetail[] => { + if (!Array.isArray(correctedRows)) { + return []; + } + + const details: ProductChangeDetail[] = []; + const maxLength = Math.max(originalRows.length, correctedRows.length); + + for (let index = 0; index < maxLength; index++) { + const original = (originalRows[index] as Record | undefined) ?? undefined; + const correctedCandidate = correctedRows[index]; + + if (!isRecord(correctedCandidate)) { + continue; + } + + const keys = new Set([ + ...Object.keys(original ?? {}), + ...Object.keys(correctedCandidate), + ]); + + const changes: ChangeDetail[] = []; + + keys.forEach((key) => { + if (key.startsWith('__')) { + return; + } + + const originalValue = original ? original[key] : undefined; + const correctedValue = correctedCandidate[key]; + + if (!valuesAreEqual(originalValue, correctedValue)) { + changes.push({ + field: key, + original: originalValue, + corrected: correctedValue, + }); + } + }); + + if (changes.length === 0) { + continue; + } + + const titleCandidate = + (original && (original.name ?? original.title)) ?? + (correctedCandidate.name ?? correctedCandidate.title); + + details.push({ + productIndex: index, + title: + typeof titleCandidate === 'string' + ? titleCandidate + : `Product ${index + 1}`, + changes, + }); + } + + return details; +}; + export interface CurrentPrompt { isOpen: boolean; prompt: string | null; @@ -374,6 +501,9 @@ export const useAiValidation = ( startTime, elapsedSeconds: 0, progressPercent: 0, + model: undefined, + reasoningEffort: undefined, + tokenUsage: undefined, ...(aiValidationProgress.estimatedSeconds ? { estimatedSeconds: aiValidationProgress.estimatedSeconds, promptLength: aiValidationProgress.promptLength @@ -476,14 +606,25 @@ export const useAiValidation = ( promptLength: prev.promptLength })); - const response = await fetch(`${getApiUrl()}/ai-validation/validate`, { + const requestBody = JSON.stringify({ products: cleanedData }); + const fetchPromise = fetch(`${getApiUrl()}/ai-validation/validate`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify({ products: cleanedData }), + body: requestBody, }); + setAiValidationProgress(prev => ({ + ...prev, + status: "Waiting on AI response...", + step: Math.max(prev.step, 2), + estimatedSeconds: prev.estimatedSeconds, + promptLength: prev.promptLength + })); + + const response = await fetchPromise; + if (!response.ok) { const errorText = await response.text(); console.error('AI validation error response:', { @@ -501,6 +642,111 @@ export const useAiValidation = ( throw new Error(result.error || 'AI validation failed'); } + const usageSource: any = + result.tokenUsage || + result.performanceMetrics?.tokenUsage || + result.usage; + const modelUsed: string | undefined = + typeof result.model === 'string' + ? result.model + : typeof result.modelName === 'string' + ? result.modelName + : typeof result.modelId === 'string' + ? result.modelId + : typeof result.performanceMetrics?.model === 'string' + ? result.performanceMetrics.model + : undefined; + const reasoningEffort = normalizeReasoningEffort( + result.reasoningEffort || + result.reasoning_effort || + result.performanceMetrics?.reasoningEffort + ); + + let normalizedTokenUsage: AiValidationTokenUsage | undefined; + if (usageSource) { + const promptTokensRaw = + usageSource.prompt ?? + usageSource.promptTokens ?? + usageSource.prompt_tokens ?? + usageSource.input; + const completionTokensRaw = + usageSource.completion ?? + usageSource.completionTokens ?? + usageSource.completion_tokens ?? + usageSource.output; + const totalTokensRaw = + usageSource.total ?? + usageSource.totalTokens ?? + usageSource.total_tokens ?? + usageSource.tokenTotal; + const reasoningTokensRaw = + usageSource.reasoning ?? + usageSource.reasoningTokens ?? + usageSource.reasoning_tokens; + const cachedPromptRaw = + usageSource.cachedPrompt ?? + usageSource.cachedTokens ?? + usageSource.cached_prompt ?? + usageSource.cached_tokens ?? + usageSource.cached; + + const promptTokens = toNumberOrNull(promptTokensRaw); + const completionTokens = toNumberOrNull(completionTokensRaw); + let totalTokens = toNumberOrNull(totalTokensRaw); + if ( + totalTokens === null && + promptTokens !== null && + completionTokens !== null + ) { + totalTokens = promptTokens + completionTokens; + } + + const reasoningTokens = toNumberOrNull(reasoningTokensRaw); + const cachedPrompt = toNumberOrNull(cachedPromptRaw); + + if ( + promptTokens !== null || + completionTokens !== null || + totalTokens !== null || + reasoningTokens !== null || + cachedPrompt !== null + ) { + normalizedTokenUsage = { + prompt: promptTokens, + completion: completionTokens, + total: totalTokens, + reasoning: reasoningTokens, + cachedPrompt, + }; + } + } + + const changesFromResult = Array.isArray(result.changes) + ? result.changes + : []; + const warningsFromResult = Array.isArray(result.warnings) + ? result.warnings + : []; + const summaryFromResult = typeof result.summary === 'string' + ? result.summary + : typeof result.overallSummary === 'string' + ? result.overallSummary + : ''; + const qualityNotesFromResult = Array.isArray(result.qualityNotes) + ? result.qualityNotes + : Array.isArray(result.quality_notes) + ? result.quality_notes + : Array.isArray(result.qualitySummary) + ? result.qualitySummary + : []; + const nextStepsFromResult = Array.isArray(result.nextSteps) + ? result.nextSteps + : Array.isArray(result.recommendedNextSteps) + ? result.recommendedNextSteps + : Array.isArray(result.followUpActions) + ? result.followUpActions + : []; + // Store the prompt sources if they exist if (result.promptSources) { setCurrentPrompt(prev => { @@ -534,14 +780,23 @@ export const useAiValidation = ( result.performanceMetrics.processingTimeSeconds || prev.estimatedSeconds, promptLength: result.performanceMetrics.promptLength || prev.promptLength, - progressPercent: 75 // 75% complete when we're processing the AI response + progressPercent: 75, // 75% complete when we're processing the AI response + model: modelUsed ?? prev.model, + reasoningEffort: reasoningEffort ?? prev.reasoningEffort, + tokenUsage: + normalizedTokenUsage ?? + result.performanceMetrics.tokenUsage ?? + prev.tokenUsage, })); } else { setAiValidationProgress(prev => ({ ...prev, status: "Processing AI response...", step: 3, - progressPercent: 75 + progressPercent: 75, + model: modelUsed ?? prev.model, + reasoningEffort: reasoningEffort ?? prev.reasoningEffort, + tokenUsage: normalizedTokenUsage ?? prev.tokenUsage, })); } @@ -616,13 +871,15 @@ export const useAiValidation = ( return processed; }); - + console.log('About to update data with AI corrections:', { originalDataSample: data.slice(0, 2), processedDataSample: processedData.slice(0, 2), - correctionCount: result.changes?.length || 0 + correctionCount: result.changes?.length || 0, + changeDetailCount: 0, + changeDetailsPreview: [] }); - + // First validate the new data to ensure all validation rules are applied try { // Validate the data with the hooks @@ -644,27 +901,63 @@ export const useAiValidation = ( hasErrors: false // We no longer check row.__errors }); - // Show changes and warnings in dialog after data is updated - setAiValidationDetails({ - changes: result.changes || [], - warnings: result.warnings || [], - changeDetails: result.changeDetails || [], - isOpen: true, - originalData: originalDataCopy // Use the stored original data + const changeDetailsForDialog = buildChangeDetailsFromData( + originalDataCopy, + validatedData as RowData[] + ); + + console.log('Computed change details after validation:', { + changeDetailCount: changeDetailsForDialog.length, + changeDetailsPreview: changeDetailsForDialog.slice(0, 2) }); + + const detailPayload: AiValidationDetails = { + changes: changesFromResult, + warnings: warningsFromResult, + changeDetails: changeDetailsForDialog, + isOpen: true, + originalData: originalDataCopy, + model: modelUsed, + reasoningEffort, + tokenUsage: normalizedTokenUsage, + summary: summaryFromResult, + qualityNotes: qualityNotesFromResult, + nextSteps: nextStepsFromResult, + }; + + // Show changes and warnings in dialog after data is updated + setAiValidationDetails(detailPayload); } catch (error) { console.error('Error validating AI corrections:', error); // Fall back to basic update without validation setData(processedData); - // Still show the result dialog even if validation failed - setAiValidationDetails({ - changes: result.changes || [], - warnings: result.warnings || [], - changeDetails: result.changeDetails || [], - isOpen: true, - originalData: originalDataCopy, // Use the stored original data + const changeDetailsForDialog = buildChangeDetailsFromData( + originalDataCopy, + processedData as RowData[] + ); + + console.log('Computed change details after fallback:', { + changeDetailCount: changeDetailsForDialog.length, + changeDetailsPreview: changeDetailsForDialog.slice(0, 2) }); + + const detailPayload: AiValidationDetails = { + changes: changesFromResult, + warnings: warningsFromResult, + changeDetails: changeDetailsForDialog, + isOpen: true, + originalData: originalDataCopy, + model: modelUsed, + reasoningEffort, + tokenUsage: normalizedTokenUsage, + summary: summaryFromResult, + qualityNotes: qualityNotesFromResult, + nextSteps: nextStepsFromResult, + }; + + // Still show the result dialog even if validation failed + setAiValidationDetails(detailPayload); } } @@ -673,7 +966,10 @@ export const useAiValidation = ( status: "Validation complete!", step: 5, estimatedSeconds: prev.estimatedSeconds, - promptLength: prev.promptLength + promptLength: prev.promptLength, + model: modelUsed ?? prev.model, + reasoningEffort: reasoningEffort ?? prev.reasoningEffort, + tokenUsage: normalizedTokenUsage ?? prev.tokenUsage, })); setTimeout(() => { diff --git a/inventory/src/components/product-import/steps/ValidationStepNew/hooks/useValidationState.tsx b/inventory/src/components/product-import/steps/ValidationStepNew/hooks/useValidationState.tsx index 3a0eaa2..2492b44 100644 --- a/inventory/src/components/product-import/steps/ValidationStepNew/hooks/useValidationState.tsx +++ b/inventory/src/components/product-import/steps/ValidationStepNew/hooks/useValidationState.tsx @@ -294,12 +294,12 @@ export const useValidationState = ({ // Track initialization task statuses for the progress UI const initializationTasks = { upcValidation: { - label: 'Validating UPCs and generating item numbers', + label: 'Generating item numbers', status: upcValidation.initialValidationDone ? 'completed' : hasPendingUpcValidation ? 'in_progress' : 'pending' }, fieldValidation: { - label: 'Validating field requirements and formats', + label: 'Checking for field errors', status: initialValidationComplete ? 'completed' : isInitialValidationRunning ? 'in_progress' : 'pending' },