From 944626960be627c489c8972ea3359e1a9f6cad9d Mon Sep 17 00:00:00 2001 From: Gsh <15170702455@163.com> Date: Sat, 13 Dec 2025 23:08:11 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=BD=91=E9=A1=B5=E7=89=88=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E5=AF=B9=E8=AF=9D=E6=96=87=E4=BB=B6=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Yi.Ai.Vue3/package.json | 5 +- Yi.Ai.Vue3/pnpm-lock.yaml | 333 +++++++++ Yi.Ai.Vue3/src/api/chat/types.ts | 12 + .../src/components/FilesSelect/index.vue | 656 ++++++++++++++---- .../pages/chat/layouts/chatWithId/index.vue | 147 +++- Yi.Ai.Vue3/src/stores/modules/chat.ts | 3 + Yi.Ai.Vue3/src/stores/modules/files.ts | 2 + 7 files changed, 1022 insertions(+), 136 deletions(-) diff --git a/Yi.Ai.Vue3/package.json b/Yi.Ai.Vue3/package.json index c1b7d0a6..043a6ff1 100644 --- a/Yi.Ai.Vue3/package.json +++ b/Yi.Ai.Vue3/package.json @@ -44,7 +44,9 @@ "fingerprintjs": "^0.5.3", "hook-fetch": "^2.0.4-beta.1", "lodash-es": "^4.17.21", + "mammoth": "^1.11.0", "nprogress": "^0.2.0", + "pdfjs-dist": "^5.4.449", "pinia": "^3.0.3", "pinia-plugin-persistedstate": "^4.4.1", "qrcode": "^1.5.4", @@ -52,7 +54,8 @@ "reset-css": "^5.0.2", "vue": "^3.5.17", "vue-element-plus-x": "1.3.7", - "vue-router": "4" + "vue-router": "4", + "xlsx": "^0.18.5" }, "devDependencies": { "@antfu/eslint-config": "^4.16.2", diff --git a/Yi.Ai.Vue3/pnpm-lock.yaml b/Yi.Ai.Vue3/pnpm-lock.yaml index bc00a400..64af91a3 100644 --- a/Yi.Ai.Vue3/pnpm-lock.yaml +++ b/Yi.Ai.Vue3/pnpm-lock.yaml @@ -53,9 +53,15 @@ importers: lodash-es: specifier: ^4.17.21 version: 4.17.21 + mammoth: + specifier: ^1.11.0 + version: 1.11.0 nprogress: specifier: ^0.2.0 version: 0.2.0 + pdfjs-dist: + specifier: ^5.4.449 + version: 5.4.449 pinia: specifier: ^3.0.3 version: 3.0.3(typescript@5.8.3)(vue@3.5.17(typescript@5.8.3)) @@ -80,6 +86,9 @@ importers: vue-router: specifier: '4' version: 4.5.1(vue@3.5.17(typescript@5.8.3)) + xlsx: + specifier: ^0.18.5 + version: 0.18.5 devDependencies: '@antfu/eslint-config': specifier: ^4.16.2 @@ -736,6 +745,70 @@ packages: '@manypkg/get-packages@1.1.3': resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} + '@napi-rs/canvas-android-arm64@0.1.84': + resolution: {integrity: sha512-pdvuqvj3qtwVryqgpAGornJLV6Ezpk39V6wT4JCnRVGy8I3Tk1au8qOalFGrx/r0Ig87hWslysPpHBxVpBMIww==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@napi-rs/canvas-darwin-arm64@0.1.84': + resolution: {integrity: sha512-A8IND3Hnv0R6abc6qCcCaOCujTLMmGxtucMTZ5vbQUrEN/scxi378MyTLtyWg+MRr6bwQJ6v/orqMS9datIcww==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@napi-rs/canvas-darwin-x64@0.1.84': + resolution: {integrity: sha512-AUW45lJhYWwnA74LaNeqhvqYKK/2hNnBBBl03KRdqeCD4tKneUSrxUqIv8d22CBweOvrAASyKN3W87WO2zEr/A==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@napi-rs/canvas-linux-arm-gnueabihf@0.1.84': + resolution: {integrity: sha512-8zs5ZqOrdgs4FioTxSBrkl/wHZB56bJNBqaIsfPL4ZkEQCinOkrFF7xIcXiHiKp93J3wUtbIzeVrhTIaWwqk+A==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@napi-rs/canvas-linux-arm64-gnu@0.1.84': + resolution: {integrity: sha512-i204vtowOglJUpbAFWU5mqsJgH0lVpNk/Ml4mQtB4Lndd86oF+Otr6Mr5KQnZHqYGhlSIKiU2SYnUbhO28zGQA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@napi-rs/canvas-linux-arm64-musl@0.1.84': + resolution: {integrity: sha512-VyZq0EEw+OILnWk7G3ZgLLPaz1ERaPP++jLjeyLMbFOF+Tr4zHzWKiKDsEV/cT7btLPZbVoR3VX+T9/QubnURQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@napi-rs/canvas-linux-riscv64-gnu@0.1.84': + resolution: {integrity: sha512-PSMTh8DiThvLRsbtc/a065I/ceZk17EXAATv9uNvHgkgo7wdEfTh2C3aveNkBMGByVO3tvnvD5v/YFtZL07cIg==} + engines: {node: '>= 10'} + cpu: [riscv64] + os: [linux] + + '@napi-rs/canvas-linux-x64-gnu@0.1.84': + resolution: {integrity: sha512-N1GY3noO1oqgEo3rYQIwY44kfM11vA0lDbN0orTOHfCSUZTUyiYCY0nZ197QMahZBm1aR/vYgsWpV74MMMDuNA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@napi-rs/canvas-linux-x64-musl@0.1.84': + resolution: {integrity: sha512-vUZmua6ADqTWyHyei81aXIt9wp0yjeNwTH0KdhdeoBb6azHmFR8uKTukZMXfLCC3bnsW0t4lW7K78KNMknmtjg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@napi-rs/canvas-win32-x64-msvc@0.1.84': + resolution: {integrity: sha512-YSs8ncurc1xzegUMNnQUTYrdrAuaXdPMOa+iYYyAxydOtg0ppV386hyYMsy00Yip1NlTgLCseRG4sHSnjQx6og==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@napi-rs/canvas@0.1.84': + resolution: {integrity: sha512-88FTNFs4uuiFKP0tUrPsEXhpe9dg7za9ILZJE08pGdUveMIDeana1zwfVkqRHJDPJFAmGY3dXmJ99dzsy57YnA==} + engines: {node: '>= 10'} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -1307,6 +1380,10 @@ packages: '@vueuse/shared@9.13.0': resolution: {integrity: sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==} + '@xmldom/xmldom@0.8.11': + resolution: {integrity: sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==} + engines: {node: '>=10.0.0'} + JSONStream@1.3.5: resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} hasBin: true @@ -1326,6 +1403,10 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + adler-32@1.3.1: + resolution: {integrity: sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==} + engines: {node: '>=0.8'} + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -1470,6 +1551,9 @@ packages: birpc@2.3.0: resolution: {integrity: sha512-ijbtkn/F3Pvzb6jHypHRyve2QApOCZDR25D/VnkY2G/lBNcXCTsnsCxgY4k4PkVB7zfwzYbY3O9Lcqe3xufS5g==} + bluebird@3.4.7: + resolution: {integrity: sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==} + bluebird@3.7.2: resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} @@ -1550,6 +1634,10 @@ packages: ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + cfb@1.2.2: + resolution: {integrity: sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==} + engines: {node: '>=0.8'} + chalk@1.1.3: resolution: {integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==} engines: {node: '>=0.10.0'} @@ -1623,6 +1711,10 @@ packages: resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} engines: {node: '>=0.8'} + codepage@1.15.0: + resolution: {integrity: sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==} + engines: {node: '>=0.8'} + collection-visit@1.0.0: resolution: {integrity: sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==} engines: {node: '>=0.10.0'} @@ -1710,6 +1802,9 @@ packages: core-js-compat@3.42.0: resolution: {integrity: sha512-bQasjMfyDGyaeWKBIu33lHh9qlSR0MFE/Nmc6nMjf/iU9b3rSMdAYz1Baxrv4lPdGUsTqZudHA4jIGSJy0SWZQ==} + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + cors@2.8.5: resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} engines: {node: '>= 0.10'} @@ -1731,6 +1826,11 @@ packages: typescript: optional: true + crc-32@1.2.2: + resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} + engines: {node: '>=0.8'} + hasBin: true + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -1878,6 +1978,9 @@ packages: dijkstrajs@1.0.3: resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==} + dingbat-to-unicode@1.0.1: + resolution: {integrity: sha512-98l0sW87ZT58pU4i61wa2OHwxbiYSbuxsCBozaVnYX2iCnr3bLM3fIes1/ej7h1YdOKuKt/MLs706TVnALA65w==} + dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -1931,6 +2034,9 @@ packages: driver.js@1.3.6: resolution: {integrity: sha512-g2nNuu+tWmPpuoyk3ffpT9vKhjPz4NrJzq6mkRDZIwXCrFhrKdDJ9TX5tJOBpvCTBrBYjgRQ17XlcQB15q4gMg==} + duck@0.1.12: + resolution: {integrity: sha512-wkctla1O6VfP89gQ+J/yDesM0S7B7XLXjKGzXxMDVFg7uEn706niAtyYovKbyq1oT9YwDcly721/iUWoc8MVRg==} + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -2384,6 +2490,10 @@ packages: resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} engines: {node: '>=0.4.x'} + frac@1.1.2: + resolution: {integrity: sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==} + engines: {node: '>=0.8'} + fragment-cache@0.2.1: resolution: {integrity: sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==} engines: {node: '>=0.10.0'} @@ -2673,6 +2783,9 @@ packages: engines: {node: '>=0.10.0'} hasBin: true + immediate@3.0.6: + resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} + immutable@5.1.2: resolution: {integrity: sha512-qHKXW1q6liAk1Oys6umoaZbDRqjcjgSrbnrifHsfsttza7zcvRAsL7mMV6xWcyhwQy7Xj5v4hhbr6b+iDYwlmQ==} @@ -2988,6 +3101,9 @@ packages: resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} engines: {'0': node >= 0.2.0} + jszip@3.10.1: + resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} + katex@0.16.22: resolution: {integrity: sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg==} hasBin: true @@ -3034,6 +3150,9 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + lie@3.3.0: + resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} + lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} @@ -3120,9 +3239,17 @@ packages: longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + lop@0.4.2: + resolution: {integrity: sha512-RefILVDQ4DKoRZsJ4Pj22TxE3omDO47yFpkIBoDKzkqPRISs5U1cnAdg/5583YPkWPaLIYHOKRMQSvjFsO26cw==} + magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + mammoth@1.11.0: + resolution: {integrity: sha512-BcEqqY/BOwIcI1iR5tqyVlqc3KIaMRa4egSoK83YAVrBf6+yqdAAbtUcFDCWX8Zef8/fgNZ6rl4VUv+vVX8ddQ==} + engines: {node: '>=12.0.0'} + hasBin: true + map-cache@0.2.2: resolution: {integrity: sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==} engines: {node: '>=0.10.0'} @@ -3452,6 +3579,9 @@ packages: resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} engines: {node: '>=12'} + option@0.2.4: + resolution: {integrity: sha512-pkEqbDyl8ou5cpq+VsnQbe/WlEy5qS7xPzMS1U55OCG9KPvwFD46zDbxQIj3egJSFc3D+XhYOPUzz49zQAVy7A==} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -3509,6 +3639,9 @@ packages: package-manager-detector@1.3.0: resolution: {integrity: sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==} + pako@1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -3545,6 +3678,10 @@ packages: resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -3559,6 +3696,10 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + pdfjs-dist@5.4.449: + resolution: {integrity: sha512-CegnUaT0QwAyQMS+7o2POr4wWUNNe8VaKKlcuoRHeYo98cVnqPpwOXNSx6Trl6szH02JrRcsPgletV6GmF3LtQ==} + engines: {node: '>=20.16.0 || >=22.3.0'} + perfect-debounce@1.0.0: resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} @@ -3730,6 +3871,9 @@ packages: resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} engines: {node: '>=6'} + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + property-information@6.5.0: resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==} @@ -3774,6 +3918,9 @@ packages: resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} engines: {node: '>=6'} + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} @@ -3930,6 +4077,9 @@ packages: resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} engines: {node: '>=0.4'} + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} @@ -4079,6 +4229,9 @@ packages: resolution: {integrity: sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==} engines: {node: '>=0.10.0'} + setimmediate@1.0.5: + resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -4199,6 +4352,10 @@ packages: sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + ssf@0.11.2: + resolution: {integrity: sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==} + engines: {node: '>=0.8'} + stable@0.1.8: resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==} deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility' @@ -4242,6 +4399,9 @@ packages: resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} engines: {node: '>= 0.4'} + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} @@ -4536,6 +4696,9 @@ packages: unctx@2.4.1: resolution: {integrity: sha512-AbaYw0Nm4mK4qjhns67C+kgxR2YWiwlDBPzxrN8h8C6VtAdCgditAY5Dezu3IJy4XVqAnbrXt9oQJvsn3fyozg==} + underscore@1.13.7: + resolution: {integrity: sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==} + undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} @@ -4814,10 +4977,18 @@ packages: engines: {node: '>= 8'} hasBin: true + wmf@1.0.2: + resolution: {integrity: sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==} + engines: {node: '>=0.8'} + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + word@0.3.0: + resolution: {integrity: sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==} + engines: {node: '>=0.8'} + wordwrap@1.0.0: resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} @@ -4837,10 +5008,19 @@ packages: resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + xlsx@0.18.5: + resolution: {integrity: sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==} + engines: {node: '>=0.8'} + hasBin: true + xml-name-validator@4.0.0: resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} engines: {node: '>=12'} + xmlbuilder@10.1.1: + resolution: {integrity: sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg==} + engines: {node: '>=4.0'} + y18n@4.0.3: resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} @@ -5522,6 +5702,50 @@ snapshots: globby: 11.1.0 read-yaml-file: 1.1.0 + '@napi-rs/canvas-android-arm64@0.1.84': + optional: true + + '@napi-rs/canvas-darwin-arm64@0.1.84': + optional: true + + '@napi-rs/canvas-darwin-x64@0.1.84': + optional: true + + '@napi-rs/canvas-linux-arm-gnueabihf@0.1.84': + optional: true + + '@napi-rs/canvas-linux-arm64-gnu@0.1.84': + optional: true + + '@napi-rs/canvas-linux-arm64-musl@0.1.84': + optional: true + + '@napi-rs/canvas-linux-riscv64-gnu@0.1.84': + optional: true + + '@napi-rs/canvas-linux-x64-gnu@0.1.84': + optional: true + + '@napi-rs/canvas-linux-x64-musl@0.1.84': + optional: true + + '@napi-rs/canvas-win32-x64-msvc@0.1.84': + optional: true + + '@napi-rs/canvas@0.1.84': + optionalDependencies: + '@napi-rs/canvas-android-arm64': 0.1.84 + '@napi-rs/canvas-darwin-arm64': 0.1.84 + '@napi-rs/canvas-darwin-x64': 0.1.84 + '@napi-rs/canvas-linux-arm-gnueabihf': 0.1.84 + '@napi-rs/canvas-linux-arm64-gnu': 0.1.84 + '@napi-rs/canvas-linux-arm64-musl': 0.1.84 + '@napi-rs/canvas-linux-riscv64-gnu': 0.1.84 + '@napi-rs/canvas-linux-x64-gnu': 0.1.84 + '@napi-rs/canvas-linux-x64-musl': 0.1.84 + '@napi-rs/canvas-win32-x64-msvc': 0.1.84 + optional: true + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -6216,6 +6440,8 @@ snapshots: - '@vue/composition-api' - vue + '@xmldom/xmldom@0.8.11': {} + JSONStream@1.3.5: dependencies: jsonparse: 1.3.1 @@ -6233,6 +6459,8 @@ snapshots: acorn@8.15.0: {} + adler-32@1.3.1: {} + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -6353,6 +6581,8 @@ snapshots: birpc@2.3.0: {} + bluebird@3.4.7: {} + bluebird@3.7.2: {} boolbase@1.0.0: {} @@ -6461,6 +6691,11 @@ snapshots: ccount@2.0.1: {} + cfb@1.2.2: + dependencies: + adler-32: 1.3.1 + crc-32: 1.2.2 + chalk@1.1.3: dependencies: ansi-styles: 2.2.1 @@ -6546,6 +6781,8 @@ snapshots: clone@2.1.2: {} + codepage@1.15.0: {} + collection-visit@1.0.0: dependencies: map-visit: 1.0.0 @@ -6621,6 +6858,8 @@ snapshots: dependencies: browserslist: 4.25.0 + core-util-is@1.0.3: {} + cors@2.8.5: dependencies: object-assign: 4.1.1 @@ -6642,6 +6881,8 @@ snapshots: optionalDependencies: typescript: 5.8.3 + crc-32@1.2.2: {} + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -6771,6 +7012,8 @@ snapshots: dijkstrajs@1.0.3: {} + dingbat-to-unicode@1.0.1: {} + dir-glob@3.0.1: dependencies: path-type: 4.0.0 @@ -6838,6 +7081,10 @@ snapshots: driver.js@1.3.6: {} + duck@0.1.12: + dependencies: + underscore: 1.13.7 + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -7458,6 +7705,8 @@ snapshots: format@0.2.2: {} + frac@1.1.2: {} + fragment-cache@0.2.1: dependencies: map-cache: 0.2.2 @@ -7818,6 +8067,8 @@ snapshots: image-size@0.5.5: {} + immediate@3.0.6: {} + immutable@5.1.2: {} import-fresh@3.3.1: @@ -8094,6 +8345,13 @@ snapshots: jsonparse@1.3.1: {} + jszip@3.10.1: + dependencies: + lie: 3.3.0 + pako: 1.0.11 + readable-stream: 2.3.8 + setimmediate: 1.0.5 + katex@0.16.22: dependencies: commander: 8.3.0 @@ -8135,6 +8393,10 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + lie@3.3.0: + dependencies: + immediate: 3.0.6 + lilconfig@3.1.3: {} lines-and-columns@1.2.4: {} @@ -8227,10 +8489,29 @@ snapshots: longest-streak@3.1.0: {} + lop@0.4.2: + dependencies: + duck: 0.1.12 + option: 0.2.4 + underscore: 1.13.7 + magic-string@0.30.17: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 + mammoth@1.11.0: + dependencies: + '@xmldom/xmldom': 0.8.11 + argparse: 1.0.10 + base64-js: 1.5.1 + bluebird: 3.4.7 + dingbat-to-unicode: 1.0.1 + jszip: 3.10.1 + lop: 0.4.2 + path-is-absolute: 1.0.1 + underscore: 1.13.7 + xmlbuilder: 10.1.1 + map-cache@0.2.2: {} map-visit@1.0.0: @@ -8777,6 +9058,8 @@ snapshots: is-docker: 2.2.1 is-wsl: 2.2.0 + option@0.2.4: {} + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -8834,6 +9117,8 @@ snapshots: package-manager-detector@1.3.0: {} + pako@1.0.11: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -8865,6 +9150,8 @@ snapshots: path-exists@5.0.0: {} + path-is-absolute@1.0.1: {} + path-key@3.1.1: {} path-type@4.0.0: {} @@ -8873,6 +9160,10 @@ snapshots: pathe@2.0.3: {} + pdfjs-dist@5.4.449: + optionalDependencies: + '@napi-rs/canvas': 0.1.84 + perfect-debounce@1.0.0: {} picocolors@1.1.1: {} @@ -9020,6 +9311,8 @@ snapshots: prismjs@1.30.0: {} + process-nextick-args@2.0.1: {} + property-information@6.5.0: {} property-information@7.1.0: {} @@ -9062,6 +9355,16 @@ snapshots: pify: 4.0.1 strip-bom: 3.0.0 + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + readable-stream@3.6.2: dependencies: inherits: 2.0.4 @@ -9277,6 +9580,8 @@ snapshots: has-symbols: 1.1.0 isarray: 2.0.5 + safe-buffer@5.1.2: {} + safe-buffer@5.2.1: {} safe-push-apply@1.0.0: @@ -9413,6 +9718,8 @@ snapshots: is-plain-object: 2.0.4 split-string: 3.1.0 + setimmediate@1.0.5: {} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -9553,6 +9860,10 @@ snapshots: sprintf-js@1.0.3: {} + ssf@0.11.2: + dependencies: + frac: 1.1.2 + stable@0.1.8: {} static-extend@0.1.2: @@ -9607,6 +9918,10 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + string_decoder@1.3.0: dependencies: safe-buffer: 5.2.1 @@ -9975,6 +10290,8 @@ snapshots: unplugin: 2.3.5 optional: true + underscore@1.13.7: {} + undici-types@6.21.0: {} unicorn-magic@0.1.0: {} @@ -10359,8 +10676,12 @@ snapshots: dependencies: isexe: 2.0.0 + wmf@1.0.2: {} + word-wrap@1.2.5: {} + word@0.3.0: {} + wordwrap@1.0.0: {} wrap-ansi@6.2.0: @@ -10386,8 +10707,20 @@ snapshots: imurmurhash: 0.1.4 signal-exit: 4.1.0 + xlsx@0.18.5: + dependencies: + adler-32: 1.3.1 + cfb: 1.2.2 + codepage: 1.15.0 + crc-32: 1.2.2 + ssf: 0.11.2 + wmf: 1.0.2 + word: 0.3.0 + xml-name-validator@4.0.0: {} + xmlbuilder@10.1.1: {} + y18n@4.0.3: {} y18n@5.0.8: {} diff --git a/Yi.Ai.Vue3/src/api/chat/types.ts b/Yi.Ai.Vue3/src/api/chat/types.ts index c53d18e1..d94d1ae4 100644 --- a/Yi.Ai.Vue3/src/api/chat/types.ts +++ b/Yi.Ai.Vue3/src/api/chat/types.ts @@ -220,4 +220,16 @@ export interface ChatMessageVo { * 用户id */ userId?: number; + /** + * 用户消息中的图片列表(前端扩展字段) + */ + images?: Array<{ url: string; name?: string }>; + /** + * 用户消息中的文件列表(前端扩展字段) + */ + files?: Array<{ name: string; size: number }>; + /** + * 创建时间(前端显示用) + */ + creationTime?: string; } diff --git a/Yi.Ai.Vue3/src/components/FilesSelect/index.vue b/Yi.Ai.Vue3/src/components/FilesSelect/index.vue index dabba464..8f22ca86 100644 --- a/Yi.Ai.Vue3/src/components/FilesSelect/index.vue +++ b/Yi.Ai.Vue3/src/components/FilesSelect/index.vue @@ -3,35 +3,52 @@ import type { FileItem } from '@/stores/modules/files'; import { useFileDialog } from '@vueuse/core'; import { ElMessage } from 'element-plus'; +import mammoth from 'mammoth'; +import * as pdfjsLib from 'pdfjs-dist'; +import * as XLSX from 'xlsx'; import Popover from '@/components/Popover/index.vue'; import SvgIcon from '@/components/SvgIcon/index.vue'; import { useFilesStore } from '@/stores/modules/files'; +// 配置 PDF.js worker - 使用稳定的 CDN +pdfjsLib.GlobalWorkerOptions.workerSrc = `https://cdn.jsdelivr.net/npm/pdfjs-dist@${pdfjsLib.version}/build/pdf.worker.min.mjs`; + const filesStore = useFilesStore(); // 文件大小限制 3MB const MAX_FILE_SIZE = 3 * 1024 * 1024; -/* 弹出面板 开始 */ -const popoverStyle = ref({ - padding: '4px', - height: 'fit-content', - background: 'var(--el-bg-color, #fff)', - border: '1px solid var(--el-border-color-light)', - borderRadius: '8px', - boxShadow: '0 2px 12px 0 rgba(0, 0, 0, 0.1)', -}); -const popoverRef = ref(); -/* 弹出面板 结束 */ +// 单个文件内容长度限制 +const MAX_TEXT_FILE_LENGTH = 50000; // 文本文件最大字符数 +const MAX_WORD_LENGTH = 30000; // Word 文档最大字符数 +const MAX_EXCEL_ROWS = 100; // Excel 最大行数 +const MAX_PDF_PAGES = 10; // PDF 最大页数 + +// 整个消息总长度限制(所有文件内容加起来,预估 token 安全限制) +// 272000 tokens * 0.55 安全系数 ≈ 150000 字符 +const MAX_TOTAL_CONTENT_LENGTH = 150000; const { reset, open, onChange } = useFileDialog({ - // 允许图片和文件 - accept: 'image/*,application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document', - directory: false, // 是否允许选择文件夹 - multiple: true, // 是否允许多选 + // 支持图片、文档、文本文件等 + accept: 'image/*,.txt,.log,.csv,.tsv,.md,.markdown,.json,.xml,.yaml,.yml,.toml,.ini,.conf,.config,.properties,.prop,.env,' + + '.js,.jsx,.ts,.tsx,.vue,.html,.htm,.css,.scss,.sass,.less,.styl,' + + '.java,.c,.cpp,.h,.hpp,.cs,.py,.rb,.go,.rs,.swift,.kt,.php,.sh,.bash,.zsh,.fish,.bat,.cmd,.ps1,' + + '.sql,.graphql,.proto,.thrift,' + + '.dockerfile,.gitignore,.gitattributes,.editorconfig,.npmrc,.nvmrc,' + + '.sln,.csproj,.vbproj,.fsproj,.props,.targets,' + + '.xlsx,.xls,.csv,.docx,.pdf', + directory: false, + multiple: true, }); -// 压缩图片 +/** + * 压缩图片 + * @param {File} file - 原始图片文件 + * @param {number} maxWidth - 最大宽度,默认 1024px + * @param {number} maxHeight - 最大高度,默认 1024px + * @param {number} quality - 压缩质量,0-1之间,默认 0.8 + * @returns {Promise} 压缩后的图片 Blob + */ function compressImage(file: File, maxWidth = 1024, maxHeight = 1024, quality = 0.8): Promise { return new Promise((resolve, reject) => { const reader = new FileReader(); @@ -60,12 +77,13 @@ function compressImage(file: File, maxWidth = 1024, maxHeight = 1024, quality = (blob) => { if (blob) { resolve(blob); - } else { + } + else { reject(new Error('压缩失败')); } }, file.type, - quality + quality, ); }; img.onerror = reject; @@ -76,7 +94,11 @@ function compressImage(file: File, maxWidth = 1024, maxHeight = 1024, quality = }); } -// 将 Blob 转换为 base64 +/** + * 将 Blob 转换为 base64 格式 + * @param {Blob} blob - 要转换的 Blob 对象 + * @returns {Promise} base64 编码的字符串(包含 data:xxx;base64, 前缀) + */ function blobToBase64(blob: Blob): Promise { return new Promise((resolve, reject) => { const reader = new FileReader(); @@ -88,11 +110,289 @@ function blobToBase64(blob: Blob): Promise { }); } +/** + * 读取文本文件内容 + * @param {File} file - 文本文件 + * @returns {Promise} 文件内容字符串 + */ +function readTextFile(file: File): Promise { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => { + resolve(reader.result as string); + }; + reader.onerror = reject; + reader.readAsText(file, 'UTF-8'); + }); +} + +/** + * 判断是否为文本文件 + * 通过 MIME 类型或文件扩展名判断 + * @param {File} file - 要判断的文件 + * @returns {boolean} 是否为文本文件 + */ +function isTextFile(file: File): boolean { + // 通过 MIME type 判断 + if (file.type.startsWith('text/')) { + return true; + } + + // 通过扩展名判断(更全面的列表) + const textExtensions = [ + // 通用文本 + 'txt', + 'log', + 'md', + 'markdown', + 'rtf', + // 配置文件 + 'json', + 'xml', + 'yaml', + 'yml', + 'toml', + 'ini', + 'conf', + 'config', + 'properties', + 'prop', + 'env', + // 前端 + 'js', + 'jsx', + 'ts', + 'tsx', + 'vue', + 'html', + 'htm', + 'css', + 'scss', + 'sass', + 'less', + 'styl', + // 编程语言 + 'java', + 'c', + 'cpp', + 'h', + 'hpp', + 'cs', + 'py', + 'rb', + 'go', + 'rs', + 'swift', + 'kt', + 'php', + // 脚本 + 'sh', + 'bash', + 'zsh', + 'fish', + 'bat', + 'cmd', + 'ps1', + // 数据库/API + 'sql', + 'graphql', + 'proto', + 'thrift', + // 版本控制/工具 + 'dockerfile', + 'gitignore', + 'gitattributes', + 'editorconfig', + 'npmrc', + 'nvmrc', + // .NET 项目文件 + 'sln', + 'csproj', + 'vbproj', + 'fsproj', + 'props', + 'targets', + // 数据文件 + 'csv', + 'tsv', + ]; + + const ext = file.name.split('.').pop()?.toLowerCase(); + return ext ? textExtensions.includes(ext) : false; +} + +/** + * 解析 Excel 文件,提取前 N 行数据转为 CSV 格式 + * @param {File} file - Excel 文件 (.xlsx, .xls) + * @returns {Promise<{content: string, totalRows: number, extractedRows: number}>} + * - content: CSV 格式的文本内容 + * - totalRows: 文件总行数 + * - extractedRows: 实际提取的行数(受 MAX_EXCEL_ROWS 限制) + */ +async function parseExcel(file: File): Promise<{ content: string; totalRows: number; extractedRows: number }> { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = (e) => { + try { + const data = new Uint8Array(e.target?.result as ArrayBuffer); + const workbook = XLSX.read(data, { type: 'array' }); + + let result = ''; + let totalRows = 0; + let extractedRows = 0; + + workbook.SheetNames.forEach((sheetName, index) => { + const worksheet = workbook.Sheets[sheetName]; + + // 获取工作表的范围 + const range = XLSX.utils.decode_range(worksheet['!ref'] || 'A1'); + const sheetTotalRows = range.e.r - range.s.r + 1; + totalRows += sheetTotalRows; + + // 限制行数 + const rowsToExtract = Math.min(sheetTotalRows, MAX_EXCEL_ROWS); + extractedRows += rowsToExtract; + + // 创建新的范围,只包含前 N 行 + const limitedRange = { + s: { r: range.s.r, c: range.s.c }, + e: { r: range.s.r + rowsToExtract - 1, c: range.e.c }, + }; + + // 提取限制范围内的数据 + const limitedData: any[][] = []; + for (let row = limitedRange.s.r; row <= limitedRange.e.r; row++) { + const rowData: any[] = []; + for (let col = limitedRange.s.c; col <= limitedRange.e.c; col++) { + const cellAddress = XLSX.utils.encode_cell({ r: row, c: col }); + const cell = worksheet[cellAddress]; + rowData.push(cell ? cell.v : ''); + } + limitedData.push(rowData); + } + + // 转换为 CSV + const csvData = limitedData.map(row => row.join(',')).join('\n'); + + if (workbook.SheetNames.length > 1) { + result += `=== Sheet: ${sheetName} ===\n`; + } + result += csvData; + if (index < workbook.SheetNames.length - 1) { + result += '\n\n'; + } + }); + + resolve({ content: result, totalRows, extractedRows }); + } + catch (error) { + reject(error); + } + }; + reader.onerror = reject; + reader.readAsArrayBuffer(file); + }); +} + +/** + * 解析 Word 文档,提取纯文本内容 + * @param {File} file - Word 文档 (.docx) + * @returns {Promise<{content: string, totalLength: number, extracted: boolean}>} + * - content: 提取的文本内容 + * - totalLength: 原始文本总长度 + * - extracted: 是否被截断(超过 MAX_WORD_LENGTH) + */ +async function parseWord(file: File): Promise<{ content: string; totalLength: number; extracted: boolean }> { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = async (e) => { + try { + const arrayBuffer = e.target?.result as ArrayBuffer; + const result = await mammoth.extractRawText({ arrayBuffer }); + const fullText = result.value; + const totalLength = fullText.length; + + if (totalLength > MAX_WORD_LENGTH) { + const truncated = fullText.substring(0, MAX_WORD_LENGTH); + resolve({ content: truncated, totalLength, extracted: true }); + } + else { + resolve({ content: fullText, totalLength, extracted: false }); + } + } + catch (error) { + reject(error); + } + }; + reader.onerror = reject; + reader.readAsArrayBuffer(file); + }); +} + +/** + * 解析 PDF 文件,提取前 N 页的文本内容 + * @param {File} file - PDF 文件 + * @returns {Promise<{content: string, totalPages: number, extractedPages: number}>} + * - content: 提取的文本内容 + * - totalPages: 文件总页数 + * - extractedPages: 实际提取的页数(受 MAX_PDF_PAGES 限制) + */ +async function parsePDF(file: File): Promise<{ content: string; totalPages: number; extractedPages: number }> { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = async (e) => { + try { + const typedArray = new Uint8Array(e.target?.result as ArrayBuffer); + const pdf = await pdfjsLib.getDocument(typedArray).promise; + const totalPages = pdf.numPages; + const pagesToExtract = Math.min(totalPages, MAX_PDF_PAGES); + + let fullText = ''; + for (let i = 1; i <= pagesToExtract; i++) { + const page = await pdf.getPage(i); + const textContent = await page.getTextContent(); + const pageText = textContent.items.map((item: any) => item.str).join(' '); + fullText += `${pageText}\n`; + } + + resolve({ content: fullText, totalPages, extractedPages: pagesToExtract }); + } + catch (error) { + reject(error); + } + }; + reader.onerror = reject; + reader.readAsArrayBuffer(file); + }); +} + +/** + * 获取文件扩展名 + * @param {string} filename - 文件名 + * @returns {string} 小写的扩展名,无点号 + */ +function getFileExtension(filename: string): string { + return filename.split('.').pop()?.toLowerCase() || ''; +} + onChange(async (files) => { if (!files) return; const arr = [] as FileItem[]; + let totalContentLength = 0; // 跟踪总内容长度 + + // 先计算已有文件的总内容长度 + filesStore.filesList.forEach((f) => { + if (f.fileType === 'text' && f.fileContent) { + totalContentLength += f.fileContent.length; + } + // 图片 base64 也计入(虽然转 token 时不同,但也要计算) + if (f.fileType === 'image' && f.base64) { + // base64 转 token 比例约 1:1.5,这里保守估计 + totalContentLength += Math.floor(f.base64.length * 0.5); + } + }); for (let i = 0; i < files!.length; i++) { const file = files![i]; @@ -103,143 +403,261 @@ onChange(async (files) => { continue; } + const ext = getFileExtension(file.name); const isImage = file.type.startsWith('image/'); + const isExcel = ['xlsx', 'xls'].includes(ext); + const isWord = ext === 'docx'; + const isPDF = ext === 'pdf'; + const isText = isTextFile(file); - // 压缩并转换为base64 - let base64 = ''; - let previewUrl = ''; + // 处理图片文件 if (isImage) { try { // 先压缩图片 const compressedBlob = await compressImage(file, 1024, 1024, 0.8); // 再转换为 base64 - base64 = await blobToBase64(compressedBlob); - // 使用压缩后的图片作为预览 - previewUrl = base64; + const base64 = await blobToBase64(compressedBlob); + + // 检查总长度(base64 保守估计占用) + const estimatedLength = Math.floor(base64.length * 0.5); + if (totalContentLength + estimatedLength > MAX_TOTAL_CONTENT_LENGTH) { + ElMessage.error(`添加 ${file.name} 会超过消息总长度限制(${MAX_TOTAL_CONTENT_LENGTH} 字符),已跳过`); + continue; + } + + totalContentLength += estimatedLength; // 计算压缩比例 const originalSize = (file.size / 1024).toFixed(2); const compressedSize = (compressedBlob.size / 1024).toFixed(2); console.log(`图片压缩: ${file.name} - 原始: ${originalSize}KB, 压缩后: ${compressedSize}KB`); - } catch (error) { + + arr.push({ + uid: crypto.randomUUID(), + name: file.name, + fileSize: file.size, + file, + maxWidth: '200px', + showDelIcon: true, + imgPreview: true, + imgVariant: 'square', + url: base64, // 使用压缩后的 base64 作为预览地址 + isUploaded: true, + base64, + fileType: 'image', + }); + } + catch (error) { console.error('压缩图片失败:', error); ElMessage.error(`${file.name} 压缩失败`); continue; } } + // 处理 Excel 文件 + else if (isExcel) { + try { + const result = await parseExcel(file); - arr.push({ - uid: crypto.randomUUID(), // 不写 uid,文件列表展示不出来,elx 1.2.0 bug 待修复 - name: file.name, - fileSize: file.size, - file, - maxWidth: '200px', - showDelIcon: true, // 显示删除图标 - imgPreview: isImage, // 图片才显示预览 - imgVariant: 'square', // 图片预览的形状 - url: isImage ? previewUrl : undefined, // 使用压缩后的 base64 作为预览地址 - isUploaded: true, // 直接标记为已完成 - base64: isImage ? base64 : undefined, // 保存压缩后的base64 - }); + // 检查总长度 + if (totalContentLength + result.content.length > MAX_TOTAL_CONTENT_LENGTH) { + ElMessage.error(`添加 ${file.name} 会超过消息总长度限制(${MAX_TOTAL_CONTENT_LENGTH} 字符),已跳过`); + continue; + } + + totalContentLength += result.content.length; + + arr.push({ + uid: crypto.randomUUID(), + name: file.name, + fileSize: file.size, + file, + maxWidth: '200px', + showDelIcon: true, + imgPreview: false, + isUploaded: true, + fileContent: result.content, + fileType: 'text', + }); + + // 提示信息 + if (result.totalRows > MAX_EXCEL_ROWS) { + ElMessage.warning(`${file.name} 共 ${result.totalRows} 行,已提取前 ${result.extractedRows} 行`); + } + + console.log(`Excel 解析: ${file.name} - 大小: ${(file.size / 1024).toFixed(2)}KB, 总行数: ${result.totalRows}, 已提取: ${result.extractedRows} 行, 内容长度: ${result.content.length} 字符`); + } + catch (error) { + console.error('解析 Excel 失败:', error); + ElMessage.error(`${file.name} 解析失败`); + continue; + } + } + // 处理 Word 文档 + else if (isWord) { + try { + const result = await parseWord(file); + + // 检查总长度 + if (totalContentLength + result.content.length > MAX_TOTAL_CONTENT_LENGTH) { + ElMessage.error(`添加 ${file.name} 会超过消息总长度限制(${MAX_TOTAL_CONTENT_LENGTH} 字符),已跳过`); + continue; + } + + totalContentLength += result.content.length; + + arr.push({ + uid: crypto.randomUUID(), + name: file.name, + fileSize: file.size, + file, + maxWidth: '200px', + showDelIcon: true, + imgPreview: false, + isUploaded: true, + fileContent: result.content, + fileType: 'text', + }); + + // 提示信息 + if (result.extracted) { + ElMessage.warning(`${file.name} 共 ${result.totalLength} 字符,已提取前 ${MAX_WORD_LENGTH} 字符`); + } + + console.log(`Word 解析: ${file.name} - 大小: ${(file.size / 1024).toFixed(2)}KB, 总长度: ${result.totalLength}, 已提取: ${result.content.length} 字符`); + } + catch (error) { + console.error('解析 Word 失败:', error); + ElMessage.error(`${file.name} 解析失败`); + continue; + } + } + // 处理 PDF 文件 + else if (isPDF) { + try { + const result = await parsePDF(file); + + // 检查总长度 + if (totalContentLength + result.content.length > MAX_TOTAL_CONTENT_LENGTH) { + ElMessage.error(`添加 ${file.name} 会超过消息总长度限制(${MAX_TOTAL_CONTENT_LENGTH} 字符),已跳过`); + continue; + } + + totalContentLength += result.content.length; + + arr.push({ + uid: crypto.randomUUID(), + name: file.name, + fileSize: file.size, + file, + maxWidth: '200px', + showDelIcon: true, + imgPreview: false, + isUploaded: true, + fileContent: result.content, + fileType: 'text', + }); + + // 提示信息 + if (result.totalPages > MAX_PDF_PAGES) { + ElMessage.warning(`${file.name} 共 ${result.totalPages} 页,已提取前 ${result.extractedPages} 页`); + } + + console.log(`PDF 解析: ${file.name} - 大小: ${(file.size / 1024).toFixed(2)}KB, 总页数: ${result.totalPages}, 已提取: ${result.extractedPages} 页, 内容长度: ${result.content.length} 字符`); + } + catch (error) { + console.error('解析 PDF 失败:', error); + ElMessage.error(`${file.name} 解析失败`); + continue; + } + } + // 处理文本文件 + else if (isText) { + try { + // 读取文本文件内容 + const content = await readTextFile(file); + + // 限制单个文本文件长度 + let finalContent = content; + let truncated = false; + if (content.length > MAX_TEXT_FILE_LENGTH) { + finalContent = content.substring(0, MAX_TEXT_FILE_LENGTH); + truncated = true; + } + + // 检查总长度 + if (totalContentLength + finalContent.length > MAX_TOTAL_CONTENT_LENGTH) { + ElMessage.error(`添加 ${file.name} 会超过消息总长度限制(${MAX_TOTAL_CONTENT_LENGTH} 字符),已跳过`); + continue; + } + + totalContentLength += finalContent.length; + + arr.push({ + uid: crypto.randomUUID(), + name: file.name, + fileSize: file.size, + file, + maxWidth: '200px', + showDelIcon: true, + imgPreview: false, + isUploaded: true, + fileContent: finalContent, + fileType: 'text', + }); + + // 提示信息 + if (truncated) { + ElMessage.warning(`${file.name} 共 ${content.length} 字符,已提取前 ${MAX_TEXT_FILE_LENGTH} 字符`); + } + + console.log(`文本文件读取: ${file.name} - 大小: ${(file.size / 1024).toFixed(2)}KB, 内容长度: ${content.length} 字符`); + } + catch (error) { + console.error('读取文件失败:', error); + ElMessage.error(`${file.name} 读取失败`); + continue; + } + } + // 不支持的文件类型 + else { + ElMessage.warning(`${file.name} 不是支持的文件类型`); + continue; + } } if (arr.length > 0) { filesStore.setFilesList([...filesStore.filesList, ...arr]); - ElMessage.success(`已添加 ${arr.length} 个文件`); + ElMessage.success(`已添加 ${arr.length} 个文件,当前总内容长度约 ${totalContentLength} 字符`); } // 重置文件选择器 nextTick(() => reset()); }); +/** + * 打开文件选择对话框 + */ function handleUploadFiles() { open(); - popoverRef.value.hide(); } diff --git a/Yi.Ai.Vue3/src/pages/chat/layouts/chatWithId/index.vue b/Yi.Ai.Vue3/src/pages/chat/layouts/chatWithId/index.vue index d25c588e..6389a3cf 100644 --- a/Yi.Ai.Vue3/src/pages/chat/layouts/chatWithId/index.vue +++ b/Yi.Ai.Vue3/src/pages/chat/layouts/chatWithId/index.vue @@ -5,7 +5,7 @@ import type { BubbleProps } from 'vue-element-plus-x/types/Bubble'; import type { BubbleListInstance } from 'vue-element-plus-x/types/BubbleList'; import type { FilesCardProps } from 'vue-element-plus-x/types/FilesCard'; import type { ThinkingStatus } from 'vue-element-plus-x/types/Thinking'; -import { ArrowLeftBold, ArrowRightBold, Loading } from '@element-plus/icons-vue'; +import { ArrowLeftBold, ArrowRightBold, Document, Loading } from '@element-plus/icons-vue'; import { ElIcon, ElMessage } from 'element-plus'; import { useHookFetch } from 'hook-fetch/vue'; import { computed, nextTick, ref, watch } from 'vue'; @@ -30,6 +30,7 @@ type MessageItem = BubbleProps & { thinlCollapse?: boolean; reasoning_content?: string; images?: Array<{ url: string; name?: string }>; // 用户消息中的图片列表 + files?: Array<{ name: string; size: number }>; // 用户消息中的文件列表 }; const route = useRoute(); @@ -114,7 +115,11 @@ watch( { immediate: true, deep: true }, ); -// 封装数据处理逻辑 +/** + * 处理流式响应的数据块 + * 解析 AI 返回的数据,更新消息内容和思考状态 + * @param {AnyObject} chunk - 流式响应的数据块 + */ function handleDataChunk(chunk: AnyObject) { try { // 安全获取 delta 和 content @@ -170,11 +175,19 @@ function handleDataChunk(chunk: AnyObject) { } } -// 封装错误处理逻辑 +/** + * 处理错误信息 + * @param {any} err - 错误对象 + */ function handleError(err: any) { console.error('Fetch error:', err); } +/** + * 发送消息并处理流式响应 + * 支持发送文本、图片和文件 + * @param {string} chatContent - 用户输入的文本内容 + */ async function startSSE(chatContent: string) { if (isSending.value) return; @@ -192,14 +205,21 @@ async function startSSE(chatContent: string) { // 清空输入框 inputValue.value = ''; - // 获取当前上传的图片(在清空之前保存) - const imageFiles = filesStore.filesList.filter(f => f.isUploaded && f.file.type.startsWith('image/')); + // 获取当前上传的图片和文件(在清空之前保存) + const imageFiles = filesStore.filesList.filter(f => f.isUploaded && f.fileType === 'image'); + const textFiles = filesStore.filesList.filter(f => f.isUploaded && f.fileType === 'text'); + const images = imageFiles.map(f => ({ url: f.base64!, // 使用base64作为URL name: f.name, })); - addMessage(chatContent, true, images); + const files = textFiles.map(f => ({ + name: f.name!, + size: f.fileSize!, + })); + + addMessage(chatContent, true, images, files); addMessage('', false); // 立即清空文件列表(不要等到响应完成) @@ -208,13 +228,13 @@ async function startSSE(chatContent: string) { // 这里有必要调用一下 BubbleList 组件的滚动到底部 手动触发 自动滚动 bubbleListRef.value?.scrollToBottom(); - // 组装消息内容,支持图片 + // 组装消息内容,支持图片和文件 const messagesContent = bubbleItems.value.slice(0, -1).slice(-6).map((item: MessageItem) => { const baseMessage: any = { role: item.role, }; - // 如果是用户消息且有附件(图片),组装成数组格式 + // 如果是用户消息且有附件(图片或文件),组装成数组格式 if (item.role === 'user' && item.key === bubbleItems.value.length - 2) { // 当前发送的消息 const contentArray: any[] = []; @@ -227,6 +247,26 @@ async function startSSE(chatContent: string) { }); } + // 添加文本文件内容(使用XML格式) + if (textFiles.length > 0) { + let fileContent = '\n\n'; + textFiles.forEach((fileItem, index) => { + fileContent += `\n`; + fileContent += `File ${index + 1}\n`; + fileContent += `${fileItem.name}\n`; + fileContent += `\n${fileItem.fileContent}\n\n`; + fileContent += `\n`; + if (index < textFiles.length - 1) { + fileContent += '\n'; + } + }); + + contentArray.push({ + type: 'text', + text: fileContent, + }); + } + // 添加图片内容(使用之前保存的 imageFiles) imageFiles.forEach((fileItem) => { if (fileItem.base64) { @@ -240,7 +280,7 @@ async function startSSE(chatContent: string) { }); // 如果有图片或文件,使用数组格式 - if (contentArray.length > 1 || imageFiles.length > 0) { + if (contentArray.length > 1 || imageFiles.length > 0 || textFiles.length > 0) { baseMessage.content = contentArray; } else { baseMessage.content = item.content; @@ -287,10 +327,18 @@ async function startSSE(chatContent: string) { latest.thinkingStatus = 'end'; } } + + // 保存聊天记录到 chatMap(本地缓存,刷新后可恢复) + if (route.params?.id && route.params.id !== 'not_login') { + chatStore.chatMap[`${route.params.id}`] = bubbleItems.value as any; + } } } -// 中断请求 +/** + * 中断正在进行的请求 + * 停止流式响应并重置状态 + */ async function cancelSSE() { try { cancel(); // 直接调用,无需参数 @@ -309,8 +357,14 @@ async function cancelSSE() { } } -// 添加消息 - 维护聊天记录 -function addMessage(message: string, isUser: boolean, images?: Array<{ url: string; name?: string }>) { +/** + * 添加消息到聊天列表 + * @param {string} message - 消息内容 + * @param {boolean} isUser - 是否为用户消息 + * @param {Array<{url: string, name?: string}>} images - 图片列表(可选) + * @param {Array<{name: string, size: number}>} files - 文件列表(可选) + */ +function addMessage(message: string, isUser: boolean, images?: Array<{ url: string; name?: string }>, files?: Array<{ name: string; size: number }>) { const i = bubbleItems.value.length; const obj: MessageItem = { key: i, @@ -328,14 +382,25 @@ function addMessage(message: string, isUser: boolean, images?: Array<{ url: stri thinlCollapse: false, noStyle: !isUser, images: images && images.length > 0 ? images : undefined, + files: files && files.length > 0 ? files : undefined, }; bubbleItems.value.push(obj); } -// 展开收起 事件展示 +/** + * 处理思考链展开/收起状态变化 + * @param {Object} payload - 状态变化的载荷 + * @param {boolean} payload.value - 展开/收起状态 + * @param {ThinkingStatus} payload.status - 思考状态 + */ function handleChange(payload: { value: boolean; status: ThinkingStatus }) { } +/** + * 删除文件卡片 + * @param {FilesCardProps} _item - 文件卡片项(未使用) + * @param {number} index - 要删除的文件索引 + */ function handleDeleteCard(_item: FilesCardProps, index: number) { filesStore.deleteFileByIndex(index); } @@ -356,14 +421,21 @@ watch( }, ); -// 复制 +/** + * 复制消息内容到剪贴板 + * @param {any} item - 消息项 + */ function copy(item: any) { navigator.clipboard.writeText(item.content || '') .then(() => ElMessage.success('已复制到剪贴板')) .catch(() => ElMessage.error('复制失败')); } -// 图片预览 +/** + * 图片预览 + * 在新窗口中打开图片 + * @param {string} url - 图片 URL + */ function handleImagePreview(url: string) { window.open(url, '_blank'); } @@ -383,7 +455,7 @@ function handleImagePreview(url: string) {