Commit e879c818 by Liu

fix: 按钮切换&&问答逻辑

parent 66d1d2fd
public-hoist-pattern[]=*@heroui/*
# On Apple Silicon, optional native deps (e.g. @rspack/binding-*) follow the arch
# of the Node that ran `pnpm install`. If you later run Node as x64 (Rosetta),
# rsbuild needs @rspack/binding-darwin-x64 as well — install both CPU variants.
supportedArchitectures.cpu=x64,arm64
......@@ -38,6 +38,9 @@ importers:
github-markdown-css:
specifier: ^5.8.0
version: 5.8.1
mermaid:
specifier: ^11.13.0
version: 11.15.0
pdfjs-dist:
specifier: ^5.4.149
version: 5.4.149
......@@ -132,6 +135,9 @@ importers:
'@types/react-dom':
specifier: ^18.3.0
version: 18.3.7(@types/react@18.3.24)
baseline-browser-mapping:
specifier: ^2.8.31
version: 2.10.29
eslint:
specifier: ^8.57.0
version: 8.57.1
......@@ -212,6 +218,9 @@ packages:
'@antfu/install-pkg@0.4.1':
resolution: {integrity: sha512-T7yB5QNG29afhWVkVq7XeIMBa5U/vs9mX69YqayXypPRmYzUmzwnYltplHmPtZ4HPCn+sQKeXW8I47wCbuBOjw==}
'@antfu/install-pkg@1.1.0':
resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==}
'@antfu/utils@0.7.10':
resolution: {integrity: sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==}
......@@ -294,6 +303,12 @@ packages:
resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==}
engines: {node: '>=6.9.0'}
'@braintree/sanitize-url@7.1.2':
resolution: {integrity: sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==}
'@chevrotain/types@11.1.2':
resolution: {integrity: sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==}
'@clack/core@0.3.5':
resolution: {integrity: sha512-5cfhQNH+1VQ2xLQlmzXMqUoiaH0lRBq9/CLW9lTyMbuKLC3+xEK01tHVvyut++mLOn5urSHmkm6I0Lg9MaJSTQ==}
......@@ -959,6 +974,12 @@ packages:
resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==}
deprecated: Use @eslint/object-schema instead
'@iconify/types@2.0.0':
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
'@iconify/utils@3.1.3':
resolution: {integrity: sha512-LPKOXPn/zV+zis1oOfGWogaXVpqUybF3ZS6SCZIsz8vg0ivVp9+fVqyYB7xq0aiST/VhUQYGO1qo6uoYSiEJqw==}
'@internationalized/date@3.6.0':
resolution: {integrity: sha512-+z6ti+CcJnRlLHok/emGEsWQhe7kfSmEW+/6qCzvKY67YPh7YOBfvc7+/+NXq+zJlbArg30tYpqLjNgcAYv2YQ==}
......@@ -1005,6 +1026,9 @@ packages:
'@jridgewell/trace-mapping@0.3.31':
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
'@mermaid-js/parser@1.1.1':
resolution: {integrity: sha512-VuHdsYMK1bT6X2JbcAaWAhugTRvRBRyuZgd+c22swUeI9g/ntaxF7CY7dYarhZovofCbUNO0G7JesfmNtjYOCw==}
'@module-federation/runtime-tools@0.5.1':
resolution: {integrity: sha512-nfBedkoZ3/SWyO0hnmaxuz0R0iGPSikHZOAZ0N/dVSQaIzlffUo35B5nlC2wgWIc0JdMZfkwkjZRrnuuDIJbzg==}
......@@ -2055,6 +2079,99 @@ packages:
'@types/cors@2.8.19':
resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==}
'@types/d3-array@3.2.2':
resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==}
'@types/d3-axis@3.0.6':
resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==}
'@types/d3-brush@3.0.6':
resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==}
'@types/d3-chord@3.0.6':
resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==}
'@types/d3-color@3.1.3':
resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==}
'@types/d3-contour@3.0.6':
resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==}
'@types/d3-delaunay@6.0.4':
resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==}
'@types/d3-dispatch@3.0.7':
resolution: {integrity: sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==}
'@types/d3-drag@3.0.7':
resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==}
'@types/d3-dsv@3.0.7':
resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==}
'@types/d3-ease@3.0.2':
resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==}
'@types/d3-fetch@3.0.7':
resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==}
'@types/d3-force@3.0.10':
resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==}
'@types/d3-format@3.0.4':
resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==}
'@types/d3-geo@3.1.0':
resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==}
'@types/d3-hierarchy@3.1.7':
resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==}
'@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-polygon@3.0.2':
resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==}
'@types/d3-quadtree@3.0.6':
resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==}
'@types/d3-random@3.0.3':
resolution: {integrity: sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==}
'@types/d3-scale-chromatic@3.1.0':
resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==}
'@types/d3-scale@4.0.9':
resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==}
'@types/d3-selection@3.0.11':
resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==}
'@types/d3-shape@3.1.8':
resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==}
'@types/d3-time-format@4.0.3':
resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==}
'@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/d3-transition@3.0.9':
resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==}
'@types/d3-zoom@3.0.8':
resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==}
'@types/d3@7.4.3':
resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==}
'@types/debug@4.1.12':
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
......@@ -2073,6 +2190,9 @@ packages:
'@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
'@types/geojson@7946.0.16':
resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==}
'@types/hast@3.0.4':
resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
......@@ -2117,6 +2237,9 @@ packages:
'@types/tapable@2.2.7':
resolution: {integrity: sha512-D6QzACV9vNX3r8HQQNTOnpG+Bv1rko+yEA82wKs3O9CQ5+XW7HI7TED17/UE7+5dIxyxZIWTxKbsBeF6uKFCwA==}
'@types/trusted-types@2.0.7':
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
'@types/unist@2.0.11':
resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==}
......@@ -2309,6 +2432,9 @@ packages:
cpu: [x64]
os: [win32]
'@upsetjs/venn.js@2.0.0':
resolution: {integrity: sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==}
'@vitest/eslint-plugin@1.3.12':
resolution: {integrity: sha512-cSEyUYGj8j8SLqKrzN7BlfsJ3wG67eRT25819PXuyoSBogLXiyagdKx4MHWHV1zv+EEuyMXsEKkBEKzXpxyBrg==}
peerDependencies:
......@@ -2508,8 +2634,9 @@ packages:
resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==}
engines: {node: ^4.5.0 || >= 5.9}
baseline-browser-mapping@2.8.6:
resolution: {integrity: sha512-wrH5NNqren/QMtKUEEJf7z86YjfqW/2uw3IL3/xpqZUC95SSVIFXYQeeGjL6FT/X68IROu6RMehZQS5foy2BXw==}
baseline-browser-mapping@2.10.29:
resolution: {integrity: sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ==}
engines: {node: '>=6.0.0'}
hasBin: true
big.js@5.2.2:
......@@ -2706,6 +2833,10 @@ packages:
resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
engines: {node: '>= 10'}
commander@8.3.0:
resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==}
engines: {node: '>= 12'}
comment-parser@1.4.1:
resolution: {integrity: sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==}
engines: {node: '>= 12.0.0'}
......@@ -2750,6 +2881,12 @@ packages:
resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
engines: {node: '>= 0.10'}
cose-base@1.0.3:
resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==}
cose-base@2.2.0:
resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==}
cosmiconfig@8.3.6:
resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==}
engines: {node: '>=14'}
......@@ -2795,6 +2932,162 @@ packages:
csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
cytoscape-cose-bilkent@4.1.0:
resolution: {integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==}
peerDependencies:
cytoscape: ^3.2.0
cytoscape-fcose@2.2.0:
resolution: {integrity: sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==}
peerDependencies:
cytoscape: ^3.2.0
cytoscape@3.33.3:
resolution: {integrity: sha512-Gej7U+OKR+LZ8kvX7rb2HhCYJ0IhvEFsnkud4SB1PR+BUY/TsSO0dmOW59WEVLu51b1Rm+gQRKoz4bLYxGSZ2g==}
engines: {node: '>=0.10'}
d3-array@2.12.1:
resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==}
d3-array@3.2.4:
resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==}
engines: {node: '>=12'}
d3-axis@3.0.0:
resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==}
engines: {node: '>=12'}
d3-brush@3.0.0:
resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==}
engines: {node: '>=12'}
d3-chord@3.0.1:
resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==}
engines: {node: '>=12'}
d3-color@3.1.0:
resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
engines: {node: '>=12'}
d3-contour@4.0.2:
resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==}
engines: {node: '>=12'}
d3-delaunay@6.0.4:
resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==}
engines: {node: '>=12'}
d3-dispatch@3.0.1:
resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==}
engines: {node: '>=12'}
d3-drag@3.0.0:
resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==}
engines: {node: '>=12'}
d3-dsv@3.0.1:
resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==}
engines: {node: '>=12'}
hasBin: true
d3-ease@3.0.1:
resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==}
engines: {node: '>=12'}
d3-fetch@3.0.1:
resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==}
engines: {node: '>=12'}
d3-force@3.0.0:
resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==}
engines: {node: '>=12'}
d3-format@3.1.2:
resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==}
engines: {node: '>=12'}
d3-geo@3.1.1:
resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==}
engines: {node: '>=12'}
d3-hierarchy@3.1.2:
resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==}
engines: {node: '>=12'}
d3-interpolate@3.0.1:
resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
engines: {node: '>=12'}
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-polygon@3.0.1:
resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==}
engines: {node: '>=12'}
d3-quadtree@3.0.1:
resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==}
engines: {node: '>=12'}
d3-random@3.0.1:
resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==}
engines: {node: '>=12'}
d3-sankey@0.12.3:
resolution: {integrity: sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==}
d3-scale-chromatic@3.1.0:
resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==}
engines: {node: '>=12'}
d3-scale@4.0.2:
resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==}
engines: {node: '>=12'}
d3-selection@3.0.0:
resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==}
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@4.1.0:
resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==}
engines: {node: '>=12'}
d3-time@3.1.0:
resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==}
engines: {node: '>=12'}
d3-timer@3.0.1:
resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
engines: {node: '>=12'}
d3-transition@3.0.1:
resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==}
engines: {node: '>=12'}
peerDependencies:
d3-selection: 2 - 3
d3-zoom@3.0.0:
resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==}
engines: {node: '>=12'}
d3@7.9.0:
resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==}
engines: {node: '>=12'}
dagre-d3-es@7.0.14:
resolution: {integrity: sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==}
date-fns@3.6.0:
resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==}
......@@ -2804,6 +3097,9 @@ packages:
dayjs@1.11.18:
resolution: {integrity: sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==}
dayjs@1.11.20:
resolution: {integrity: sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==}
debounce@1.2.1:
resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==}
......@@ -2860,6 +3156,9 @@ packages:
defu@6.1.4:
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
delaunator@5.1.0:
resolution: {integrity: sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==}
delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
......@@ -2902,6 +3201,9 @@ packages:
resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
engines: {node: '>= 4'}
dompurify@3.4.3:
resolution: {integrity: sha512-VVwJidIJcp1hpg2OMXML3ZVRPYSZiq4aX7qBh83BSIpOaRDqI+qxhXjjIWnpzkOXhmp0L81lnoME1mnCc9H48A==}
domutils@3.2.2:
resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
......@@ -2999,6 +3301,9 @@ packages:
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
engines: {node: '>= 0.4'}
es-toolkit@1.46.1:
resolution: {integrity: sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ==}
escalade@3.2.0:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
......@@ -3546,6 +3851,9 @@ packages:
resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==}
engines: {node: '>=10'}
hachure-fill@0.5.2:
resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==}
has-flag@3.0.0:
resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
engines: {node: '>=4'}
......@@ -3617,6 +3925,10 @@ packages:
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
engines: {node: '>=0.10.0'}
iconv-lite@0.6.3:
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
engines: {node: '>=0.10.0'}
ignore@5.3.2:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'}
......@@ -3635,6 +3947,9 @@ packages:
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
engines: {node: '>=6'}
import-meta-resolve@4.2.0:
resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==}
imurmurhash@0.1.4:
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
engines: {node: '>=0.8.19'}
......@@ -3659,6 +3974,13 @@ packages:
react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc
internmap@1.0.1:
resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==}
internmap@2.0.3:
resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
engines: {node: '>=12'}
intersection-observer@0.12.2:
resolution: {integrity: sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==}
......@@ -3846,9 +4168,22 @@ packages:
jszip@3.10.1:
resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==}
katex@0.16.46:
resolution: {integrity: sha512-WHy4Coo+bGZyH7NwJKHkS04YFsFcarWbAEOAC3EMndzdN6VSZqklLLIgfxzyaW9jDoeGYJX9SWbJPKpecox0Uw==}
hasBin: true
keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
khroma@2.1.0:
resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==}
layout-base@1.0.2:
resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==}
layout-base@2.0.1:
resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==}
levn@0.4.1:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
......@@ -3896,6 +4231,9 @@ packages:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
lodash-es@4.18.1:
resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==}
lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
......@@ -3934,6 +4272,11 @@ packages:
markdown-table@3.0.4:
resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==}
marked@16.4.2:
resolution: {integrity: sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==}
engines: {node: '>= 20'}
hasBin: true
math-intrinsics@1.1.0:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'}
......@@ -4014,6 +4357,9 @@ packages:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'}
mermaid@11.15.0:
resolution: {integrity: sha512-pTMbcf3rWdtLiYGpmoTjHEpeY8seiy6sR+9nD7LOs8KfUbHE4lOUAprTRqRAcWSQ6MQpdX+YEsxShtGsINtPtw==}
micromark-core-commonmark@2.0.3:
resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==}
......@@ -4279,6 +4625,9 @@ packages:
package-manager-detector@0.2.11:
resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==}
package-manager-detector@1.6.0:
resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==}
pako@1.0.11:
resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==}
......@@ -4316,6 +4665,9 @@ packages:
path-browserify@1.0.1:
resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
path-data-parser@0.1.0:
resolution: {integrity: sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==}
path-exists@4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
......@@ -4388,6 +4740,12 @@ packages:
resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
engines: {node: '>=4'}
points-on-curve@0.2.0:
resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==}
points-on-path@0.2.1:
resolution: {integrity: sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==}
popper.js@1.16.1:
resolution: {integrity: sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==}
deprecated: You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1
......@@ -4710,12 +5068,21 @@ packages:
deprecated: Rimraf versions prior to v4 are no longer supported
hasBin: true
robust-predicates@3.0.3:
resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==}
roughjs@4.6.6:
resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==}
rslog@1.2.11:
resolution: {integrity: sha512-YgMMzQf6lL9q4rD9WS/lpPWxVNJ1ttY9+dOXJ0+7vJrKCAOT4GH0EiRnBi9mKOitcHiOwjqJPV1n/HRqqgZmOQ==}
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
rw@1.3.3:
resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==}
safe-buffer@5.1.2:
resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
......@@ -4946,6 +5313,9 @@ packages:
style-to-object@1.0.9:
resolution: {integrity: sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw==}
stylis@4.4.0:
resolution: {integrity: sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==}
sucrase@3.35.0:
resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==}
engines: {node: '>=16 || 14 >=14.17'}
......@@ -5054,6 +5424,10 @@ packages:
tinyexec@0.3.2:
resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
tinyexec@1.1.2:
resolution: {integrity: sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==}
engines: {node: '>=18'}
to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
......@@ -5093,6 +5467,10 @@ packages:
peerDependencies:
typescript: '>=4.0.0'
ts-dedent@2.2.0:
resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==}
engines: {node: '>=6.10'}
ts-interface-checker@0.1.13:
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
......@@ -5224,6 +5602,10 @@ packages:
resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
engines: {node: '>= 0.4.0'}
uuid@14.0.0:
resolution: {integrity: sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==}
hasBin: true
validate-npm-package-license@3.0.4:
resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
......@@ -5438,6 +5820,11 @@ snapshots:
package-manager-detector: 0.2.11
tinyexec: 0.3.2
'@antfu/install-pkg@1.1.0':
dependencies:
package-manager-detector: 1.6.0
tinyexec: 1.1.2
'@antfu/utils@0.7.10': {}
'@babel/code-frame@7.24.7':
......@@ -5554,6 +5941,10 @@ snapshots:
'@babel/helper-string-parser': 7.27.1
'@babel/helper-validator-identifier': 7.27.1
'@braintree/sanitize-url@7.1.2': {}
'@chevrotain/types@11.1.2': {}
'@clack/core@0.3.5':
dependencies:
picocolors: 1.1.1
......@@ -6800,6 +7191,14 @@ snapshots:
'@humanwhocodes/object-schema@2.0.3': {}
'@iconify/types@2.0.0': {}
'@iconify/utils@3.1.3':
dependencies:
'@antfu/install-pkg': 1.1.0
'@iconify/types': 2.0.0
import-meta-resolve: 4.2.0
'@internationalized/date@3.6.0':
dependencies:
'@swc/helpers': 0.5.17
......@@ -6860,6 +7259,10 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5
'@mermaid-js/parser@1.1.1':
dependencies:
'@chevrotain/types': 11.1.2
'@module-federation/runtime-tools@0.5.1':
dependencies:
'@module-federation/runtime': 0.5.1
......@@ -8402,6 +8805,123 @@ snapshots:
dependencies:
'@types/node': 22.18.6
'@types/d3-array@3.2.2': {}
'@types/d3-axis@3.0.6':
dependencies:
'@types/d3-selection': 3.0.11
'@types/d3-brush@3.0.6':
dependencies:
'@types/d3-selection': 3.0.11
'@types/d3-chord@3.0.6': {}
'@types/d3-color@3.1.3': {}
'@types/d3-contour@3.0.6':
dependencies:
'@types/d3-array': 3.2.2
'@types/geojson': 7946.0.16
'@types/d3-delaunay@6.0.4': {}
'@types/d3-dispatch@3.0.7': {}
'@types/d3-drag@3.0.7':
dependencies:
'@types/d3-selection': 3.0.11
'@types/d3-dsv@3.0.7': {}
'@types/d3-ease@3.0.2': {}
'@types/d3-fetch@3.0.7':
dependencies:
'@types/d3-dsv': 3.0.7
'@types/d3-force@3.0.10': {}
'@types/d3-format@3.0.4': {}
'@types/d3-geo@3.1.0':
dependencies:
'@types/geojson': 7946.0.16
'@types/d3-hierarchy@3.1.7': {}
'@types/d3-interpolate@3.0.4':
dependencies:
'@types/d3-color': 3.1.3
'@types/d3-path@3.1.1': {}
'@types/d3-polygon@3.0.2': {}
'@types/d3-quadtree@3.0.6': {}
'@types/d3-random@3.0.3': {}
'@types/d3-scale-chromatic@3.1.0': {}
'@types/d3-scale@4.0.9':
dependencies:
'@types/d3-time': 3.0.4
'@types/d3-selection@3.0.11': {}
'@types/d3-shape@3.1.8':
dependencies:
'@types/d3-path': 3.1.1
'@types/d3-time-format@4.0.3': {}
'@types/d3-time@3.0.4': {}
'@types/d3-timer@3.0.2': {}
'@types/d3-transition@3.0.9':
dependencies:
'@types/d3-selection': 3.0.11
'@types/d3-zoom@3.0.8':
dependencies:
'@types/d3-interpolate': 3.0.4
'@types/d3-selection': 3.0.11
'@types/d3@7.4.3':
dependencies:
'@types/d3-array': 3.2.2
'@types/d3-axis': 3.0.6
'@types/d3-brush': 3.0.6
'@types/d3-chord': 3.0.6
'@types/d3-color': 3.1.3
'@types/d3-contour': 3.0.6
'@types/d3-delaunay': 6.0.4
'@types/d3-dispatch': 3.0.7
'@types/d3-drag': 3.0.7
'@types/d3-dsv': 3.0.7
'@types/d3-ease': 3.0.2
'@types/d3-fetch': 3.0.7
'@types/d3-force': 3.0.10
'@types/d3-format': 3.0.4
'@types/d3-geo': 3.1.0
'@types/d3-hierarchy': 3.1.7
'@types/d3-interpolate': 3.0.4
'@types/d3-path': 3.1.1
'@types/d3-polygon': 3.0.2
'@types/d3-quadtree': 3.0.6
'@types/d3-random': 3.0.3
'@types/d3-scale': 4.0.9
'@types/d3-scale-chromatic': 3.1.0
'@types/d3-selection': 3.0.11
'@types/d3-shape': 3.1.8
'@types/d3-time': 3.0.4
'@types/d3-time-format': 4.0.3
'@types/d3-timer': 3.0.2
'@types/d3-transition': 3.0.9
'@types/d3-zoom': 3.0.8
'@types/debug@4.1.12':
dependencies:
'@types/ms': 2.1.0
......@@ -8424,6 +8944,8 @@ snapshots:
'@types/estree@1.0.8': {}
'@types/geojson@7946.0.16': {}
'@types/hast@3.0.4':
dependencies:
'@types/unist': 3.0.3
......@@ -8469,6 +8991,9 @@ snapshots:
dependencies:
tapable: 2.2.3
'@types/trusted-types@2.0.7':
optional: true
'@types/unist@2.0.11': {}
'@types/unist@3.0.3': {}
......@@ -8665,6 +9190,11 @@ snapshots:
'@unrs/resolver-binding-win32-x64-msvc@1.11.1':
optional: true
'@upsetjs/venn.js@2.0.0':
optionalDependencies:
d3-selection: 3.0.0
d3-transition: 3.0.1(d3-selection@3.0.0)
'@vitest/eslint-plugin@1.3.12(eslint@8.57.1)(typescript@5.9.2)':
dependencies:
'@typescript-eslint/scope-manager': 8.44.0
......@@ -8901,7 +9431,7 @@ snapshots:
base64id@2.0.0: {}
baseline-browser-mapping@2.8.6: {}
baseline-browser-mapping@2.10.29: {}
big.js@5.2.2: {}
......@@ -8943,7 +9473,7 @@ snapshots:
browserslist@4.26.2:
dependencies:
baseline-browser-mapping: 2.8.6
baseline-browser-mapping: 2.10.29
caniuse-lite: 1.0.30001743
electron-to-chromium: 1.5.222
node-releases: 2.0.21
......@@ -9088,6 +9618,8 @@ snapshots:
commander@7.2.0: {}
commander@8.3.0: {}
comment-parser@1.4.1: {}
compare-versions@6.1.1: {}
......@@ -9126,6 +9658,14 @@ snapshots:
object-assign: 4.1.1
vary: 1.1.2
cose-base@1.0.3:
dependencies:
layout-base: 1.0.2
cose-base@2.2.0:
dependencies:
layout-base: 2.0.1
cosmiconfig@8.3.6(typescript@5.9.2):
dependencies:
import-fresh: 3.3.1
......@@ -9171,12 +9711,198 @@ snapshots:
csstype@3.1.3: {}
cytoscape-cose-bilkent@4.1.0(cytoscape@3.33.3):
dependencies:
cose-base: 1.0.3
cytoscape: 3.33.3
cytoscape-fcose@2.2.0(cytoscape@3.33.3):
dependencies:
cose-base: 2.2.0
cytoscape: 3.33.3
cytoscape@3.33.3: {}
d3-array@2.12.1:
dependencies:
internmap: 1.0.1
d3-array@3.2.4:
dependencies:
internmap: 2.0.3
d3-axis@3.0.0: {}
d3-brush@3.0.0:
dependencies:
d3-dispatch: 3.0.1
d3-drag: 3.0.0
d3-interpolate: 3.0.1
d3-selection: 3.0.0
d3-transition: 3.0.1(d3-selection@3.0.0)
d3-chord@3.0.1:
dependencies:
d3-path: 3.1.0
d3-color@3.1.0: {}
d3-contour@4.0.2:
dependencies:
d3-array: 3.2.4
d3-delaunay@6.0.4:
dependencies:
delaunator: 5.1.0
d3-dispatch@3.0.1: {}
d3-drag@3.0.0:
dependencies:
d3-dispatch: 3.0.1
d3-selection: 3.0.0
d3-dsv@3.0.1:
dependencies:
commander: 7.2.0
iconv-lite: 0.6.3
rw: 1.3.3
d3-ease@3.0.1: {}
d3-fetch@3.0.1:
dependencies:
d3-dsv: 3.0.1
d3-force@3.0.0:
dependencies:
d3-dispatch: 3.0.1
d3-quadtree: 3.0.1
d3-timer: 3.0.1
d3-format@3.1.2: {}
d3-geo@3.1.1:
dependencies:
d3-array: 3.2.4
d3-hierarchy@3.1.2: {}
d3-interpolate@3.0.1:
dependencies:
d3-color: 3.1.0
d3-path@1.0.9: {}
d3-path@3.1.0: {}
d3-polygon@3.0.1: {}
d3-quadtree@3.0.1: {}
d3-random@3.0.1: {}
d3-sankey@0.12.3:
dependencies:
d3-array: 2.12.1
d3-shape: 1.3.7
d3-scale-chromatic@3.1.0:
dependencies:
d3-color: 3.1.0
d3-interpolate: 3.0.1
d3-scale@4.0.2:
dependencies:
d3-array: 3.2.4
d3-format: 3.1.2
d3-interpolate: 3.0.1
d3-time: 3.1.0
d3-time-format: 4.1.0
d3-selection@3.0.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@4.1.0:
dependencies:
d3-time: 3.1.0
d3-time@3.1.0:
dependencies:
d3-array: 3.2.4
d3-timer@3.0.1: {}
d3-transition@3.0.1(d3-selection@3.0.0):
dependencies:
d3-color: 3.1.0
d3-dispatch: 3.0.1
d3-ease: 3.0.1
d3-interpolate: 3.0.1
d3-selection: 3.0.0
d3-timer: 3.0.1
d3-zoom@3.0.0:
dependencies:
d3-dispatch: 3.0.1
d3-drag: 3.0.0
d3-interpolate: 3.0.1
d3-selection: 3.0.0
d3-transition: 3.0.1(d3-selection@3.0.0)
d3@7.9.0:
dependencies:
d3-array: 3.2.4
d3-axis: 3.0.0
d3-brush: 3.0.0
d3-chord: 3.0.1
d3-color: 3.1.0
d3-contour: 4.0.2
d3-delaunay: 6.0.4
d3-dispatch: 3.0.1
d3-drag: 3.0.0
d3-dsv: 3.0.1
d3-ease: 3.0.1
d3-fetch: 3.0.1
d3-force: 3.0.0
d3-format: 3.1.2
d3-geo: 3.1.1
d3-hierarchy: 3.1.2
d3-interpolate: 3.0.1
d3-path: 3.1.0
d3-polygon: 3.0.1
d3-quadtree: 3.0.1
d3-random: 3.0.1
d3-scale: 4.0.2
d3-scale-chromatic: 3.1.0
d3-selection: 3.0.0
d3-shape: 3.2.0
d3-time: 3.1.0
d3-time-format: 4.1.0
d3-timer: 3.0.1
d3-transition: 3.0.1(d3-selection@3.0.0)
d3-zoom: 3.0.0
dagre-d3-es@7.0.14:
dependencies:
d3: 7.9.0
lodash-es: 4.18.1
date-fns@3.6.0: {}
dayjs@1.11.12: {}
dayjs@1.11.18: {}
dayjs@1.11.20: {}
debounce@1.2.1: {}
debug@2.6.9:
......@@ -9211,6 +9937,10 @@ snapshots:
defu@6.1.4: {}
delaunator@5.1.0:
dependencies:
robust-predicates: 3.0.3
delayed-stream@1.0.0: {}
depd@2.0.0: {}
......@@ -9247,6 +9977,10 @@ snapshots:
dependencies:
domelementtype: 2.3.0
dompurify@3.4.3:
optionalDependencies:
'@types/trusted-types': 2.0.7
domutils@3.2.2:
dependencies:
dom-serializer: 2.0.0
......@@ -9344,6 +10078,8 @@ snapshots:
has-tostringtag: 1.0.2
hasown: 2.0.2
es-toolkit@1.46.1: {}
escalade@3.2.0: {}
escape-html@1.0.3: {}
......@@ -10022,6 +10758,8 @@ snapshots:
dependencies:
duplexer: 0.1.2
hachure-fill@0.5.2: {}
has-flag@3.0.0: {}
has-flag@4.0.0: {}
......@@ -10139,6 +10877,10 @@ snapshots:
dependencies:
safer-buffer: 2.1.2
iconv-lite@0.6.3:
dependencies:
safer-buffer: 2.1.2
ignore@5.3.2: {}
ignore@7.0.5: {}
......@@ -10152,6 +10894,8 @@ snapshots:
parent-module: 1.0.1
resolve-from: 4.0.0
import-meta-resolve@4.2.0: {}
imurmurhash@0.1.4: {}
indent-string@4.0.0: {}
......@@ -10170,6 +10914,10 @@ snapshots:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
internmap@1.0.1: {}
internmap@2.0.3: {}
intersection-observer@0.12.2: {}
intl-messageformat@10.7.16:
......@@ -10329,10 +11077,20 @@ snapshots:
readable-stream: 2.3.8
setimmediate: 1.0.5
katex@0.16.46:
dependencies:
commander: 8.3.0
keyv@4.5.4:
dependencies:
json-buffer: 3.0.1
khroma@2.1.0: {}
layout-base@1.0.2: {}
layout-base@2.0.1: {}
levn@0.4.1:
dependencies:
prelude-ls: 1.2.1
......@@ -10393,6 +11151,8 @@ snapshots:
dependencies:
p-locate: 5.0.0
lodash-es@4.18.1: {}
lodash.merge@4.6.2: {}
lodash@4.17.21: {}
......@@ -10431,6 +11191,8 @@ snapshots:
markdown-table@3.0.4: {}
marked@16.4.2: {}
math-intrinsics@1.1.0: {}
mdast-util-find-and-replace@3.0.2:
......@@ -10612,6 +11374,30 @@ snapshots:
merge2@1.4.1: {}
mermaid@11.15.0:
dependencies:
'@braintree/sanitize-url': 7.1.2
'@iconify/utils': 3.1.3
'@mermaid-js/parser': 1.1.1
'@types/d3': 7.4.3
'@upsetjs/venn.js': 2.0.0
cytoscape: 3.33.3
cytoscape-cose-bilkent: 4.1.0(cytoscape@3.33.3)
cytoscape-fcose: 2.2.0(cytoscape@3.33.3)
d3: 7.9.0
d3-sankey: 0.12.3
dagre-d3-es: 7.0.14
dayjs: 1.11.20
dompurify: 3.4.3
es-toolkit: 1.46.1
katex: 0.16.46
khroma: 2.1.0
marked: 16.4.2
roughjs: 4.6.6
stylis: 4.4.0
ts-dedent: 2.2.0
uuid: 14.0.0
micromark-core-commonmark@2.0.3:
dependencies:
decode-named-character-reference: 1.2.0
......@@ -10971,6 +11757,8 @@ snapshots:
dependencies:
quansync: 0.2.11
package-manager-detector@1.6.0: {}
pako@1.0.11: {}
parent-module@1.0.1:
......@@ -11019,6 +11807,8 @@ snapshots:
path-browserify@1.0.1: {}
path-data-parser@0.1.0: {}
path-exists@4.0.0: {}
path-is-absolute@1.0.1: {}
......@@ -11068,6 +11858,13 @@ snapshots:
pluralize@8.0.0: {}
points-on-curve@0.2.0: {}
points-on-path@0.2.1:
dependencies:
path-data-parser: 0.1.0
points-on-curve: 0.2.0
popper.js@1.16.1: {}
postcss-import@15.1.0(postcss@8.5.6):
......@@ -11427,12 +12224,23 @@ snapshots:
dependencies:
glob: 7.2.3
robust-predicates@3.0.3: {}
roughjs@4.6.6:
dependencies:
hachure-fill: 0.5.2
path-data-parser: 0.1.0
points-on-curve: 0.2.0
points-on-path: 0.2.1
rslog@1.2.11: {}
run-parallel@1.2.0:
dependencies:
queue-microtask: 1.2.3
rw@1.3.3: {}
safe-buffer@5.1.2: {}
safe-buffer@5.2.1: {}
......@@ -11701,6 +12509,8 @@ snapshots:
dependencies:
inline-style-parser: 0.2.4
stylis@4.4.0: {}
sucrase@3.35.0:
dependencies:
'@jridgewell/gen-mapping': 0.3.13
......@@ -11825,6 +12635,8 @@ snapshots:
tinyexec@0.3.2: {}
tinyexec@1.1.2: {}
to-regex-range@5.0.1:
dependencies:
is-number: 7.0.0
......@@ -11860,6 +12672,8 @@ snapshots:
picomatch: 4.0.3
typescript: 5.9.2
ts-dedent@2.2.0: {}
ts-interface-checker@0.1.13: {}
ts-pattern@5.8.0: {}
......@@ -12000,6 +12814,8 @@ snapshots:
utils-merge@1.0.1: {}
uuid@14.0.0: {}
validate-npm-package-license@3.0.4:
dependencies:
spdx-correct: 3.2.0
......
......@@ -24,7 +24,7 @@ export function fetchToolList() {
* @params
* conversationId: 会话id
* busiType: 业务类型,固定值 '01'
* recordType: 记录类型,固定值 'A14'
* recordType: 记录类型,由当前工具 get_tool_list 项的 userRole 第二段决定
*/
export function fetchEfficiencyQuestionList(data: any) {
return http.post('/conversation/api/conversation/mobile/v1/query_strategy_recommend_question', data)
......
......@@ -8,17 +8,20 @@ import type { RootState } from '@/store'
import SendIcon from '@/assets/svg/send.svg?react'
import { type WithAuthProps, withAuth } from '@/auth/withAuth'
import { useAppDispatch, useAppSelector } from '@/store/hook'
import { clearCurrentToolId, setCurrentToolId } from '@/store/conversationSlice'
import { clearBusinessInsightWorkflow, clearCurrentToolId, setCurrentToolId } from '@/store/conversationSlice'
import { setIsAsking } from '@/store/chatSlice'
import { fetchToolList } from '@/api/home'
import { safeSessionStorageGetItem, safeSessionStorageRemoveItem, safeSessionStorageSetItem } from '@/lib/utils'
import { clearWorkflowToolCaches, getWorkflowToolIdAndRecordTypeForRequest, setWorkflowRequestSeedPair } from '@/lib/workflowToolParams'
import type { ToolListRow } from '@/types/toolList'
import { getToolQuestionPairFromRow, isGeneralInstitutionalToolRow, normalizeToolId, readToolListFromSessionStorage } from '@/lib/toolRoleRecordTypes'
import { fetchSessionConversationId, fetchUserQaRecordPage } from '@/api/conversation'
interface ChatEditorProps {
onChange?: (value: string) => void
onFocus?: () => void
onSubmit?: (value: string, toolId?: string) => void
onToolClick?: (isToolBtn: boolean, toolId?: string, toolName?: string, shouldChangeStyle?: boolean, conversationId?: string, isLoading?: boolean) => void
onToolClick?: (isToolBtn: boolean, toolId?: string, toolName?: string, shouldChangeStyle?: boolean, conversationId?: string, isLoading?: boolean, toolMeta?: Pick<ToolListRow, 'commonQuestionApi' | 'businessInsightConfigTypes' | 'showStructuredQueryPlaceholder'>) => void
placeholders: string[]
showContentTips?: boolean
initialValue?: string
......@@ -34,7 +37,7 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth,
const intervalRef = useRef<NodeJS.Timeout | null>(null)
const [isOpenLoginModal, isOpenLoginModalActions] = useToggle()
const isAsking = useAppSelector((state: RootState) => state.chat.isAsking)
const [toolList, setToolList] = useState<any[]>([])
const [toolList, setToolList] = useState<ToolListRow[]>([])
const [selectedToolId, setSelectedToolId] = useState<string | null>(null)
const [isToolBtnActive, setIsToolBtnActive] = useState<boolean>(true)
const currentToolId = useAppSelector((state: RootState) => state.conversation.currentToolId)
......@@ -136,7 +139,7 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth,
const cached = sessionStorage.getItem(cacheKey)
if (cached) {
try {
const { toolList, timestamp } = JSON.parse(cached)
const { toolList, timestamp } = JSON.parse(cached) as { toolList: ToolListRow[], timestamp: number }
if (Date.now() - timestamp < 5 * 60 * 1000) {
setToolList(toolList)
return
......@@ -149,10 +152,10 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth,
// 调用真实 API 获取工具列表
const res = await fetchToolList()
if (res?.data && Array.isArray(res.data) && res.data.length > 0) {
setToolList(res.data)
// 缓存工具列表(包含时间戳)
const rows = res.data as ToolListRow[]
setToolList(rows)
sessionStorage.setItem(cacheKey, JSON.stringify({
toolList: res.data,
toolList: rows,
timestamp: Date.now(),
}))
}
......@@ -277,8 +280,11 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth,
const handleSubmit = () => {
if (isAsking)
return
// 以 DOM 为准,避免 content 状态与 contentEditable 不同步(如 IME、快速连点)导致误判为空
const textFromDom = (editorRef.current?.textContent ?? '').trim()
const text = textFromDom || content.trim()
if (checkAuth()) {
if (content.trim()) {
if (text) {
// 只在提质增效模式下传递 toolId,制度活化不传
let toolId: string | undefined
......@@ -290,7 +296,7 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth,
// 制度活化:不传递 toolId
toolId = undefined
}
onSubmit?.(content.trim(), toolId)
onSubmit?.(text, toolId)
setContent('')
if (editorRef.current) {
editorRef.current.textContent = ''
......@@ -317,90 +323,58 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth,
document.execCommand('insertText', false, text)
}
// 处理制度活化按钮点击:先创建新会话
const handleGeneralClick = async () => {
const pickToolMeta = (t: ToolListRow) => ({
commonQuestionApi: t.commonQuestionApi ?? undefined,
businessInsightConfigTypes: t.businessInsightConfigTypes ?? undefined,
showStructuredQueryPlaceholder: t.showStructuredQueryPlaceholder ?? undefined,
})
/** 底部工具条:统一「清缓存 → 建会话 → 换路由 → 拉 QA → 通知上层」;仅会话参数与后续是否自动 workflow 随是否通用行变化 */
const handleWorkflowToolButtonPress = async (tool: ToolListRow) => {
if (!checkAuth())
return
// 通知上层开始 loading
onToolClick?.(true, undefined, '制度活化', false, undefined, true)
clearWorkflowToolCaches()
dispatch(clearBusinessInsightWorkflow())
const isGeneral = isGeneralInstitutionalToolRow(tool)
const normalizedToolId = normalizeToolId(tool.toolId)
const busiIdForSession = isGeneral ? '' : normalizedToolId
const displayName = tool.toolName?.trim() || ''
const parentIsToolBtn = isGeneral
const eventToolId = isGeneral ? undefined : normalizedToolId
const shouldChangeStyleForParent = !isGeneral
onToolClick?.(
parentIsToolBtn,
eventToolId,
displayName,
shouldChangeStyleForParent,
undefined,
true,
pickToolMeta(tool),
)
try {
// 重置提交按钮状态
dispatch(setIsAsking(false))
// 先更新 Redux,确保状态同步
if (isGeneral) {
dispatch(clearCurrentToolId())
// 立即更新本地状态,让 UI 立即响应
setIsToolBtnActive(true)
setSelectedToolId(null)
safeSessionStorageRemoveItem('showToolQuestion')
safeSessionStorageRemoveItem('currentToolId')
setSessionToolId(null)
setShowToolQuestion(false)
// 先通知上层更新欢迎语(即便后续接口异常也能生效)
onToolClick?.(true, undefined, '制度活化', false)
// 清空路由中的 toolId 参数
onToolClick?.(true, undefined, displayName, false, undefined, undefined, pickToolMeta(tool))
if (toolIdFromUrl) {
const newSearchParams = new URLSearchParams(searchParams)
newSearchParams.delete('toolId')
setSearchParams(newSearchParams, { replace: true })
}
// 先调用 fetchSessionConversationId 获取会话ID
// 等待 token 就绪后再调用接口
await waitForToken()
const requestData = {
busiId: '',
}
const res = await fetchSessionConversationId(requestData)
if (res?.data?.conversationId) {
const conversationId = res.data.conversationId
// 获取到会话ID后,通知上层更新欢迎语并传递 conversationId
onToolClick?.(true, undefined, '制度活化', false, conversationId)
// 在 navigate 之前设置标记,避免 Chat 组件的 useEffect 重复调用接口
sessionStorage.setItem('toolHistoryLoading', conversationId)
// 更新路由到新的会话ID
navigate(`/chat/${conversationId}`, {
replace: true,
state: {
toolId: null,
skipHistoryLoad: true, // 标记跳过历史记录加载,因为 ChatEditor 会处理
},
})
// 使用获取到的会话ID调用历史会话
await waitForToken()
const qaRes = await fetchUserQaRecordPage(conversationId, '')
console.log('qaRes chatEditor11111', qaRes)
// 通过自定义事件将历史记录数据传递给父组件进行渲染
window.dispatchEvent(new CustomEvent('toolHistoryLoaded', {
detail: {
conversationId,
toolId: '',
toolName: '制度活化',
qaRecords: qaRes?.data || [],
},
}))
// 清除标记,避免影响后续路由切换
sessionStorage.removeItem('toolHistoryLoading')
}
}
catch (error) {
console.error('获取会话ID或历史记录失败:', error)
// 出错时也要清除标记
sessionStorage.removeItem('toolHistoryLoading')
}
finally {
// 通知上层结束 loading
onToolClick?.(true, undefined, '制度活化', false, undefined, false)
}
}
// 处理工具按钮点击:先创建新会话,再切换工具
const handleToolClick = async (tool: any) => {
// 通知上层开始 loading
onToolClick?.(false, tool.toolId, tool.toolName, true, undefined, true)
try {
// 重置提交按钮状态
dispatch(setIsAsking(false))
if (tool.toolName === '数据助手') {
else {
if (tool.showStructuredQueryPlaceholder === true) {
safeSessionStorageSetItem('showToolQuestion', 'true')
setShowToolQuestion(true)
}
......@@ -408,80 +382,106 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth,
safeSessionStorageRemoveItem('showToolQuestion')
setShowToolQuestion(false)
}
dispatch(setCurrentToolId(tool.toolId))
setSelectedToolId(tool.toolId)
dispatch(setCurrentToolId(normalizedToolId))
setSelectedToolId(normalizedToolId)
setIsToolBtnActive(false)
safeSessionStorageSetItem('currentToolId', tool.toolId)
setSessionToolId(tool.toolId)
safeSessionStorageSetItem('currentToolId', normalizedToolId)
setSessionToolId(normalizedToolId)
}
// 先调用 fetchSessionConversationId 获取会话ID
// 等待 token 就绪后再调用接口
await waitForToken()
const requestData = {
busiId: tool.toolId,
}
const res = await fetchSessionConversationId(requestData)
const res = await fetchSessionConversationId({ busiId: busiIdForSession })
if (res?.data?.conversationId) {
const conversationId = res.data.conversationId
// 获取到会话ID后,通知上层更新欢迎语并传递 conversationId
onToolClick?.(false, tool.toolId, tool.toolName, true, conversationId)
// 在 navigate 之前设置标记,避免 Chat 组件的 useEffect 重复调用接口
// 使用 sessionStorage 作为标记,因为 location.state 可能有时序问题
onToolClick?.(
parentIsToolBtn,
eventToolId,
displayName,
shouldChangeStyleForParent,
conversationId,
undefined,
pickToolMeta(tool),
)
sessionStorage.setItem('toolHistoryLoading', conversationId)
// 更新路由到新的会话ID,并携带 toolId 参数
navigate(`/chat/${conversationId}?toolId=${tool.toolId}`, {
if (isGeneral) {
navigate(`/chat/${conversationId}`, {
replace: true,
state: {
toolId: null,
skipHistoryLoad: true,
},
})
}
else {
navigate(`/chat/${conversationId}?toolId=${normalizedToolId}`, {
replace: true,
state: {
toolId: tool.toolId,
skipHistoryLoad: true, // 标记跳过历史记录加载,因为 ChatEditor 会处理
toolId: normalizedToolId,
skipHistoryLoad: true,
},
})
// 使用获取到的会话ID调用历史会话
}
await waitForToken()
const qaRes = await fetchUserQaRecordPage(conversationId, tool.toolId)
const qaToolIdArg = isGeneral ? '' : normalizedToolId
const qaRes = await fetchUserQaRecordPage(conversationId, qaToolIdArg)
console.log('qaRes chatEditor11111', qaRes)
// 通过自定义事件将历史记录数据传递给父组件进行渲染
window.dispatchEvent(new CustomEvent('toolHistoryLoaded', {
detail: {
conversationId,
toolId: tool.toolId,
toolName: tool.toolName,
toolId: isGeneral ? '' : normalizedToolId,
toolName: displayName,
qaRecords: qaRes?.data || [],
},
}))
// 提质增效模式下,延迟触发自动提问接口
// 使用双重 requestAnimationFrame 确保 Chat 组件的 currentIdRef 已经更新
if (tool.toolId === '6712395743241') {
console.log('[ChatEditor] 提质增效模式 - 准备延迟触发 autoSubmitQuestion', { conversationId, toolId: tool.toolId })
if (!isGeneral) {
const listForPair = toolList?.length
? toolList
: readToolListFromSessionStorage()
const clickedPair = getToolQuestionPairFromRow(tool)
if (clickedPair.toolId && clickedPair.recordType)
setWorkflowRequestSeedPair(clickedPair.toolId, clickedPair.recordType)
const { toolId: wfToolId, recordType: wfRecordType } = getWorkflowToolIdAndRecordTypeForRequest(listForPair)
if (wfToolId && wfRecordType) {
console.log('[ChatEditor] 工具会话 - 准备延迟触发 autoSubmitQuestion', { conversationId, toolId: wfToolId, recordType: wfRecordType })
requestAnimationFrame(() => {
requestAnimationFrame(() => {
console.log('[ChatEditor] 提质增效模式 - 触发 autoSubmitQuestion 事件', { conversationId, toolId: tool.toolId })
console.log('[ChatEditor] 工具会话 - 触发 autoSubmitQuestion 事件', { conversationId, toolId: wfToolId, recordType: wfRecordType })
window.dispatchEvent(new CustomEvent('autoSubmitQuestion', {
detail: {
conversationId,
question: '',
toolId: tool.toolId,
recordType: 'B03',
toolId: wfToolId,
recordType: wfRecordType,
busiType: '01',
},
}))
})
})
}
// 清除标记,避免影响后续路由切换
}
sessionStorage.removeItem('toolHistoryLoading')
}
}
catch (error) {
console.error('获取会话ID或历史记录失败:', error)
// 出错时也要通知上层更新欢迎语(不传递 conversationId)
onToolClick?.(false, tool.toolId, tool.toolName, true)
// 出错时也要清除标记
sessionStorage.removeItem('toolHistoryLoading')
if (!isGeneral) {
onToolClick?.(false, eventToolId, displayName, shouldChangeStyleForParent, undefined, undefined, pickToolMeta(tool))
}
}
finally {
// 通知上层结束 loading
onToolClick?.(false, tool.toolId, tool.toolName, true, undefined, false)
onToolClick?.(
parentIsToolBtn,
eventToolId,
displayName,
shouldChangeStyleForParent,
undefined,
false,
pickToolMeta(tool),
)
}
}
......@@ -500,12 +500,11 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth,
}
}, [content])
// 组件加载时和路由参数变化时获取工具列表
// 仅进入带工具条的页面时拉取一次(含 5 分钟 session 缓存);会话/路由切换不重复请求 get_tool_list
useEffect(() => {
if (!hideTools) {
getToolList()
}
}, [location.pathname, location.search, hideTools])
if (!hideTools)
void getToolList()
}, [hideTools])
// 监听 sessionStorage 中的 showToolQuestion
useEffect(() => {
......@@ -605,14 +604,13 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth,
</div>
{!hideTools && toolList && toolList.length > 0 && (
<div className="absolute left-4 bottom-2 flex items-center gap-3 pointer-events-auto pl-[16px]">
{toolList.map((tool: any, index: number) => {
{toolList.map((tool: ToolListRow, index: number) => {
// 根据 selectedToolId 或路由中的 toolId 进行匹配,注意数据类型统一转换为字符串比较
// 优先使用 selectedToolId(用户点击后的状态),其次使用 sessionStorage 中的 currentToolId(刷新时使用),再次使用路由中的 toolId(初始化时使用)
const toolIdStr = String(tool.toolId)
const isSelectedByState = selectedToolId && toolIdStr === String(selectedToolId)
const toolIdStr = normalizeToolId(tool.toolId)
const isSelectedByState = Boolean(selectedToolId) && toolIdStr === String(selectedToolId)
const isSelectedBySession = !selectedToolId && sessionToolId && toolIdStr === String(sessionToolId)
// 制度活化高亮:路由内没有 toolId 或 toolId 为空时默认高亮,点击后也要高亮
const isGeneralMode = tool.toolName === '制度活化' && isToolBtnActive && !selectedToolId && !sessionToolId && !toolIdFromUrl
const isGeneralMode = isGeneralInstitutionalToolRow(tool) && isToolBtnActive && !selectedToolId && !sessionToolId && !toolIdFromUrl
const isSelected = isSelectedByState || isSelectedBySession || isGeneralMode
const baseBtnClass
......@@ -627,10 +625,7 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth,
// 高亮状态直接返回,避免重复触发
if (isSelected)
return
if (tool.toolName === '制度活化')
await handleGeneralClick()
else
await handleToolClick(tool)
await handleWorkflowToolButtonPress(tool)
}
// 处理后端返回的 base64 图标
......@@ -647,7 +642,7 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth,
return (
<Button
key={tool.toolId || `tool-${index}`}
key={toolIdStr || `tool-${index}`}
className={`${baseBtnClass}${selectedClass}`}
radius="full"
variant={selectedVariant}
......
......@@ -10,6 +10,7 @@ import type { Conversation } from '@/types/conversation'
import { processConversationData } from '@/store/conversationSlice.helper'
import { clearCurrentToolId, clearShouldSendQuestion, setCurrentToolId, setShouldSendQuestion } from '@/store/conversationSlice'
import { isMobile } from '@/utils'
import { findToolRowBySessionToolId, readToolListFromSessionStorage, toolRowTriggersHistoryEmptyQuestionSend } from '@/lib/toolRoleRecordTypes'
interface HistoryBarListProps {
searchValue: string
......@@ -40,10 +41,11 @@ export const HistoryBarList: React.FC<HistoryBarListProps> = ({ searchValue, onS
dispatch(clearCurrentToolId())
}
// 仅当历史记录为“提质增效”时,进入对话页后自动触发一次提问接口
// 用空格字符串触发 shouldSendQuestion 的监听,但不会在 UI 中新增用户问题气泡(question.trim() 为 false)。
const isQualityImprovement = conversation.toolId === '6712395743241'
if (isQualityImprovement) {
// 工具会话且列表配置需要时:进入对话页后用空格触发 shouldSendQuestion(trim 后为空,不展示用户气泡)
const list = readToolListFromSessionStorage()
const toolRow = findToolRowBySessionToolId(list, conversation.toolId || '')
const shouldHistoryAutoQuestion = toolRowTriggersHistoryEmptyQuestionSend(toolRow)
if (shouldHistoryAutoQuestion) {
const autoShouldSendQuestion = ' '
dispatch(setShouldSendQuestion(autoShouldSendQuestion))
console.log('[HistoryBarList] shouldSendQuestion 000000:', {
......
......@@ -14,6 +14,7 @@ import { useAppDispatch, useAppSelector } from '@/store/hook'
import { isMobile } from '@/utils'
import { safeLocalStorageSetItem, waitForToken } from '@/lib/utils'
import { fetchUserQaRecordPage } from '@/api/conversation'
import { findToolRowBySessionToolId, getToolQuestionPairFromRow, readToolListFromSessionStorage, toolRowTriggersEmptyQuestionBootstrap } from '@/lib/toolRoleRecordTypes'
// import { User } from './components/User'
interface NavbarProps {
......@@ -129,39 +130,36 @@ const NavbarBase: React.FC<NavbarProps & WithAuthProps> = ({ isHistoryVisible, c
: `/chat/${currentConversationId}`
// 通过 location.state 传递 toolId,避免在 Chat 页面被误判为"外链残留参数"而强制清空
// 如果有 shouldSendQuestion 或者是提质增效模式,传递 skipHistoryLoad 标记,跳过历史记录加载
const isQualityImprovement = currentToolId === '6712395743241'
const hasSessionTool = Boolean(currentToolId?.trim())
navigate(url, {
state: {
toolId: currentToolId || null,
skipHistoryLoad: Boolean(shouldSendQuestion) || isQualityImprovement,
skipHistoryLoad: Boolean(shouldSendQuestion) || hasSessionTool,
},
})
dispatch(clearNavigationFlag())
// 提质增效模式下,创建会话后:
// 1. 使用新会话ID + toolId 主动拉取一次历史记录(与数据助手保持一致)
// 2. 再自动触发一次空问题提问(欢迎语)
if (isQualityImprovement && currentToolId) {
// 1) 拉取历史记录并通过自定义事件交给 Chat 渲染
// 有当前工具且列表配置为需要首包空问题时:拉历史并触发 autoSubmitQuestion
if (hasSessionTool && currentToolId) {
;(async () => {
try {
const list = readToolListFromSessionStorage()
const row = findToolRowBySessionToolId(list, currentToolId)
if (!toolRowTriggersEmptyQuestionBootstrap(row))
return
await waitForToken()
const qaRes = await fetchUserQaRecordPage(currentConversationId, currentToolId)
window.dispatchEvent(new CustomEvent('toolHistoryLoaded', {
detail: {
conversationId: currentConversationId,
toolId: currentToolId,
// 这里不传 toolName,由 Chat 自己根据 currentToolId 解析名称
qaRecords: qaRes?.data || [],
},
}))
}
catch (error) {
console.error('[Navbar] 提质增效模式 - 创建会话后拉取历史记录失败:', error)
}
})()
// 2) 使用 requestAnimationFrame 确保在下一个渲染周期执行自动提问
const pair = getToolQuestionPairFromRow(row)
const recordTypeForAuto = pair.recordType || '01'
requestAnimationFrame(() => {
requestAnimationFrame(() => {
window.dispatchEvent(new CustomEvent('autoSubmitQuestion', {
......@@ -169,13 +167,18 @@ const NavbarBase: React.FC<NavbarProps & WithAuthProps> = ({ isHistoryVisible, c
conversationId: currentConversationId,
question: '',
toolId: currentToolId,
recordType: 'B03',
recordType: recordTypeForAuto,
busiType: '01',
},
}))
})
})
}
catch (error) {
console.error('[Navbar] 创建会话后拉取历史或自动提问失败:', error)
}
})()
}
}
}, [shouldNavigateToNewConversation, currentConversationId, currentToolId, shouldSendQuestion, navigate, dispatch])
......
import type { ToolListRow } from '@/types/toolList'
import { getUserRolesForApi } from '@/lib/utils'
/** 与 ChatEditor 写入的 session 缓存键一致 */
export const TOOL_LIST_SESSION_KEY = 'toolList'
function asToolRows(toolList: unknown): ToolListRow[] {
return Array.isArray(toolList) ? (toolList as ToolListRow[]) : []
}
/** 规范化 toolId:空串视为无工具(制度活化会话) */
export function normalizeToolId(toolId: unknown): string {
if (toolId == null)
return ''
const s = String(toolId).trim()
return s
}
/**
* userRole:逗号分隔,依次为
* 1 提问流 recordType;2 左侧常见问题 recordType;3 答后推荐问 recordType
*/
export function parseUserRoleRecordTypes(userRole: unknown): {
questionRecordType: string
commonQuestionRecordType: string
recommendRecordType: string
} {
const raw = userRole != null ? String(userRole) : ''
const parts = raw.split(',').map(p => p.trim()).filter(p => p.length > 0)
return {
questionRecordType: parts[0] ?? '',
commonQuestionRecordType: parts[1] ?? '',
recommendRecordType: parts[2] ?? '',
}
}
/** 列表中无 toolId(或空)的项视为「制度活化 / 通用」会话行 */
export function isGeneralInstitutionalToolRow(row: ToolListRow | null | undefined): boolean {
if (!row)
return false
if (row.isGeneralSession === true || row.isGeneralSession === 1)
return true
return !normalizeToolId(row.toolId)
}
export function findGeneralToolRow(toolList: unknown): ToolListRow | null {
const list = asToolRows(toolList)
const hit = list.find(isGeneralInstitutionalToolRow)
return hit ?? null
}
export function findToolRowBySessionToolId(toolList: unknown, sessionToolId: string | null | undefined): ToolListRow | null {
const list = asToolRows(toolList)
const tid = normalizeToolId(sessionToolId)
if (!tid)
return findGeneralToolRow(toolList)
return list.find(r => normalizeToolId(r.toolId) === tid) ?? null
}
/**
* 提问接口用的 toolId + recordType(recordType 优先 userRole 首段,否则回退行上 recordType)
*/
export function getToolQuestionPairFromRow(row: ToolListRow | null | undefined): { toolId: string, recordType: string } {
if (!row) {
return { toolId: '', recordType: '' }
}
const toolId = normalizeToolId(row.toolId)
const fromRole = parseUserRoleRecordTypes(row.userRole)
const recordType = fromRole.questionRecordType
|| (row.recordType != null ? String(row.recordType).trim() : '')
return { toolId, recordType }
}
/** 左侧常见问题 recordType */
export function getCommonQuestionRecordTypeForToolRow(row: ToolListRow | null | undefined): string {
if (!row)
return ''
const { commonQuestionRecordType, questionRecordType } = parseUserRoleRecordTypes(row.userRole)
return commonQuestionRecordType || questionRecordType
|| (row.recordType != null ? String(row.recordType).trim() : '')
}
/** 答后推荐问 recordType */
export function getRecommendRecordTypeForToolRow(row: ToolListRow | null | undefined): string {
if (!row)
return ''
const { recommendRecordType, questionRecordType } = parseUserRoleRecordTypes(row.userRole)
return recommendRecordType || questionRecordType
|| (row.recordType != null ? String(row.recordType).trim() : '')
}
/** 是否走业务洞察常见问题(由列表行配置驱动,不写死名称或 toolId) */
export function toolRowUsesBusinessInsightCommonQuestions(row: ToolListRow | null | undefined): boolean {
if (!row)
return false
if (row.commonQuestionApi === 'business_insight')
return true
return Array.isArray(row.businessInsightConfigTypes) && row.businessInsightConfigTypes.length > 0
}
export function getBusinessInsightConfigTypesForRow(row: ToolListRow | null | undefined): string[] {
if (!row)
return ['10']
if (Array.isArray(row.businessInsightConfigTypes) && row.businessInsightConfigTypes.length > 0)
return row.businessInsightConfigTypes.map(String)
return ['10']
}
/** 新建会话进入聊天页后,是否需要与底部工具一致:拉历史并触发空问题首包(由配置驱动) */
export function toolRowTriggersEmptyQuestionBootstrap(row: ToolListRow | null | undefined): boolean {
if (!row || !normalizeToolId(row.toolId))
return false
return Boolean(parseUserRoleRecordTypes(row.userRole).questionRecordType)
}
/** 从历史进入会话时,是否派发 shouldSendQuestion 空格触发静默补问(与 toolRowTriggersEmptyQuestionBootstrap 一致) */
export function toolRowTriggersHistoryEmptyQuestionSend(row: ToolListRow | null | undefined): boolean {
return toolRowTriggersEmptyQuestionBootstrap(row)
}
export function readToolListFromSessionStorage(): ToolListRow[] {
try {
const raw = sessionStorage.getItem(TOOL_LIST_SESSION_KEY)
if (!raw)
return tryReadLegacyToolListCache()
const parsed = JSON.parse(raw) as { toolList?: unknown, timestamp?: number }
if (parsed?.toolList && Array.isArray(parsed.toolList))
return asToolRows(parsed.toolList)
if (Array.isArray(parsed))
return asToolRows(parsed)
}
catch {
// ignore
}
return tryReadLegacyToolListCache()
}
function tryReadLegacyToolListCache(): ToolListRow[] {
try {
const userRoles = getUserRolesForApi()
const cacheKey = `toolList_${JSON.stringify(userRoles)}`
const raw = sessionStorage.getItem(cacheKey)
if (!raw)
return []
const { toolList } = JSON.parse(raw) as { toolList?: unknown }
return asToolRows(toolList)
}
catch {
return []
}
}
import { getToolQuestionPairFromRow } from '@/lib/toolRoleRecordTypes'
import type { ToolListRow } from '@/types/toolList'
/**
* 工作流提问参数:首次使用列表项的 userRole 首段(或行上 recordType);
* 之后各次提问使用接口流式返回中最后一次带上的 toolId / recordType。
*/
const STORAGE_KEY = 'workflowLastToolPair'
/** 本次点击会话写入的「列表第 0 项」快照,供 Chat 首次提问在流式返回前读取 */
const SEED_KEY = 'workflowRequestSeedPair'
export function clearWorkflowLastToolPair(): void {
try {
sessionStorage.removeItem(STORAGE_KEY)
}
catch {
// ignore
}
}
export function setWorkflowRequestSeedPair(toolId: string, recordType: string): void {
const tid = toolId?.trim?.() ?? ''
const rt = recordType?.trim?.() ?? ''
if (!tid || !rt)
return
try {
sessionStorage.setItem(SEED_KEY, JSON.stringify({ toolId: tid, recordType: rt }))
}
catch {
// ignore
}
}
export function getWorkflowRequestSeedPair(): { toolId: string, recordType: string } | null {
try {
const raw = sessionStorage.getItem(SEED_KEY)
if (!raw)
return null
const parsed = JSON.parse(raw) as { toolId?: unknown, recordType?: unknown }
const toolId = parsed?.toolId != null ? String(parsed.toolId).trim() : ''
const recordType = parsed?.recordType != null ? String(parsed.recordType).trim() : ''
if (!toolId || !recordType)
return null
return { toolId, recordType }
}
catch {
return null
}
}
export function clearWorkflowRequestSeedPair(): void {
try {
sessionStorage.removeItem(SEED_KEY)
}
catch {
// ignore
}
}
/** 制度活化等场景:清空流式 last 与点击 seed */
export function clearWorkflowToolCaches(): void {
clearWorkflowLastToolPair()
clearWorkflowRequestSeedPair()
}
export function setWorkflowLastToolPair(toolId: string, recordType: string): void {
const tid = toolId?.trim?.() ?? ''
const rt = recordType?.trim?.() ?? ''
if (!tid || !rt)
return
try {
sessionStorage.setItem(STORAGE_KEY, JSON.stringify({ toolId: tid, recordType: rt }))
}
catch {
// ignore
}
}
export function getWorkflowLastToolPair(): { toolId: string, recordType: string } | null {
try {
const raw = sessionStorage.getItem(STORAGE_KEY)
if (!raw)
return null
const parsed = JSON.parse(raw) as { toolId?: unknown, recordType?: unknown }
const toolId = parsed?.toolId != null ? String(parsed.toolId).trim() : ''
const recordType = parsed?.recordType != null ? String(parsed.recordType).trim() : ''
if (!toolId || !recordType)
return null
return { toolId, recordType }
}
catch {
return null
}
}
/** get_tool_list 第 0 项的提问用 toolId / recordType(来自 userRole 首段) */
export function getToolListFirstPair(toolList: unknown[]): { toolId: string, recordType: string } {
const row = Array.isArray(toolList) && toolList.length > 0 ? (toolList[0] as ToolListRow) : null
return getToolQuestionPairFromRow(row)
}
/**
* 组装「提问主工作流」等接口所需的 toolId / recordType:
* 若已有流式返回缓存则优先;否则使用列表第 0 项的 userRole 首段(首次)。
*/
export function getWorkflowToolIdAndRecordTypeForRequest(toolList: unknown[]): { toolId: string, recordType: string } {
const last = getWorkflowLastToolPair()
if (last)
return last
const seed = getWorkflowRequestSeedPair()
if (seed)
return seed
return getToolListFirstPair(toolList)
}
/** Chat 提交:流式最后一次优先,否则为本次点击写入的 seed(get_tool_list[0]) */
export function getWorkflowToolIdAndRecordTypeForSubmit(): { toolId: string, recordType: string } | null {
return getWorkflowLastToolPair() ?? getWorkflowRequestSeedPair()
}
......@@ -4,7 +4,7 @@ import { Button } from '@heroui/react'
import { motion } from 'framer-motion'
import { useScroll } from 'ahooks'
import styles from './Chat.module.less'
import { processApiResponse } from './helper'
import { getLastQaRecordWorkflowContext, processApiResponse } from './helper'
import { ChatWelcome } from './components/ChatWelcome'
import { ChatItemUser } from './components/ChatItem/ChatItemUser'
import { ChatAnswerBox } from './components/ChatItem/ChatAnswerBox'
......@@ -13,9 +13,9 @@ import { ChatEditor } from '@/components/ChatEditor'
import type { ChatRecord } from '@/types/chat'
import { fetchSessionConversationId, fetchUserQaRecordPage } from '@/api/conversation'
import { fetchCheckTokenApi, fetchStreamResponse } from '@/api/chat'
import { fetchToolList } from '@/api/home'
import { clearBusinessInsightWorkflow, clearCurrentToolId, clearShouldSendQuestion, exitBusinessInsightSubWorkflow, fetchConversations, setBusinessInsightMainWorkflow, setBusinessInsightPendingQuestion, setBusinessInsightSubWorkflow, setCurrentToolId, updateBusinessInsightParamMap } from '@/store/conversationSlice'
import { clearCurrentToolId, clearShouldSendQuestion, exitBusinessInsightSubWorkflow, fetchConversations, setBusinessInsightMainWorkflow, setBusinessInsightPendingQuestion, setBusinessInsightSubWorkflow, setCurrentToolId } from '@/store/conversationSlice'
import { getUserRolesForApi, safeSessionStorageGetItem, waitForToken } from '@/lib/utils'
import { findGeneralToolRow, findToolRowBySessionToolId, readToolListFromSessionStorage } from '@/lib/toolRoleRecordTypes'
import { getWorkflowToolIdAndRecordTypeForSubmit, setWorkflowLastToolPair } from '@/lib/workflowToolParams'
import type { RootState } from '@/store'
import { useAppDispatch, useAppSelector } from '@/store/hook'
......@@ -71,6 +71,12 @@ export const Chat: React.FC = () => {
const fromCollectRef = useRef<boolean>(false)
// 记录正在处理的 conversationId,避免重复调用
const processingConversationIdRef = useRef<string | null>(null)
/** 最近一次 query_user_qa_record_list 最后一条上的 workflow 字段,供自动提问透传 */
const lastLoadedQaWorkflowContextRef = useRef<{
recordType: string
workFlowSessionId: string
busiType?: string
} | null>(null)
// 标记是否为提质增效模式的自动调用(用于提取欢迎语)
const isAutoSubmitQualityImprovementRef = useRef<boolean>(false)
// 提质增效自动调用欢迎语候选(用于流结束后写入 sessionStorage)
......@@ -92,6 +98,10 @@ export const Chat: React.FC = () => {
isInSubWorkflow: false,
pendingQuestion: null,
})
/** 主工作流返回「进子」JSON 后,已发起空问题「开子」请求,等待本轮流结束写入子 session */
const expectingSubWorkflowOpenRef = useRef(false)
const pendingSubOpenRecordTypeRef = useRef<string | null>(null)
const pendingSubQuestionRef = useRef<string | null>(null)
// 当外部系统直接以 /chat/:id 链接进入(没有 location.state,且 URL 中也没有 toolId)时,
// 视为一次新的会话入口:重置为制度活化,清除历史遗留的工具模式状态
......@@ -347,9 +357,12 @@ export const Chat: React.FC = () => {
}
}
}
// 空问题 + 显式传入 recordType/busiType(如工具入口 autoSubmit)时走自动欢迎语流式逻辑
// 空问题 + 显式传入 recordType/busiType(如工具入口 autoSubmit)时走自动欢迎语流式逻辑;
// 已有主会话后的空问题(如「开子工作流」)不应走欢迎语分支
const isEmptyQuestion = !question.trim()
const isAutoToolEmptySubmit = isEmptyQuestion && Boolean(extraParams?.recordType && extraParams?.busiType)
const isAutoToolEmptySubmit = isEmptyQuestion
&& Boolean(extraParams?.recordType && extraParams?.busiType)
&& !businessInsightWorkflowRef.current.mainWorkflowSessionId
isAutoSubmitQualityImprovementRef.current = isAutoToolEmptySubmit
if (isAutoSubmitQualityImprovementRef.current) {
// 重置欢迎语候选
......@@ -364,10 +377,7 @@ export const Chat: React.FC = () => {
const isNew = allItems.length <= 1
dispatch(setIsAsking(true))
// 检查token
await fetchCheckTokenApi()
// 如果 question 不为空,才添加用户问题和空的AI回答(自动提问时 question 为空,不显示)
// 先展示用户气泡与 AI 占位,避免 check_token 失败时界面毫无反馈且流式接口未发出
if (question.trim()) {
setAllItems(prevItems => [
...prevItems,
......@@ -384,6 +394,14 @@ export const Chat: React.FC = () => {
])
}
// 检查 token:失败不阻断提问(流式请求仍带 X-Token,由服务端校验)
try {
await fetchCheckTokenApi()
}
catch (e) {
console.warn('[Chat] check_token 失败,仍尝试发起流式提问:', e)
}
// 创建新的 AbortController
abortControllerRef.current = new AbortController()
......@@ -424,32 +442,31 @@ export const Chat: React.FC = () => {
// workFlowSessionId 的传递逻辑:
// 1. 如果是重新问答(isReask === true),不传递 workFlowSessionId
// 2. 如果是提质增效模式的自动调用(空问题),不传递 workFlowSessionId
// 2. 提质增效等空问题自动调用:未显式传入 workFlowSessionId 时不传(欢迎语首轮);若 extra/参数中已带历史最后一条的 session 则仍传递
// 3. 优先使用 workflowExtraParams 中的值
// 4. 其次使用直接传递的参数
// 5. 如果都没有,从会话历史中找到最后一个有 workFlowSessionId 的答案
// 5. 若仍没有且非「无显式 session 的提质增效自动调用」,从会话历史中找到最后一个有 workFlowSessionId 的答案
const isReask = workflowExtraParams?.isReask === true
const isAutoQualityImprovement = isAutoSubmitQualityImprovementRef.current
// 已选择工具(非制度活化)时允许携带 / 回填 workFlowSessionId
const explicitWorkFlowSessionId = workflowExtraParams?.workFlowSessionId || workFlowSessionId
const shouldUseWorkflowSession = Boolean(resolvedToolId && String(resolvedToolId).trim() !== '')
if (shouldUseWorkflowSession && !isReask && !isAutoQualityImprovement) {
if (workflowExtraParams?.workFlowSessionId) {
requestBody.workFlowSessionId = workflowExtraParams.workFlowSessionId
}
else if (workFlowSessionId) {
requestBody.workFlowSessionId = workFlowSessionId
if (shouldUseWorkflowSession && !isReask) {
let resolvedWorkflowSessionId: string | undefined
if (explicitWorkFlowSessionId) {
resolvedWorkflowSessionId = explicitWorkFlowSessionId
}
else {
// 从会话历史中找到最后一个有 workFlowSessionId 的答案
// 注意:这里使用 allItems 的当前值(在添加新记录之前),所以需要从倒数第二个开始查找
else if (!isAutoQualityImprovement) {
for (let i = allItems.length - 1; i >= 0; i--) {
const item = allItems[i]
if (item.role === 'ai' && item.answerList?.[0]?.workFlowSessionId) {
requestBody.workFlowSessionId = item.answerList[0].workFlowSessionId
resolvedWorkflowSessionId = item.answerList[0].workFlowSessionId
break
}
}
}
if (resolvedWorkflowSessionId) {
requestBody.workFlowSessionId = resolvedWorkflowSessionId
}
}
// 如果传入了 recordType,优先使用传入的值(workflow 提交场景)
......@@ -540,7 +557,7 @@ export const Chat: React.FC = () => {
// 如果是提质增效自动调用且本轮存在有效欢迎语候选,将其写入 sessionStorage 供欢迎语使用
if (isAutoSubmitQualityImprovementRef.current && hasAutoWelcomeCandidateRef.current) {
try {
const key = 'qualityWelcomeText'
const key = 'toolStreamWelcomeText'
sessionStorage.setItem(key, autoWelcomeCandidateRef.current)
}
catch (error) {
......@@ -595,23 +612,88 @@ export const Chat: React.FC = () => {
|| Boolean(wf?.mainWorkflowSessionId)
|| Boolean(wf?.isInSubWorkflow)
if (lastAnswer && shouldHandleWorkflowAnswer) {
let parsedAnswer = null
const resolveWorkflowToolId = () => {
try {
parsedAnswer = JSON.parse(lastAnswer.answer || '')
return (sessionStorage.getItem('currentToolId') ?? '').trim()
|| (currentToolId != null ? String(currentToolId).trim() : '')
}
catch {
// 不是 JSON,保持正常回答
return currentToolId != null ? String(currentToolId).trim() : ''
}
if (parsedAnswer && parsedAnswer.recordType && parsedAnswer.question) {
}
// 刚结束的流是「开子工作流」空问题请求:写入子 session,无卡片则自动「问子」
if (expectingSubWorkflowOpenRef.current && lastAnswer.workFlowSessionId) {
expectingSubWorkflowOpenRef.current = false
const subRt = (
pendingSubOpenRecordTypeRef.current?.trim()
|| (lastAnswer.recordType != null ? String(lastAnswer.recordType).trim() : '')
|| ''
).trim()
pendingSubOpenRecordTypeRef.current = null
if (subRt) {
dispatch(setBusinessInsightSubWorkflow({
sessionId: '',
recordType: parsedAnswer.recordType,
sessionId: String(lastAnswer.workFlowSessionId),
recordType: subRt,
}))
dispatch(setBusinessInsightPendingQuestion(parsedAnswer.question))
const hasCards = Array.isArray(lastAnswer.cardList) && lastAnswer.cardList.length > 0
const pq = pendingSubQuestionRef.current
pendingSubQuestionRef.current = null
if (!hasCards && pq && pq.trim()) {
queueMicrotask(() => {
void handleSubmitQuestion(pq, undefined, resolveWorkflowToolId() || undefined, { busiType: '01' })
})
}
}
}
else if (expectingSubWorkflowOpenRef.current) {
// 开子未带回 session,避免状态卡死
expectingSubWorkflowOpenRef.current = false
pendingSubOpenRecordTypeRef.current = null
pendingSubQuestionRef.current = null
}
else {
let parsedAnswer: { recordType?: unknown, question?: unknown, end?: unknown } | null = null
try {
parsedAnswer = JSON.parse(lastAnswer.answer || '')
}
catch {
// 不是 JSON,保持正常回答
}
// 主工作流返回进子 JSON:先空问题开子(带主 session),再在流结束后写入子 session
if (parsedAnswer && parsedAnswer.recordType && parsedAnswer.question != null) {
const subRecordType = String(parsedAnswer.recordType).trim()
const pendingQ = String(parsedAnswer.question)
if (subRecordType) {
dispatch(setBusinessInsightPendingQuestion(pendingQ))
pendingSubQuestionRef.current = pendingQ
pendingSubOpenRecordTypeRef.current = subRecordType
expectingSubWorkflowOpenRef.current = true
queueMicrotask(() => {
void handleSubmitQuestion('', undefined, resolveWorkflowToolId() || undefined, {
recordType: subRecordType,
busiType: '01',
})
})
}
}
else if (businessInsightWorkflowRef.current.isInSubWorkflow) {
if (parsedAnswer && parsedAnswer.end === true) {
const reopenRt = parsedAnswer.recordType != null ? String(parsedAnswer.recordType).trim() : ''
const followQ = parsedAnswer.question != null ? String(parsedAnswer.question).trim() : ''
dispatch(exitBusinessInsightSubWorkflow())
dispatch(setBusinessInsightPendingQuestion(null))
queueMicrotask(() => {
void handleSubmitQuestion('', undefined, resolveWorkflowToolId() || undefined, {
busiType: '01',
...(reopenRt ? { recordType: reopenRt } : {}),
})
if (followQ) {
window.setTimeout(() => {
void handleSubmitQuestion(followQ, undefined, resolveWorkflowToolId() || undefined, { busiType: '01' })
}, 400)
}
})
}
}
else if (lastAnswer.workFlowSessionId && !businessInsightWorkflowRef.current.mainWorkflowSessionId) {
......@@ -619,6 +701,7 @@ export const Chat: React.FC = () => {
}
}
}
}
})
.catch(() => {
// 流式请求出错时的处理
......@@ -653,12 +736,13 @@ export const Chat: React.FC = () => {
/** 处理 workflow 表单提交 */
const handleWorkflowSubmit = useCallback(async (paramMap: Record<string, string>, recordId?: string, workFlowSessionId?: string) => {
// workflow 提交时,question 可以为空字符串,但需要传递 paramMap、recordId 和 workFlowSessionId
const recordType = businessInsightWorkflowRef.current.subWorkflowRecordType?.trim() || 'A01'
await handleSubmitQuestion('', undefined, undefined, {
paramMap,
recordId,
workFlowSessionId,
recordType: 'A01',
recordType,
busiType: '01',
})
}, [])
......@@ -715,6 +799,14 @@ export const Chat: React.FC = () => {
await waitForToken()
const res = await fetchUserQaRecordPage(finalConversationId, toolId || safeSessionStorageGetItem('currentToolId') || '')
const qaRecords = res.data || []
const wfCtx = getLastQaRecordWorkflowContext(qaRecords)
lastLoadedQaWorkflowContextRef.current = wfCtx.recordType && wfCtx.workFlowSessionId
? {
recordType: wfCtx.recordType,
workFlowSessionId: wfCtx.workFlowSessionId,
busiType: wfCtx.busiType ?? '01',
}
: null
const messages = [{ role: 'system' } as ChatRecord, ...processApiResponse(qaRecords)]
// 处理历史记录中的参考文档标记
const processedMessages = messages.map((item) => {
......@@ -804,7 +896,7 @@ export const Chat: React.FC = () => {
}
}
catch {
// 错误处理
lastLoadedQaWorkflowContextRef.current = null
}
finally {
setIsLoading(false)
......@@ -825,8 +917,12 @@ export const Chat: React.FC = () => {
dispatch(clearShouldSendQuestion())
// 延迟发送,确保状态已更新
setTimeout(() => {
// 左侧常见问题自动发送:按普通提问处理,允许沿用历史 workFlowSessionId
handleSubmitQuestion(questionToSend, undefined, currentToolId)
// 左侧常见问题自动发送:若 QA 列表最后一条带 recordType + workFlowSessionId,一并透传
const wfExtra = lastLoadedQaWorkflowContextRef.current
const autoExtra = wfExtra?.recordType && wfExtra?.workFlowSessionId
? { recordType: wfExtra.recordType, workFlowSessionId: wfExtra.workFlowSessionId, busiType: wfExtra.busiType ?? '01' }
: undefined
handleSubmitQuestion(questionToSend, undefined, currentToolId, autoExtra)
}, 100)
}
}
......@@ -865,6 +961,7 @@ export const Chat: React.FC = () => {
// 检查是否跳过历史记录加载(点击常见问题时)
const skipHistoryLoad = Boolean((location.state as { skipHistoryLoad?: boolean } | null)?.skipHistoryLoad)
if (skipHistoryLoad) {
lastLoadedQaWorkflowContextRef.current = null
setAllItems(prev => (prev.length > 0 ? prev : ([{ role: 'system' } as ChatRecord])))
currentIdRef.current = id
// 更新 toolId 相关状态(如果有)
......@@ -894,6 +991,7 @@ export const Chat: React.FC = () => {
pendingQuestionRef.current = null // 清除待发送的问题
// 重置历史记录加载标记,确保新会话时能正确触发自动发送
historyLoadedRef.current.conversationId = null
lastLoadedQaWorkflowContextRef.current = null
// 保存 fromCollect 标记到 ref,避免 location.state 被清除后丢失
// 优先使用 location.state,其次使用 sessionStorage(用于 navigate(-1) 的情况)
fromCollectRef.current = Boolean(location.state?.fromCollect) || Boolean(sessionStorage.getItem('fromCollect'))
......@@ -935,8 +1033,11 @@ export const Chat: React.FC = () => {
// 确保历史记录加载完成后再发送问题
setTimeout(() => {
console.log('[Chat] 执行自动发送问题:', questionToSend)
// 左侧常见问题自动发送:按普通提问处理,允许沿用历史 workFlowSessionId
handleSubmitQuestion(questionToSend, undefined, currentToolId)
const wfExtra = lastLoadedQaWorkflowContextRef.current
const autoExtra = wfExtra?.recordType && wfExtra?.workFlowSessionId
? { recordType: wfExtra.recordType, workFlowSessionId: wfExtra.workFlowSessionId, busiType: wfExtra.busiType ?? '01' }
: undefined
handleSubmitQuestion(questionToSend, undefined, currentToolId, autoExtra)
}, 100)
}
else {
......@@ -951,20 +1052,23 @@ export const Chat: React.FC = () => {
const getToolNameFromToolId = async () => {
if (currentToolId) {
try {
// 优先从缓存读取工具列表,避免重复调用接口
const cachedRow = findToolRowBySessionToolId(readToolListFromSessionStorage(), currentToolId)
if (cachedRow?.toolName?.trim()) {
setCurrentToolName(cachedRow.toolName.trim())
return
}
// 兼容旧缓存键
const userRoles = getUserRolesForApi()
const cacheKey = `toolList_${JSON.stringify(userRoles)}`
const cached = sessionStorage.getItem(cacheKey)
const legacyKey = `toolList_${JSON.stringify(userRoles)}`
const cached = sessionStorage.getItem(legacyKey)
if (cached) {
try {
const { toolList, timestamp } = JSON.parse(cached)
// 缓存有效(5分钟内)
if (Date.now() - timestamp < 5 * 60 * 1000) {
const tool = toolList.find((t: any) => t.toolId === currentToolId)
const tool = toolList.find((t: any) => String(t.toolId) === String(currentToolId))
if (tool?.toolName) {
setCurrentToolName(tool.toolName)
return
setCurrentToolName(String(tool.toolName).trim())
}
}
}
......@@ -972,24 +1076,15 @@ export const Chat: React.FC = () => {
// 缓存解析失败,继续调用接口
}
}
// 缓存无效或找不到工具时才调用接口
await waitForToken()
const res = await fetchToolList()
if (res?.data) {
const tool = res.data.find((t: any) => t.toolId === currentToolId)
if (tool?.toolName) {
setCurrentToolName(tool.toolName)
}
}
}
catch (error) {
console.error('获取工具列表失败:', error)
console.error('获取工具名称失败:', error)
}
}
else {
// 制度活化
setCurrentToolName('制度活化')
const list = readToolListFromSessionStorage()
const general = findGeneralToolRow(list)
setCurrentToolName(general?.toolName?.trim() || undefined)
}
}
getToolNameFromToolId()
......@@ -1024,12 +1119,21 @@ export const Chat: React.FC = () => {
const { conversationId, toolId, toolName, qaRecords } = event.detail
setIsLoading(true)
try {
const qaList = Array.isArray(qaRecords) ? qaRecords : []
const wfEvt = getLastQaRecordWorkflowContext(qaList)
lastLoadedQaWorkflowContextRef.current = wfEvt.recordType && wfEvt.workFlowSessionId
? {
recordType: wfEvt.recordType,
workFlowSessionId: wfEvt.workFlowSessionId,
busiType: wfEvt.busiType ?? '01',
}
: null
// 标记已通过事件加载数据,避免 useEffect 重复调用接口
toolHistoryLoadedRef.current.conversationId = conversationId
// 更新当前会话ID
currentIdRef.current = conversationId
// 处理历史记录数据
const messages = [{ role: 'system' } as ChatRecord, ...processApiResponse(qaRecords)]
const messages = [{ role: 'system' } as ChatRecord, ...processApiResponse(qaList)]
// 处理历史记录中的参考文档标记
const processedMessages = messages.map((item) => {
if (item.role === 'ai' && item.answerList?.[0]?.answer) {
......@@ -1059,6 +1163,7 @@ export const Chat: React.FC = () => {
}
catch (error) {
console.error('处理历史记录失败:', error)
lastLoadedQaWorkflowContextRef.current = null
}
finally {
setIsLoading(false)
......@@ -1083,8 +1188,18 @@ export const Chat: React.FC = () => {
const { conversationId, question, toolId, recordType, busiType } = event.detail
// 确保当前会话ID匹配
if (conversationId === currentIdRef.current) {
const wf = lastLoadedQaWorkflowContextRef.current
if (wf?.recordType && wf?.workFlowSessionId) {
handleSubmitQuestion(question, undefined, toolId, {
recordType: wf.recordType,
workFlowSessionId: wf.workFlowSessionId,
busiType: wf.busiType ?? busiType ?? '01',
})
}
else {
handleSubmitQuestion(question, undefined, toolId, { recordType, busiType })
}
}
else {
console.warn('[Chat] conversationId 不匹配,跳过自动提交', {
eventConversationId: conversationId,
......@@ -1184,7 +1299,7 @@ export const Chat: React.FC = () => {
</div>
<ChatEditor
onSubmit={(question, toolId) => handleSubmitQuestion(question, undefined, toolId)}
onToolClick={(isToolBtn, toolId, toolName, shouldChangeStyle, conversationId, isLoading) => {
onToolClick={(isToolBtn, toolId, toolName, shouldChangeStyle, conversationId, isLoading, toolMeta) => {
// 如果传递了 loading 状态,更新 isLoading
if (isLoading !== undefined) {
setIsLoading(isLoading)
......@@ -1192,7 +1307,7 @@ export const Chat: React.FC = () => {
// 只在真正的工具切换时触发事件(有 conversationId 时),避免 loading 状态更新时重复触发
if (conversationId) {
window.dispatchEvent(new CustomEvent('toolButtonClick', {
detail: { isToolBtn, toolId, toolName, shouldChangeStyle, conversationId, source: 'toolButton' },
detail: { isToolBtn, toolId, toolName, shouldChangeStyle, conversationId, source: 'toolButton', toolMeta },
}))
}
}}
......
......@@ -3,6 +3,7 @@ import { useSearchParams } from 'react-router-dom'
import { Button, Skeleton } from '@heroui/react'
import type { Answer } from '@/types/chat'
import { fetchOrderQueryRecommendQuestion } from '@/api/chat'
import { findToolRowBySessionToolId, getRecommendRecordTypeForToolRow, readToolListFromSessionStorage } from '@/lib/toolRoleRecordTypes'
import SendIcon from '@/assets/svg/sendBlack.svg?react'
interface ChatAnswerRecommendProps {
......@@ -47,17 +48,13 @@ export const ChatAnswerRecommend: React.FC<ChatAnswerRecommendProps> = ({ answer
recordType = 'A19'
}
}
// 非策略场景
else {
// 制度活化:toolId 为空或不存在
if (!toolId) {
const list = readToolListFromSessionStorage()
const row = findToolRowBySessionToolId(list, toolId || '')
const rt = getRecommendRecordTypeForToolRow(row)
if (rt) {
busiType = '01'
recordType = 'A17'
}
// 特殊工具ID
else if (toolId === '6712395743241') {
busiType = '01'
recordType = 'A15'
recordType = rt
}
}
......
......@@ -2,6 +2,7 @@ import { Avatar } from '@heroui/react'
import { motion } from 'framer-motion'
import AvatarBot from '@/assets/avatarBot.png'
import AIIcon from '@/assets/ai-icon.png'
import { findToolRowBySessionToolId, readToolListFromSessionStorage } from '@/lib/toolRoleRecordTypes'
interface ChatWelcomeProps {
toolName?: string
......@@ -11,39 +12,31 @@ interface ChatWelcomeProps {
export const ChatWelcome: React.FC<ChatWelcomeProps> = ({ toolName: _toolName, welcomeText }) => {
const viteOutputObj = import.meta.env.VITE_OUTPUT_OBJ || 'open'
// 根据不同的 toolName 显示不同的提示语
const getWelcomeText = () => {
// 优先使用从接口返回的欢迎语(历史逻辑)
if (welcomeText) {
return welcomeText
}
// 回退到基于当前工具的欢迎语逻辑
const currentToolId = typeof window !== 'undefined' ? sessionStorage.getItem('currentToolId') : ''
const list = readToolListFromSessionStorage()
const row = findToolRowBySessionToolId(list, currentToolId || '')
const name = row?.toolName?.trim()
// 提质增效:若 sessionStorage 中已有自动调用写入的欢迎语,优先使用该文案
if (currentToolId === '6712395743241') {
try {
const stored = typeof window !== 'undefined' ? sessionStorage.getItem('qualityWelcomeText') : null
if (stored && stored.trim()) {
const stored = typeof window !== 'undefined' ? sessionStorage.getItem('toolStreamWelcomeText') : null
if (stored && stored.trim() && currentToolId?.trim()) {
return stored
}
}
catch {
// 读取失败时回退到默认文案
}
return 'Hi,我是AI提质增效助手,可以为您解答研发需求计划和业务功能流程等相关问题。请问有什么可以帮您?'
}
if (currentToolId === '6712395743240') {
return 'Hi~我是您的数据助手,可以帮你查询业务数据哦'
// 读取失败时回退
}
if (currentToolId === '6712395743242') {
return '业务洞察助手姨就位!当前支持客群洞察,分析客户特征、识别客群价值,请告诉我您的需求吧~'
if (name) {
return `Hi,我是${name},请问有什么可以帮您?`
}
return 'Hi,我是AI制度活化助手,可以为您提供公司规章制度、政策条款的查询与解读服务。请问您想了解什么呢?'
return 'Hi,我是您的智能助手,请问有什么可以帮您?'
}
return (
......@@ -62,7 +55,6 @@ export const ChatWelcome: React.FC<ChatWelcomeProps> = ({ toolName: _toolName, w
>
{getWelcomeText()}
</p>
{/* <p className="text-[15px] mt-[4px] sm:mt-[8px] sm:text-13px text-[#27353C] font-300">作为您的智能保险伙伴,您有各类专业相关的问题都可以抛给我哟~让我们互相帮助共同成长吧~</p> */}
</div>
</motion.div>
</div>
......
import type { ChatRecord, OriginalRecord } from '@/types/chat'
import type { Answer, ChatRecord, OriginalRecord } from '@/types/chat'
/**
* 从 query_user_qa_record_list 返回列表的「最后一条」中提取 workflow 上下文。
* 优先在最后一条记录的 answerList 中从后往前找到同时带有 recordType 与 workFlowSessionId 的答案。
*/
export function getLastQaRecordWorkflowContext(qaRecords: OriginalRecord[] | null | undefined): {
recordType?: string
workFlowSessionId?: string
busiType?: string
} {
if (!Array.isArray(qaRecords) || qaRecords.length === 0)
return {}
const lastRecord = qaRecords[qaRecords.length - 1]
const list = lastRecord?.answerList
if (!Array.isArray(list) || list.length === 0)
return {}
for (let i = list.length - 1; i >= 0; i--) {
const a = list[i] as Answer
const recordType = a?.recordType != null ? String(a.recordType).trim() : ''
const workFlowSessionId = a?.workFlowSessionId != null ? String(a.workFlowSessionId).trim() : ''
if (recordType && workFlowSessionId) {
const busiType = a?.busiType != null ? String(a.busiType).trim() : ''
return {
recordType,
workFlowSessionId,
...(busiType ? { busiType } : {}),
}
}
}
return {}
}
export function processApiResponse(data: OriginalRecord[]): ChatRecord[] {
const chatRecord: ChatRecord[] = []
......
......@@ -15,6 +15,13 @@ import { fetchLoginByToken, fetchLoginByUid } from '@/api/common'
import { fetchSessionConversationId } from '@/api/conversation'
import { fetchBusinessInsightList, fetchCheckTokenApi } from '@/api/chat'
import { getUserRolesFromRoute, safeLocalStorageGetItem, safeLocalStorageSetItem, safeSessionStorageGetItem, safeSessionStorageRemoveItem, safeSessionStorageSetItem, waitForToken } from '@/lib/utils'
import {
findToolRowBySessionToolId,
getBusinessInsightConfigTypesForRow,
getCommonQuestionRecordTypeForToolRow,
readToolListFromSessionStorage,
toolRowUsesBusinessInsightCommonQuestions,
} from '@/lib/toolRoleRecordTypes'
// 从 localStorage 获取 token 的辅助函数
function getTokenFromStorage(): string | null {
......@@ -68,6 +75,7 @@ export const Home: React.FC = () => {
const dispatch = useAppDispatch()
const location = useLocation()
const currentConversationId = useAppSelector(state => state.conversation.currentConversationId)
const businessInsightConfigTypesRef = useRef<string[]>(['10'])
const hasFetched = useRef(false)
const hasCalledBusinessApis = useRef(false) // 防止业务接口重复调用
const prevPathRef = useRef<string>(location.pathname) // 记录上一次路径,用于检测从收藏页面返回
......@@ -106,7 +114,7 @@ export const Home: React.FC = () => {
}
const fetchBusinessInsightQuestions = useCallback(async () => {
const res = await fetchBusinessInsightList({ configTypeList: ['10'] })
const res = await fetchBusinessInsightList({ configTypeList: businessInsightConfigTypesRef.current })
const data = Array.isArray(res?.data) ? res.data[0] : res?.data
const normalized = parseBusinessInsightQuestions(data?.content ?? data?.questionList)
setOtherQuestions((prev: any) => ({
......@@ -116,20 +124,11 @@ export const Home: React.FC = () => {
}))
}, [])
// 根据 toolId 获取对应的 recordType
const getRecordTypeByToolId = (toolId: string | null | undefined): string => {
if (!toolId) {
return 'A16' // 制度活化
}
if (toolId === '6712395743240') {
return 'A18' // 数据助手
}
if (toolId === '6712395743241') {
return 'A14' // 提质增效
}
// 未知的 toolId,默认返回制度活化
return 'A16'
}
const resolveCommonQuestionRecordType = useCallback(async (sessionToolId: string): Promise<string> => {
const list = readToolListFromSessionStorage()
const row = findToolRowBySessionToolId(list, sessionToolId)
return getCommonQuestionRecordTypeForToolRow(row) || '01'
}, [])
// 获取会话ID并加载历史记录
const getSessionConversationId = async (data?: any): Promise<string | null> => {
......@@ -185,9 +184,8 @@ export const Home: React.FC = () => {
return
}
// 从 sessionStorage 获取 toolId 并确定 recordType
const toolId = safeSessionStorageGetItem('currentToolId') || ''
const recordType = getRecordTypeByToolId(toolId)
const recordType = await resolveCommonQuestionRecordType(toolId)
const res = await fetchEfficiencyQuestionList({
conversationId,
......@@ -208,7 +206,7 @@ export const Home: React.FC = () => {
finally {
setIsDataLoaded(true) // 无论成功失败都标记为已加载
}
}, [location.search, isBusinessInsightMode, fetchBusinessInsightQuestions])
}, [location.search, isBusinessInsightMode, fetchBusinessInsightQuestions, resolveCommonQuestionRecordType])
// 处理工具按钮点击
const _handleToolClick = useCallback(async (isToolBtn: boolean, toolId?: string, ignoreUrlToolId?: boolean, conversationIdParam?: string | null) => {
......@@ -249,9 +247,8 @@ export const Home: React.FC = () => {
return
}
// 从 sessionStorage 获取 toolId 并确定 recordType
const currentToolId = safeSessionStorageGetItem('currentToolId') || ''
const recordType = getRecordTypeByToolId(currentToolId)
const recordType = await resolveCommonQuestionRecordType(currentToolId)
// 调用真实 API 获取常见问题列表
const res = await fetchEfficiencyQuestionList({
......@@ -272,12 +269,12 @@ export const Home: React.FC = () => {
finally {
setIsDataLoaded(true) // 无论成功失败都标记为已加载
}
}, [originalOtherQuestions, location.search, location.pathname, currentConversationId])
}, [originalOtherQuestions, location.search, location.pathname, currentConversationId, resolveCommonQuestionRecordType])
// 监听工具按钮点击事件
useEffect(() => {
const handleToolClickEvent = (event: CustomEvent) => {
const { isToolBtn, toolId, conversationId, toolName, source } = event.detail
const { isToolBtn, toolId, conversationId, source } = event.detail
setIsToolBtnActive(isToolBtn)
// 更新样式控制状态
setShouldChangeStyle(true)
......@@ -292,16 +289,14 @@ export const Home: React.FC = () => {
dispatch(clearCurrentToolId())
}
const isBusinessInsightClick = source === 'toolButton' && toolName === '业务洞察'
const list = readToolListFromSessionStorage()
const rowForTool = findToolRowBySessionToolId(list, toolId || '')
const isBusinessInsightClick = source === 'toolButton' && toolRowUsesBusinessInsightCommonQuestions(rowForTool)
// 仅在点击“业务洞察”工具按钮时调用业务洞察常见问题接口;其余情况保持原有逻辑
if (isBusinessInsightClick) {
setIsBusinessInsightMode(true)
// 清空业务洞察工作流状态
dispatch(clearBusinessInsightWorkflow())
// 保存业务洞察工具ID
dispatch(setCurrentToolId('6712395743242'))
safeSessionStorageSetItem('currentToolId', '6712395743242')
businessInsightConfigTypesRef.current = getBusinessInsightConfigTypesForRow(rowForTool)
// 先清空旧数据
setOtherQuestions((prev: any) => ({
...prev,
......
/**
* get_tool_list 单条(字段以后端为准;部分为前端可选扩展)
*/
export interface ToolListRow {
toolId?: string | number | null
toolName?: string | null
userRole?: string | null
/** 兼容旧字段:无 userRole 时可回退 */
recordType?: string | null
toolType?: string | null
toolIcon?: string | null
/** 左侧常见问题走业务洞察配置接口时传入的 configType 列表,如 ['10'] */
businessInsightConfigTypes?: string[] | null
/** 左侧常见问题接口类型:efficiency = query_strategy_recommend_question;business_insight = query_config_list */
commonQuestionApi?: 'efficiency' | 'business_insight' | null
/** 为 true 时展示数据类输入提示并跳过答后推荐问请求 */
showStructuredQueryPlaceholder?: boolean | null
/**
* 为 true 或 1 时表示该列表项对应「无 busiId / 通用」会话(与 toolId 是否为空无关,便于后端仍返回 toolId)
*/
isGeneralSession?: boolean | number | null
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment