From 8b15f9815e888f0152ea96aafeb789096bf0111a Mon Sep 17 00:00:00 2001 From: Usama Date: Wed, 15 Oct 2025 15:28:51 +0000 Subject: [PATCH 1/8] - biome rules updated - package json rules added for biome --- echo/frontend/biome.json | 54 +++++++++++++++++++++++++++++--------- echo/frontend/package.json | 12 ++++++--- 2 files changed, 50 insertions(+), 16 deletions(-) diff --git a/echo/frontend/biome.json b/echo/frontend/biome.json index 30da9ce0..3a4839b7 100644 --- a/echo/frontend/biome.json +++ b/echo/frontend/biome.json @@ -1,23 +1,56 @@ { "$schema": "https://biomejs.dev/schemas/2.2.4/schema.json", - "vcs": { - "enabled": false, - "clientKind": "git", - "useIgnoreFile": false + "assist": { + "enabled": true, + "actions": { + "source": { + "useSortedKeys": "off", + "organizeImports": "on" + } + } + }, + "css": { + "linter": { + "enabled": true + } }, "files": { "ignoreUnknown": false }, "formatter": { - "enabled": true, "indentStyle": "tab" }, + "json": { + "formatter": { + "indentStyle": "tab" + } + }, "linter": { "enabled": true, "rules": { "recommended": true, + "style": { + "noParameterAssign": "error", + "useDefaultParameterLast": "error", + "useSingleVarDeclarator": "error", + "useConst": "error", + "noInferrableTypes": "error", + "useSelfClosingElements": "error", + "useNumberNamespace": "error", + "useAsConstAssertion": "error", + "noUnusedTemplateLiteral": "error", + "useTemplate": "error", + "useEnumInitializers": "error" + }, "correctness": { + "noUndeclaredDependencies": "error", + "noUnusedVariables": "error", "useUniqueElementIds": "off" + }, + "suspicious": { + "noVar": "on", + "noConsole": "warn", + "noDebugger": "error" } } }, @@ -26,12 +59,9 @@ "quoteStyle": "double" } }, - "assist": { + "vcs": { "enabled": true, - "actions": { - "source": { - "organizeImports": "on" - } - } + "clientKind": "git", + "useIgnoreFile": true } -} +} \ No newline at end of file diff --git a/echo/frontend/package.json b/echo/frontend/package.json index 84375eb6..f6152c40 100644 --- a/echo/frontend/package.json +++ b/echo/frontend/package.json @@ -8,10 +8,14 @@ "participant:dev": "VITE_DISABLE_SENTRY=1 VITE_USE_PARTICIPANT_ROUTER=1 vite --port 5174 --host 0.0.0.0", "messages:extract": "lingui extract", "messages:compile": "lingui compile --typescript", - "build": "tsc && vite build", - "participant:build": "tsc && VITE_API_BASE_URL=http://localhost:8000/api VITE_USE_PARTICIPANT_ROUTER=1 vite build", - "format": "prettier --write .", - "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "build": "biome check . && tsc && vite build", + "participant:build": "biome check . && tsc && VITE_API_BASE_URL=http://localhost:8000/api VITE_USE_PARTICIPANT_ROUTER=1 vite build", + "format": "biome format --write .", + "format:check": "biome format .", + "lint": "biome lint .", + "lint:fix": "biome lint --write .", + "check": "biome check .", + "fix": "biome check --write .", "preview": "vite preview" }, "dependencies": { From 8d70d1b9ac84dfda5603f656717a576ea959ff48 Mon Sep 17 00:00:00 2001 From: Usama Date: Thu, 16 Oct 2025 09:33:51 +0000 Subject: [PATCH 2/8] - sorted keys enabled --- echo/frontend/biome.json | 52 ++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/echo/frontend/biome.json b/echo/frontend/biome.json index 3a4839b7..3a07d8fd 100644 --- a/echo/frontend/biome.json +++ b/echo/frontend/biome.json @@ -1,13 +1,13 @@ { "$schema": "https://biomejs.dev/schemas/2.2.4/schema.json", "assist": { - "enabled": true, "actions": { "source": { - "useSortedKeys": "off", - "organizeImports": "on" + "organizeImports": "on", + "useSortedKeys": "on" } - } + }, + "enabled": true }, "css": { "linter": { @@ -20,6 +20,11 @@ "formatter": { "indentStyle": "tab" }, + "javascript": { + "formatter": { + "quoteStyle": "double" + } + }, "json": { "formatter": { "indentStyle": "tab" @@ -28,40 +33,35 @@ "linter": { "enabled": true, "rules": { + "correctness": { + "noUndeclaredDependencies": "error", + "noUnusedVariables": "error", + "useUniqueElementIds": "off" + }, "recommended": true, "style": { + "noInferrableTypes": "error", "noParameterAssign": "error", - "useDefaultParameterLast": "error", - "useSingleVarDeclarator": "error", + "noUnusedTemplateLiteral": "error", + "useAsConstAssertion": "error", "useConst": "error", - "noInferrableTypes": "error", - "useSelfClosingElements": "error", + "useDefaultParameterLast": "error", + "useEnumInitializers": "error", "useNumberNamespace": "error", - "useAsConstAssertion": "error", - "noUnusedTemplateLiteral": "error", - "useTemplate": "error", - "useEnumInitializers": "error" - }, - "correctness": { - "noUndeclaredDependencies": "error", - "noUnusedVariables": "error", - "useUniqueElementIds": "off" + "useSelfClosingElements": "error", + "useSingleVarDeclarator": "error", + "useTemplate": "error" }, "suspicious": { - "noVar": "on", "noConsole": "warn", - "noDebugger": "error" + "noDebugger": "error", + "noVar": "on" } } }, - "javascript": { - "formatter": { - "quoteStyle": "double" - } - }, "vcs": { - "enabled": true, "clientKind": "git", + "enabled": true, "useIgnoreFile": true } -} \ No newline at end of file +} From 20c5badd3e9d60239307b4bfd050edff07a93639 Mon Sep 17 00:00:00 2001 From: Usama Date: Thu, 16 Oct 2025 10:58:19 +0000 Subject: [PATCH 3/8] - rules updated --- echo/frontend/biome.json | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/echo/frontend/biome.json b/echo/frontend/biome.json index 3a07d8fd..16fea7d8 100644 --- a/echo/frontend/biome.json +++ b/echo/frontend/biome.json @@ -38,12 +38,13 @@ "noUnusedVariables": "error", "useUniqueElementIds": "off" }, - "recommended": true, "style": { "noInferrableTypes": "error", "noParameterAssign": "error", "noUnusedTemplateLiteral": "error", + "useArrayLiterals": "error", "useAsConstAssertion": "error", + "useCollapsedElseIf": "error", "useConst": "error", "useDefaultParameterLast": "error", "useEnumInitializers": "error", @@ -53,9 +54,9 @@ "useTemplate": "error" }, "suspicious": { - "noConsole": "warn", - "noDebugger": "error", - "noVar": "on" + "noAlert": "warn", + "noCatchAssign": "error", + "noVar": "error" } } }, From b9aa4012f968c84ff15d10902c69ec178fc5c143 Mon Sep 17 00:00:00 2001 From: Usama Date: Thu, 16 Oct 2025 12:16:45 +0000 Subject: [PATCH 4/8] - ignore files node_modules, dist, package.json, pnpm-lock.yaml files - remove eslint and prettier --- echo/frontend/.eslintrc | 22 - echo/frontend/.prettierrc | 3 - echo/frontend/biome.json | 3 +- echo/frontend/package.json | 8 - echo/frontend/pnpm-lock.yaml | 844 ----------------------------------- 5 files changed, 2 insertions(+), 878 deletions(-) delete mode 100644 echo/frontend/.eslintrc delete mode 100644 echo/frontend/.prettierrc diff --git a/echo/frontend/.eslintrc b/echo/frontend/.eslintrc deleted file mode 100644 index f05f4e45..00000000 --- a/echo/frontend/.eslintrc +++ /dev/null @@ -1,22 +0,0 @@ -// yes, i am aware that this is a deprecated eslintrc format -{ - "root": true, - "env": { "browser": true, "es2020": true }, - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended", - "plugin:react-hooks/recommended" - ], - "ignorePatterns": ["dist", ".eslintrc.cjs"], - "parser": "@typescript-eslint/parser", - "plugins": ["react-refresh", "eslint-plugin-react-compiler"], - "rules": { - "@typescript-eslint/no-explicit-any": "warn", - "@typescript-eslint/no-unused-vars": "warn", - "react-compiler/react-compiler": "warn", - "react-refresh/only-export-components": [ - "warn", - { "allowConstantExport": true } - ] - } -} diff --git a/echo/frontend/.prettierrc b/echo/frontend/.prettierrc deleted file mode 100644 index b4bfed35..00000000 --- a/echo/frontend/.prettierrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "plugins": ["prettier-plugin-tailwindcss"] -} diff --git a/echo/frontend/biome.json b/echo/frontend/biome.json index 16fea7d8..7c53fa1b 100644 --- a/echo/frontend/biome.json +++ b/echo/frontend/biome.json @@ -15,7 +15,8 @@ } }, "files": { - "ignoreUnknown": false + "ignoreUnknown": false, + "includes": ["**/*", "!**/dist", "!**/node_modules", "!**/package-lock.json", "!**/package.json"] }, "formatter": { "indentStyle": "tab" diff --git a/echo/frontend/package.json b/echo/frontend/package.json index f6152c40..b4275cb2 100644 --- a/echo/frontend/package.json +++ b/echo/frontend/package.json @@ -63,7 +63,6 @@ "next-themes": "^0.4.6", "notifications\n": "link:@mantine/notifications\n", "plausible-tracker": "^0.3.9", - "prettier-plugin-tailwindcss": "^0.6.14", "re-resizable": "^6.11.2", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -100,20 +99,13 @@ "@types/react": "^19.0.12", "@types/react-dom": "^19.0.4", "@types/showdown": "^2.0.6", - "@typescript-eslint/eslint-plugin": "^8.28.0", - "@typescript-eslint/parser": "^8.28.0", "@vitejs/plugin-react": "^4.7.0", "autoprefixer": "^10.4.21", "babel-plugin-react-compiler": "19.0.0-beta-37ed2a7-20241206", "debug": "^4.4.0", - "eslint": "~9.16.0", - "eslint-plugin-react-compiler": "19.0.0-beta-37ed2a7-20241206", - "eslint-plugin-react-hooks": "^5.2.0", - "eslint-plugin-react-refresh": "^0.4.19", "postcss": "^8.5.3", "postcss-preset-mantine": "^1.17.0", "postcss-simple-vars": "^7.0.1", - "prettier": "^3.5.3", "tailwindcss": "^3.4.17", "typescript": "^5.8.2", "vite": "^6.3.5" diff --git a/echo/frontend/pnpm-lock.yaml b/echo/frontend/pnpm-lock.yaml index 40478b9e..60c66125 100644 --- a/echo/frontend/pnpm-lock.yaml +++ b/echo/frontend/pnpm-lock.yaml @@ -145,9 +145,6 @@ importers: plausible-tracker: specifier: ^0.3.9 version: 0.3.9 - prettier-plugin-tailwindcss: - specifier: ^0.6.14 - version: 0.6.14(prettier@3.5.3) re-resizable: specifier: ^6.11.2 version: 6.11.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -251,12 +248,6 @@ importers: '@types/showdown': specifier: ^2.0.6 version: 2.0.6 - '@typescript-eslint/eslint-plugin': - specifier: ^8.28.0 - version: 8.28.0(@typescript-eslint/parser@8.28.0(eslint@9.16.0(jiti@1.21.7))(typescript@5.8.2))(eslint@9.16.0(jiti@1.21.7))(typescript@5.8.2) - '@typescript-eslint/parser': - specifier: ^8.28.0 - version: 8.28.0(eslint@9.16.0(jiti@1.21.7))(typescript@5.8.2) '@vitejs/plugin-react': specifier: ^4.7.0 version: 4.7.0(vite@6.3.5(@types/node@22.13.14)(jiti@1.21.7)(sugarss@4.0.1(postcss@8.5.3))(yaml@2.7.0)) @@ -269,18 +260,6 @@ importers: debug: specifier: ^4.4.0 version: 4.4.0 - eslint: - specifier: ~9.16.0 - version: 9.16.0(jiti@1.21.7) - eslint-plugin-react-compiler: - specifier: 19.0.0-beta-37ed2a7-20241206 - version: 19.0.0-beta-37ed2a7-20241206(eslint@9.16.0(jiti@1.21.7)) - eslint-plugin-react-hooks: - specifier: ^5.2.0 - version: 5.2.0(eslint@9.16.0(jiti@1.21.7)) - eslint-plugin-react-refresh: - specifier: ^0.4.19 - version: 0.4.19(eslint@9.16.0(jiti@1.21.7)) postcss: specifier: ^8.5.3 version: 8.5.3 @@ -290,9 +269,6 @@ importers: postcss-simple-vars: specifier: ^7.0.1 version: 7.0.1(postcss@8.5.3) - prettier: - specifier: ^3.5.3 - version: 3.5.3 tailwindcss: specifier: ^3.4.17 version: 3.4.17 @@ -371,10 +347,6 @@ packages: resolution: {integrity: sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==} engines: {node: '>=6.9.0'} - '@babel/helper-annotate-as-pure@7.25.9': - resolution: {integrity: sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==} - engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.27.0': resolution: {integrity: sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==} engines: {node: '>=6.9.0'} @@ -383,20 +355,10 @@ packages: resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} engines: {node: '>=6.9.0'} - '@babel/helper-create-class-features-plugin@7.27.0': - resolution: {integrity: sha512-vSGCvMecvFCd/BdpGlhpXYNhhC4ccxyvQWpbGL4CWbvfEoLFWUZuSuf7s9Aw70flgQF+6vptvgK2IfOnKlRmBg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - '@babel/helper-globals@7.28.0': resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} engines: {node: '>=6.9.0'} - '@babel/helper-member-expression-to-functions@7.25.9': - resolution: {integrity: sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==} - engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.25.9': resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} engines: {node: '>=6.9.0'} @@ -417,28 +379,10 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-optimise-call-expression@7.25.9': - resolution: {integrity: sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==} - engines: {node: '>=6.9.0'} - - '@babel/helper-plugin-utils@7.26.5': - resolution: {integrity: sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==} - engines: {node: '>=6.9.0'} - '@babel/helper-plugin-utils@7.27.1': resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} engines: {node: '>=6.9.0'} - '@babel/helper-replace-supers@7.26.5': - resolution: {integrity: sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-skip-transparent-expression-wrappers@7.25.9': - resolution: {integrity: sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==} - engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.25.9': resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} engines: {node: '>=6.9.0'} @@ -481,13 +425,6 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - '@babel/plugin-proposal-private-methods@7.18.6': - resolution: {integrity: sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==} - engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead. - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-jsx-self@7.27.1': resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} engines: {node: '>=6.9.0'} @@ -1160,44 +1097,6 @@ packages: cpu: [x64] os: [win32] - '@eslint-community/eslint-utils@4.5.1': - resolution: {integrity: sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - - '@eslint-community/regexpp@4.12.1': - resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - - '@eslint/config-array@0.19.2': - resolution: {integrity: sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/core@0.12.0': - resolution: {integrity: sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/core@0.9.1': - resolution: {integrity: sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/eslintrc@3.3.1': - resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/js@9.16.0': - resolution: {integrity: sha512-tw2HxzQkrbeuvyj1tG2Yqq+0H9wGoI2IMk4EOsQeX+vmd75FtJAzf+gTA69WF+baUKRYQ3x2kbLE08js5OsTVg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/object-schema@2.1.6': - resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/plugin-kit@0.2.7': - resolution: {integrity: sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@floating-ui/core@1.6.9': resolution: {integrity: sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==} @@ -1251,26 +1150,6 @@ packages: peerDependencies: react-hook-form: ^7.0.0 - '@humanfs/core@0.19.1': - resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} - engines: {node: '>=18.18.0'} - - '@humanfs/node@0.16.6': - resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} - engines: {node: '>=18.18.0'} - - '@humanwhocodes/module-importer@1.0.1': - resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} - engines: {node: '>=12.22'} - - '@humanwhocodes/retry@0.3.1': - resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} - engines: {node: '>=18.18'} - - '@humanwhocodes/retry@0.4.2': - resolution: {integrity: sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==} - engines: {node: '>=18.18'} - '@isaacs/balanced-match@4.0.1': resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} engines: {node: 20 || >=22} @@ -2423,9 +2302,6 @@ packages: '@types/js-cookie@3.0.6': resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==} - '@types/json-schema@7.0.15': - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/lodash@4.17.20': resolution: {integrity: sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==} @@ -2464,53 +2340,6 @@ packages: '@types/yargs@17.0.33': resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} - '@typescript-eslint/eslint-plugin@8.28.0': - resolution: {integrity: sha512-lvFK3TCGAHsItNdWZ/1FkvpzCxTHUVuFrdnOGLMa0GGCFIbCgQWVk3CzCGdA7kM3qGVc+dfW9tr0Z/sHnGDFyg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' - - '@typescript-eslint/parser@8.28.0': - resolution: {integrity: sha512-LPcw1yHD3ToaDEoljFEfQ9j2xShY367h7FZ1sq5NJT9I3yj4LHer1Xd1yRSOdYy9BpsrxU7R+eoDokChYM53lQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' - - '@typescript-eslint/scope-manager@8.28.0': - resolution: {integrity: sha512-u2oITX3BJwzWCapoZ/pXw6BCOl8rJP4Ij/3wPoGvY8XwvXflOzd1kLrDUUUAIEdJSFh+ASwdTHqtan9xSg8buw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/type-utils@8.28.0': - resolution: {integrity: sha512-oRoXu2v0Rsy/VoOGhtWrOKDiIehvI+YNrDk5Oqj40Mwm0Yt01FC/Q7nFqg088d3yAsR1ZcZFVfPCTTFCe/KPwg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' - - '@typescript-eslint/types@8.28.0': - resolution: {integrity: sha512-bn4WS1bkKEjx7HqiwG2JNB3YJdC1q6Ue7GyGlwPHyt0TnVq6TtD/hiOdTZt71sq0s7UzqBFXD8t8o2e63tXgwA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/typescript-estree@8.28.0': - resolution: {integrity: sha512-H74nHEeBGeklctAVUvmDkxB1mk+PAZ9FiOMPFncdqeRBXxk1lWSYraHw8V12b7aa6Sg9HOBNbGdSHobBPuQSuA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <5.9.0' - - '@typescript-eslint/utils@8.28.0': - resolution: {integrity: sha512-OELa9hbTYciYITqgurT1u/SzpQVtDLmQMFzy/N8pQE+tefOyCWT79jHsav294aTqV1q1u+VzqDGbuujvRYaeSQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' - - '@typescript-eslint/visitor-keys@8.28.0': - resolution: {integrity: sha512-hbn8SZ8w4u2pRwgQ1GlUrPKE+t2XvcCW5tTRF7j6SMYIuYG37XuzIW44JCZPa36evi0Oy2SnM664BlIaAuQcvg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} @@ -2548,9 +2377,6 @@ packages: react: optional: true - ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - anser@2.3.2: resolution: {integrity: sha512-PMqBCBvrOVDRqLGooQb+z+t1Q0PiPyurUQeZRR5uHBOVZcW8B04KMmnT12USnhpNX2wCPagWzLVppQMUG3u0Dw==} @@ -2638,9 +2464,6 @@ packages: bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} - brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} - brace-expansion@2.0.1: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} @@ -2813,9 +2636,6 @@ packages: compute-scroll-into-view@2.0.4: resolution: {integrity: sha512-y/ZA3BGnxoM/QHHQ2Uy49CLtnWPbt4tTPpEEZiEmmiWBFKjej7nEyH8Ryz54jH0MLXflUYA3Er2zUxPSJu5R+g==} - concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -2931,9 +2751,6 @@ packages: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} - deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - defaults@1.0.4: resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} @@ -3068,83 +2885,20 @@ packages: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} - escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - escape-string-regexp@5.0.0: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} - eslint-plugin-react-compiler@19.0.0-beta-37ed2a7-20241206: - resolution: {integrity: sha512-5Pex1fUCJwLwwqEJe6NkgTn45kUjjj9TZP6IrW4IcpWM/YaEe+QvcOeF60huDjBq0kz1svGeW2nw8WdY+qszAw==} - engines: {node: ^14.17.0 || ^16.0.0 || >= 18.0.0} - peerDependencies: - eslint: '>=7' - - eslint-plugin-react-hooks@5.2.0: - resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==} - engines: {node: '>=10'} - peerDependencies: - eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 - - eslint-plugin-react-refresh@0.4.19: - resolution: {integrity: sha512-eyy8pcr/YxSYjBoqIFSrlbn9i/xvxUFa8CjzAYo9cFjgGXqq1hyjihcpZvxRLalpaWmueWR81xn7vuKmAFijDQ==} - peerDependencies: - eslint: '>=8.40' - - eslint-scope@8.3.0: - resolution: {integrity: sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - eslint-visitor-keys@3.4.3: - resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - eslint-visitor-keys@4.2.0: - resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - eslint@9.16.0: - resolution: {integrity: sha512-whp8mSQI4C8VXd+fLgSM0lh3UlmcFtVwUQjyKCFfsp+2ItAIYhlq/hqGahGqHE6cv9unM41VlqKk2VtKYR2TaA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - hasBin: true - peerDependencies: - jiti: '*' - peerDependenciesMeta: - jiti: - optional: true - esniff@2.0.1: resolution: {integrity: sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==} engines: {node: '>=0.10'} - espree@10.3.0: - resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - esquery@1.6.0: - resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} - engines: {node: '>=0.10'} - - esrecurse@4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} - - estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} - estree-util-is-identifier-name@3.0.0: resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} estree-util-visit@2.0.0: resolution: {integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==} - esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} - event-emitter@0.3.5: resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==} @@ -3180,12 +2934,6 @@ packages: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} - fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - - fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fastq@1.19.1: resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} @@ -3204,10 +2952,6 @@ packages: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} engines: {node: '>=8'} - file-entry-cache@8.0.0: - resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} - engines: {node: '>=16.0.0'} - fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -3216,17 +2960,6 @@ packages: resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} engines: {node: '>=6'} - find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} - - flat-cache@4.0.1: - resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} - engines: {node: '>=16'} - - flatted@3.3.3: - resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} - follow-redirects@1.15.9: resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} engines: {node: '>=4.0'} @@ -3324,17 +3057,10 @@ packages: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} - globals@14.0.0: - resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} - engines: {node: '>=18'} - gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} - graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -3360,12 +3086,6 @@ packages: hast-util-whitespace@3.0.0: resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} - hermes-estree@0.25.1: - resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} - - hermes-parser@0.25.1: - resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} - hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} @@ -3391,18 +3111,10 @@ packages: ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - ignore@5.3.2: - resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} - engines: {node: '>= 4'} - import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} - imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} - inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -3531,21 +3243,12 @@ packages: engines: {node: '>=6'} hasBin: true - json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - json-schema@0.4.0: resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} - json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -3556,17 +3259,10 @@ packages: engines: {node: ^18.0.0 || >=20.0.0} hasBin: true - keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - leven@3.1.0: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} - levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} - lexical@0.32.1: resolution: {integrity: sha512-Rvr9p00zUwzjXIqElIjMDyl/24QHw68yaqmXUWIT3lSdSAr8OpjSJK3iWBLZwVZwwpVhwShZRckomc+3vSb/zw==} @@ -3589,10 +3285,6 @@ packages: resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} engines: {node: '>=6'} - locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} - lodash.castarray@4.4.0: resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==} @@ -3877,9 +3569,6 @@ packages: resolution: {integrity: sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==} engines: {node: 20 || >=22} - minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - minimatch@9.0.5: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} @@ -3934,9 +3623,6 @@ packages: napi-build-utils@2.0.0: resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} - natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - next-themes@0.4.6: resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==} peerDependencies: @@ -3982,10 +3668,6 @@ packages: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} - optionator@0.9.4: - resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} - engines: {node: '>= 0.8.0'} - ora@5.4.1: resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} engines: {node: '>=10'} @@ -4001,18 +3683,10 @@ packages: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} - p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} - p-locate@3.0.0: resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} engines: {node: '>=6'} - p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} - p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} @@ -4044,10 +3718,6 @@ packages: resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} engines: {node: '>=4'} - path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} - path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -4175,76 +3845,6 @@ packages: engines: {node: '>=10'} hasBin: true - prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} - - prettier-plugin-tailwindcss@0.6.14: - resolution: {integrity: sha512-pi2e/+ZygeIqntN+vC573BcW5Cve8zUB0SSAGxqpB4f96boZF4M3phPVoOFCeypwkpRYdi7+jQ5YJJUwrkGUAg==} - engines: {node: '>=14.21.3'} - peerDependencies: - '@ianvs/prettier-plugin-sort-imports': '*' - '@prettier/plugin-hermes': '*' - '@prettier/plugin-oxc': '*' - '@prettier/plugin-pug': '*' - '@shopify/prettier-plugin-liquid': '*' - '@trivago/prettier-plugin-sort-imports': '*' - '@zackad/prettier-plugin-twig': '*' - prettier: ^3.0 - prettier-plugin-astro: '*' - prettier-plugin-css-order: '*' - prettier-plugin-import-sort: '*' - prettier-plugin-jsdoc: '*' - prettier-plugin-marko: '*' - prettier-plugin-multiline-arrays: '*' - prettier-plugin-organize-attributes: '*' - prettier-plugin-organize-imports: '*' - prettier-plugin-sort-imports: '*' - prettier-plugin-style-order: '*' - prettier-plugin-svelte: '*' - peerDependenciesMeta: - '@ianvs/prettier-plugin-sort-imports': - optional: true - '@prettier/plugin-hermes': - optional: true - '@prettier/plugin-oxc': - optional: true - '@prettier/plugin-pug': - optional: true - '@shopify/prettier-plugin-liquid': - optional: true - '@trivago/prettier-plugin-sort-imports': - optional: true - '@zackad/prettier-plugin-twig': - optional: true - prettier-plugin-astro: - optional: true - prettier-plugin-css-order: - optional: true - prettier-plugin-import-sort: - optional: true - prettier-plugin-jsdoc: - optional: true - prettier-plugin-marko: - optional: true - prettier-plugin-multiline-arrays: - optional: true - prettier-plugin-organize-attributes: - optional: true - prettier-plugin-organize-imports: - optional: true - prettier-plugin-sort-imports: - optional: true - prettier-plugin-style-order: - optional: true - prettier-plugin-svelte: - optional: true - - prettier@3.5.3: - resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==} - engines: {node: '>=14'} - hasBin: true - pretty-format@29.7.0: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -4552,11 +4152,6 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - semver@7.7.1: - resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} - engines: {node: '>=10'} - hasBin: true - semver@7.7.2: resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} engines: {node: '>=10'} @@ -4638,10 +4233,6 @@ packages: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} - strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - style-mod@4.1.2: resolution: {integrity: sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==} @@ -4742,12 +4333,6 @@ packages: trough@2.2.0: resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} - ts-api-utils@2.1.0: - resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} - engines: {node: '>=18.12'} - peerDependencies: - typescript: '>=4.8.4' - ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} @@ -4760,10 +4345,6 @@ packages: tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} - type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} - type-fest@0.21.3: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} @@ -4822,9 +4403,6 @@ packages: peerDependencies: browserslist: '>= 4.21.0' - uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - use-callback-ref@1.3.3: resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} engines: {node: '>=10'} @@ -4972,10 +4550,6 @@ packages: engines: {node: '>= 8'} hasBin: true - word-wrap@1.2.5: - resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} - engines: {node: '>=0.10.0'} - wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -5003,10 +4577,6 @@ packages: resolution: {integrity: sha512-+mJxdbmitioqqsql1Zro4dqT3t9HgmW4dxlPtkcsKFJhXSAMyk3lwawhQFxZjj2upJXzhrTUDsaDkZgJWnv3NA==} engines: {node: '>=16.0.0', npm: '>=8.0.0'} - yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - yoga-layout@3.2.1: resolution: {integrity: sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==} @@ -5015,12 +4585,6 @@ packages: peerDependencies: zod: ^3.24.1 - zod-validation-error@3.4.0: - resolution: {integrity: sha512-ZOPR9SVY6Pb2qqO5XHt+MkkTRxGXb4EVtnjc9JpXUOtUB1T9Ru7mZOT361AN3MsetVe7R0a1KZshJDZdgp9miQ==} - engines: {node: '>=18.0.0'} - peerDependencies: - zod: ^3.18.0 - zod@3.24.2: resolution: {integrity: sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==} @@ -5136,10 +4700,6 @@ snapshots: '@jridgewell/trace-mapping': 0.3.29 jsesc: 3.1.0 - '@babel/helper-annotate-as-pure@7.25.9': - dependencies: - '@babel/types': 7.28.1 - '@babel/helper-compilation-targets@7.27.0': dependencies: '@babel/compat-data': 7.26.8 @@ -5156,28 +4716,8 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.27.0(@babel/core@7.26.10)': - dependencies: - '@babel/core': 7.26.10 - '@babel/helper-annotate-as-pure': 7.25.9 - '@babel/helper-member-expression-to-functions': 7.25.9 - '@babel/helper-optimise-call-expression': 7.25.9 - '@babel/helper-replace-supers': 7.26.5(@babel/core@7.26.10) - '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 - '@babel/traverse': 7.27.0 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - '@babel/helper-globals@7.28.0': {} - '@babel/helper-member-expression-to-functions@7.25.9': - dependencies: - '@babel/traverse': 7.27.0 - '@babel/types': 7.28.1 - transitivePeerDependencies: - - supports-color - '@babel/helper-module-imports@7.25.9': dependencies: '@babel/traverse': 7.27.0 @@ -5210,30 +4750,8 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-optimise-call-expression@7.25.9': - dependencies: - '@babel/types': 7.28.1 - - '@babel/helper-plugin-utils@7.26.5': {} - '@babel/helper-plugin-utils@7.27.1': {} - '@babel/helper-replace-supers@7.26.5(@babel/core@7.26.10)': - dependencies: - '@babel/core': 7.26.10 - '@babel/helper-member-expression-to-functions': 7.25.9 - '@babel/helper-optimise-call-expression': 7.25.9 - '@babel/traverse': 7.27.0 - transitivePeerDependencies: - - supports-color - - '@babel/helper-skip-transparent-expression-wrappers@7.25.9': - dependencies: - '@babel/traverse': 7.27.0 - '@babel/types': 7.28.1 - transitivePeerDependencies: - - supports-color - '@babel/helper-string-parser@7.25.9': {} '@babel/helper-string-parser@7.27.1': {} @@ -5264,14 +4782,6 @@ snapshots: dependencies: '@babel/types': 7.28.1 - '@babel/plugin-proposal-private-methods@7.18.6(@babel/core@7.26.10)': - dependencies: - '@babel/core': 7.26.10 - '@babel/helper-create-class-features-plugin': 7.27.0(@babel/core@7.26.10) - '@babel/helper-plugin-utils': 7.26.5 - transitivePeerDependencies: - - supports-color - '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 @@ -5911,52 +5421,6 @@ snapshots: '@esbuild/win32-x64@0.25.8': optional: true - '@eslint-community/eslint-utils@4.5.1(eslint@9.16.0(jiti@1.21.7))': - dependencies: - eslint: 9.16.0(jiti@1.21.7) - eslint-visitor-keys: 3.4.3 - - '@eslint-community/regexpp@4.12.1': {} - - '@eslint/config-array@0.19.2': - dependencies: - '@eslint/object-schema': 2.1.6 - debug: 4.4.0 - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color - - '@eslint/core@0.12.0': - dependencies: - '@types/json-schema': 7.0.15 - - '@eslint/core@0.9.1': - dependencies: - '@types/json-schema': 7.0.15 - - '@eslint/eslintrc@3.3.1': - dependencies: - ajv: 6.12.6 - debug: 4.4.0 - espree: 10.3.0 - globals: 14.0.0 - ignore: 5.3.2 - import-fresh: 3.3.1 - js-yaml: 4.1.0 - minimatch: 3.1.2 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - - '@eslint/js@9.16.0': {} - - '@eslint/object-schema@2.1.6': {} - - '@eslint/plugin-kit@0.2.7': - dependencies: - '@eslint/core': 0.12.0 - levn: 0.4.1 - '@floating-ui/core@1.6.9': dependencies: '@floating-ui/utils': 0.2.9 @@ -6015,19 +5479,6 @@ snapshots: dependencies: react-hook-form: 7.54.2(react@19.0.0) - '@humanfs/core@0.19.1': {} - - '@humanfs/node@0.16.6': - dependencies: - '@humanfs/core': 0.19.1 - '@humanwhocodes/retry': 0.3.1 - - '@humanwhocodes/module-importer@1.0.1': {} - - '@humanwhocodes/retry@0.3.1': {} - - '@humanwhocodes/retry@0.4.2': {} - '@isaacs/balanced-match@4.0.1': {} '@isaacs/brace-expansion@5.0.0': @@ -7429,8 +6880,6 @@ snapshots: '@types/js-cookie@3.0.6': {} - '@types/json-schema@7.0.15': {} - '@types/lodash@4.17.20': {} '@types/mdast@4.0.4': @@ -7465,83 +6914,6 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@8.28.0(@typescript-eslint/parser@8.28.0(eslint@9.16.0(jiti@1.21.7))(typescript@5.8.2))(eslint@9.16.0(jiti@1.21.7))(typescript@5.8.2)': - dependencies: - '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.28.0(eslint@9.16.0(jiti@1.21.7))(typescript@5.8.2) - '@typescript-eslint/scope-manager': 8.28.0 - '@typescript-eslint/type-utils': 8.28.0(eslint@9.16.0(jiti@1.21.7))(typescript@5.8.2) - '@typescript-eslint/utils': 8.28.0(eslint@9.16.0(jiti@1.21.7))(typescript@5.8.2) - '@typescript-eslint/visitor-keys': 8.28.0 - eslint: 9.16.0(jiti@1.21.7) - graphemer: 1.4.0 - ignore: 5.3.2 - natural-compare: 1.4.0 - ts-api-utils: 2.1.0(typescript@5.8.2) - typescript: 5.8.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/parser@8.28.0(eslint@9.16.0(jiti@1.21.7))(typescript@5.8.2)': - dependencies: - '@typescript-eslint/scope-manager': 8.28.0 - '@typescript-eslint/types': 8.28.0 - '@typescript-eslint/typescript-estree': 8.28.0(typescript@5.8.2) - '@typescript-eslint/visitor-keys': 8.28.0 - debug: 4.4.0 - eslint: 9.16.0(jiti@1.21.7) - typescript: 5.8.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/scope-manager@8.28.0': - dependencies: - '@typescript-eslint/types': 8.28.0 - '@typescript-eslint/visitor-keys': 8.28.0 - - '@typescript-eslint/type-utils@8.28.0(eslint@9.16.0(jiti@1.21.7))(typescript@5.8.2)': - dependencies: - '@typescript-eslint/typescript-estree': 8.28.0(typescript@5.8.2) - '@typescript-eslint/utils': 8.28.0(eslint@9.16.0(jiti@1.21.7))(typescript@5.8.2) - debug: 4.4.0 - eslint: 9.16.0(jiti@1.21.7) - ts-api-utils: 2.1.0(typescript@5.8.2) - typescript: 5.8.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/types@8.28.0': {} - - '@typescript-eslint/typescript-estree@8.28.0(typescript@5.8.2)': - dependencies: - '@typescript-eslint/types': 8.28.0 - '@typescript-eslint/visitor-keys': 8.28.0 - debug: 4.4.0 - fast-glob: 3.3.3 - is-glob: 4.0.3 - minimatch: 9.0.5 - semver: 7.7.1 - ts-api-utils: 2.1.0(typescript@5.8.2) - typescript: 5.8.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/utils@8.28.0(eslint@9.16.0(jiti@1.21.7))(typescript@5.8.2)': - dependencies: - '@eslint-community/eslint-utils': 4.5.1(eslint@9.16.0(jiti@1.21.7)) - '@typescript-eslint/scope-manager': 8.28.0 - '@typescript-eslint/types': 8.28.0 - '@typescript-eslint/typescript-estree': 8.28.0(typescript@5.8.2) - eslint: 9.16.0(jiti@1.21.7) - typescript: 5.8.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/visitor-keys@8.28.0': - dependencies: - '@typescript-eslint/types': 8.28.0 - eslint-visitor-keys: 4.2.0 - '@ungap/structured-clone@1.3.0': {} '@vitejs/plugin-react-swc@3.11.0(vite@6.3.5(@types/node@22.13.14)(jiti@1.21.7)(sugarss@4.0.1(postcss@8.5.3))(yaml@2.7.0))': @@ -7584,13 +6956,6 @@ snapshots: optionalDependencies: react: 19.0.0 - ajv@6.12.6: - dependencies: - fast-deep-equal: 3.1.3 - fast-json-stable-stringify: 2.1.0 - json-schema-traverse: 0.4.1 - uri-js: 4.4.1 - anser@2.3.2: {} ansi-escapes@4.3.2: @@ -7674,11 +7039,6 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 - brace-expansion@1.1.11: - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - brace-expansion@2.0.1: dependencies: balanced-match: 1.0.2 @@ -7854,8 +7214,6 @@ snapshots: compute-scroll-into-view@2.0.4: {} - concat-map@0.0.1: {} - convert-source-map@2.0.0: {} cookie@1.0.2: {} @@ -7956,8 +7314,6 @@ snapshots: deep-extend@0.6.0: optional: true - deep-is@0.1.4: {} - defaults@1.0.4: dependencies: clone: 1.0.4 @@ -8152,80 +7508,8 @@ snapshots: escape-string-regexp@1.0.5: {} - escape-string-regexp@4.0.0: {} - escape-string-regexp@5.0.0: {} - eslint-plugin-react-compiler@19.0.0-beta-37ed2a7-20241206(eslint@9.16.0(jiti@1.21.7)): - dependencies: - '@babel/core': 7.26.10 - '@babel/parser': 7.27.0 - '@babel/plugin-proposal-private-methods': 7.18.6(@babel/core@7.26.10) - eslint: 9.16.0(jiti@1.21.7) - hermes-parser: 0.25.1 - zod: 3.24.2 - zod-validation-error: 3.4.0(zod@3.24.2) - transitivePeerDependencies: - - supports-color - - eslint-plugin-react-hooks@5.2.0(eslint@9.16.0(jiti@1.21.7)): - dependencies: - eslint: 9.16.0(jiti@1.21.7) - - eslint-plugin-react-refresh@0.4.19(eslint@9.16.0(jiti@1.21.7)): - dependencies: - eslint: 9.16.0(jiti@1.21.7) - - eslint-scope@8.3.0: - dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 - - eslint-visitor-keys@3.4.3: {} - - eslint-visitor-keys@4.2.0: {} - - eslint@9.16.0(jiti@1.21.7): - dependencies: - '@eslint-community/eslint-utils': 4.5.1(eslint@9.16.0(jiti@1.21.7)) - '@eslint-community/regexpp': 4.12.1 - '@eslint/config-array': 0.19.2 - '@eslint/core': 0.9.1 - '@eslint/eslintrc': 3.3.1 - '@eslint/js': 9.16.0 - '@eslint/plugin-kit': 0.2.7 - '@humanfs/node': 0.16.6 - '@humanwhocodes/module-importer': 1.0.1 - '@humanwhocodes/retry': 0.4.2 - '@types/estree': 1.0.7 - '@types/json-schema': 7.0.15 - ajv: 6.12.6 - chalk: 4.1.2 - cross-spawn: 7.0.6 - debug: 4.4.0 - escape-string-regexp: 4.0.0 - eslint-scope: 8.3.0 - eslint-visitor-keys: 4.2.0 - espree: 10.3.0 - esquery: 1.6.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 8.0.0 - find-up: 5.0.0 - glob-parent: 6.0.2 - ignore: 5.3.2 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - json-stable-stringify-without-jsonify: 1.0.1 - lodash.merge: 4.6.2 - minimatch: 3.1.2 - natural-compare: 1.4.0 - optionator: 0.9.4 - optionalDependencies: - jiti: 1.21.7 - transitivePeerDependencies: - - supports-color - esniff@2.0.1: dependencies: d: 1.0.2 @@ -8233,22 +7517,6 @@ snapshots: event-emitter: 0.3.5 type: 2.7.3 - espree@10.3.0: - dependencies: - acorn: 8.14.1 - acorn-jsx: 5.3.2(acorn@8.14.1) - eslint-visitor-keys: 4.2.0 - - esquery@1.6.0: - dependencies: - estraverse: 5.3.0 - - esrecurse@4.3.0: - dependencies: - estraverse: 5.3.0 - - estraverse@5.3.0: {} - estree-util-is-identifier-name@3.0.0: {} estree-util-visit@2.0.0: @@ -8256,8 +7524,6 @@ snapshots: '@types/estree-jsx': 1.0.5 '@types/unist': 3.0.3 - esutils@2.0.3: {} - event-emitter@0.3.5: dependencies: d: 1.0.2 @@ -8294,10 +7560,6 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 - fast-json-stable-stringify@2.1.0: {} - - fast-levenshtein@2.0.6: {} - fastq@1.19.1: dependencies: reusify: 1.1.0 @@ -8314,10 +7576,6 @@ snapshots: dependencies: escape-string-regexp: 1.0.5 - file-entry-cache@8.0.0: - dependencies: - flat-cache: 4.0.1 - fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -8326,18 +7584,6 @@ snapshots: dependencies: locate-path: 3.0.0 - find-up@5.0.0: - dependencies: - locate-path: 6.0.0 - path-exists: 4.0.0 - - flat-cache@4.0.1: - dependencies: - flatted: 3.3.3 - keyv: 4.5.4 - - flatted@3.3.3: {} - follow-redirects@1.15.9(debug@4.4.0): optionalDependencies: debug: 4.4.0 @@ -8450,12 +7696,8 @@ snapshots: globals@11.12.0: {} - globals@14.0.0: {} - gopd@1.2.0: {} - graphemer@1.4.0: {} - has-flag@4.0.0: {} has-symbols@1.1.0: {} @@ -8506,12 +7748,6 @@ snapshots: dependencies: '@types/hast': 3.0.4 - hermes-estree@0.25.1: {} - - hermes-parser@0.25.1: - dependencies: - hermes-estree: 0.25.1 - hoist-non-react-statics@3.3.2: dependencies: react-is: 16.13.1 @@ -8534,15 +7770,11 @@ snapshots: ieee754@1.2.1: {} - ignore@5.3.2: {} - import-fresh@3.3.1: dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 - imurmurhash@0.1.4: {} - inherits@2.0.4: {} ini@1.3.8: @@ -8658,16 +7890,10 @@ snapshots: jsesc@3.1.0: {} - json-buffer@3.0.1: {} - json-parse-even-better-errors@2.3.1: {} - json-schema-traverse@0.4.1: {} - json-schema@0.4.0: {} - json-stable-stringify-without-jsonify@1.0.1: {} - json5@2.2.3: {} jsondiffpatch@0.6.0: @@ -8676,17 +7902,8 @@ snapshots: chalk: 5.4.1 diff-match-patch: 1.0.5 - keyv@4.5.4: - dependencies: - json-buffer: 3.0.1 - leven@3.1.0: {} - levn@0.4.1: - dependencies: - prelude-ls: 1.2.1 - type-check: 0.4.0 - lexical@0.32.1: {} lib0@0.2.114: @@ -8707,10 +7924,6 @@ snapshots: p-locate: 3.0.0 path-exists: 3.0.0 - locate-path@6.0.0: - dependencies: - p-locate: 5.0.0 - lodash.castarray@4.4.0: {} lodash.debounce@4.0.8: {} @@ -9268,10 +8481,6 @@ snapshots: dependencies: '@isaacs/brace-expansion': 5.0.0 - minimatch@3.1.2: - dependencies: - brace-expansion: 1.1.11 - minimatch@9.0.5: dependencies: brace-expansion: 2.0.1 @@ -9315,8 +8524,6 @@ snapshots: napi-build-utils@2.0.0: optional: true - natural-compare@1.4.0: {} - next-themes@0.4.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: react: 19.0.0 @@ -9355,15 +8562,6 @@ snapshots: dependencies: mimic-fn: 2.1.0 - optionator@0.9.4: - dependencies: - deep-is: 0.1.4 - fast-levenshtein: 2.0.6 - levn: 0.4.1 - prelude-ls: 1.2.1 - type-check: 0.4.0 - word-wrap: 1.2.5 - ora@5.4.1: dependencies: bl: 4.1.0 @@ -9384,18 +8582,10 @@ snapshots: dependencies: p-try: 2.2.0 - p-limit@3.1.0: - dependencies: - yocto-queue: 0.1.0 - p-locate@3.0.0: dependencies: p-limit: 2.3.0 - p-locate@5.0.0: - dependencies: - p-limit: 3.1.0 - p-try@2.2.0: {} package-json-from-dist@1.0.1: {} @@ -9429,8 +8619,6 @@ snapshots: path-exists@3.0.0: {} - path-exists@4.0.0: {} - path-key@3.1.1: {} path-parse@1.0.7: {} @@ -9551,14 +8739,6 @@ snapshots: tunnel-agent: 0.6.0 optional: true - prelude-ls@1.2.1: {} - - prettier-plugin-tailwindcss@0.6.14(prettier@3.5.3): - dependencies: - prettier: 3.5.3 - - prettier@3.5.3: {} - pretty-format@29.7.0: dependencies: '@jest/schemas': 29.6.3 @@ -9916,8 +9096,6 @@ snapshots: semver@6.3.1: {} - semver@7.7.1: {} - semver@7.7.2: optional: true @@ -10001,8 +9179,6 @@ snapshots: strip-json-comments@2.0.1: optional: true - strip-json-comments@3.1.1: {} - style-mod@4.1.2: {} style-to-js@1.1.16: @@ -10130,10 +9306,6 @@ snapshots: trough@2.2.0: {} - ts-api-utils@2.1.0(typescript@5.8.2): - dependencies: - typescript: 5.8.2 - ts-interface-checker@0.1.13: {} tslib@1.14.1: {} @@ -10145,10 +9317,6 @@ snapshots: safe-buffer: 5.2.1 optional: true - type-check@0.4.0: - dependencies: - prelude-ls: 1.2.1 - type-fest@0.21.3: {} type-fest@4.38.0: {} @@ -10224,10 +9392,6 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 - uri-js@4.4.1: - dependencies: - punycode: 2.3.1 - use-callback-ref@1.3.3(@types/react@19.0.12)(react@19.0.0): dependencies: react: 19.0.0 @@ -10353,8 +9517,6 @@ snapshots: dependencies: isexe: 2.0.0 - word-wrap@1.2.5: {} - wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -10380,18 +9542,12 @@ snapshots: dependencies: lib0: 0.2.114 - yocto-queue@0.1.0: {} - yoga-layout@3.2.1: {} zod-to-json-schema@3.24.6(zod@3.24.2): dependencies: zod: 3.24.2 - zod-validation-error@3.4.0(zod@3.24.2): - dependencies: - zod: 3.24.2 - zod@3.24.2: {} zwitch@2.0.4: {} From cee8e028c6f37e8ac7ef13a41fafd00f747d686d Mon Sep 17 00:00:00 2001 From: Usama Date: Fri, 17 Oct 2025 08:31:09 +0000 Subject: [PATCH 5/8] - fixed 163 files for biome --- echo/frontend/biome.json | 9 +- echo/frontend/lingui.config.ts | 22 +- echo/frontend/postcss.config.js | 28 +- echo/frontend/src/App.tsx | 40 +- echo/frontend/src/Router.tsx | 494 ++- .../announcement/AnnouncementDrawerHeader.tsx | 92 +- .../announcement/AnnouncementErrorState.tsx | 40 + .../announcement/AnnouncementIcon.tsx | 112 +- .../announcement/AnnouncementItem.tsx | 271 +- .../announcement/AnnouncementSkeleton.tsx | 126 +- .../components/announcement/Announcements.tsx | 296 +- .../announcement/TopAnnouncementBar.tsx | 183 +- .../hooks/useProcessedAnnouncements.ts | 57 +- .../announcement/utils/dateUtils.ts | 36 +- .../src/components/aspect/AspectCard.tsx | 120 +- .../components/aspect/hooks/useCopyAspect.tsx | 156 +- .../components/aspect/hooks/useCopyQuote.ts | 213 +- .../src/components/auth/hooks/index.ts | 370 +- .../src/components/auth/utils/errorUtils.ts | 40 +- .../src/components/chat/BaseMessage.tsx | 92 +- .../src/components/chat/ChatAccordion.tsx | 440 +-- .../components/chat/ChatContextProgress.tsx | 146 +- .../components/chat/ChatHistoryMessage.tsx | 322 +- .../src/components/chat/ChatMessage.tsx | 68 +- .../src/components/chat/ChatSkeleton.tsx | 32 +- .../src/components/chat/ChatTemplatesMenu.tsx | 172 +- .../src/components/chat/References.tsx | 94 +- echo/frontend/src/components/chat/Sources.tsx | 70 +- .../src/components/chat/SourcesSearch.tsx | 19 +- .../src/components/chat/SourcesSearched.tsx | 24 +- .../src/components/chat/TemplatesModal.tsx | 230 +- .../frontend/src/components/chat/chatUtils.ts | 88 +- .../frontend/src/components/chat/templates.ts | 58 +- .../src/components/common/BaseSkeleton.tsx | 37 +- .../src/components/common/Breadcrumbs.tsx | 54 +- .../src/components/common/ClosableAlert.tsx | 22 +- .../common/ConnectionHealthStatus.tsx | 53 +- .../src/components/common/CopyIconButton.tsx | 44 +- .../common/CopyRichTextIconButton.tsx | 38 +- .../DembraneLoading.css | 40 +- .../common/DembraneLoadingSpinner/index.tsx | 107 +- .../src/components/common/DiffViewer.tsx | 803 ++-- .../frontend/src/components/common/Drawer.tsx | 53 +- .../components/common/DynamicLucideIcon.tsx | 20 +- .../components/common/ExponentialProgress.tsx | 114 +- .../components/common/InformationTooltip.tsx | 24 +- .../src/components/common/LazyRoute.tsx | 192 +- .../src/components/common/LoadingSpinner.tsx | 6 +- echo/frontend/src/components/common/Logo.tsx | 83 +- .../src/components/common/Markdown.tsx | 58 +- .../components/common/NavigationButton.tsx | 208 +- .../src/components/common/Protected.tsx | 32 +- .../frontend/src/components/common/QRCode.tsx | 40 +- .../common/ReferencesIconButton.tsx | 52 +- .../src/components/common/ScrollToBottom.tsx | 44 +- .../src/components/common/SummaryCard.tsx | 36 +- .../src/components/common/TipBanner.tsx | 162 +- .../src/components/common/Toaster.tsx | 44 +- .../src/components/common/i18nLink.tsx | 35 +- .../conversation/AutoSelectConversations.tsx | 324 +- .../conversation/ConversationAccordion.tsx | 38 +- .../conversation/ConversationDangerZone.tsx | 99 +- .../conversation/ConversationEdit.tsx | 293 +- .../conversation/ConversationLink.tsx | 134 +- .../conversation/ConversationLinks.tsx | 40 +- .../conversation/MoveConversationButton.tsx | 420 ++- .../OngoingConversationsSummaryCard.tsx | 140 +- .../OpenForParticipationSummaryCard.tsx | 73 +- .../conversation/RetranscribeConversation.tsx | 2 +- .../src/components/dropzone/Dropzone.tsx | 95 +- .../dropzone/UploadConversationDropzone.tsx | 1531 ++++---- .../src/components/dropzone/hooks/index.ts | 408 +- .../src/components/error/ErrorBoundary.tsx | 68 +- .../src/components/error/ErrorPage.tsx | 58 +- .../src/components/form/EditableTextBox.tsx | 180 +- .../src/components/form/FormLabel.tsx | 42 +- .../form/MarkdownWYSIWYG/MarkdownWYSIWYG.tsx | 80 +- .../form/MarkdownWYSIWYG/styles.css | 3348 +++++++++-------- .../src/components/form/SaveStatus.tsx | 122 +- .../src/components/form/UnsavedChanges.tsx | 34 +- .../src/components/insight/Insight.tsx | 52 +- .../components/language/LanguagePicker.tsx | 154 +- .../src/components/layout/AuthLayout.tsx | 58 +- .../src/components/layout/BaseLayout.tsx | 34 +- .../frontend/src/components/layout/Footer.tsx | 20 +- .../frontend/src/components/layout/Header.tsx | 344 +- .../src/components/layout/I18nProvider.tsx | 12 +- .../src/components/layout/LanguageLayout.tsx | 10 +- .../components/layout/ParticipantLayout.tsx | 113 +- .../layout/ProjectConversationLayout.tsx | 80 +- .../src/components/layout/ProjectLayout.tsx | 133 +- .../layout/ProjectLibraryLayout.tsx | 4 +- .../layout/ProjectOverviewLayout.tsx | 60 +- .../src/components/layout/TabsWithRouter.tsx | 120 +- .../components/layout/hooks/useSidebar.tsx | 46 +- .../library/LibraryTemplatesMenu.tsx | 98 +- .../src/components/library/hooks/index.ts | 118 +- .../components/participant/EchoErrorAlert.tsx | 48 +- .../participant/ParticipantBody.tsx | 460 ++- .../participant/ParticipantInitiateForm.tsx | 228 +- .../ParticipantOnboardingCards.css | 36 +- .../ParticipantOnboardingCards.tsx | 750 ++-- .../participant/ParticipantSettingsModal.tsx | 60 +- .../components/participant/SpikeMessage.tsx | 51 +- .../components/participant/SystemMessage.tsx | 52 +- .../participant/UserChunkMessage.tsx | 193 +- .../hooks/useChunkedAudioRecorder.ts | 540 +-- .../hooks/useConversationIssueBanner.ts | 56 +- .../hooks/useConversationsHealthStream.ts | 122 +- .../components/project/ProjectAccordion.tsx | 30 +- .../project/ProjectAnalysisRunStatus.tsx | 151 +- .../components/project/ProjectBasicEdit.tsx | 232 +- .../src/components/project/ProjectCard.tsx | 90 +- .../ProjectConversationStatusSection.tsx | 61 +- .../components/project/ProjectDangerZone.tsx | 296 +- .../project/ProjectExportSection.tsx | 52 +- .../components/project/ProjectListItem.tsx | 77 +- .../project/ProjectListSkeleton.tsx | 54 +- .../project/ProjectSettingsSection.tsx | 92 +- .../src/components/project/ProjectSidebar.tsx | 210 +- .../components/project/ProjectTagsInput.tsx | 478 +-- .../report/ConversationStatusTable.tsx | 175 +- .../components/report/CreateReportForm.tsx | 437 ++- .../report/ReportModalNavigationButton.tsx | 103 +- .../src/components/report/ReportRenderer.tsx | 218 +- .../src/components/report/ReportTimeline.tsx | 641 ++-- .../report/UpdateReportModalButton.tsx | 193 +- .../src/components/unsubscribe/hooks/index.ts | 24 +- .../src/components/view/CreateViewForm.tsx | 249 +- .../src/components/view/DummyViews.tsx | 50 +- echo/frontend/src/components/view/View.tsx | 245 +- .../src/components/view/hooks/index.ts | 14 +- .../src/components/view/hooks/useCopyView.tsx | 210 +- echo/frontend/src/config.ts | 50 +- echo/frontend/src/hooks/useAutoSave.ts | 110 +- echo/frontend/src/hooks/useI18nNavigate.ts | 51 +- echo/frontend/src/hooks/useLanguage.ts | 60 +- echo/frontend/src/hooks/useOnlineStatus.ts | 28 +- echo/frontend/src/index.css | 17 +- echo/frontend/src/lib/analytics.ts | 8 +- echo/frontend/src/lib/analyticsEvents.ts | 4 +- echo/frontend/src/lib/links.ts | 4 +- echo/frontend/src/lib/utils.ts | 49 +- echo/frontend/src/main.tsx | 94 +- echo/frontend/src/routes/404.tsx | 32 +- .../src/routes/auth/CheckYourEmail.tsx | 34 +- .../src/routes/auth/PasswordReset.tsx | 133 +- echo/frontend/src/routes/auth/Register.tsx | 226 +- .../src/routes/auth/RequestPasswordReset.tsx | 74 +- .../routes/participant/ParticipantStart.tsx | 284 +- .../src/routes/project/ProjectsHome.tsx | 452 ++- .../ProjectConversationOverview.tsx | 310 +- .../project/library/ProjectLibraryAspect.tsx | 173 +- .../project/library/ProjectLibraryView.tsx | 136 +- .../project/report/ProjectReportRoute.tsx | 623 ++- .../unsubscribe/ProjectUnsubscribe.tsx | 158 +- echo/frontend/src/styles/accordion.module.css | 12 +- echo/frontend/src/theme.tsx | 489 ++- echo/frontend/tailwind.config.js | 110 +- echo/frontend/tsconfig.json | 52 +- echo/frontend/tsconfig.node.json | 18 +- echo/frontend/vercel.json | 18 +- echo/frontend/vite.config.ts | 98 +- 163 files changed, 13529 insertions(+), 13302 deletions(-) create mode 100644 echo/frontend/src/components/announcement/AnnouncementErrorState.tsx diff --git a/echo/frontend/biome.json b/echo/frontend/biome.json index 7c53fa1b..bab909a3 100644 --- a/echo/frontend/biome.json +++ b/echo/frontend/biome.json @@ -15,8 +15,13 @@ } }, "files": { - "ignoreUnknown": false, - "includes": ["**/*", "!**/dist", "!**/node_modules", "!**/package-lock.json", "!**/package.json"] + "includes": [ + "**/*", + "!**/dist", + "!**/node_modules", + "!**/package-lock.json", + "!**/package.json" + ] }, "formatter": { "indentStyle": "tab" diff --git a/echo/frontend/lingui.config.ts b/echo/frontend/lingui.config.ts index 69d12444..5ee4e219 100644 --- a/echo/frontend/lingui.config.ts +++ b/echo/frontend/lingui.config.ts @@ -1,17 +1,17 @@ import type { LinguiConfig } from "@lingui/conf"; const config: LinguiConfig = { - locales: ["en-US", "nl-NL", "de-DE", "fr-FR", "es-ES"], - sourceLocale: "en-US", - fallbackLocales: { - default: "en-US", - }, - catalogs: [ - { - path: "/src/locales/{locale}", - include: ["src"], - }, - ], + catalogs: [ + { + include: ["src"], + path: "/src/locales/{locale}", + }, + ], + fallbackLocales: { + default: "en-US", + }, + locales: ["en-US", "nl-NL", "de-DE", "fr-FR", "es-ES"], + sourceLocale: "en-US", }; export default config; diff --git a/echo/frontend/postcss.config.js b/echo/frontend/postcss.config.js index 40f851e8..d197605d 100644 --- a/echo/frontend/postcss.config.js +++ b/echo/frontend/postcss.config.js @@ -1,16 +1,16 @@ export default { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - "postcss-preset-mantine": {}, - "postcss-simple-vars": { - variables: { - "mantine-breakpoint-xs": "36em", - "mantine-breakpoint-sm": "48em", - "mantine-breakpoint-md": "62em", - "mantine-breakpoint-lg": "75em", - "mantine-breakpoint-xl": "88em", - }, - }, - }, + plugins: { + autoprefixer: {}, + "postcss-preset-mantine": {}, + "postcss-simple-vars": { + variables: { + "mantine-breakpoint-lg": "75em", + "mantine-breakpoint-md": "62em", + "mantine-breakpoint-sm": "48em", + "mantine-breakpoint-xl": "88em", + "mantine-breakpoint-xs": "36em", + }, + }, + tailwindcss: {}, + }, }; diff --git a/echo/frontend/src/App.tsx b/echo/frontend/src/App.tsx index 243c6fe9..9b41b19d 100644 --- a/echo/frontend/src/App.tsx +++ b/echo/frontend/src/App.tsx @@ -4,35 +4,35 @@ import "@mantine/dropzone/styles.css"; import { MantineProvider } from "@mantine/core"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; +import { useEffect } from "react"; import { RouterProvider } from "react-router/dom"; import { I18nProvider } from "./components/layout/I18nProvider"; -import { mainRouter, participantRouter } from "./Router"; import { USE_PARTICIPANT_ROUTER } from "./config"; -import { theme } from "./theme"; -import { useEffect } from "react"; import { analytics } from "./lib/analytics"; -import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; +import { mainRouter, participantRouter } from "./Router"; +import { theme } from "./theme"; const queryClient = new QueryClient(); const router = USE_PARTICIPANT_ROUTER ? participantRouter : mainRouter; export const App = () => { - useEffect(() => { - const cleanup = analytics.enableAutoPageviews(); + useEffect(() => { + const cleanup = analytics.enableAutoPageviews(); - return () => { - cleanup(); - }; - }, []); - return ( - - - - - - - - - ); + return () => { + cleanup(); + }; + }, []); + return ( + + + + + + + + + ); }; diff --git a/echo/frontend/src/Router.tsx b/echo/frontend/src/Router.tsx index 2cc3294f..2c2f27ce 100644 --- a/echo/frontend/src/Router.tsx +++ b/echo/frontend/src/Router.tsx @@ -1,302 +1,298 @@ -import { Navigate, createBrowserRouter } from "react-router"; +import { createBrowserRouter, Navigate } from "react-router"; import { - createLazyRoute, - createLazyNamedRoute, + createLazyNamedRoute, + createLazyRoute, } from "./components/common/LazyRoute"; - +import { Protected } from "./components/common/Protected"; +import { ErrorPage } from "./components/error/ErrorPage"; +import { AuthLayout } from "./components/layout/AuthLayout"; // Layout components - keep as regular imports since they're used frequently import { BaseLayout } from "./components/layout/BaseLayout"; -import { ProjectLayout } from "./components/layout/ProjectLayout"; import { LanguageLayout } from "./components/layout/LanguageLayout"; +import { ParticipantLayout } from "./components/layout/ParticipantLayout"; import { ProjectConversationLayout } from "./components/layout/ProjectConversationLayout"; +import { ProjectLayout } from "./components/layout/ProjectLayout"; import { ProjectLibraryLayout } from "./components/layout/ProjectLibraryLayout"; -import { AuthLayout } from "./components/layout/AuthLayout"; import { ProjectOverviewLayout } from "./components/layout/ProjectOverviewLayout"; -import { ParticipantLayout } from "./components/layout/ParticipantLayout"; -import { Protected } from "./components/common/Protected"; -import { ErrorPage } from "./components/error/ErrorPage"; - -// Tab-based routes - import directly for now to debug import { - ProjectPortalSettingsRoute, - ProjectSettingsRoute, -} from "./routes/project/ProjectRoutes"; + ParticipantConversationAudioRoute, + ParticipantConversationTextRoute, +} from "./routes/participant/ParticipantConversation"; +import { ParticipantPostConversation } from "./routes/participant/ParticipantPostConversation"; +import { ParticipantStartRoute } from "./routes/participant/ParticipantStart"; import { ProjectConversationOverviewRoute } from "./routes/project/conversation/ProjectConversationOverview"; import { ProjectConversationTranscript } from "./routes/project/conversation/ProjectConversationTranscript"; -import { ParticipantPostConversation } from "./routes/participant/ParticipantPostConversation"; +// Tab-based routes - import directly for now to debug import { - ParticipantConversationAudioRoute, - ParticipantConversationTextRoute, -} from "./routes/participant/ParticipantConversation"; -import { ParticipantStartRoute } from "./routes/participant/ParticipantStart"; + ProjectPortalSettingsRoute, + ProjectSettingsRoute, +} from "./routes/project/ProjectRoutes"; // Lazy-loaded route components const ProjectsHomeRoute = createLazyNamedRoute( - () => import("./routes/project/ProjectsHome"), - "ProjectsHomeRoute", + () => import("./routes/project/ProjectsHome"), + "ProjectsHomeRoute", ); const ProjectLibraryRoute = createLazyNamedRoute( - () => import("./routes/project/library/ProjectLibrary"), - "ProjectLibraryRoute", + () => import("./routes/project/library/ProjectLibrary"), + "ProjectLibraryRoute", ); const ProjectLibraryView = createLazyNamedRoute( - () => import("./routes/project/library/ProjectLibraryView"), - "ProjectLibraryView", + () => import("./routes/project/library/ProjectLibraryView"), + "ProjectLibraryView", ); const ProjectLibraryAspect = createLazyNamedRoute( - () => import("./routes/project/library/ProjectLibraryAspect"), - "ProjectLibraryAspect", + () => import("./routes/project/library/ProjectLibraryAspect"), + "ProjectLibraryAspect", ); const LoginRoute = createLazyNamedRoute( - () => import("./routes/auth/Login"), - "LoginRoute", + () => import("./routes/auth/Login"), + "LoginRoute", ); const RegisterRoute = createLazyNamedRoute( - () => import("./routes/auth/Register"), - "RegisterRoute", + () => import("./routes/auth/Register"), + "RegisterRoute", ); const CheckYourEmailRoute = createLazyNamedRoute( - () => import("./routes/auth/CheckYourEmail"), - "CheckYourEmailRoute", + () => import("./routes/auth/CheckYourEmail"), + "CheckYourEmailRoute", ); const VerifyEmailRoute = createLazyNamedRoute( - () => import("./routes/auth/VerifyEmail"), - "VerifyEmailRoute", + () => import("./routes/auth/VerifyEmail"), + "VerifyEmailRoute", ); const PasswordResetRoute = createLazyNamedRoute( - () => import("./routes/auth/PasswordReset"), - "PasswordResetRoute", + () => import("./routes/auth/PasswordReset"), + "PasswordResetRoute", ); const RequestPasswordResetRoute = createLazyNamedRoute( - () => import("./routes/auth/RequestPasswordReset"), - "RequestPasswordResetRoute", + () => import("./routes/auth/RequestPasswordReset"), + "RequestPasswordResetRoute", ); const ProjectChatRoute = createLazyNamedRoute( - () => import("./routes/project/chat/ProjectChatRoute"), - "ProjectChatRoute", + () => import("./routes/project/chat/ProjectChatRoute"), + "ProjectChatRoute", ); const ProjectReportRoute = createLazyNamedRoute( - () => import("./routes/project/report/ProjectReportRoute"), - "ProjectReportRoute", + () => import("./routes/project/report/ProjectReportRoute"), + "ProjectReportRoute", ); const ParticipantReport = createLazyNamedRoute( - () => import("./routes/participant/ParticipantReport"), - "ParticipantReport", + () => import("./routes/participant/ParticipantReport"), + "ParticipantReport", ); const ProjectUnsubscribe = createLazyNamedRoute( - () => import("./routes/project/unsubscribe/ProjectUnsubscribe"), - "ProjectUnsubscribe", + () => import("./routes/project/unsubscribe/ProjectUnsubscribe"), + "ProjectUnsubscribe", ); const DebugPage = createLazyRoute(() => import("./routes/Debug")); export const mainRouter = createBrowserRouter([ - { - path: "/:language?", - element: , - errorElement: , - children: [ - { - path: "", - element: , - }, - { - path: "login", - element: ( - - - - ), - }, - { - path: "register", - element: ( - - - - ), - }, - { - path: "check-your-email", - element: ( - - - - ), - }, - { - path: "password-reset", - element: ( - - - - ), - }, - { - path: "request-password-reset", - element: ( - - - - ), - }, - { - path: "verify-email", - element: ( - - - - ), - }, - { - path: "projects", - element: ( - - - - ), - - children: [ - { - index: true, - element: , - }, - { - path: ":projectId", - children: [ - { - element: , - children: [ - { - path: "", - element: , - children: [ - { - index: true, - element: , - }, - { - path: "overview", - element: , - }, - { - path: "portal-editor", - element: , - }, - - ], - }, - { - path: "chats/:chatId", - element: , - }, - { - path: "chats/:chatId/debug", - element: , - }, - { - path: "conversation/:conversationId", - element: , - children: [ - { - index: true, - element: , - }, - { - path: "overview", - element: , - }, - { - path: "transcript", - element: , - }, - { - path: "debug", - element: , - }, - ], - }, + { + children: [ + { + element: , + path: "", + }, + { + element: ( + + + + ), + path: "login", + }, + { + element: ( + + + + ), + path: "register", + }, + { + element: ( + + + + ), + path: "check-your-email", + }, + { + element: ( + + + + ), + path: "password-reset", + }, + { + element: ( + + + + ), + path: "request-password-reset", + }, + { + element: ( + + + + ), + path: "verify-email", + }, + { + children: [ + { + element: , + index: true, + }, + { + children: [ + { + children: [ + { + children: [ + { + element: , + index: true, + }, + { + element: , + path: "overview", + }, + { + element: , + path: "portal-editor", + }, + ], + element: , + path: "", + }, + { + element: , + path: "chats/:chatId", + }, + { + element: , + path: "chats/:chatId/debug", + }, + { + children: [ + { + element: , + index: true, + }, + { + element: , + path: "overview", + }, + { + element: , + path: "transcript", + }, + { + element: , + path: "debug", + }, + ], + element: , + path: "conversation/:conversationId", + }, - { - path: "library", - element: , - children: [ - { - path: "views/:viewId/aspects/:aspectId", - element: , - }, - { - path: "views/:viewId", - element: , - }, - { - index: true, - element: , - }, - ], - }, - { - path: "report", - element: , - }, - { - path: "debug", - element: , - }, - ], - }, - ], - }, - ], - }, - { - path: "*", - element: , - }, - ], - }, + { + children: [ + { + element: , + path: "views/:viewId/aspects/:aspectId", + }, + { + element: , + path: "views/:viewId", + }, + { + element: , + index: true, + }, + ], + element: , + path: "library", + }, + { + element: , + path: "report", + }, + { + element: , + path: "debug", + }, + ], + element: , + }, + ], + path: ":projectId", + }, + ], + element: ( + + + + ), + path: "projects", + }, + { + element: , + path: "*", + }, + ], + element: , + errorElement: , + path: "/:language?", + }, ]); export const participantRouter = createBrowserRouter([ - { - path: "/:language?/:projectId", - element: , - errorElement: , - children: [ - { - path: "", - element: , - }, - { - path: "start", - element: , - }, - { - path: "conversation/:conversationId", - element: , - }, - { - path: "conversation/:conversationId/text", - element: , - }, - { - path: "conversation/:conversationId/finish", - element: , - }, - { - path: "report", - element: , - }, - { - path: "unsubscribe", - element: , - }, - { - path: "*", - element: , - }, - ], - }, + { + children: [ + { + element: , + path: "", + }, + { + element: , + path: "start", + }, + { + element: , + path: "conversation/:conversationId", + }, + { + element: , + path: "conversation/:conversationId/text", + }, + { + element: , + path: "conversation/:conversationId/finish", + }, + { + element: , + path: "report", + }, + { + element: , + path: "unsubscribe", + }, + { + element: , + path: "*", + }, + ], + element: , + errorElement: , + path: "/:language?/:projectId", + }, ]); diff --git a/echo/frontend/src/components/announcement/AnnouncementDrawerHeader.tsx b/echo/frontend/src/components/announcement/AnnouncementDrawerHeader.tsx index 45455723..2c1f4f17 100644 --- a/echo/frontend/src/components/announcement/AnnouncementDrawerHeader.tsx +++ b/echo/frontend/src/components/announcement/AnnouncementDrawerHeader.tsx @@ -5,51 +5,53 @@ import { IconX } from "@tabler/icons-react"; import { useUnreadAnnouncements } from "./hooks"; export const AnnouncementDrawerHeader = ({ - onClose, - onMarkAllAsRead, - isPending, + onClose, + onMarkAllAsRead, + isPending, }: { - onClose: () => void; - onMarkAllAsRead: () => void; - isPending: boolean; + onClose: () => void; + onMarkAllAsRead: () => void; + isPending: boolean; }) => { - const { data: unreadCount, isLoading: isLoadingUnread } = - useUnreadAnnouncements(); - const hasUnreadAnnouncements = unreadCount && unreadCount > 0; - - return ( - - - - Announcements - - - - - - - {hasUnreadAnnouncements && ( - - {unreadCount} {unreadCount === 1 ? t`unread announcement` : t`unread announcements`} - - )} - {hasUnreadAnnouncements && ( - - )} - - - ); + const { data: unreadCount } = useUnreadAnnouncements(); + const hasUnreadAnnouncements = unreadCount && unreadCount > 0; + + return ( + + + + Announcements + + + + + + + {hasUnreadAnnouncements && ( + + {unreadCount}{" "} + {unreadCount === 1 + ? t`unread announcement` + : t`unread announcements`} + + )} + {hasUnreadAnnouncements && ( + + )} + + + ); }; diff --git a/echo/frontend/src/components/announcement/AnnouncementErrorState.tsx b/echo/frontend/src/components/announcement/AnnouncementErrorState.tsx new file mode 100644 index 00000000..592d8192 --- /dev/null +++ b/echo/frontend/src/components/announcement/AnnouncementErrorState.tsx @@ -0,0 +1,40 @@ +import { Trans } from "@lingui/react/macro"; +import { Alert, Box, Button, Stack, Text } from "@mantine/core"; +import { IconAlertCircle, IconRefresh } from "@tabler/icons-react"; + +interface AnnouncementErrorStateProps { + onRetry: () => void; + isLoading?: boolean; +} + +export const AnnouncementErrorState = ({ + onRetry, + isLoading = false, +}: AnnouncementErrorStateProps) => { + return ( + + } + color="red" + variant="light" + title={Error loading announcements} + > + + + Failed to get announcements + + + + + + ); +}; diff --git a/echo/frontend/src/components/announcement/AnnouncementIcon.tsx b/echo/frontend/src/components/announcement/AnnouncementIcon.tsx index c70e8b6f..2ff3ec31 100644 --- a/echo/frontend/src/components/announcement/AnnouncementIcon.tsx +++ b/echo/frontend/src/components/announcement/AnnouncementIcon.tsx @@ -1,70 +1,70 @@ import { ActionIcon, Box, Group, Indicator, Loader, Text } from "@mantine/core"; import { IconSpeakerphone } from "@tabler/icons-react"; -import { useLatestAnnouncement, useUnreadAnnouncements } from "./hooks"; -import { useLanguage } from "@/hooks/useLanguage"; import { useAnnouncementDrawer } from "@/components/announcement/hooks"; import { getTranslatedContent } from "@/components/announcement/hooks/useProcessedAnnouncements"; import { Markdown } from "@/components/common/Markdown"; +import { useLanguage } from "@/hooks/useLanguage"; +import { useLatestAnnouncement, useUnreadAnnouncements } from "./hooks"; export const AnnouncementIcon = () => { - const { open } = useAnnouncementDrawer(); - const { language } = useLanguage(); - const { data: latestAnnouncement, isLoading: isLoadingLatest } = - useLatestAnnouncement(); - const { data: unreadCount, isLoading: isLoadingUnread } = - useUnreadAnnouncements(); + const { open } = useAnnouncementDrawer(); + const { language } = useLanguage(); + const { data: latestAnnouncement, isLoading: isLoadingLatest } = + useLatestAnnouncement(); + const { data: unreadCount, isLoading: isLoadingUnread } = + useUnreadAnnouncements(); - // Get latest urgent announcement message - const message = latestAnnouncement - ? getTranslatedContent(latestAnnouncement as Announcement, language).message - : ""; + // Get latest urgent announcement message + const message = latestAnnouncement + ? getTranslatedContent(latestAnnouncement as Announcement, language).message + : ""; - // Check if the latest announcement is unread - const isUnread = latestAnnouncement - ? !latestAnnouncement.activity?.some( - (activity: AnnouncementActivity) => activity.read === true, - ) - : false; + // Check if the latest announcement is unread + const isUnread = latestAnnouncement + ? !latestAnnouncement.activity?.some( + (activity: AnnouncementActivity) => activity.read === true, + ) + : false; - const showMessage = - isUnread && message && latestAnnouncement?.level === "info"; + const showMessage = + isUnread && message && latestAnnouncement?.level === "info"; - const isLoading = isLoadingLatest || isLoadingUnread; + const isLoading = isLoadingLatest || isLoadingUnread; - return ( - - - - {unreadCount || 0} - - } - size={20} - disabled={(unreadCount || 0) === 0} - withBorder - > - - {isLoading ? ( - - ) : ( - - )} - - - + return ( + + + + {unreadCount || 0} + + } + size={20} + disabled={(unreadCount || 0) === 0} + withBorder + > + + {isLoading ? ( + + ) : ( + + )} + + + - {showMessage && ( - - - - )} - - ); + {showMessage && ( + + + + )} + + ); }; diff --git a/echo/frontend/src/components/announcement/AnnouncementItem.tsx b/echo/frontend/src/components/announcement/AnnouncementItem.tsx index 6c68c2f2..8e30e0d9 100644 --- a/echo/frontend/src/components/announcement/AnnouncementItem.tsx +++ b/echo/frontend/src/components/announcement/AnnouncementItem.tsx @@ -1,161 +1,160 @@ +import { Trans } from "@lingui/react/macro"; import { - Box, - Button, - Group, - Stack, - Text, - useMantineTheme, - ThemeIcon, + Box, + Button, + Group, + Stack, + Text, + ThemeIcon, + useMantineTheme, } from "@mantine/core"; import { - IconChecks, - IconChevronDown, - IconChevronUp, - IconInfoCircle, - IconAlertTriangle, + IconAlertTriangle, + IconChevronDown, + IconChevronUp, + IconInfoCircle, } from "@tabler/icons-react"; -import { Trans } from "@lingui/react/macro"; -import { useEffect, useRef, useState, forwardRef } from "react"; +import { forwardRef, useEffect, useRef, useState } from "react"; import { Markdown } from "@/components/common/Markdown"; import { useFormatDate } from "./utils/dateUtils"; type Announcement = { - id: string; - title: string; - message: string; - created_at: string | Date | null | undefined; - expires_at?: string | Date | null | undefined; - read?: boolean | null; - level: "info" | "urgent"; + id: string; + title: string; + message: string; + created_at: string | Date | null | undefined; + expires_at?: string | Date | null | undefined; + read?: boolean | null; + level: "info" | "urgent"; }; interface AnnouncementItemProps { - announcement: Announcement; - onMarkAsRead: (id: string) => void; - index: number; + announcement: Announcement; + onMarkAsRead: (id: string) => void; + index: number; } export const AnnouncementItem = forwardRef< - HTMLDivElement, - AnnouncementItemProps + HTMLDivElement, + AnnouncementItemProps >(({ announcement, onMarkAsRead, index }, ref) => { - const theme = useMantineTheme(); - const [showMore, setShowMore] = useState(false); - const [showReadMoreButton, setShowReadMoreButton] = useState(false); - const messageRef = useRef(null); - const formatDate = useFormatDate(); + const theme = useMantineTheme(); + const [showMore, setShowMore] = useState(false); + const [showReadMoreButton, setShowReadMoreButton] = useState(false); + const messageRef = useRef(null); + const formatDate = useFormatDate(); - useEffect(() => { - if (messageRef.current) { - setShowReadMoreButton( - messageRef.current.scrollHeight !== messageRef.current.clientHeight, - ); - } - }, []); + useEffect(() => { + if (messageRef.current) { + setShowReadMoreButton( + messageRef.current.scrollHeight !== messageRef.current.clientHeight, + ); + } + }, []); - return ( - - - - { - - {announcement.level === "urgent" ? ( - - ) : ( - - )} - - } - - -
- -
+ return ( + + + + { + + {announcement.level === "urgent" ? ( + + ) : ( + + )} + + } + + +
+ +
- - - {formatDate(announcement.created_at)} - + + + {formatDate(announcement.created_at)} + - {/* this part needs a second look */} - {!announcement.read && ( -
- )} - {/* this part needs a second look */} - - + {/* this part needs a second look */} + {!announcement.read && ( +
+ )} + {/* this part needs a second look */} + + - - - + + + - - {showReadMoreButton && ( - - )} + + {showReadMoreButton && ( + + )} - - {!announcement.read && ( - - )} - - - - - - - ); + + {!announcement.read && ( + + )} + + + + + + + ); }); AnnouncementItem.displayName = "AnnouncementItem"; diff --git a/echo/frontend/src/components/announcement/AnnouncementSkeleton.tsx b/echo/frontend/src/components/announcement/AnnouncementSkeleton.tsx index 9a5d0b00..3e06a361 100644 --- a/echo/frontend/src/components/announcement/AnnouncementSkeleton.tsx +++ b/echo/frontend/src/components/announcement/AnnouncementSkeleton.tsx @@ -1,67 +1,67 @@ import { Box, Group, Stack, ThemeIcon } from "@mantine/core"; -import { IconAlertTriangle, IconInfoCircle } from "@tabler/icons-react"; +import { IconInfoCircle } from "@tabler/icons-react"; export const AnnouncementSkeleton = () => ( - - {[1, 2, 3, 4, 5, 6].map((i) => ( - - - - - {/* Use a generic icon skeleton */} - - - - - - - - - - - - - - - - - - - - - - - - - ))} - + + {[1, 2, 3, 4, 5, 6].map((i) => ( + + + + + {/* Use a generic icon skeleton */} + + + + + + + + + + + + + + + + + + + + + + + + + ))} + ); diff --git a/echo/frontend/src/components/announcement/Announcements.tsx b/echo/frontend/src/components/announcement/Announcements.tsx index 25e9dd85..b33d8e79 100644 --- a/echo/frontend/src/components/announcement/Announcements.tsx +++ b/echo/frontend/src/components/announcement/Announcements.tsx @@ -1,183 +1,151 @@ -import { - Box, - ScrollArea, - Stack, - Text, - Loader, - Center, - Alert, - Button, -} from "@mantine/core"; import { Trans } from "@lingui/react/macro"; -import { useState, useEffect } from "react"; +import { Box, Center, Loader, ScrollArea, Stack, Text } from "@mantine/core"; +import { useEffect, useState } from "react"; import { useInView } from "react-intersection-observer"; +import { useAnnouncementDrawer } from "@/components/announcement/hooks"; +import { useProcessedAnnouncements } from "@/components/announcement/hooks/useProcessedAnnouncements"; +import { useLanguage } from "@/hooks/useLanguage"; import { Drawer } from "../common/Drawer"; +import { AnnouncementDrawerHeader } from "./AnnouncementDrawerHeader"; +import { AnnouncementErrorState } from "./AnnouncementErrorState"; import { AnnouncementItem } from "./AnnouncementItem"; +import { AnnouncementSkeleton } from "./AnnouncementSkeleton"; import { - useInfiniteAnnouncements, - useMarkAsReadMutation, - useMarkAllAsReadMutation, + useInfiniteAnnouncements, + useMarkAllAsReadMutation, + useMarkAsReadMutation, } from "./hooks"; -import { useLanguage } from "@/hooks/useLanguage"; -import { AnnouncementSkeleton } from "./AnnouncementSkeleton"; -import { AnnouncementDrawerHeader } from "./AnnouncementDrawerHeader"; -import { useProcessedAnnouncements } from "@/components/announcement/hooks/useProcessedAnnouncements"; -import { useAnnouncementDrawer } from "@/components/announcement/hooks"; -import { IconAlertCircle, IconRefresh } from "@tabler/icons-react"; export const Announcements = () => { - const { isOpen, close } = useAnnouncementDrawer(); - const { language } = useLanguage(); - const markAsReadMutation = useMarkAsReadMutation(); - const markAllAsReadMutation = useMarkAllAsReadMutation(); - const [openedOnce, setOpenedOnce] = useState(false); + const { isOpen, close } = useAnnouncementDrawer(); + const { language } = useLanguage(); + const markAsReadMutation = useMarkAsReadMutation(); + const markAllAsReadMutation = useMarkAllAsReadMutation(); + const [openedOnce, setOpenedOnce] = useState(false); - const { ref: loadMoreRef, inView } = useInView(); + const { ref: loadMoreRef, inView } = useInView(); - // Track when drawer is opened for the first time - useEffect(() => { - if (isOpen && !openedOnce) { - setOpenedOnce(true); - } - }, [isOpen, openedOnce]); + // Track when drawer is opened for the first time + useEffect(() => { + if (isOpen && !openedOnce) { + setOpenedOnce(true); + } + }, [isOpen, openedOnce]); - const { - data: announcementsData, - fetchNextPage, - hasNextPage, - isFetchingNextPage, - isLoading, - isError, - error, - refetch, - } = useInfiniteAnnouncements({ - options: { - initialLimit: 10, - }, - enabled: openedOnce, - }); + const { + data: announcementsData, + fetchNextPage, + hasNextPage, + isFetchingNextPage, + isLoading, + isError, + refetch, + } = useInfiniteAnnouncements({ + enabled: openedOnce, + options: { + initialLimit: 10, + }, + }); - // Flatten all announcements from all pages - const allAnnouncements = - announcementsData?.pages.flatMap((page) => page.announcements) ?? []; + // Flatten all announcements from all pages, with type safety + const allAnnouncements = + announcementsData?.pages.flatMap( + (page) => (page as { announcements: Announcement[] }).announcements, + ) ?? []; - // Process announcements with translations and read status - const processedAnnouncements = useProcessedAnnouncements( - allAnnouncements as Announcement[], - language, - ); + // Process announcements with translations and read status + const processedAnnouncements = useProcessedAnnouncements( + allAnnouncements, + language, + ); - // Load more announcements when user scrolls to bottom - useEffect(() => { - if (inView && hasNextPage && !isFetchingNextPage) { - fetchNextPage(); - } - }, [inView, hasNextPage, isFetchingNextPage, fetchNextPage]); + // Load more announcements when user scrolls to bottom + useEffect(() => { + if (inView && hasNextPage && !isFetchingNextPage) { + fetchNextPage(); + } + }, [inView, hasNextPage, isFetchingNextPage, fetchNextPage]); - const handleMarkAsRead = async (id: string) => { - markAsReadMutation.mutate({ - announcementId: id, - }); - }; + const handleMarkAsRead = async (id: string) => { + markAsReadMutation.mutate({ + announcementId: id, + }); + }; - const handleMarkAllAsRead = async () => { - markAllAsReadMutation.mutate(); - }; + const handleMarkAllAsRead = async () => { + markAllAsReadMutation.mutate(); + }; - const handleRetry = () => { - refetch(); - }; - // Error state component - const ErrorState = () => ( - - } - color="red" - variant="light" - title={Error loading announcements} - > - - - Failed to get announcements - - - - - - ); + const handleRetry = () => { + refetch(); + }; - return ( - - } - classNames={{ - content: "border-0", - title: "px-3 w-full", - header: "border-b", - body: "p-0", - }} - withCloseButton={false} - styles={{ - content: { - maxWidth: "95%", - }, - }} - > - - - - {isError ? ( - - ) : isLoading ? ( - - ) : processedAnnouncements.length === 0 ? ( - - - No announcements available - - - ) : ( - <> - {processedAnnouncements.map((announcement, index) => ( - - ))} - {isFetchingNextPage && ( -
- -
- )} - - )} -
-
-
-
- ); + return ( + + } + classNames={{ + body: "p-0", + content: "border-0", + header: "border-b", + title: "px-3 w-full", + }} + withCloseButton={false} + styles={{ + content: { + maxWidth: "95%", + }, + }} + > + + + + {isError ? ( + + ) : isLoading ? ( + + ) : processedAnnouncements.length === 0 ? ( + + + No announcements available + + + ) : ( + <> + {processedAnnouncements.map((announcement, index) => ( + + ))} + {isFetchingNextPage && ( +
+ +
+ )} + + )} +
+
+
+
+ ); }; diff --git a/echo/frontend/src/components/announcement/TopAnnouncementBar.tsx b/echo/frontend/src/components/announcement/TopAnnouncementBar.tsx index 15fd4236..8496bb69 100644 --- a/echo/frontend/src/components/announcement/TopAnnouncementBar.tsx +++ b/echo/frontend/src/components/announcement/TopAnnouncementBar.tsx @@ -1,107 +1,114 @@ import { - Box, - Group, - Text, - useMantineTheme, - ActionIcon, - ThemeIcon, + ActionIcon, + Box, + Group, + ThemeIcon, + useMantineTheme, } from "@mantine/core"; import { IconAlertTriangle, IconX } from "@tabler/icons-react"; -import { useLatestAnnouncement, useMarkAsReadMutation } from "./hooks"; -import { theme } from "@/theme"; -import { useState, useEffect } from "react"; +import { useEffect, useState } from "react"; import { useAnnouncementDrawer } from "@/components/announcement/hooks"; -import { useLanguage } from "@/hooks/useLanguage"; -import { Markdown } from "@/components/common/Markdown"; import { getTranslatedContent } from "@/components/announcement/hooks/useProcessedAnnouncements"; -import { toast } from "@/components/common/Toaster"; -import { t } from "@lingui/core/macro"; +import { Markdown } from "@/components/common/Markdown"; +import { useLanguage } from "@/hooks/useLanguage"; +import { useLatestAnnouncement, useMarkAsReadMutation } from "./hooks"; export function TopAnnouncementBar() { - const theme = useMantineTheme(); - const { data: announcement, isLoading } = useLatestAnnouncement(); - const markAsReadMutation = useMarkAsReadMutation(); - const [isClosed, setIsClosed] = useState(false); - const { open } = useAnnouncementDrawer(); - const { language } = useLanguage(); + const theme = useMantineTheme(); + const { data: announcement, isLoading } = useLatestAnnouncement(); + const markAsReadMutation = useMarkAsReadMutation(); + const [isClosed, setIsClosed] = useState(false); + const { open } = useAnnouncementDrawer(); + const { language } = useLanguage(); - // Check if the announcement has been read by the current user - // Directus already filters activity data for the current user - const isRead = announcement?.activity?.some( - (activity: AnnouncementActivity) => activity.read === true, - ); + // Check if the announcement has been read by the current user + // Directus already filters activity data for the current user + const isRead = announcement?.activity?.some( + (activity: AnnouncementActivity) => activity.read === true, + ); - useEffect(() => { - const shouldUseDefaultHeight = - isLoading || - !announcement || - announcement.level !== "urgent" || - isClosed || - isRead; + useEffect(() => { + const shouldUseDefaultHeight = + isLoading || + !announcement || + announcement.level !== "urgent" || + isClosed || + isRead; - const height = shouldUseDefaultHeight ? "60px" : "112px"; - const root = document.documentElement.style; + const height = shouldUseDefaultHeight ? "60px" : "112px"; + const root = document.documentElement.style; - root.setProperty("--base-layout-height", `calc(100% - ${height})`, "important"); - root.setProperty("--base-layout-padding", height, "important"); - root.setProperty("--project-layout-height", `calc(100vh - ${height})`, "important"); - }, [isLoading, announcement, isClosed, isRead]); + root.setProperty( + "--base-layout-height", + `calc(100% - ${height})`, + "important", + ); + root.setProperty("--base-layout-padding", height, "important"); + root.setProperty( + "--project-layout-height", + `calc(100vh - ${height})`, + "important", + ); + }, [isLoading, announcement, isClosed, isRead]); - // Only show if we have an urgent announcement, it's not closed, and it's not read - if ( - isLoading || - !announcement || - announcement.level !== "urgent" || - isClosed || - isRead - ) { - return null; - } + // Only show if we have an urgent announcement, it's not closed, and it's not read + if ( + isLoading || + !announcement || + announcement.level !== "urgent" || + isClosed || + isRead + ) { + return null; + } - const { title } = getTranslatedContent(announcement as Announcement, language); + const { title } = getTranslatedContent( + announcement as Announcement, + language, + ); - const handleClose = async (e: React.MouseEvent) => { - e.stopPropagation(); - setIsClosed(true); + const handleClose = async (e: React.MouseEvent) => { + e.stopPropagation(); + setIsClosed(true); - // Mark announcement as read - if (announcement.id) { - markAsReadMutation.mutate({ - announcementId: announcement.id, - }); - } - }; + // Mark announcement as read + if (announcement.id) { + markAsReadMutation.mutate({ + announcementId: announcement.id, + }); + } + }; - const handleBarClick = () => { - open(); - }; + const handleBarClick = () => { + open(); + }; - return ( - - - - - - - + return ( + + + + + + + - - - - - ); + + + + + ); } diff --git a/echo/frontend/src/components/announcement/hooks/useProcessedAnnouncements.ts b/echo/frontend/src/components/announcement/hooks/useProcessedAnnouncements.ts index 0aaa8fa4..f2095181 100644 --- a/echo/frontend/src/components/announcement/hooks/useProcessedAnnouncements.ts +++ b/echo/frontend/src/components/announcement/hooks/useProcessedAnnouncements.ts @@ -1,36 +1,41 @@ import { useMemo } from "react"; -export const getTranslatedContent = (announcement: Announcement, language: string) => { - const translation = - announcement.translations?.find( - (t: AnnouncementTranslations) => t.languages_code === language && t.title, - ) || - announcement.translations?.find((t: AnnouncementTranslations) => t.languages_code === "en-US"); +export const getTranslatedContent = ( + announcement: Announcement, + language: string, +) => { + const translation = + announcement.translations?.find( + (t: AnnouncementTranslations) => t.languages_code === language && t.title, + ) || + announcement.translations?.find( + (t: AnnouncementTranslations) => t.languages_code === "en-US", + ); - return { - title: translation?.title || "", - message: translation?.message || "", - }; + return { + message: translation?.message || "", + title: translation?.title || "", + }; }; // @FIXME: this doesn't need to be a hook, it can be a simple function, memo for a .find is overkill export function useProcessedAnnouncements( - announcements: Announcement[], - language: string, + announcements: Announcement[], + language: string, ) { - return useMemo(() => { - return announcements.map((announcement) => { - const { title, message } = getTranslatedContent(announcement, language); + return useMemo(() => { + return announcements.map((announcement) => { + const { title, message } = getTranslatedContent(announcement, language); - return { - id: announcement.id, - title, - message, - created_at: announcement.created_at, - expires_at: announcement.expires_at, - level: announcement.level as "info" | "urgent", - read: announcement.activity?.[0]?.read || false, - }; - }); - }, [announcements, language]); + return { + created_at: announcement.created_at, + expires_at: announcement.expires_at, + id: announcement.id, + level: announcement.level as "info" | "urgent", + message, + read: announcement.activity?.[0]?.read || false, + title, + }; + }); + }, [announcements, language]); } diff --git a/echo/frontend/src/components/announcement/utils/dateUtils.ts b/echo/frontend/src/components/announcement/utils/dateUtils.ts index 6ae0cb40..e7650331 100644 --- a/echo/frontend/src/components/announcement/utils/dateUtils.ts +++ b/echo/frontend/src/components/announcement/utils/dateUtils.ts @@ -1,38 +1,38 @@ import { formatRelative } from "date-fns"; -import { enUS, nl, de, fr, es } from "date-fns/locale"; +import { de, enUS, es, fr, nl } from "date-fns/locale"; import { useLanguage } from "@/hooks/useLanguage"; // Map of supported locales to date-fns locales const localeMap = { - "en-US": enUS, - "nl-NL": nl, - "de-DE": de, - "fr-FR": fr, - "es-ES": es, + "de-DE": de, + "en-US": enUS, + "es-ES": es, + "fr-FR": fr, + "nl-NL": nl, } as const; type SupportedLocale = keyof typeof localeMap; export const formatDate = ( - date: string | Date | null | undefined, - locale: string = "en-US", + date: string | Date | null | undefined, + locale = "en-US", ): string => { - if (!date) return ""; + if (!date) return ""; - const dateObj = typeof date === "string" ? new Date(date) : date; + const dateObj = typeof date === "string" ? new Date(date) : date; - if (isNaN(dateObj.getTime())) return ""; + if (isNaN(dateObj.getTime())) return ""; - const currentLocale = - localeMap[locale as SupportedLocale] || localeMap["en-US"]; + const currentLocale = + localeMap[locale as SupportedLocale] || localeMap["en-US"]; - return formatRelative(dateObj, new Date(), { locale: currentLocale }); + return formatRelative(dateObj, new Date(), { locale: currentLocale }); }; export const useFormatDate = () => { - const { i18n } = useLanguage(); + const { i18n } = useLanguage(); - return (date: string | Date | null | undefined): string => { - return formatDate(date, i18n.locale); - }; + return (date: string | Date | null | undefined): string => { + return formatDate(date, i18n.locale); + }; }; diff --git a/echo/frontend/src/components/aspect/AspectCard.tsx b/echo/frontend/src/components/aspect/AspectCard.tsx index aa250e05..d033abc5 100644 --- a/echo/frontend/src/components/aspect/AspectCard.tsx +++ b/echo/frontend/src/components/aspect/AspectCard.tsx @@ -1,70 +1,70 @@ import { Trans } from "@lingui/react/macro"; -import { cn, sanitizeImageUrl } from "@/lib/utils"; import { Box, Button, LoadingOverlay, Paper, Stack, Text } from "@mantine/core"; import { IconArrowsDiagonal } from "@tabler/icons-react"; import { useParams } from "react-router"; import { I18nLink } from "@/components/common/i18nLink"; import { useProjectById } from "@/components/project/hooks"; +import { cn, sanitizeImageUrl } from "@/lib/utils"; export const AspectCard = ({ - data, - className, + data, + className, }: { - data: Aspect; - className?: string; + data: Aspect; + className?: string; }) => { - const { projectId } = useParams(); + const { projectId } = useParams(); - const project = useProjectById({ - projectId: projectId ?? "", - query: { - fields: ["image_generation_model"], - }, - }); + const project = useProjectById({ + projectId: projectId ?? "", + query: { + fields: ["image_generation_model"], + }, + }); - return ( - - - - - - - - - {project.data?.image_generation_model !== "PLACEHOLDER" && ( - {data.name - )} - + return ( + + + + + + + + + {project.data?.image_generation_model !== "PLACEHOLDER" && ( + {data.name + )} + - - - - - {data.name} - - - {data.short_summary ?? data.description ?? ""} - - - {/* + + + + + {data.name} + + + {data.short_summary ?? data.description ?? ""} + + + {/* @@ -76,10 +76,10 @@ export const AspectCard = ({ */} - - - - - - ); + + + + + + ); }; diff --git a/echo/frontend/src/components/aspect/hooks/useCopyAspect.tsx b/echo/frontend/src/components/aspect/hooks/useCopyAspect.tsx index 70cf6db5..1a4bca3e 100644 --- a/echo/frontend/src/components/aspect/hooks/useCopyAspect.tsx +++ b/echo/frontend/src/components/aspect/hooks/useCopyAspect.tsx @@ -1,99 +1,89 @@ -import { directus } from "@/lib/directus"; import { readItem } from "@directus/sdk"; -import useCopyToRichText from "@/hooks/useCopyToRichText"; import { useParams } from "react-router"; - -interface Quote { - quote_id: { - text: string; - conversation_id: { - id: string; - participant_name: string; - }; - }; -} +import useCopyToRichText from "@/hooks/useCopyToRichText"; +import { directus } from "@/lib/directus"; export const useCopyAspect = () => { - const { language, projectId } = useParams(); - const { copied, copy } = useCopyToRichText(); + const { language, projectId } = useParams(); + const { copied, copy } = useCopyToRichText(); - const copyAspect = async (aspectId: string) => { - const stringBuilder: string[] = []; - const aspect = await directus.request( - readItem("aspect", aspectId, { - fields: [ - "id", - "name", - "short_summary", - "long_summary", - "image_url", - "view_id", - { - aspect_segment: [ - { - segment: [ - { - conversation_id: ["id", "participant_name"], - }, - "description", - "verbatim_transcript", - "relevant_index", - ], - }, - ], - }, - ], - }), - ); + const copyAspect = async (aspectId: string) => { + const stringBuilder: string[] = []; + const aspect = await directus.request( + readItem("aspect", aspectId, { + fields: [ + "id", + "name", + "short_summary", + "long_summary", + "image_url", + "view_id", + { + aspect_segment: [ + { + segment: [ + { + conversation_id: ["id", "participant_name"], + }, + "description", + "verbatim_transcript", + "relevant_index", + ], + }, + ], + }, + ], + }), + ); - stringBuilder.push( - `# Aspect: [${aspect.name}](${window.location.origin}/${language}/projects/${projectId}/library/views/${aspect.view_id}/aspects/${aspectId})`, - ); + stringBuilder.push( + `# Aspect: [${aspect.name}](${window.location.origin}/${language}/projects/${projectId}/library/views/${aspect.view_id}/aspects/${aspectId})`, + ); - if (aspect.image_url) { - stringBuilder.push(`![${aspect.name}](${aspect.image_url})`); - } + if (aspect.image_url) { + stringBuilder.push(`![${aspect.name}](${aspect.image_url})`); + } - if (aspect.long_summary) { - stringBuilder.push(aspect.long_summary); - } else if (aspect.short_summary) { - stringBuilder.push(aspect.short_summary); - } else { - stringBuilder.push( - "The summary for this aspect is not available. Please try again later.", - ); - } + if (aspect.long_summary) { + stringBuilder.push(aspect.long_summary); + } else if (aspect.short_summary) { + stringBuilder.push(aspect.short_summary); + } else { + stringBuilder.push( + "The summary for this aspect is not available. Please try again later.", + ); + } - const quotes = Array.isArray(aspect.aspect_segment) - ? (aspect.aspect_segment as AspectSegment[]) - : []; - if (quotes.length > 0) { - stringBuilder.push(`## Top Quotes`); + const quotes = Array.isArray(aspect.aspect_segment) + ? (aspect.aspect_segment as AspectSegment[]) + : []; + if (quotes.length > 0) { + stringBuilder.push("## Top Quotes"); - for (const quote of quotes) { - if (!quote.segment) continue; + for (const quote of quotes) { + if (!quote.segment) continue; - const conversationId = (quote.segment as ConversationSegment).conversation_id as string; - const description = quote.description ?? "No description available"; - const conversation = (quote.segment as ConversationSegment)?.conversation_id as Conversation; - const participantName = conversation?.participant_name ?? "Unknown"; + const conversationId = (quote.segment as ConversationSegment) + .conversation_id as string; + const description = quote.description ?? "No description available"; + const conversation = (quote.segment as ConversationSegment) + ?.conversation_id as Conversation; + const participantName = conversation?.participant_name ?? "Unknown"; - const conversationUrl = - window.location.origin + - `/${language}/projects/${projectId}/conversation/${conversationId}/transcript`; + const conversationUrl = + window.location.origin + + `/${language}/projects/${projectId}/conversation/${conversationId}/transcript`; - stringBuilder.push(`"${description}"\n`); - stringBuilder.push( - `from [${participantName}](${conversationUrl})\n\n`, - ); - } - } + stringBuilder.push(`"${description}"\n`); + stringBuilder.push(`from [${participantName}](${conversationUrl})\n\n`); + } + } - copy(stringBuilder.join("\n")); - }; + copy(stringBuilder.join("\n")); + }; - return { - copyAspect, - copied, - }; + return { + copied, + copyAspect, + }; }; diff --git a/echo/frontend/src/components/aspect/hooks/useCopyQuote.ts b/echo/frontend/src/components/aspect/hooks/useCopyQuote.ts index 47784cc5..5631ad8b 100644 --- a/echo/frontend/src/components/aspect/hooks/useCopyQuote.ts +++ b/echo/frontend/src/components/aspect/hooks/useCopyQuote.ts @@ -1,113 +1,110 @@ -import { directus } from "@/lib/directus"; import { readItem } from "@directus/sdk"; -import useCopyToRichText from "@/hooks/useCopyToRichText"; import { useParams } from "react-router"; -import { string } from "zod"; - -// Define types inline since the .d.ts file is not a module -type QuoteWithConversation = { - id: string; - text: string; - timestamp: string | null; - conversation_id: { - id: string; - title: string | null; - participant_name: string | null; - tags: Array<{ - project_tag_id: { text: string | null }; - }>; - }; -}; +import useCopyToRichText from "@/hooks/useCopyToRichText"; +import { directus } from "@/lib/directus"; export const useCopyQuote = () => { - const { language, projectId } = useParams<{ language: string; projectId: string }>(); - const { copied, copy } = useCopyToRichText(); - - // actually aspect Segment ID - const copyQuote = async (quoteId: string) => { - const stringBuilder: string[] = []; - - // @ts-expect-error - Directus SDK has incorrect types for nested fields - const quote: AspectSegment = await directus.request( - readItem("aspect_segment", quoteId, { - fields: [ - "id", - "description", - "verbatim_transcript", - "relevant_index", - { - segment: [ - { - conversation_id: ["id", "participant_name", "created_at",], - }, - ], - }, - ], - }), - ); - - const conversation = (quote.segment as ConversationSegment)?.conversation_id as Conversation; - - // Format timestamp if available - const timestamp = conversation?.created_at ?? "" - - // // Format tags if available - // const tags = ((quote.segment as ConversationSegment)?.conversation_id as Conversation)?.tags - // ?.map( - // (tag: ConversationProjectTag) => - // tag.project_tag_id?.text ?? "", - // ) - // .join(", "); - - // Build the formatted quote with context - stringBuilder.push( - `# Quote from [${conversation?.participant_name}](${window.location.origin}/${language}/projects/${projectId}/conversation/${conversation?.id}/transcript)`, - ); - stringBuilder.push(`"${quote.description}"`); - stringBuilder.push(`${quote.verbatim_transcript}`); - - try { - const startIndex = parseInt(quote.relevant_index?.split(":")[0] ?? "0"); - const endIndex = parseInt(quote.relevant_index?.split(":")[1] ?? "0"); - - const relevantTranscript = quote.verbatim_transcript?.slice(startIndex, endIndex); - - if (relevantTranscript) { - stringBuilder.push(`${relevantTranscript}`); - } - } catch (e) { - console.error(e); - } - - stringBuilder.push("---"); - - stringBuilder.push(""); // Empty line for spacing - - // Add metadata - if (conversation?.participant_name ) { - stringBuilder.push(`**Conversation:** ${conversation.participant_name}`); - } - - if (timestamp) { - stringBuilder.push(`${timestamp}`); - stringBuilder.push(""); - } - - // if (tags) { - // stringBuilder.push(`**Tags:** ${tags}`); - // stringBuilder.push(""); - // } - - // Add source link - const sourceUrl = `${window.location.origin}/${language}/projects/${projectId}/conversation/${conversation?.id}/transcript`; - stringBuilder.push(""); // Empty line before source - stringBuilder.push(`[View in conversation](${sourceUrl})`); - - copy(stringBuilder.join("\n")); - }; - - return { - copyQuote, - copied, - }; + const { language, projectId } = useParams<{ + language: string; + projectId: string; + }>(); + const { copied, copy } = useCopyToRichText(); + + // actually aspect Segment ID + const copyQuote = async (quoteId: string) => { + const stringBuilder: string[] = []; + + // @ts-expect-error - Directus SDK has incorrect types for nested fields + const quote: AspectSegment = await directus.request( + readItem("aspect_segment", quoteId, { + fields: [ + "id", + "description", + "verbatim_transcript", + "relevant_index", + { + segment: [ + { + conversation_id: ["id", "participant_name", "created_at"], + }, + ], + }, + ], + }), + ); + + const conversation = (quote.segment as ConversationSegment) + ?.conversation_id as Conversation; + + // Format timestamp if available + const timestamp = conversation?.created_at ?? ""; + + // // Format tags if available + // const tags = ((quote.segment as ConversationSegment)?.conversation_id as Conversation)?.tags + // ?.map( + // (tag: ConversationProjectTag) => + // tag.project_tag_id?.text ?? "", + // ) + // .join(", "); + + // Build the formatted quote with context + stringBuilder.push( + `# Quote from [${conversation?.participant_name}](${window.location.origin}/${language}/projects/${projectId}/conversation/${conversation?.id}/transcript)`, + ); + stringBuilder.push(`"${quote.description}"`); + stringBuilder.push(`${quote.verbatim_transcript}`); + + try { + const startIndex = Number.parseInt( + quote.relevant_index?.split(":")[0] ?? "0", + 10, + ); + const endIndex = Number.parseInt( + quote.relevant_index?.split(":")[1] ?? "0", + 10, + ); + + const relevantTranscript = quote.verbatim_transcript?.slice( + startIndex, + endIndex, + ); + + if (relevantTranscript) { + stringBuilder.push(`${relevantTranscript}`); + } + } catch (e) { + console.error(e); + } + + stringBuilder.push("---"); + + stringBuilder.push(""); // Empty line for spacing + + // Add metadata + if (conversation?.participant_name) { + stringBuilder.push(`**Conversation:** ${conversation.participant_name}`); + } + + if (timestamp) { + stringBuilder.push(`${timestamp}`); + stringBuilder.push(""); + } + + // if (tags) { + // stringBuilder.push(`**Tags:** ${tags}`); + // stringBuilder.push(""); + // } + + // Add source link + const sourceUrl = `${window.location.origin}/${language}/projects/${projectId}/conversation/${conversation?.id}/transcript`; + stringBuilder.push(""); // Empty line before source + stringBuilder.push(`[View in conversation](${sourceUrl})`); + + copy(stringBuilder.join("\n")); + }; + + return { + copied, + copyQuote, + }; }; diff --git a/echo/frontend/src/components/auth/hooks/index.ts b/echo/frontend/src/components/auth/hooks/index.ts index e531d5ed..91a7c37e 100644 --- a/echo/frontend/src/components/auth/hooks/index.ts +++ b/echo/frontend/src/components/auth/hooks/index.ts @@ -1,217 +1,217 @@ -import { toast } from "@/components/common/Toaster"; -import { useI18nNavigate } from "@/hooks/useI18nNavigate"; -import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; -import { directus } from "@/lib/directus"; import { - passwordRequest, - passwordReset, - readUser, - registerUser, - registerUserVerify, + passwordRequest, + passwordReset, + readUser, + registerUser, + registerUserVerify, } from "@directus/sdk"; -import { ADMIN_BASE_URL } from "@/config"; -import { throwWithMessage } from "../utils/errorUtils"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useEffect, useState } from "react"; import { useLocation, useSearchParams } from "react-router"; +import { toast } from "@/components/common/Toaster"; +import { ADMIN_BASE_URL } from "@/config"; +import { useI18nNavigate } from "@/hooks/useI18nNavigate"; +import { directus } from "@/lib/directus"; +import { throwWithMessage } from "../utils/errorUtils"; export const useCurrentUser = () => - useQuery({ - queryKey: ["users", "me"], - queryFn: () => { - try { - return directus.request(readUser("me")); - } catch (error) { - return null; - } - }, - }); + useQuery({ + queryFn: () => { + try { + return directus.request(readUser("me")); + } catch (error) { + return null; + } + }, + queryKey: ["users", "me"], + }); export const useResetPasswordMutation = () => { - const navigate = useI18nNavigate(); - return useMutation({ - mutationFn: async ({ - token, - password, - }: { - token: string; - password: string; - }) => { - try { - const response = await directus.request(passwordReset(token, password)); - return response; - } catch (e) { - throwWithMessage(e); - } - }, - onSuccess: () => { - toast.success( - "Password reset successfully. Please login with new password.", - ); - navigate("/login"); - }, - onError: (e) => { - try { - toast.error(e.message); - } catch (e) { - toast.error("Error resetting password. Please contact support."); - } - }, - }); + const navigate = useI18nNavigate(); + return useMutation({ + mutationFn: async ({ + token, + password, + }: { + token: string; + password: string; + }) => { + try { + const response = await directus.request(passwordReset(token, password)); + return response; + } catch (e) { + throwWithMessage(e); + } + }, + onError: (e) => { + try { + toast.error(e.message); + } catch (e) { + toast.error("Error resetting password. Please contact support."); + } + }, + onSuccess: () => { + toast.success( + "Password reset successfully. Please login with new password.", + ); + navigate("/login"); + }, + }); }; export const useRequestPasswordResetMutation = () => { - const navigate = useI18nNavigate(); - return useMutation({ - mutationFn: async (email: string) => { - try { - const response = await directus.request( - passwordRequest(email, `${ADMIN_BASE_URL}/password-reset`), - ); - return response; - } catch (e) { - throwWithMessage(e); - } - }, - onSuccess: () => { - toast.success("Password reset email sent successfully"); - navigate("/check-your-email"); - }, - onError: (e) => { - toast.error(e.message); - }, - }); + const navigate = useI18nNavigate(); + return useMutation({ + mutationFn: async (email: string) => { + try { + const response = await directus.request( + passwordRequest(email, `${ADMIN_BASE_URL}/password-reset`), + ); + return response; + } catch (e) { + throwWithMessage(e); + } + }, + onError: (e) => { + toast.error(e.message); + }, + onSuccess: () => { + toast.success("Password reset email sent successfully"); + navigate("/check-your-email"); + }, + }); }; -export const useVerifyMutation = (doRedirect: boolean = true) => { - const navigate = useI18nNavigate(); +export const useVerifyMutation = (doRedirect = true) => { + const navigate = useI18nNavigate(); - return useMutation({ - mutationFn: async (data: { token: string }) => { - try { - const response = await directus.request(registerUserVerify(data.token)); - return response; - } catch (e) { - throwWithMessage(e); - } - }, - onSuccess: () => { - toast.success("Email verified successfully."); - if (doRedirect) { - setTimeout(() => { - // window.location.href = `/login?new=true`; - navigate(`/login?new=true`); - }, 4500); - } - }, - onError: (e) => { - toast.error(e.message); - }, - }); + return useMutation({ + mutationFn: async (data: { token: string }) => { + try { + const response = await directus.request(registerUserVerify(data.token)); + return response; + } catch (e) { + throwWithMessage(e); + } + }, + onError: (e) => { + toast.error(e.message); + }, + onSuccess: () => { + toast.success("Email verified successfully."); + if (doRedirect) { + setTimeout(() => { + // window.location.href = `/login?new=true`; + navigate("/login?new=true"); + }, 4500); + } + }, + }); }; export const useRegisterMutation = () => { - const navigate = useI18nNavigate(); - return useMutation({ - mutationFn: async (payload: Parameters) => { - try { - const response = await directus.request(registerUser(...payload)); - return response; - } catch (e) { - try { - throwWithMessage(e); - } catch (inner) { - if (inner instanceof Error) { - if (inner.message === "You don't have permission to access this.") { - throw new Error( - "Oops! It seems your email is not eligible for registration at this time. Please consider joining our waitlist for future updates!", - ); - } - } - } - } - }, - onSuccess: () => { - toast.success("Please check your email to verify your account."); - navigate("/check-your-email"); - }, - onError: (e) => { - toast.error(e.message); - }, - }); + const navigate = useI18nNavigate(); + return useMutation({ + mutationFn: async (payload: Parameters) => { + try { + const response = await directus.request(registerUser(...payload)); + return response; + } catch (e) { + try { + throwWithMessage(e); + } catch (inner) { + if (inner instanceof Error) { + if (inner.message === "You don't have permission to access this.") { + throw new Error( + "Oops! It seems your email is not eligible for registration at this time. Please consider joining our waitlist for future updates!", + ); + } + } + } + } + }, + onError: (e) => { + toast.error(e.message); + }, + onSuccess: () => { + toast.success("Please check your email to verify your account."); + navigate("/check-your-email"); + }, + }); }; // todo: add redirection logic here export const useLoginMutation = () => { - return useMutation({ - mutationFn: (payload: Parameters) => { - return directus.login(...payload); - }, - onSuccess: () => { - toast.success("Login successful"); - }, - }); + return useMutation({ + mutationFn: (payload: Parameters) => { + return directus.login(...payload); + }, + onSuccess: () => { + toast.success("Login successful"); + }, + }); }; export const useLogoutMutation = () => { - const queryClient = useQueryClient(); - const navigate = useI18nNavigate(); + const queryClient = useQueryClient(); + const navigate = useI18nNavigate(); - return useMutation({ - mutationFn: async ({ - next: _, - }: { - next?: string; - reason?: string; - doRedirect: boolean; - }) => { - try { - await directus.logout(); - } catch (e) { - throwWithMessage(e); - } - }, - onMutate: async ({ next, reason, doRedirect }) => { - queryClient.resetQueries(); - if (doRedirect) { - navigate( - "/login" + - (next ? `?next=${encodeURIComponent(next)}` : "") + - (reason ? `&reason=${reason}` : ""), - ); - } - }, - }); + return useMutation({ + mutationFn: async ({ + next: _, + }: { + next?: string; + reason?: string; + doRedirect: boolean; + }) => { + try { + await directus.logout(); + } catch (e) { + throwWithMessage(e); + } + }, + onMutate: async ({ next, reason, doRedirect }) => { + queryClient.resetQueries(); + if (doRedirect) { + navigate( + "/login" + + (next ? `?next=${encodeURIComponent(next)}` : "") + + (reason ? `&reason=${reason}` : ""), + ); + } + }, + }); }; export const useAuthenticated = (doRedirect = false) => { - const logoutMutation = useLogoutMutation(); - const [loading, setLoading] = useState(false); - const [isAuthenticated, setIsAuthenticated] = useState(false); - const location = useLocation(); - const [searchParams] = useSearchParams(); + const logoutMutation = useLogoutMutation(); + const [loading, setLoading] = useState(false); + const [isAuthenticated, setIsAuthenticated] = useState(false); + const location = useLocation(); + const [searchParams] = useSearchParams(); - const checkAuth = async () => { - try { - await directus.refresh(); - setIsAuthenticated(true); - } catch (e) { - setIsAuthenticated(false); - await logoutMutation.mutateAsync({ - next: location.pathname, - reason: searchParams.get("reason") ?? "", - doRedirect, - }); - } - }; + const checkAuth = async () => { + try { + await directus.refresh(); + setIsAuthenticated(true); + } catch (e) { + setIsAuthenticated(false); + await logoutMutation.mutateAsync({ + doRedirect, + next: location.pathname, + reason: searchParams.get("reason") ?? "", + }); + } + }; - useEffect(() => { - setLoading(true); - checkAuth() - .catch((_e) => {}) - .finally(() => { - setLoading(false); - }); - }, []); + useEffect(() => { + setLoading(true); + checkAuth() + .catch((_e) => {}) + .finally(() => { + setLoading(false); + }); + }, []); - return { loading, isAuthenticated }; + return { isAuthenticated, loading }; }; diff --git a/echo/frontend/src/components/auth/utils/errorUtils.ts b/echo/frontend/src/components/auth/utils/errorUtils.ts index 22b813d7..0fc2aa0a 100644 --- a/echo/frontend/src/components/auth/utils/errorUtils.ts +++ b/echo/frontend/src/components/auth/utils/errorUtils.ts @@ -1,22 +1,24 @@ // always throws a error with a message export function throwWithMessage(e: unknown): never { - if ( - e && - typeof e === "object" && - "errors" in e && - Array.isArray((e as any).errors) - ) { - // Handle Directus error format - const message = (e as any).errors[0].message; - console.log(message); - throw new Error(message); - } else if (e instanceof Error) { - // Handle generic errors - console.log(e.message); - throw new Error(e.message); - } else { - // Handle unknown errors - console.log("An unknown error occurred"); - throw new Error("Something went wrong"); - } + if ( + e && + typeof e === "object" && + "errors" in e && + // biome-ignore lint/suspicious/noExplicitAny: need to address this later + Array.isArray((e as any).errors) + ) { + // Handle Directus error format + // biome-ignore lint/suspicious/noExplicitAny: need to address this later + const message = (e as any).errors[0].message; + console.log(message); + throw new Error(message); + } else if (e instanceof Error) { + // Handle generic errors + console.log(e.message); + throw new Error(e.message); + } else { + // Handle unknown errors + console.log("An unknown error occurred"); + throw new Error("Something went wrong"); + } } diff --git a/echo/frontend/src/components/chat/BaseMessage.tsx b/echo/frontend/src/components/chat/BaseMessage.tsx index 007ad57e..5b179a4d 100644 --- a/echo/frontend/src/components/chat/BaseMessage.tsx +++ b/echo/frontend/src/components/chat/BaseMessage.tsx @@ -1,55 +1,55 @@ import { t } from "@lingui/core/macro"; -import { Icons } from "@/icons"; import { - Box, - Group, - LoadingOverlay, - Paper, - PaperProps, - Stack, - Text, + Box, + Group, + LoadingOverlay, + Paper, + type PaperProps, + Stack, + Text, } from "@mantine/core"; -import React, { PropsWithChildren } from "react"; +import type React from "react"; +import type { PropsWithChildren } from "react"; export const BaseMessage = ( - props: PropsWithChildren<{ - text?: string; - title?: React.ReactNode; - rightSection?: React.ReactNode; - bottomSection?: React.ReactNode; - paperProps?: PaperProps; - loading?: boolean; - }>, + props: PropsWithChildren<{ + text?: string; + title?: React.ReactNode; + rightSection?: React.ReactNode; + bottomSection?: React.ReactNode; + paperProps?: PaperProps; + loading?: boolean; + }>, ) => { - return ( - - - - {/*
+ return ( + + + + {/*
*/} - - - - {props.title ?? t`You`} - - {props.rightSection} - -
- - {props.text && {props.text}} - {props.children} -
-
-
- {props.bottomSection} -
-
- ); + + + + {props.title ?? t`You`} + + {props.rightSection} + +
+ + {props.text && {props.text}} + {props.children} +
+
+ + {props.bottomSection} + + + ); }; diff --git a/echo/frontend/src/components/chat/ChatAccordion.tsx b/echo/frontend/src/components/chat/ChatAccordion.tsx index 9ddff900..e2d190a6 100644 --- a/echo/frontend/src/components/chat/ChatAccordion.tsx +++ b/echo/frontend/src/components/chat/ChatAccordion.tsx @@ -1,236 +1,242 @@ import { t } from "@lingui/core/macro"; import { Trans } from "@lingui/react/macro"; import { - useDeleteChatMutation, - useInfiniteProjectChats, - useProjectChatsCount, - useUpdateChatMutation, -} from "./hooks"; -import { - Accordion, - ActionIcon, - Group, - Menu, - Stack, - Text, - Title, - Center, - Loader, + Accordion, + ActionIcon, + Center, + Group, + Loader, + Menu, + Stack, + Text, + Title, } from "@mantine/core"; -import { useParams } from "react-router"; import { IconDotsVertical, IconPencil, IconTrash } from "@tabler/icons-react"; import { formatRelative } from "date-fns"; -import { NavigationButton } from "../common/NavigationButton"; -import { useI18nNavigate } from "@/hooks/useI18nNavigate"; import { Suspense, useEffect } from "react"; -import { ChatSkeleton } from "./ChatSkeleton"; import { useInView } from "react-intersection-observer"; +import { useParams } from "react-router"; +import { useI18nNavigate } from "@/hooks/useI18nNavigate"; +import { NavigationButton } from "../common/NavigationButton"; +import { ChatSkeleton } from "./ChatSkeleton"; +import { + useDeleteChatMutation, + useInfiniteProjectChats, + useProjectChatsCount, + useUpdateChatMutation, +} from "./hooks"; export const ChatAccordionItemMenu = ({ - chat, - size = "sm", + chat, + size = "sm", }: { - chat: Partial; - size?: "sm" | "md"; + chat: Partial; + size?: "sm" | "md"; }) => { - const deleteChatMutation = useDeleteChatMutation(); - const updateChatMutation = useUpdateChatMutation(); - const navigate = useI18nNavigate(); + const deleteChatMutation = useDeleteChatMutation(); + const updateChatMutation = useUpdateChatMutation(); + const navigate = useI18nNavigate(); - return ( - - - - - - + return ( + + + + + + - - - } - disabled={deleteChatMutation.isPending} - onClick={() => { - const newName = prompt( - t`Enter new name for the chat:`, - chat.name ?? "", - ); - if (newName) { - updateChatMutation.mutate({ - chatId: chat.id ?? "", - projectId: (chat.project_id as string) ?? "", - payload: { name: newName }, - }); - } - }} - > - Rename - - } - disabled={deleteChatMutation.isPending} - onClick={() => { - if (confirm(`Are you sure you want to delete this chat?`)) { - deleteChatMutation.mutate({ - chatId: chat.id ?? "", - projectId: (chat.project_id as string) ?? "", - }); - navigate(`/projects/${chat.project_id}/overview`); - } - }} - > - Delete - - - - - ); + + + } + disabled={deleteChatMutation.isPending} + onClick={() => { + const newName = prompt( + t`Enter new name for the chat:`, + chat.name ?? "", + ); + if (newName) { + updateChatMutation.mutate({ + chatId: chat.id ?? "", + payload: { name: newName }, + projectId: (chat.project_id as string) ?? "", + }); + } + }} + > + Rename + + } + disabled={deleteChatMutation.isPending} + onClick={() => { + if (confirm("Are you sure you want to delete this chat?")) { + deleteChatMutation.mutate({ + chatId: chat.id ?? "", + projectId: (chat.project_id as string) ?? "", + }); + navigate(`/projects/${chat.project_id}/overview`); + } + }} + > + Delete + + + + + ); }; // Chat Accordion export const ChatAccordionMain = ({ projectId }: { projectId: string }) => { - const { chatId: activeChatId } = useParams(); - const { ref: loadMoreRef, inView } = useInView(); + const { chatId: activeChatId } = useParams(); + const { ref: loadMoreRef, inView } = useInView(); - const chatsQuery = useInfiniteProjectChats( - projectId, - { - filter: { - project_id: { - _eq: projectId, - }, - _or: [ - // @ts-ignore - ...(activeChatId - ? [ - { - id: { - _eq: activeChatId, - }, - }, - ] - : []), - // @ts-ignore - { - "count(project_chat_messages)": { - _gt: 0, - }, - }, - ], - }, - }, - { - initialLimit: 15, - }, - ); + const chatsQuery = useInfiniteProjectChats( + projectId, + { + filter: { + _or: [ + // @ts-expect-error + ...(activeChatId + ? [ + { + id: { + _eq: activeChatId, + }, + }, + ] + : []), + // @ts-expect-error + { + "count(project_chat_messages)": { + _gt: 0, + }, + }, + ], + project_id: { + _eq: projectId, + }, + }, + }, + { + initialLimit: 15, + }, + ); - // Get total count of chats for display - const chatsCountQuery = useProjectChatsCount(projectId, { - filter: { - project_id: { - _eq: projectId, - }, - _or: [ - // @ts-ignore - ...(activeChatId - ? [ - { - id: { - _eq: activeChatId, - }, - }, - ] - : []), - // @ts-ignore - { - "count(project_chat_messages)": { - _gt: 0, - }, - }, - ], - }, - }); + // Get total count of chats for display + const chatsCountQuery = useProjectChatsCount(projectId, { + filter: { + _or: [ + // @ts-expect-error + ...(activeChatId + ? [ + { + id: { + _eq: activeChatId, + }, + }, + ] + : []), + // @ts-expect-error + { + "count(project_chat_messages)": { + _gt: 0, + }, + }, + ], + project_id: { + _eq: projectId, + }, + }, + }); - // Load more chats when user scrolls to bottom - useEffect(() => { - if (inView && chatsQuery.hasNextPage && !chatsQuery.isFetchingNextPage) { - chatsQuery.fetchNextPage(); - } - }, [ - inView, - chatsQuery.hasNextPage, - chatsQuery.isFetchingNextPage, - chatsQuery.fetchNextPage, - ]); + // Load more chats when user scrolls to bottom + useEffect(() => { + if (inView && chatsQuery.hasNextPage && !chatsQuery.isFetchingNextPage) { + chatsQuery.fetchNextPage(); + } + }, [ + inView, + chatsQuery.hasNextPage, + chatsQuery.isFetchingNextPage, + chatsQuery.fetchNextPage, + ]); - // Flatten all chats from all pages - const allChats = chatsQuery.data?.pages.flatMap((page) => page.chats) ?? []; - const totalChats = Number(chatsCountQuery.data) ?? 0; + // Flatten all chats from all pages + const allChats = + ( + chatsQuery.data?.pages as Array<{ + chats: ProjectChat[]; + nextOffset?: number; + }> + )?.flatMap((page) => page.chats) ?? []; + const totalChats = Number(chatsCountQuery.data) ?? 0; - return ( - - - - - <span className="min-w-[48px] pr-2 font-normal text-gray-500"> - {totalChats} - </span> - <Trans id="project.sidebar.chat.title">Chats</Trans> - - - + return ( + + + + + <span className="min-w-[48px] pr-2 font-normal text-gray-500"> + {totalChats} + </span> + <Trans id="project.sidebar.chat.title">Chats</Trans> + + + - - - {totalChats === 0 && ( - - - No chats found. Start a chat using the "Ask" button. - - - )} - {allChats.map((item, index) => ( - - } - ref={index === allChats.length - 1 ? loadMoreRef : undefined} - > - - - {item.name - ? item.name - : formatRelative( - new Date(item.date_created ?? new Date()), - new Date(), - )} - + + + {totalChats === 0 && ( + + + No chats found. Start a chat using the "Ask" button. + + + )} + {allChats.map((item, index) => ( + + } + ref={index === allChats.length - 1 ? loadMoreRef : undefined} + > + + + {item.name + ? item.name + : formatRelative( + new Date(item.date_created ?? new Date()), + new Date(), + )} + - {item.name && ( - - {formatRelative( - new Date(item.date_created ?? new Date()), - new Date(), - )} - - )} - - - ))} - {chatsQuery.isFetchingNextPage && ( -
- -
- )} - {/* {!chatsQuery.hasNextPage && allChats.length > 0 && ( + {item.name && ( + + {formatRelative( + new Date(item.date_created ?? new Date()), + new Date(), + )} + + )} +
+
+ ))} + {chatsQuery.isFetchingNextPage && ( +
+ +
+ )} + {/* {!chatsQuery.hasNextPage && allChats.length > 0 && (
@@ -239,16 +245,16 @@ export const ChatAccordionMain = ({ projectId }: { projectId: string }) => {
)} */} -
-
-
- ); + + +
+ ); }; export const ChatAccordion = ({ projectId }: { projectId: string }) => { - return ( - }> - - - ); + return ( + }> + + + ); }; diff --git a/echo/frontend/src/components/chat/ChatContextProgress.tsx b/echo/frontend/src/components/chat/ChatContextProgress.tsx index 96905bb0..4b05ca91 100644 --- a/echo/frontend/src/components/chat/ChatContextProgress.tsx +++ b/echo/frontend/src/components/chat/ChatContextProgress.tsx @@ -1,89 +1,89 @@ import { t } from "@lingui/core/macro"; -import { capitalize } from "@/lib/utils"; import { Box, Progress, Skeleton, Tooltip } from "@mantine/core"; import { ENABLE_CHAT_AUTO_SELECT } from "@/config"; +import { capitalize } from "@/lib/utils"; import { useProjectChatContext } from "./hooks"; export const ChatContextProgress = ({ chatId }: { chatId: string }) => { - const chatContextQuery = useProjectChatContext(chatId); + const chatContextQuery = useProjectChatContext(chatId); - if (chatContextQuery.isLoading) { - return ( - - ); - } + if (chatContextQuery.isLoading) { + return ( + + ); + } - if ( - ENABLE_CHAT_AUTO_SELECT && - chatContextQuery.data?.auto_select_bool && - chatContextQuery.data?.conversations.length === 0 - ) { - return null; - } + if ( + ENABLE_CHAT_AUTO_SELECT && + chatContextQuery.data?.auto_select_bool && + chatContextQuery.data?.conversations.length === 0 + ) { + return null; + } - const conversationsAlreadyAdded = chatContextQuery.data?.conversations - .filter((c) => c.locked) - .sort((a, b) => b.token_usage - a.token_usage); + const conversationsAlreadyAdded = chatContextQuery.data?.conversations + .filter((c) => c.locked) + .sort((a, b) => b.token_usage - a.token_usage); - const conversationsToBeAdded = chatContextQuery.data?.conversations - .filter((c) => !c.locked) - .sort((a, b) => b.token_usage - a.token_usage); + const conversationsToBeAdded = chatContextQuery.data?.conversations + .filter((c) => !c.locked) + .sort((a, b) => b.token_usage - a.token_usage); - const getColor = (baseColor: string) => { - if (ENABLE_CHAT_AUTO_SELECT && chatContextQuery.data?.auto_select_bool) { - return "green.6"; - } + const getColor = (baseColor: string) => { + if (ENABLE_CHAT_AUTO_SELECT && chatContextQuery.data?.auto_select_bool) { + return "green.6"; + } - return baseColor; - }; + return baseColor; + }; - return ( - - - {conversationsAlreadyAdded?.map((m, idx) => ( - - - - ))} + return ( + + + {conversationsAlreadyAdded?.map((m) => ( + + + + ))} - {conversationsToBeAdded?.map((m, idx) => ( - - - - ))} + {conversationsToBeAdded?.map((m) => ( + + + + ))} - {chatContextQuery.data?.messages.map((m, idx) => ( - - - - ))} - - - ); + {chatContextQuery.data?.messages.map((m, idx) => ( + + + + ))} + + + ); }; diff --git a/echo/frontend/src/components/chat/ChatHistoryMessage.tsx b/echo/frontend/src/components/chat/ChatHistoryMessage.tsx index cb07c2b2..c6f8ce12 100644 --- a/echo/frontend/src/components/chat/ChatHistoryMessage.tsx +++ b/echo/frontend/src/components/chat/ChatHistoryMessage.tsx @@ -1,189 +1,177 @@ -import { t } from "@lingui/core/macro"; import { Trans } from "@lingui/react/macro"; -import { ChatMessage } from "@/components/chat/ChatMessage"; -import { - Badge, - Box, - Group, - Text, - ActionIcon, - Tooltip, - Collapse, - Divider, -} from "@mantine/core"; -import { Markdown } from "@/components/common/Markdown"; -import React, { useEffect, useState } from "react"; +import { Box, Collapse, Divider, Group, Text } from "@mantine/core"; import { formatDate } from "date-fns"; -import { cn } from "@/lib/utils"; +import type React from "react"; +import { useEffect, useState } from "react"; +import { useParams } from "react-router"; +import { ChatMessage } from "@/components/chat/ChatMessage"; import { CopyRichTextIconButton } from "@/components/common/CopyRichTextIconButton"; +import { Markdown } from "@/components/common/Markdown"; import { ConversationLinks } from "@/components/conversation/ConversationLinks"; -import SourcesSearched from "./SourcesSearched"; -import { I18nLink } from "@/components/common/i18nLink"; -import { useParams } from "react-router"; -import { IconInfoCircle } from "@tabler/icons-react"; -import { Sources } from "./Sources"; -import { ReferencesIconButton } from "../common/ReferencesIconButton"; -import { References } from "./References"; import { ENABLE_CHAT_AUTO_SELECT } from "@/config"; +import { cn } from "@/lib/utils"; +import { ReferencesIconButton } from "../common/ReferencesIconButton"; import { extractMessageMetadata } from "./chatUtils"; +import { References } from "./References"; +import { Sources } from "./Sources"; +import SourcesSearched from "./SourcesSearched"; export const ChatHistoryMessage = ({ - message, - section, - referenceIds, - setReferenceIds, + message, + section, + referenceIds, + setReferenceIds, }: { - message: ChatHistory[number]; - section?: React.ReactNode; - referenceIds?: string[]; - setReferenceIds?: (ids: string[]) => void; + message: ChatHistory[number]; + section?: React.ReactNode; + referenceIds?: string[]; + setReferenceIds?: (ids: string[]) => void; }) => { - const [metadata, setMetadata] = useState([]); - const { projectId } = useParams(); + const [metadata, setMetadata] = useState([]); + const { projectId } = useParams(); - useEffect(() => { - const flattenedItems = extractMessageMetadata(message); - setMetadata(flattenedItems); - }, [message]); + useEffect(() => { + const flattenedItems = extractMessageMetadata(message); + setMetadata(flattenedItems); + }, [message]); - const isSelected = referenceIds?.includes(message.id) ?? false; + const isSelected = referenceIds?.includes(message.id) ?? false; - if (message.role === "system") { - return null; - } + if (message.role === "system") { + return null; + } - if (["user", "assistant"].includes(message.role)) { - return ( - <> - {ENABLE_CHAT_AUTO_SELECT && - metadata?.length > 0 && - metadata?.some((item) => item.type === "reference") && ( -
- -
- )} - {message?.metadata?.some( - (metadata) => metadata.type === "reference", - ) && ( -
- -
- )} + if (["user", "assistant"].includes(message.role)) { + return ( + <> + {ENABLE_CHAT_AUTO_SELECT && + metadata?.length > 0 && + metadata?.some((item) => item.type === "reference") && ( +
+ +
+ )} + {message?.metadata?.some( + (metadata) => metadata.type === "reference", + ) && ( +
+ +
+ )} - {message.content && ( - - - {formatDate( - // @ts-expect-error message is not typed - new Date(message.createdAt ?? new Date()), - "MMM d, h:mm a", - )} - - - + {message.content && ( + + + {formatDate( + // @ts-expect-error message is not typed + new Date(message.createdAt ?? new Date()), + "MMM d, h:mm a", + )} + + + - {/* Info button for citations */} - {ENABLE_CHAT_AUTO_SELECT && - metadata?.length > 0 && - metadata?.some((item) => item.type === "citation") && ( - { - if (setReferenceIds) { - setReferenceIds( - show - ? [...(referenceIds || []), message.id] - : (referenceIds || []).filter( - (id) => id !== message.id, - ), - ); - } - }} - /> - )} - {message?.metadata?.length > 0 && - message?.metadata?.some( - (item) => item.type === "citation", - ) && ( - { - if (setReferenceIds) { - setReferenceIds( - show - ? [...(referenceIds || []), message.id] - : (referenceIds || []).filter( - (id) => id !== message.id, - ), - ); - } - }} - /> - )} - - - } - > - + {/* Info button for citations */} + {ENABLE_CHAT_AUTO_SELECT && + metadata?.length > 0 && + metadata?.some((item) => item.type === "citation") && ( + { + if (setReferenceIds) { + setReferenceIds( + show + ? [...(referenceIds || []), message.id] + : (referenceIds || []).filter( + (id) => id !== message.id, + ), + ); + } + }} + /> + )} + {message?.metadata?.length > 0 && + message?.metadata?.some( + (item) => item.type === "citation", + ) && ( + { + if (setReferenceIds) { + setReferenceIds( + show + ? [...(referenceIds || []), message.id] + : (referenceIds || []).filter( + (id) => id !== message.id, + ), + ); + } + }} + /> + )} + + + } + > + - {/* Show citations inside the chat bubble when toggled */} - - -
- {ENABLE_CHAT_AUTO_SELECT && - metadata.length > 0 && - metadata.some((item) => item.type === "citation") && ( - - )} - {message?.metadata?.length > 0 && - message?.metadata?.some( - (item) => item.type === "citation", - ) && ( - - )} -
-
-
- )} - - ); - } + {/* Show citations inside the chat bubble when toggled */} + + +
+ {ENABLE_CHAT_AUTO_SELECT && + metadata.length > 0 && + metadata.some((item) => item.type === "citation") && ( + + )} + {message?.metadata?.length > 0 && + message?.metadata?.some( + (item) => item.type === "citation", + ) && ( + + )} +
+
+ + )} + + ); + } - if (message.role === "dembrane") { - if (message.content === "searched") { - return ( - - - - ); - } - } + if (message.role === "dembrane") { + if (message.content === "searched") { + return ( + + + + ); + } + } - if (message?._original?.added_conversations?.length > 0) { - const conversations = message?._original?.added_conversations - .map((ac) => ac.conversation_id) - .filter((conv) => conv != null); + if (message?._original?.added_conversations?.length > 0) { + const conversations = message?._original?.added_conversations + .map((ac) => ac.conversation_id) + .filter((conv) => conv != null); - return conversations.length > 0 ? ( - - - - Context added: - - - - - ) : null; - } + return conversations.length > 0 ? ( + // biome-ignore lint/a11y/useValidAriaRole: role is a component prop for styling, not an ARIA attribute + + + + Context added: + + + + + ) : null; + } - return null; + return null; }; diff --git a/echo/frontend/src/components/chat/ChatMessage.tsx b/echo/frontend/src/components/chat/ChatMessage.tsx index c3834a4b..a9c0856a 100644 --- a/echo/frontend/src/components/chat/ChatMessage.tsx +++ b/echo/frontend/src/components/chat/ChatMessage.tsx @@ -1,41 +1,41 @@ -import { cn } from "@/lib/utils"; import { Paper, Stack, Text } from "@mantine/core"; -import React from "react"; +import type React from "react"; +import { cn } from "@/lib/utils"; type Props = { - children?: React.ReactNode; - section?: React.ReactNode; - role: "user" | "dembrane" | "assistant"; + children?: React.ReactNode; + section?: React.ReactNode; + role: "user" | "dembrane" | "assistant"; }; export const ChatMessage = ({ children, section, role }: Props) => { - return ( -
- {role === "dembrane" && ( - - {children} - - )} - {["user", "assistant"].includes(role) && ( - - -
{children}
- {section &&
{section}
} -
-
- )} -
- ); + return ( +
+ {role === "dembrane" && ( + + {children} + + )} + {["user", "assistant"].includes(role) && ( + + +
{children}
+ {section &&
{section}
} +
+
+ )} +
+ ); }; diff --git a/echo/frontend/src/components/chat/ChatSkeleton.tsx b/echo/frontend/src/components/chat/ChatSkeleton.tsx index b69fb68e..7d225e8a 100644 --- a/echo/frontend/src/components/chat/ChatSkeleton.tsx +++ b/echo/frontend/src/components/chat/ChatSkeleton.tsx @@ -1,23 +1,23 @@ import { Trans } from "@lingui/react/macro"; -import { Accordion, Group, Skeleton, Stack, Title } from "@mantine/core"; +import { Accordion, Group, Title } from "@mantine/core"; import { BaseSkeleton } from "../common/BaseSkeleton"; import { LoadingSpinner } from "../common/LoadingSpinner"; export const ChatSkeleton = () => { - return ( - - - - + return ( + + + + - - <Trans id="chat.accordion.skeleton.title">Chats</Trans> - - - - - - - - ); + + <Trans id="chat.accordion.skeleton.title">Chats</Trans> + + + + + + + + ); }; diff --git a/echo/frontend/src/components/chat/ChatTemplatesMenu.tsx b/echo/frontend/src/components/chat/ChatTemplatesMenu.tsx index c8ec2a5e..a422e043 100644 --- a/echo/frontend/src/components/chat/ChatTemplatesMenu.tsx +++ b/echo/frontend/src/components/chat/ChatTemplatesMenu.tsx @@ -1,14 +1,6 @@ import { t } from "@lingui/core/macro"; import { Trans } from "@lingui/react/macro"; -import { - ActionIcon, - Group, - Paper, - Pill, - Stack, - Text, - Tooltip, -} from "@mantine/core"; +import { ActionIcon, Group, Paper, Stack, Text, Tooltip } from "@mantine/core"; import { useDisclosure } from "@mantine/hooks"; import { IconDots } from "@tabler/icons-react"; import { TemplatesModal } from "./TemplatesModal"; @@ -17,90 +9,90 @@ import { quickAccessTemplates, Templates } from "./templates"; // all this function does is that if someone clicks on a template, it will set the input to the template content // we need input to check if there is something in the chat box already export const ChatTemplatesMenu = ({ - onTemplateSelect, - selectedTemplateKey, + onTemplateSelect, + selectedTemplateKey, }: { - onTemplateSelect: ({ - content, - key, - }: { - content: string; - key: string; - }) => void; - selectedTemplateKey?: string | null; + onTemplateSelect: ({ + content, + key, + }: { + content: string; + key: string; + }) => void; + selectedTemplateKey?: string | null; }) => { - const [opened, { open, close }] = useDisclosure(false); + const [opened, { open, close }] = useDisclosure(false); - // Check if selected template is from modal (not in quick access) - const isModalTemplateSelected = - selectedTemplateKey && - !quickAccessTemplates.some((t) => t.title === selectedTemplateKey); + // Check if selected template is from modal (not in quick access) + const isModalTemplateSelected = + selectedTemplateKey && + !quickAccessTemplates.some((t) => t.title === selectedTemplateKey); - const selectedModalTemplate = isModalTemplateSelected - ? Templates.find((t) => t.title === selectedTemplateKey) - : null; + const selectedModalTemplate = isModalTemplateSelected + ? Templates.find((t) => t.title === selectedTemplateKey) + : null; - return ( - <> - - - - Suggested: - - {quickAccessTemplates.map((t) => { - const isSelected = selectedTemplateKey === t.title; - return ( - // no translations for now - - onTemplateSelect({ content: t.content, key: t.title }) - } - > - {t.title} - - ); - })} - {/* Show selected modal template */} - {selectedModalTemplate && ( - - onTemplateSelect({ - content: selectedModalTemplate.content, - key: selectedModalTemplate.title, - }) - } - > - {selectedModalTemplate.title} - - )} - - - - - - - - - - ); + return ( + <> + + + + Suggested: + + {quickAccessTemplates.map((t) => { + const isSelected = selectedTemplateKey === t.title; + return ( + // no translations for now + + onTemplateSelect({ content: t.content, key: t.title }) + } + > + {t.title} + + ); + })} + {/* Show selected modal template */} + {selectedModalTemplate && ( + + onTemplateSelect({ + content: selectedModalTemplate.content, + key: selectedModalTemplate.title, + }) + } + > + {selectedModalTemplate.title} + + )} + + + + + + + + + + ); }; diff --git a/echo/frontend/src/components/chat/References.tsx b/echo/frontend/src/components/chat/References.tsx index 91d3c594..bef2e637 100644 --- a/echo/frontend/src/components/chat/References.tsx +++ b/echo/frontend/src/components/chat/References.tsx @@ -1,55 +1,57 @@ -import { Box, Badge, Group, Text, HoverCard, List } from "@mantine/core"; -import { I18nLink } from "@/components/common/i18nLink"; import { Trans } from "@lingui/react/macro"; +import { Badge, Box, Text } from "@mantine/core"; +import { I18nLink } from "@/components/common/i18nLink"; export const References = ({ - metadata, - projectId, + metadata, + projectId, }: { - metadata: any[]; - projectId: string | undefined; + // biome-ignore lint/suspicious/noExplicitAny: needs to be fixed + metadata: any[]; + projectId: string | undefined; }) => { - const citations = metadata.filter((m) => m.type === "citation"); + const citations = metadata.filter((m) => m.type === "citation"); - if (citations.length === 0) return null; + if (citations.length === 0) return null; - return ( - - - References - + return ( + + + References + -
    - {citations.map((citation, index) => ( -
  • - - - {citation.reference_text} - - - - - {citation?.conversation_title || - citation?.conversation?.participant_name || ( - Untitled Conversation - )} - - - - -
  • - ))} -
-
- ); +
    + {citations.map((citation, index) => ( + // biome-ignore lint/suspicious/noArrayIndexKey: needs to be fixed +
  • + + + {citation.reference_text} + + + + + {citation?.conversation_title || + citation?.conversation?.participant_name || ( + Untitled Conversation + )} + + + + +
  • + ))} +
+
+ ); }; diff --git a/echo/frontend/src/components/chat/Sources.tsx b/echo/frontend/src/components/chat/Sources.tsx index 55fe1811..cc243993 100644 --- a/echo/frontend/src/components/chat/Sources.tsx +++ b/echo/frontend/src/components/chat/Sources.tsx @@ -1,41 +1,45 @@ -import { Box, Badge, Group, Text } from "@mantine/core"; -import { I18nLink } from "@/components/common/i18nLink"; import { Trans } from "@lingui/react/macro"; +import { Badge, Box, Group, Text } from "@mantine/core"; +import { I18nLink } from "@/components/common/i18nLink"; export const Sources = ({ - metadata, - projectId, + metadata, + projectId, }: { - metadata: any[]; - projectId: string | undefined; + // biome-ignore lint/suspicious/noExplicitAny: needs to be fixed + metadata: any[]; + projectId: string | undefined; }) => { - const references = metadata.filter((m) => m.type === "reference"); + const references = metadata.filter((m) => m.type === "reference"); - if (references.length === 0) return null; + if (references.length === 0) return null; - return ( - - - - - The following conversations were automatically added to the context - - - - {references.map((ref, index) => ( - - - {ref?.conversation_title || - ref?.conversation?.participant_name || ( - Source {index + 1} - )} - - - ))} - - - ); + return ( + + + + + + The following conversations were automatically added to the context + + + + + {references.map((ref, index) => ( + + + {ref?.conversation_title || + ref?.conversation?.participant_name || ( + Source {index + 1} + )} + + + ))} + + + ); }; diff --git a/echo/frontend/src/components/chat/SourcesSearch.tsx b/echo/frontend/src/components/chat/SourcesSearch.tsx index 8454ea2c..4104e5fb 100644 --- a/echo/frontend/src/components/chat/SourcesSearch.tsx +++ b/echo/frontend/src/components/chat/SourcesSearch.tsx @@ -1,20 +1,19 @@ import { Trans } from "@lingui/react/macro"; import { Box, Progress, Text } from "@mantine/core"; - type SourcesSearchProps = { - progressValue: number; + progressValue: number; }; const SourcesSearch = ({ progressValue }: SourcesSearchProps) => { - return ( - - - Searching through the most relevant sources - - - - ); + return ( + + + Searching through the most relevant sources + + + + ); }; export default SourcesSearch; diff --git a/echo/frontend/src/components/chat/SourcesSearched.tsx b/echo/frontend/src/components/chat/SourcesSearched.tsx index aec9f4df..01daee2d 100644 --- a/echo/frontend/src/components/chat/SourcesSearched.tsx +++ b/echo/frontend/src/components/chat/SourcesSearched.tsx @@ -3,18 +3,18 @@ import { Box, Group, Text } from "@mantine/core"; import { IconCheck } from "@tabler/icons-react"; const SourcesSearched = () => { - return ( - - - - - - - Searched through the most relevant sources - - - - ); + return ( + + + + + + + Searched through the most relevant sources + + + + ); }; export default SourcesSearched; diff --git a/echo/frontend/src/components/chat/TemplatesModal.tsx b/echo/frontend/src/components/chat/TemplatesModal.tsx index 5098c60e..30ec6576 100644 --- a/echo/frontend/src/components/chat/TemplatesModal.tsx +++ b/echo/frontend/src/components/chat/TemplatesModal.tsx @@ -1,134 +1,134 @@ import { t } from "@lingui/core/macro"; import { Trans } from "@lingui/react/macro"; import { - ActionIcon, - Anchor, - Group, - Modal, - Paper, - ScrollArea, - Stack, - Text, - TextInput, - UnstyledButton, + ActionIcon, + Anchor, + Group, + Modal, + Paper, + ScrollArea, + Stack, + Text, + TextInput, + UnstyledButton, } from "@mantine/core"; import { IconSearch, IconX } from "@tabler/icons-react"; import { useState } from "react"; -import { Template, Templates } from "./templates"; +import { type Template, Templates } from "./templates"; type TemplatesModalProps = { - opened: boolean; - onClose: () => void; - onTemplateSelect: (template: { content: string; key: string }) => void; - selectedTemplateKey?: string | null; + opened: boolean; + onClose: () => void; + onTemplateSelect: (template: { content: string; key: string }) => void; + selectedTemplateKey?: string | null; }; export const TemplatesModal = ({ - opened, - onClose, - onTemplateSelect, - selectedTemplateKey, + opened, + onClose, + onTemplateSelect, + selectedTemplateKey, }: TemplatesModalProps) => { - const [searchQuery, setSearchQuery] = useState(""); + const [searchQuery, setSearchQuery] = useState(""); - const filteredTemplates = Templates.filter((template) => - template.title.toLowerCase().includes(searchQuery.toLowerCase()), - ); + const filteredTemplates = Templates.filter((template) => + template.title.toLowerCase().includes(searchQuery.toLowerCase()), + ); - const handleTemplateClick = (template: Template) => { - onTemplateSelect({ content: template.content, key: template.title }); - onClose(); - }; + const handleTemplateClick = (template: Template) => { + onTemplateSelect({ content: template.content, key: template.title }); + onClose(); + }; - return ( - setSearchQuery("")} - title={ - - Templates - - } - size="md" - withinPortal - classNames={{ - content: "h-[500px] flex flex-col overflow-hidden", - body: "flex-1 flex flex-col overflow-hidden", - }} - > -
- } - rightSection={ - searchQuery ? ( - setSearchQuery("")} - > - - - ) : null - } - rightSectionPointerEvents="all" - value={searchQuery} - onChange={(e) => setSearchQuery(e.currentTarget.value)} - className="mb-3" - /> + return ( + setSearchQuery("")} + title={ + + Templates + + } + size="md" + withinPortal + classNames={{ + body: "flex-1 flex flex-col overflow-hidden", + content: "h-[500px] flex flex-col overflow-hidden", + }} + > +
+ } + rightSection={ + searchQuery ? ( + setSearchQuery("")} + > + + + ) : null + } + rightSectionPointerEvents="all" + value={searchQuery} + onChange={(e) => setSearchQuery(e.currentTarget.value)} + className="mb-3" + /> - - - {filteredTemplates.map((template) => { - const isSelected = selectedTemplateKey === template.title; + + + {filteredTemplates.map((template) => { + const isSelected = selectedTemplateKey === template.title; - return ( - handleTemplateClick(template)} - className="w-full" - > - - {template.title} - - - ); - })} - - + return ( + handleTemplateClick(template)} + className="w-full" + > + + {template.title} + + + ); + })} + + - - - Want to add a template to ECHO?{" "} - - Let us know! - - - -
-
- ); + + + Want to add a template to ECHO?{" "} + + Let us know! + + + +
+
+ ); }; diff --git a/echo/frontend/src/components/chat/chatUtils.ts b/echo/frontend/src/components/chat/chatUtils.ts index 8a101a4c..aa369077 100644 --- a/echo/frontend/src/components/chat/chatUtils.ts +++ b/echo/frontend/src/components/chat/chatUtils.ts @@ -1,52 +1,52 @@ import { formatDate } from "date-fns"; export const formatMessage = ( - message: ChatHistory[number], - userName: string = "User", - assistantName: string = "Dembrane", + message: ChatHistory[number], + userName = "User", + assistantName = "Dembrane", ) => { - let date = "Unknown"; - try { - date = formatDate( - new Date(message._original?.date_created ?? new Date()), - "MMM d yy, h:mm:ss a", - ); - } catch (e) { - console.error(e); - } - - if (!["user", "dembrane"].includes(message.role)) { - return ``; - } - - return `*${message.role === "user" ? userName : assistantName} at ${date}:*\n\n${message.content}\n\n\n\n---`; + let date = "Unknown"; + try { + date = formatDate( + new Date(message._original?.date_created ?? new Date()), + "MMM d yy, h:mm:ss a", + ); + } catch (e) { + console.error(e); + } + + if (!["user", "dembrane"].includes(message.role)) { + return ""; + } + + return `*${message.role === "user" ? userName : assistantName} at ${date}:*\n\n${message.content}\n\n\n\n---`; }; export function extractMessageMetadata(message: any) { - if (!Array.isArray(message?.parts)) return []; - - return message.parts - ?.filter((part: any) => part.type === "source") - .flatMap((part: any) => { - const source = part.source?.[0] || {}; - const ratio = source.ratio ?? 0; - - const references = Array.isArray(source.references) - ? source.references.map((item: ProjectChatMessageMetadata) => ({ - ...item, - type: "reference", - ratio: item.ratio ?? ratio, - })) - : []; - - const citations = Array.isArray(source.citations) - ? source.citations.map((item: ProjectChatMessageMetadata) => ({ - ...item, - type: "citation", - ratio: item.ratio ?? ratio, - })) - : []; - - return [...references, ...citations]; - }); + if (!Array.isArray(message?.parts)) return []; + + return message.parts + ?.filter((part: any) => part.type === "source") + .flatMap((part: any) => { + const source = part.source?.[0] || {}; + const ratio = source.ratio ?? 0; + + const references = Array.isArray(source.references) + ? source.references.map((item: ProjectChatMessageMetadata) => ({ + ...item, + ratio: item.ratio ?? ratio, + type: "reference", + })) + : []; + + const citations = Array.isArray(source.citations) + ? source.citations.map((item: ProjectChatMessageMetadata) => ({ + ...item, + ratio: item.ratio ?? ratio, + type: "citation", + })) + : []; + + return [...references, ...citations]; + }); } diff --git a/echo/frontend/src/components/chat/templates.ts b/echo/frontend/src/components/chat/templates.ts index 131d96d8..1da25524 100644 --- a/echo/frontend/src/components/chat/templates.ts +++ b/echo/frontend/src/components/chat/templates.ts @@ -1,19 +1,16 @@ import { t } from "@lingui/core/macro"; -import { IconCalculator, IconNotes, IconBulb } from "@tabler/icons-react"; +import { IconBulb, IconCalculator, IconNotes } from "@tabler/icons-react"; export interface Template { - id: string; - title: string; - icon?: typeof IconNotes; - content: string; + id: string; + title: string; + icon?: typeof IconNotes; + content: string; } export const Templates: Template[] = [ - { - id: "summarize", - title: t`Summarize`, - icon: IconNotes, - content: t`Transform this content into insights that actually matter. Please: + { + content: t`Transform this content into insights that actually matter. Please: Extract core ideas that challenge standard thinking Write like someone who understands nuance, not a textbook @@ -24,12 +21,12 @@ Structure for clarity and impact Balance depth with accessibility Note: If the similarities/differences are too superficial, let me know we need more complex material to analyze.`, - }, - { - id: "compare-contrast", - title: t`Compare & Contrast`, - icon: IconCalculator, - content: t`Analyze these elements with depth and nuance. Please: + icon: IconNotes, + id: "summarize", + title: t`Summarize`, + }, + { + content: t`Analyze these elements with depth and nuance. Please: Focus on unexpected connections and contrasts Go beyond obvious surface-level comparisons @@ -40,12 +37,12 @@ Structure the analysis to build understanding Draw insights that challenge conventional wisdom Note: If the similarities/differences are too superficial, let me know we need more complex material to analyze.`, - }, - { - id: "meeting-notes", - title: t`Meeting Notes`, - icon: IconNotes, - content: t`Transform this discussion into actionable intelligence. Please: + icon: IconCalculator, + id: "compare-contrast", + title: t`Compare & Contrast`, + }, + { + content: t`Transform this discussion into actionable intelligence. Please: Capture the strategic implications, not just talking points Structure it like a thought leader's analysis, not minutes @@ -56,12 +53,12 @@ Organize for clarity and future reference Balance tactical details with strategic vision Note: If the discussion lacks substantial decision points or insights, flag it for deeper exploration next time.`, - }, - { - id: "strategic-planning", - title: t`Strategic Planning`, - icon: IconBulb, - content: t`Develop a strategic framework that drives meaningful outcomes. Please: + icon: IconNotes, + id: "meeting-notes", + title: t`Meeting Notes`, + }, + { + content: t`Develop a strategic framework that drives meaningful outcomes. Please: Identify core objectives and their interdependencies Map out implementation pathways with realistic timelines @@ -72,7 +69,10 @@ Structure the plan for both immediate action and long-term vision Include decision gates and pivot points Note: Focus on strategies that create sustainable competitive advantages, not just incremental improvements.`, - }, + icon: IconBulb, + id: "strategic-planning", + title: t`Strategic Planning`, + }, ]; export const quickAccessTemplates = Templates.slice(0, 3); diff --git a/echo/frontend/src/components/common/BaseSkeleton.tsx b/echo/frontend/src/components/common/BaseSkeleton.tsx index da81be98..a326406d 100644 --- a/echo/frontend/src/components/common/BaseSkeleton.tsx +++ b/echo/frontend/src/components/common/BaseSkeleton.tsx @@ -1,25 +1,26 @@ -import { Skeleton, Stack, Group, Box } from "@mantine/core"; +import { Skeleton, Stack } from "@mantine/core"; interface BaseSkeletonProps { - count?: number; - height?: string; - width?: string; - radius?: string; - className?: string; + count?: number; + height?: string; + width?: string; + radius?: string; + className?: string; } export const BaseSkeleton = ({ - count = 1, - height = "20px", - width = "100%", - radius = "xs", - className = "", + count = 1, + height = "20px", + width = "100%", + radius = "xs", + className = "", }: BaseSkeletonProps) => { - return ( - - {Array.from({ length: count }).map((_, index) => ( - - ))} - - ); + return ( + + {Array.from({ length: count }).map((_, index) => ( + // biome-ignore lint/suspicious/noArrayIndexKey: needs to be fixed + + ))} + + ); }; diff --git a/echo/frontend/src/components/common/Breadcrumbs.tsx b/echo/frontend/src/components/common/Breadcrumbs.tsx index 4f825f22..22538ab0 100644 --- a/echo/frontend/src/components/common/Breadcrumbs.tsx +++ b/echo/frontend/src/components/common/Breadcrumbs.tsx @@ -1,38 +1,40 @@ import { Breadcrumbs as MantineBreadcrumbs, Text } from "@mantine/core"; -import React from "react"; +import type React from "react"; import { I18nLink } from "@/components/common/i18nLink"; interface BreadcrumbItem { - label: React.ReactNode; - link?: string; + label: React.ReactNode; + link?: string; } interface BreadcrumbsProps { - items: BreadcrumbItem[]; + items: BreadcrumbItem[]; } export const Breadcrumbs = ({ items }: BreadcrumbsProps) => { - return ( - - {items.map((item, index) => { - if (item.link) { - return ( - - {item.label} - - ); - } + return ( + + {items.map((item, index) => { + const key = item.link || `${item.label}-${index}`; - return ( - - {item.label} - - ); - })} - - ); + if (item.link) { + return ( + + {item.label} + + ); + } + + return ( + + {item.label} + + ); + })} + + ); }; diff --git a/echo/frontend/src/components/common/ClosableAlert.tsx b/echo/frontend/src/components/common/ClosableAlert.tsx index 0038413c..331d5b2c 100644 --- a/echo/frontend/src/components/common/ClosableAlert.tsx +++ b/echo/frontend/src/components/common/ClosableAlert.tsx @@ -1,15 +1,15 @@ -import { Alert, AlertProps } from "@mantine/core"; +import { Alert, type AlertProps } from "@mantine/core"; import { useDisclosure } from "@mantine/hooks"; export const CloseableAlert = (props: AlertProps) => { - const [alertOpened, alertHandlers] = useDisclosure(true); - return ( - <> - {alertOpened && ( - - {props.children} - - )} - - ); + const [alertOpened, alertHandlers] = useDisclosure(true); + return ( + <> + {alertOpened && ( + + {props.children} + + )} + + ); }; diff --git a/echo/frontend/src/components/common/ConnectionHealthStatus.tsx b/echo/frontend/src/components/common/ConnectionHealthStatus.tsx index 43b58e95..8d392d73 100644 --- a/echo/frontend/src/components/common/ConnectionHealthStatus.tsx +++ b/echo/frontend/src/components/common/ConnectionHealthStatus.tsx @@ -1,34 +1,35 @@ +import { t } from "@lingui/core/macro"; import { Group, Text } from "@mantine/core"; import clsx from "clsx"; -import { t } from "@lingui/core/macro"; type Props = { - isOnline: boolean; - sseConnectionHealthy: boolean; + isOnline: boolean; + sseConnectionHealthy: boolean; }; -export const ConnectionHealthStatus = ({ isOnline, sseConnectionHealthy }: Props) => { - const isHealthy = isOnline && sseConnectionHealthy; +export const ConnectionHealthStatus = ({ + isOnline, + sseConnectionHealthy, +}: Props) => { + const isHealthy = isOnline && sseConnectionHealthy; - return ( - - -
- - {isHealthy ? t`Connection healthy` : t`Connection unhealthy`} - - - - ); + return ( + + +
+ + {isHealthy ? t`Connection healthy` : t`Connection unhealthy`} + + + + ); }; diff --git a/echo/frontend/src/components/common/CopyIconButton.tsx b/echo/frontend/src/components/common/CopyIconButton.tsx index bb2ab6cc..76bdaea1 100644 --- a/echo/frontend/src/components/common/CopyIconButton.tsx +++ b/echo/frontend/src/components/common/CopyIconButton.tsx @@ -1,29 +1,29 @@ import { t } from "@lingui/core/macro"; +import { ActionIcon, type ActionIconProps, Tooltip } from "@mantine/core"; import { IconCheck, IconCopy } from "@tabler/icons-react"; -import { ActionIcon, ActionIconProps, Tooltip } from "@mantine/core"; export const CopyIconButton = ({ - onCopy, - copied, - copyTooltip = t`Copy`, - size = 16, - ...props + onCopy, + copied, + copyTooltip = t`Copy`, + size = 16, + ...props }: { - copyTooltip?: string; - onCopy: () => void; - copied: boolean; + copyTooltip?: string; + onCopy: () => void; + copied: boolean; } & ActionIconProps) => { - return ( - - - {copied ? : } - - - ); + return ( + + + {copied ? : } + + + ); }; diff --git a/echo/frontend/src/components/common/CopyRichTextIconButton.tsx b/echo/frontend/src/components/common/CopyRichTextIconButton.tsx index 159924ca..ded934e3 100644 --- a/echo/frontend/src/components/common/CopyRichTextIconButton.tsx +++ b/echo/frontend/src/components/common/CopyRichTextIconButton.tsx @@ -1,25 +1,25 @@ -import { IconCheck, IconCopy } from "@tabler/icons-react"; import { ActionIcon, Tooltip } from "@mantine/core"; +import { IconCheck, IconCopy } from "@tabler/icons-react"; import useCopyToRichText from "@/hooks/useCopyToRichText"; export const CopyRichTextIconButton = ({ markdown }: { markdown: string }) => { - const { copy, copied } = useCopyToRichText(); + const { copy, copied } = useCopyToRichText(); - return ( - - copy(markdown)} - > - {copied ? : } - - - ); + return ( + + copy(markdown)} + > + {copied ? : } + + + ); }; diff --git a/echo/frontend/src/components/common/DembraneLoadingSpinner/DembraneLoading.css b/echo/frontend/src/components/common/DembraneLoadingSpinner/DembraneLoading.css index 04fbe123..6ed6b010 100644 --- a/echo/frontend/src/components/common/DembraneLoadingSpinner/DembraneLoading.css +++ b/echo/frontend/src/components/common/DembraneLoadingSpinner/DembraneLoading.css @@ -1,33 +1,33 @@ @keyframes rotate { - from { - transform: rotate(0deg); - } + from { + transform: rotate(0deg); + } - to { - transform: rotate(360deg); - } + to { + transform: rotate(360deg); + } } @keyframes fadeInOut { - 0%, - 100% { - opacity: 0.4; - } + 0%, + 100% { + opacity: 0.4; + } - 50% { - opacity: 1; - } + 50% { + opacity: 1; + } } .loading-container { - display: flex; - justify-content: center; - align-items: center; - height: 100%; + display: flex; + justify-content: center; + align-items: center; + height: 100%; } .loading-image { - animation: - rotate 10s linear infinite, - fadeInOut 1.5s ease-in-out infinite; + animation: + rotate 10s linear infinite, + fadeInOut 1.5s ease-in-out infinite; } diff --git a/echo/frontend/src/components/common/DembraneLoadingSpinner/index.tsx b/echo/frontend/src/components/common/DembraneLoadingSpinner/index.tsx index 32d47271..e6643fcc 100644 --- a/echo/frontend/src/components/common/DembraneLoadingSpinner/index.tsx +++ b/echo/frontend/src/components/common/DembraneLoadingSpinner/index.tsx @@ -1,72 +1,73 @@ import { t } from "@lingui/core/macro"; -import React, { useEffect, useState } from "react"; +import type React from "react"; +import { useEffect, useState } from "react"; import "./DembraneLoading.css"; -import dembraneLogoHQ from "../../../assets/dembrane-logo-hq.png"; import { cn } from "@/lib/utils"; +import dembraneLogoHQ from "../../../assets/dembrane-logo-hq.png"; interface DembraneLoadingSpinnerProps { - isLoading: boolean; - showMessage?: boolean; - className?: string; + isLoading: boolean; + showMessage?: boolean; + className?: string; } const DembraneLoadingSpinner: React.FC = ({ - isLoading, - className, - showMessage = true, + isLoading, + className, + showMessage = true, }) => { - const [messageIndex, setMessageIndex] = useState(0); - const [visible, setVisible] = useState(true); + const [messageIndex, setMessageIndex] = useState(0); + const [visible, setVisible] = useState(true); - const messages = [ - t`Welcome to Dembrane!`, - t`Loading`, - t`Preparing your experience`, - t`Almost there`, - t`Just a moment`, - ]; + const messages = [ + t`Welcome to Dembrane!`, + t`Loading`, + t`Preparing your experience`, + t`Almost there`, + t`Just a moment`, + ]; - useEffect(() => { - const interval = setInterval(() => { - setMessageIndex((index) => (index + 1) % messages.length); - }, 3000); // Change message every 3 seconds + useEffect(() => { + const interval = setInterval(() => { + setMessageIndex((index) => (index + 1) % messages.length); + }, 3000); // Change message every 3 seconds - return () => clearInterval(interval); - }, []); + return () => clearInterval(interval); + }, []); - useEffect(() => { - if (!isLoading) { - // Delay the fade-out animation by 1 second - const timer = setTimeout(() => { - setVisible(false); - }, 2500); // 2000ms (2 second) delay + 500ms for fade-out animation + useEffect(() => { + if (!isLoading) { + // Delay the fade-out animation by 1 second + const timer = setTimeout(() => { + setVisible(false); + }, 2500); // 2000ms (2 second) delay + 500ms for fade-out animation - return () => clearTimeout(timer); - } - }, [isLoading]); + return () => clearTimeout(timer); + } + }, [isLoading]); - if (!visible) { - return null; - } + if (!visible) { + return null; + } - return ( -
- Spinning Dembrane Logo to indicate loading - {showMessage &&

{messages[messageIndex]}

} -
- ); + return ( +
+ Spinning Dembrane Logo to indicate loading + {showMessage &&

{messages[messageIndex]}

} +
+ ); }; export default DembraneLoadingSpinner; diff --git a/echo/frontend/src/components/common/DiffViewer.tsx b/echo/frontend/src/components/common/DiffViewer.tsx index d3ddd8eb..e8f8c97e 100644 --- a/echo/frontend/src/components/common/DiffViewer.tsx +++ b/echo/frontend/src/components/common/DiffViewer.tsx @@ -1,40 +1,40 @@ -import React, { useEffect, useMemo, useState } from "react"; +import { Badge, Button, Group, Text, Tooltip } from "@mantine/core"; +import { diffLines, diffSentences, diffWords } from "diff"; +import type React from "react"; import type { ReactNode } from "react"; -import { Text, Group, Button, Switch, Badge, Tooltip } from "@mantine/core"; -import { diffLines, diffWords, diffSentences } from "diff"; - +import { useEffect, useMemo, useState } from "react"; export type DiffViewerProps = { - leftText: string; - rightText: string; - leftTitle?: string; - rightTitle?: string; - note?: string; - /** If provided, note becomes editable. */ - onNoteChange?: (value: string) => void; - /** Start in compact mode (collapses large unchanged blocks). */ - compact?: boolean; - /** Threshold for collapsing unchanged blocks in compact mode. Default 8. */ - collapseThreshold?: number; - /** Number of context lines to show around collapsed blocks. Default 2. */ - contextLines?: number; - /** Component width. Default '100%'. */ - width?: number | string; - /** Component height. Default '70vh' to fit typical modals. */ - height?: number | string; - /** Optional className passthrough. */ - className?: string; - /** Optional sticky area rendered above diff rows inside the scroll container. */ - topStickyContent?: ReactNode; + leftText: string; + rightText: string; + leftTitle?: string; + rightTitle?: string; + note?: string; + /** If provided, note becomes editable. */ + onNoteChange?: (value: string) => void; + /** Start in compact mode (collapses large unchanged blocks). */ + compact?: boolean; + /** Threshold for collapsing unchanged blocks in compact mode. Default 8. */ + collapseThreshold?: number; + /** Number of context lines to show around collapsed blocks. Default 2. */ + contextLines?: number; + /** Component width. Default '100%'. */ + width?: number | string; + /** Component height. Default '70vh' to fit typical modals. */ + height?: number | string; + /** Optional className passthrough. */ + className?: string; + /** Optional sticky area rendered above diff rows inside the scroll container. */ + topStickyContent?: ReactNode; }; // Row rendering type type RowKind = "unchanged" | "added" | "removed" | "modified"; interface Row { - left: string | null; - right: string | null; - kind: RowKind; + left: string | null; + right: string | null; + kind: RowKind; } type SplitMode = "line" | "sentence"; @@ -44,348 +44,443 @@ const normalizeNewlines = (s: string) => (s ?? "").replace(/\r\n/g, "\n"); const sentenceRegex = /[^.!?\n]+[.!?]?(?:\s+|$)/g; const splitValue = (s: string, mode: SplitMode) => { - const normalized = normalizeNewlines(s); - if (mode === "line") return normalized.split("\n"); - const matches = normalized.match(sentenceRegex); - if (!matches) return normalized ? [normalized] : []; - return matches - .map((part) => part.trim()) - .filter((part) => part.length > 0); + const normalized = normalizeNewlines(s); + if (mode === "line") return normalized.split("\n"); + const matches = normalized.match(sentenceRegex); + if (!matches) return normalized ? [normalized] : []; + return matches.map((part) => part.trim()).filter((part) => part.length > 0); }; // Build aligned side-by-side rows from diff blocks function buildRows(leftText: string, rightText: string): Row[] { - const left = normalizeNewlines(leftText ?? ""); - const right = normalizeNewlines(rightText ?? ""); - const useSentenceDiff = !/\n/.test(left) && !/\n/.test(right); - const mode: SplitMode = useSentenceDiff ? "sentence" : "line"; - - const parts = useSentenceDiff - ? diffSentences(left, right) - : diffLines(left, right, { newlineIsToken: true }); - const rows: Row[] = []; - - let i = 0; - while (i < parts.length) { - const cur = parts[i] as any; - - if (cur.added) { - const prev = parts[i - 1] as any | undefined; - // Pair added with a preceding removed when present - if (prev && prev.removed) { - const L = splitValue(prev.value, mode); - const R = splitValue(cur.value, mode); - const max = Math.max(L.length, R.length); - for (let k = 0; k < max; k++) { - const l = k < L.length ? L[k] : null; - const r = k < R.length ? R[k] : null; - if (l !== null && r !== null) { - rows.push({ left: l, right: r, kind: l === r ? "unchanged" : "modified" }); - } else if (l !== null) { - rows.push({ left: l, right: null, kind: "removed" }); - } else { - rows.push({ left: null, right: r, kind: "added" }); - } - } - } else { - const R = splitValue(cur.value, mode); - for (const r of R) rows.push({ left: null, right: r, kind: "added" }); - } - i++; - continue; - } - - if ((cur as any).removed) { - // Only push standalone removals. Paired removals are handled when their matching add appears. - const next = parts[i + 1] as any | undefined; - if (!(next && next.added)) { - const L = splitValue(cur.value, mode); - for (const l of L) rows.push({ left: l, right: null, kind: "removed" }); - } - i++; - continue; - } - - // Unchanged block - const U = splitValue((cur as any).value, mode); - for (const u of U) rows.push({ left: u, right: u, kind: "unchanged" }); - i++; - } - - return rows; + const left = normalizeNewlines(leftText ?? ""); + const right = normalizeNewlines(rightText ?? ""); + const useSentenceDiff = !/\n/.test(left) && !/\n/.test(right); + const mode: SplitMode = useSentenceDiff ? "sentence" : "line"; + + const parts = useSentenceDiff + ? diffSentences(left, right) + : diffLines(left, right, { newlineIsToken: true }); + const rows: Row[] = []; + + let i = 0; + while (i < parts.length) { + const cur = parts[i] as any; + + if (cur.added) { + const prev = parts[i - 1] as any | undefined; + // Pair added with a preceding removed when present + if (prev?.removed) { + const L = splitValue(prev.value, mode); + const R = splitValue(cur.value, mode); + const max = Math.max(L.length, R.length); + for (let k = 0; k < max; k++) { + const l = k < L.length ? L[k] : null; + const r = k < R.length ? R[k] : null; + if (l !== null && r !== null) { + rows.push({ + kind: l === r ? "unchanged" : "modified", + left: l, + right: r, + }); + } else if (l !== null) { + rows.push({ kind: "removed", left: l, right: null }); + } else { + rows.push({ kind: "added", left: null, right: r }); + } + } + } else { + const R = splitValue(cur.value, mode); + for (const r of R) rows.push({ kind: "added", left: null, right: r }); + } + i++; + continue; + } + + if ((cur as any).removed) { + // Only push standalone removals. Paired removals are handled when their matching add appears. + const next = parts[i + 1] as any | undefined; + if (!(next && next.added)) { + const L = splitValue(cur.value, mode); + for (const l of L) rows.push({ kind: "removed", left: l, right: null }); + } + i++; + continue; + } + + // Unchanged block + const U = splitValue((cur as any).value, mode); + for (const u of U) rows.push({ kind: "unchanged", left: u, right: u }); + i++; + } + + return rows; } // Inline word-level highlighting for modified lines. -function WordDiff({ left, right, side }: { left: string | null; right: string | null; side: "left" | "right" }) { - if (left === null || right === null) return {left ?? right ?? ""}; - const chunks = diffWords(left, right); - if (side === "left") { - return ( - - {chunks.map((p: any, i: number) => { - if (p.added) return null; // hide additions on left - const cls = p.removed ? "bg-red-200/70" : undefined; - return ( - - {p.value} - - ); - })} - - ); - } - return ( - - {chunks.map((p: any, i: number) => { - if (p.removed) return null; // hide removals on right - const cls = p.added ? "bg-green-200/70" : undefined; - return ( - - {p.value} - - ); - })} - - ); +function WordDiff({ + left, + right, + side, +}: { + left: string | null; + right: string | null; + side: "left" | "right"; +}) { + if (left === null || right === null) + return {left ?? right ?? ""}; + const chunks = diffWords(left, right); + if (side === "left") { + return ( + + {/** biome-ignore lint/suspicious/noExplicitAny: needs to be fixed */} + {chunks.map((p: any, i: number) => { + if (p.added) return null; // hide additions on left + const cls = p.removed ? "bg-red-200/70" : undefined; + return ( + // biome-ignore lint/suspicious/noArrayIndexKey: needs to be fixed + + {p.value} + + ); + })} + + ); + } + return ( + + {/** biome-ignore lint/suspicious/noExplicitAny: needs to be fixed */} + {chunks.map((p: any, i: number) => { + if (p.removed) return null; // hide removals on right + const cls = p.added ? "bg-green-200/70" : undefined; + return ( + // biome-ignore lint/suspicious/noArrayIndexKey: needs to be fixed + + {p.value} + + ); + })} + + ); } function cn(...classes: Array) { - return classes.filter(Boolean).join(" "); + return classes.filter(Boolean).join(" "); } export const DiffViewer: React.FC = ({ - leftText, - rightText, - leftTitle = "Original", - rightTitle = "Revised", - note, - onNoteChange, - compact = false, - collapseThreshold = 8, - contextLines = 2, - width = "100%", - height = "100%", - className, - topStickyContent, + leftText, + rightText, + leftTitle = "Original", + rightTitle = "Revised", + note, + onNoteChange, + compact = false, + collapseThreshold = 8, + contextLines = 2, + width = "100%", + height = "100%", + className, + topStickyContent, }) => { - const [isCompact, setIsCompact] = useState(compact); - const [expanded, setExpanded] = useState>({}); - - // Reset expansions when inputs change or when compact mode toggles back on - useEffect(() => setExpanded({}), [leftText, rightText]); - useEffect(() => { - if (isCompact) setExpanded({}); - }, [isCompact]); - - const rows = useMemo(() => buildRows(leftText, rightText), [leftText, rightText]); - - const stats = useMemo(() => { - let added = 0; - let removed = 0; - let modified = 0; - let unchanged = 0; - - for (const row of rows) { - switch (row.kind) { - case "added": - added++; - break; - case "removed": - removed++; - break; - case "modified": - modified++; - break; - default: - unchanged++; - } - } - - return { - added, - removed, - modified, - unchanged, - total: rows.length, - delta: added - removed, - }; - }, [rows]); - - // Compute unchanged ranges for compact mode - const plan = useMemo(() => { - if (!isCompact) - return rows.length ? [{ type: "show", from: 0, to: rows.length - 1 }] : []; - - const ranges: Array<{ start: number; end: number }> = []; - let s = -1; - for (let i = 0; i < rows.length; i++) { - if (rows[i].kind === "unchanged") { - if (s === -1) s = i; - } else if (s !== -1) { - const e = i - 1; - if (e - s + 1 >= collapseThreshold) ranges.push({ start: s, end: e }); - s = -1; - } - } - if (s !== -1) { - const e = rows.length - 1; - if (e - s + 1 >= collapseThreshold) ranges.push({ start: s, end: e }); - } - - const out: Array<{ type: "show"; from: number; to: number } | { type: "gap"; at: number; count: number; key: string }> = []; - let idx = 0; - for (const r of ranges) { - if (idx < r.start) out.push({ type: "show", from: idx, to: r.start - 1 }); - const key = `${r.start}-${r.end}`; - out.push({ type: "gap", at: r.start, count: r.end - r.start + 1, key }); - idx = r.end + 1; - } - if (idx < rows.length) out.push({ type: "show", from: idx, to: rows.length - 1 }); - return out; - }, [rows, isCompact, collapseThreshold]); - - // Line numbers for each visible row - const lineNumbers = useMemo(() => { - const nums: Array<{ l?: number; r?: number }> = []; - let l = 1; - let r = 1; - for (const row of rows) { - const obj: { l?: number; r?: number } = {}; - if (row.left !== null) obj.l = l++; - if (row.right !== null) obj.r = r++; - nums.push(obj); - } - return nums; - }, [rows]); - - const RowView: React.FC<{ row: Row; index: number }> = ({ row, index }) => { - const leftCls = - row.kind === "removed" - ? "bg-red-50/80 border-l-4 border-red-400" - : row.kind === "modified" - ? "bg-yellow-50/60 border-l-4 border-yellow-400" - : undefined; - - const rightCls = - row.kind === "added" - ? "bg-green-50/80 border-l-4 border-green-400" - : row.kind === "modified" - ? "bg-yellow-50/60 border-l-4 border-yellow-400" - : undefined; - - return ( -
- {/* Left line */} -
{ - lineNumbers[index].l ?? "" - }
-
- {row.kind === "modified" ? : row.left} -
- {/* Right line */} -
{ - lineNumbers[index].r ?? "" - }
-
- {row.kind === "modified" ? : row.right} -
-
- ); - }; - - const renderRows = () => { - const out: Array = []; - if (!plan.length) return out; - - for (const step of plan as any[]) { - if (step.type === "show") { - for (let i = step.from; i <= step.to; i++) out.push(); - } else { - const { at, count, key } = step; - const isOpen = expanded[key]; - if (!isOpen) { - // top context - for (let i = at; i < Math.min(at + contextLines, at + count); i++) out.push(); - out.push( -
-
-
- {count} unchanged lines - -
-
-
- ); - // bottom context - for (let i = Math.max(at, at + count - contextLines); i < at + count; i++) out.push(); - } else { - for (let i = at; i < at + count; i++) out.push(); - out.push( -
-
-
- -
-
-
- ); - } - } - } - return out; - }; - - return ( -
- {/* Note */} - {onNoteChange ? ( -