diff --git a/frontend/package.json b/frontend/package.json index a4a2cd21fd..48ffebd06d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -59,6 +59,7 @@ "react-resizable-panels": "^2.1.7", "react-router-dom": "^6.15.0", "react-runner": "^1.0.5", + "recharts": "2.15.4", "recoil": "^0.7.7", "rehype-katex": "^7.0.1", "rehype-raw": "^7.0.0", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 530d04377e..e524857160 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -159,6 +159,9 @@ importers: react-runner: specifier: ^1.0.5 version: 1.0.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + recharts: + specifier: 2.15.4 + version: 2.15.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) recoil: specifier: ^0.7.7 version: 0.7.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -1400,7 +1403,7 @@ packages: resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==} engines: {node: '>=14.0.0'} peerDependencies: - rollup: '>=4.22.4' + rollup: '>=3.29.5' peerDependenciesMeta: rollup: optional: true @@ -1706,6 +1709,33 @@ packages: '@types/chai@4.3.13': resolution: {integrity: sha512-+LxQEbg4BDUf88utmhpUpTyYn1zHao443aGnXIAQak9ZMt9Rtsic0Oig0OS1xyIqdDXc5uMekoC6NaiUlkT/qA==} + '@types/d3-array@3.2.1': + resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-ease@3.0.2': + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + + '@types/d3-shape@3.1.7': + resolution: {integrity: sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + + '@types/d3-timer@3.0.2': + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} @@ -2206,6 +2236,10 @@ packages: d3-array@1.2.4: resolution: {integrity: sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==} + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + d3-collection@1.0.7: resolution: {integrity: sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==} @@ -2216,6 +2250,10 @@ packages: d3-dispatch@1.0.6: resolution: {integrity: sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==} + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + d3-force@1.2.1: resolution: {integrity: sha512-HHvehyaiUlVo5CxBJ0yF/xny4xoaxFxDnBXNvNcfW9adORGZfyNF1dj6DGLKyk4Yh3brP/1h3rnDzdIAwL08zg==} @@ -2239,21 +2277,41 @@ packages: d3-path@1.0.9: resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==} + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + d3-quadtree@1.0.7: resolution: {integrity: sha512-RKPAeXnkC59IDGD0Wu5mANy0Q2V28L+fNe65pOCXVdVuTJS3WPKaJlFHer32Rbh9gIo9qMuJXio8ra4+YmIymA==} + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + d3-shape@1.3.7: resolution: {integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==} + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + d3-time-format@2.3.0: resolution: {integrity: sha512-guv6b2H37s2Uq/GefleCDtbe0XZAuy7Wa49VGkPVPMfLL9qObgBST3lEHJBMUp8S7NdLQAGIvr2KXk8Hc98iKQ==} d3-time@1.1.0: resolution: {integrity: sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==} + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + d3-timer@1.0.10: resolution: {integrity: sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==} + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + d@1.0.2: resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==} engines: {node: '>=0.12'} @@ -2287,6 +2345,9 @@ packages: supports-color: optional: true + decimal.js-light@2.5.1: + resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} + decimal.js@10.4.3: resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} @@ -2346,6 +2407,9 @@ packages: dom-accessibility-api@0.5.16: resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + dom-helpers@5.2.1: + resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} + domexception@4.0.0: resolution: {integrity: sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==} engines: {node: '>=12'} @@ -2495,6 +2559,9 @@ packages: event-emitter@0.3.5: resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==} + eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + events@3.3.0: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} @@ -2513,6 +2580,10 @@ packages: resolution: {integrity: sha512-HuC1qF9iTnHDnML9YZAdCDQwT0yKl/U55K4XSUXqGAA2GLoafFgWRqdAbhWJxXaYD4pyoVxAJ8wH670jMpI9DQ==} engines: {node: '>=0.4.0'} + fast-equals@5.2.2: + resolution: {integrity: sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==} + engines: {node: '>=6.0.0'} + fast-glob@3.3.2: resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} engines: {node: '>=8.6.0'} @@ -2821,6 +2892,10 @@ packages: resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} engines: {node: '>= 0.4'} + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + is-alphabetical@2.0.1: resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} @@ -3640,6 +3715,9 @@ packages: react-is@18.2.0: resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + react-markdown@9.0.1: resolution: {integrity: sha512-186Gw/vF1uRkydbsOIkcGXw7aHq0sZOCRFFjGrr7b9+nVZg4UfA4enXCaxm4fUzecU38sWfrNDitGhshuU7rdg==} peerDependencies: @@ -3721,6 +3799,12 @@ packages: react: ^16.0.0 || ^17 || ^18 react-dom: ^16.0.0 || ^17 || ^18 + react-smooth@4.0.4: + resolution: {integrity: sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-style-singleton@2.2.3: resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} engines: {node: '>=10'} @@ -3731,6 +3815,12 @@ packages: '@types/react': optional: true + react-transition-group@4.4.5: + resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} + peerDependencies: + react: '>=16.6.0' + react-dom: '>=16.6.0' + react@18.3.1: resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} @@ -3748,6 +3838,16 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} + recharts-scale@0.4.5: + resolution: {integrity: sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==} + + recharts@2.15.4: + resolution: {integrity: sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==} + engines: {node: '>=14'} + peerDependencies: + react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + recoil@0.7.7: resolution: {integrity: sha512-8Og5KPQW9LwC577Vc7Ug2P0vQshkv1y3zG3tSSkWMqkWSwHmE+by06L8JtnGocjW6gcCvfwB3YtrJG6/tWivNQ==} peerDependencies: @@ -4064,6 +4164,9 @@ packages: through2@2.0.5: resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + tinybench@2.6.0: resolution: {integrity: sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA==} @@ -4247,6 +4350,9 @@ packages: vfile@6.0.1: resolution: {integrity: sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==} + victory-vendor@36.9.2: + resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==} + vite-node@0.34.6: resolution: {integrity: sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA==} engines: {node: '>=v14.18.0'} @@ -5850,6 +5956,30 @@ snapshots: '@types/chai@4.3.13': {} + '@types/d3-array@3.2.1': {} + + '@types/d3-color@3.1.3': {} + + '@types/d3-ease@3.0.2': {} + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-path@3.1.1': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + + '@types/d3-shape@3.1.7': + dependencies: + '@types/d3-path': 3.1.1 + + '@types/d3-time@3.0.4': {} + + '@types/d3-timer@3.0.2': {} + '@types/debug@4.1.12': dependencies: '@types/ms': 0.7.34 @@ -6357,12 +6487,18 @@ snapshots: d3-array@1.2.4: {} + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + d3-collection@1.0.7: {} d3-color@3.1.0: {} d3-dispatch@1.0.6: {} + d3-ease@3.0.1: {} + d3-force@1.2.1: dependencies: d3-collection: 1.0.7 @@ -6391,20 +6527,40 @@ snapshots: d3-path@1.0.9: {} + d3-path@3.1.0: {} + d3-quadtree@1.0.7: {} + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 1.4.5 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 2.3.0 + d3-shape@1.3.7: dependencies: d3-path: 1.0.9 + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + d3-time-format@2.3.0: dependencies: d3-time: 1.1.0 d3-time@1.1.0: {} + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + d3-timer@1.0.10: {} + d3-timer@3.0.1: {} + d@1.0.2: dependencies: es5-ext: 0.10.64 @@ -6428,6 +6584,8 @@ snapshots: dependencies: ms: 2.1.2 + decimal.js-light@2.5.1: {} + decimal.js@10.4.3: {} decode-named-character-reference@1.0.2: @@ -6495,6 +6653,11 @@ snapshots: dom-accessibility-api@0.5.16: {} + dom-helpers@5.2.1: + dependencies: + '@babel/runtime': 7.25.6 + csstype: 3.1.2 + domexception@4.0.0: dependencies: webidl-conversions: 7.0.0 @@ -6669,6 +6832,8 @@ snapshots: d: 1.0.2 es5-ext: 0.10.64 + eventemitter3@4.0.7: {} + events@3.3.0: {} expect@29.7.0: @@ -6690,6 +6855,8 @@ snapshots: acorn: 7.4.1 isarray: 2.0.5 + fast-equals@5.2.2: {} + fast-glob@3.3.2: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -7124,6 +7291,8 @@ snapshots: hasown: 2.0.0 side-channel: 1.0.6 + internmap@2.0.3: {} + is-alphabetical@2.0.1: {} is-alphanumerical@2.0.1: @@ -8224,6 +8393,8 @@ snapshots: react-is@18.2.0: {} + react-is@18.3.1: {} + react-markdown@9.0.1(@types/react@18.3.18)(react@18.3.1): dependencies: '@types/hast': 3.0.4 @@ -8319,6 +8490,14 @@ snapshots: react-dom: 18.3.1(react@18.3.1) sucrase: 3.35.0 + react-smooth@4.0.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + fast-equals: 5.2.2 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-style-singleton@2.2.3(@types/react@18.3.18)(react@18.3.1): dependencies: get-nonce: 1.0.1 @@ -8327,6 +8506,15 @@ snapshots: optionalDependencies: '@types/react': 18.3.18 + react-transition-group@4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.25.6 + dom-helpers: 5.2.1 + loose-envify: 1.4.0 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react@18.3.1: dependencies: loose-envify: 1.4.0 @@ -8356,6 +8544,23 @@ snapshots: dependencies: picomatch: 2.3.1 + recharts-scale@0.4.5: + dependencies: + decimal.js-light: 2.5.1 + + recharts@2.15.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + clsx: 2.1.1 + eventemitter3: 4.0.7 + lodash: 4.17.21 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-is: 18.3.1 + react-smooth: 4.0.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + recharts-scale: 0.4.5 + tiny-invariant: 1.3.3 + victory-vendor: 36.9.2 + recoil@0.7.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: hamt_plus: 1.0.2 @@ -8806,6 +9011,8 @@ snapshots: readable-stream: 2.3.8 xtend: 4.0.2 + tiny-invariant@1.3.3: {} + tinybench@2.6.0: {} tinycolor2@1.6.0: {} @@ -8980,6 +9187,23 @@ snapshots: unist-util-stringify-position: 4.0.0 vfile-message: 4.0.2 + victory-vendor@36.9.2: + dependencies: + '@types/d3-array': 3.2.1 + '@types/d3-ease': 3.0.2 + '@types/d3-interpolate': 3.0.4 + '@types/d3-scale': 4.0.9 + '@types/d3-shape': 3.1.7 + '@types/d3-time': 3.0.4 + '@types/d3-timer': 3.0.2 + d3-array: 3.2.4 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-scale: 4.0.2 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-timer: 3.0.1 + vite-node@0.34.6(@types/node@20.5.7): dependencies: cac: 6.7.14 diff --git a/frontend/src/components/Elements/CustomElement/Imports.ts b/frontend/src/components/Elements/CustomElement/Imports.ts index b81822846e..c6d6f2ed1a 100644 --- a/frontend/src/components/Elements/CustomElement/Imports.ts +++ b/frontend/src/components/Elements/CustomElement/Imports.ts @@ -1,6 +1,7 @@ import * as LucideIcons from 'lucide-react'; import React from 'react'; import * as ReactHookForm from 'react-hook-form'; +import * as Recharts from 'recharts'; import * as Recoil from 'recoil'; import * as Sonner from 'sonner'; import * as Zod from 'zod'; @@ -15,6 +16,7 @@ import * as BadgeComponents from '@/components/ui/badge'; import * as ButtonComponents from '@/components/ui/button'; import * as CardComponents from '@/components/ui/card'; import * as CarouselComponents from '@/components/ui/carousel'; +import * as ChartComponents from '@/components/ui/chart'; import * as CheckboxComponents from '@/components/ui/checkbox'; import * as CommandComponents from '@/components/ui/command'; import * as DialogComponents from '@/components/ui/dialog'; @@ -41,6 +43,7 @@ const Imports = { react: React, sonner: Sonner, zod: Zod, + recharts: Recharts, recoil: Recoil, '@chainlit/react-client': ChainlitReactClient, '@/components/markdown': Markdown, @@ -54,6 +57,7 @@ const Imports = { '@/components/ui/button': ButtonComponents, '@/components/ui/card': CardComponents, '@/components/ui/carousel': CarouselComponents, + '@/components/ui/chart': ChartComponents, '@/components/ui/checkbox': CheckboxComponents, '@/components/ui/command': CommandComponents, '@/components/ui/dialog': DialogComponents, diff --git a/frontend/src/components/chat/Messages/Message/Step.tsx b/frontend/src/components/chat/Messages/Message/Step.tsx index bfc0e27215..a2609efb10 100644 --- a/frontend/src/components/chat/Messages/Message/Step.tsx +++ b/frontend/src/components/chat/Messages/Message/Step.tsx @@ -1,4 +1,14 @@ import { cn } from '@/lib/utils'; +import { + AlertCircle, + BookOpen, + Brain, + Calculator, + Database, + LayoutPanelLeft, + Loader, + Workflow +} from 'lucide-react'; import { PropsWithChildren, useMemo } from 'react'; import type { IStep } from '@chainlit/react-client'; @@ -16,81 +26,240 @@ interface Props { isRunning?: boolean; } -export default function Step({ - step, - children, - isRunning -}: PropsWithChildren) { - const using = useMemo(() => { - return isRunning && step.start && !step.end && !step.isError; - }, [step, isRunning]); +/* -------------------- Icon mapping -------------------- */ +function StepIcon({ + name, + using, + isError +}: { + name: string; + using: boolean; + isError: boolean; +}) { + if (isError) return ; + if (using) return ; + + const n = (name || '').toLowerCase(); + if (n.includes('tænker') || n.includes('think')) + return ; + if (n.includes('hukommelse') || n.includes('memory')) + return ; + if (n.includes('henter data') || n.includes('fetch') || n.includes('data')) + return ; + if (n.includes('beregner') || n.includes('compute') || n.includes('python')) + return ; + if (n.includes('dashboard') || n.includes('update')) + return ; + return ; +} + +/* -------------------- Header row (28px) -------------------- */ +function StepHeader({ + label, + isError, + using +}: { + label: string; + isError: boolean; + using: boolean; +}) { + return ( +
+ {using ? ( + <> + {label} + + ) : ( + <> + {label} + + )} +
+ ); +} + +/* -------------------- Step card (Accordion) -------------------- */ +function StepCard({ step, isRunning }: Props) { + const using = useMemo( + () => Boolean(isRunning && step.start && !step.end && !step.isError), + [isRunning, step.start, step.end, step.isError] + ); const hasContent = step.input || step.output || step.steps?.length; - const isError = step.isError; + const isError = !!step.isError; const stepName = step.name; - // If there's no content, just render the status without accordion if (!hasContent) { - return ( -
-

; + } + + return ( + + + - {using ? ( - <> - {stepName} - - ) : ( - <> - {stepName} - + + + + + {/* The content wrapper MUST be allowed to shrink inside grid/flex → min-w-0 */} +

+ {step.input && ( +
+
+ Input +
+
+                  {step.language === 'python'
+                    ? step.input
+                    : typeof step.input === 'string'
+                    ? step.input
+                    : JSON.stringify(step.input, null, 2)}
+                
+
+ )} + + {step.output && ( +
+
+ Output +
+
+                  {typeof step.output === 'string'
+                    ? step.output
+                    : JSON.stringify(step.output, null, 2)}
+                
+
+ )} +
+ + + + ); +} + +/* -------------------- One item in the vertical stepper -------------------- */ +function StepperItem({ + step, + isRunning, + isFirst, + isLast +}: Props & { isFirst: boolean; isLast: boolean }) { + const using = Boolean(isRunning && step.start && !step.end && !step.isError); + + const NODE_SIZE = 28; + const HEADER_OFFSET = 2; + + return ( +
  • + {' '} + {/* ⬅ allow grid to shrink */} + {/* Left rail + node */} +
    + {!isFirst && ( + + )} + + + style={{ marginTop: HEADER_OFFSET }} + aria-hidden + > + + + + {!isLast && ( + + )}
    - ); - } + {/* Right column: IMPORTANT → min-w-0 to contain long text */} +
    + +
    +
  • + ); +} + +/* -------------------- Stepper list -------------------- */ +function StepperList({ + steps, + isRunning +}: { + steps: IStep[]; + isRunning?: boolean; +}) { + if (steps.length === 0) return null; return ( -
    - - - - {using ? ( - <> - {stepName} - - ) : ( - <> - {stepName} - - )} - - -
    - {children} -
    -
    -
    -
    +
      + {' '} + {/* ⬅ list itself can shrink */} + {steps.map((child, idx) => ( + + ))} +
    + ); +} + +/* -------------------- Public component -------------------- */ +export default function Step({ step, isRunning }: PropsWithChildren) { + const children = (step.steps?.length ?? 0) > 0 ? step.steps! : [step]; + + return ( +
    + {' '} + {/* ⬅ outer container can also shrink */} +
    ); } diff --git a/frontend/src/components/ui/chart.tsx b/frontend/src/components/ui/chart.tsx new file mode 100644 index 0000000000..de4a132634 --- /dev/null +++ b/frontend/src/components/ui/chart.tsx @@ -0,0 +1,362 @@ +import { cn } from '@/lib/utils'; +import * as React from 'react'; +import * as RechartsPrimitive from 'recharts'; + +// Format: { THEME_NAME: CSS_SELECTOR } +const THEMES = { light: '', dark: '.dark' } as const; + +export type ChartConfig = { + [k in string]: { + label?: React.ReactNode; + icon?: React.ComponentType; + } & ( + | { color?: string; theme?: never } + | { color?: never; theme: Record } + ); +}; + +type ChartContextProps = { + config: ChartConfig; +}; + +const ChartContext = React.createContext(null); + +function useChart() { + const context = React.useContext(ChartContext); + + if (!context) { + throw new Error('useChart must be used within a '); + } + + return context; +} + +const ChartContainer = React.forwardRef< + HTMLDivElement, + React.ComponentProps<'div'> & { + config: ChartConfig; + children: React.ComponentProps< + typeof RechartsPrimitive.ResponsiveContainer + >['children']; + } +>(({ id, className, children, config, ...props }, ref) => { + const uniqueId = React.useId(); + const chartId = `chart-${id || uniqueId.replace(/:/g, '')}`; + + return ( + +
    + + + {children} + +
    +
    + ); +}); +ChartContainer.displayName = 'Chart'; + +const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => { + const colorConfig = Object.entries(config).filter( + ([, config]) => config.theme || config.color + ); + + if (!colorConfig.length) { + return null; + } + + return ( +