diff --git a/.idea/ApifoxUploaderProjectSetting.xml b/.idea/ApifoxUploaderProjectSetting.xml new file mode 100644 index 0000000..5df016c --- /dev/null +++ b/.idea/ApifoxUploaderProjectSetting.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000..0d43583 --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,17 @@ + + + + + mysql.8 + true + com.mysql.cj.jdbc.Driver + jdbc:mysql://localhost:3307/main + + + + + + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml new file mode 100644 index 0000000..56782ca --- /dev/null +++ b/.idea/sqldialects.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/client/package.json b/client/package.json index 812f790..b864f31 100644 --- a/client/package.json +++ b/client/package.json @@ -9,12 +9,15 @@ "preview": "vite preview" }, "dependencies": { - "@microsoft/fetch-event-source": "^2.0.1", + "chart.js": "^4.4.8", + "echarts": "^5.6.0", + "eventsource-client": "^1.1.3", + "licia": "^1.46.0", "markdown-it": "^14.1.0", + "markdown-it-container": "^4.0.0", "mermaid": "^11.4.1", "mermaid-it-markdown": "^1.0.8", - "vue": "^3.5.13", - "vue3-markdown-it": "^1.0.10" + "vue": "^3.5.13" }, "devDependencies": { "@vitejs/plugin-vue": "^5.2.1", diff --git a/client/pnpm-lock.yaml b/client/pnpm-lock.yaml index b4cce8a..3a83f25 100644 --- a/client/pnpm-lock.yaml +++ b/client/pnpm-lock.yaml @@ -8,12 +8,24 @@ importers: .: dependencies: - '@microsoft/fetch-event-source': - specifier: ^2.0.1 - version: 2.0.1 + chart.js: + specifier: ^4.4.8 + version: 4.4.8 + echarts: + specifier: ^5.6.0 + version: 5.6.0 + eventsource-client: + specifier: ^1.1.3 + version: 1.1.3 + licia: + specifier: ^1.46.0 + version: 1.46.0 markdown-it: specifier: ^14.1.0 version: 14.1.0 + markdown-it-container: + specifier: ^4.0.0 + version: 4.0.0 mermaid: specifier: ^11.4.1 version: 11.4.1 @@ -23,9 +35,6 @@ importers: vue: specifier: ^3.5.13 version: 3.5.13 - vue3-markdown-it: - specifier: ^1.0.10 - version: 1.0.10(@types/markdown-it@14.1.2) devDependencies: '@vitejs/plugin-vue': specifier: ^5.2.1 @@ -387,12 +396,12 @@ packages: '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@kurkle/color@0.3.4': + resolution: {integrity: sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==, tarball: https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz} + '@mermaid-js/parser@0.3.0': resolution: {integrity: sha512-HsvL6zgE5sUPGgkIDlmAWR1HTNHz2Iy11BAWPTa4Jjabkpguy4Ze2gzfLrg6pdRuBvFwgUYyxiaNqZwrEEXepA==} - '@microsoft/fetch-event-source@2.0.1': - resolution: {integrity: sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA==} - '@one-ini/wasm@0.1.1': resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} @@ -623,15 +632,6 @@ packages: '@types/geojson@7946.0.16': resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} - '@types/linkify-it@5.0.0': - resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} - - '@types/markdown-it@14.1.2': - resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} - - '@types/mdurl@2.0.0': - resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} - '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} @@ -754,6 +754,10 @@ packages: caniuse-lite@1.0.30001700: resolution: {integrity: sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ==} + chart.js@4.4.8: + resolution: {integrity: sha512-IkGZlVpXP+83QpMm4uxEiGqSI7jFizwVtF3+n5Pc3k7sMO+tkd0qxh2OzLhenM0K80xtmAONWGBn082EiBQSDA==, tarball: https://registry.npmjs.org/chart.js/-/chart.js-4.4.8.tgz} + engines: {pnpm: '>=8'} + chevrotain-allstar@0.3.1: resolution: {integrity: sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==} peerDependencies: @@ -1002,6 +1006,9 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + echarts@5.6.0: + resolution: {integrity: sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==} + editorconfig@1.0.4: resolution: {integrity: sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==} engines: {node: '>=14'} @@ -1016,9 +1023,6 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - entities@2.1.0: - resolution: {integrity: sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==} - entities@3.0.1: resolution: {integrity: sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==} engines: {node: '>=0.12'} @@ -1042,6 +1046,14 @@ packages: estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + eventsource-client@1.1.3: + resolution: {integrity: sha512-6GJGePDMxin/6VH1Dex7RqnZRguweO1BVKhXpBYwKcQbVLOZPLfeDr9vYSb9ra88kjs0NpT8FaEDz5rExedDkg==, tarball: https://registry.npmjs.org/eventsource-client/-/eventsource-client-1.1.3.tgz} + engines: {node: '>=18.0.0'} + + eventsource-parser@3.0.0: + resolution: {integrity: sha512-T1C0XCUimhxVQzW4zFipdx0SficT651NnkR0ZSH3yQwh+mFMdLfgjABVi4YtMTtaL4s168593DaoaRLMqryavA==, tarball: https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.0.tgz} + engines: {node: '>=18.0.0'} + execa@9.5.2: resolution: {integrity: sha512-EHlpxMCpHWSAh1dgS6bVeoLAXGnJNdR93aabr4QCGbzOM73o5XmRfM/e5FUqsw3aagP8S8XEWUWFAxnRBnAF0Q==} engines: {node: ^18.19.0 || >=20.5.0} @@ -1203,8 +1215,8 @@ packages: layout-base@2.0.1: resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==} - linkify-it@3.0.3: - resolution: {integrity: sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==} + licia@1.46.0: + resolution: {integrity: sha512-Zms2AjJB+KdqUKFF87J5J/w9DwXnGN/lKlbjpRgvaPf0BIQ0mOZ/2lX4E79zwNafHGMUq5RtN54FN6Af5G92cA==} linkify-it@4.0.1: resolution: {integrity: sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==} @@ -1219,9 +1231,6 @@ packages: lodash-es@4.17.21: resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} - lodash.flow@3.5.0: - resolution: {integrity: sha512-ff3BX/tSioo+XojX4MOsOMhJw0nZoUEF011LX8g8d3gvjVbxd89cCio4BCXronjxcTUIJUoqKEUA+n4CqvvRPw==} - lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -1231,48 +1240,8 @@ packages: magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} - markdown-it-abbr@1.0.4: - resolution: {integrity: sha512-ZeA4Z4SaBbYysZap5iZcxKmlPL6bYA8grqhzJIHB1ikn7njnzaP8uwbtuXc4YXD5LicI4/2Xmc0VwmSiFV04gg==} - - markdown-it-anchor@8.6.7: - resolution: {integrity: sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==} - peerDependencies: - '@types/markdown-it': '*' - markdown-it: '*' - - markdown-it-deflist@2.1.0: - resolution: {integrity: sha512-3OuqoRUlSxJiuQYu0cWTLHNhhq2xtoSFqsZK8plANg91+RJQU1ziQ6lA2LzmFAEes18uPBsHZpcX6We5l76Nzg==} - - markdown-it-emoji@2.0.2: - resolution: {integrity: sha512-zLftSaNrKuYl0kR5zm4gxXjHaOI3FAOEaloKmRA5hijmJZvSjmxcokOLlzycb/HXlUFWzXqpIEoyEMCE4i9MvQ==} - - markdown-it-footnote@3.0.3: - resolution: {integrity: sha512-YZMSuCGVZAjzKMn+xqIco9d1cLGxbELHZ9do/TSYVzraooV8ypsppKNmUJ0fVH5ljkCInQAtFpm8Rb3eXSrt5w==} - - markdown-it-highlightjs@3.6.0: - resolution: {integrity: sha512-ex+Lq3cVkprh0GpGwFyc53A/rqY6GGzopPCG1xMsf8Ya3XtGC8Uw9tChN1rWbpyDae7tBBhVHVcMM29h4Btamw==} - - markdown-it-ins@3.0.1: - resolution: {integrity: sha512-32SSfZqSzqyAmmQ4SHvhxbFqSzPDqsZgMHDwxqPzp+v+t8RsmqsBZRG+RfRQskJko9PfKC2/oxyOs4Yg/CfiRw==} - - markdown-it-mark@3.0.1: - resolution: {integrity: sha512-HyxjAu6BRsdt6Xcv6TKVQnkz/E70TdGXEFHRYBGLncRE9lBFwDNLVtFojKxjJWgJ+5XxUwLaHXy+2sGBbDn+4A==} - - markdown-it-sub@1.0.0: - resolution: {integrity: sha512-z2Rm/LzEE1wzwTSDrI+FlPEveAAbgdAdPhdWarq/ZGJrGW/uCQbKAnhoCsE4hAbc3SEym26+W2z/VQB0cQiA9Q==} - - markdown-it-sup@1.0.0: - resolution: {integrity: sha512-E32m0nV9iyhRR7CrhnzL5msqic7rL1juWre6TQNxsnApg7Uf+F97JOKxUijg5YwXz86lZ0mqfOnutoryyNdntQ==} - - markdown-it-task-lists@2.1.1: - resolution: {integrity: sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA==} - - markdown-it-toc-done-right@4.2.0: - resolution: {integrity: sha512-UB/IbzjWazwTlNAX0pvWNlJS8NKsOQ4syrXZQ/C72j+jirrsjVRT627lCaylrKJFBQWfRsPmIVQie8x38DEhAQ==} - - markdown-it@12.3.2: - resolution: {integrity: sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==} - hasBin: true + markdown-it-container@4.0.0: + resolution: {integrity: sha512-HaNccxUH0l7BNGYbFbjmGpf5aLHAMTinqRZQAEQbMr2cdD3z91Q6kIo1oUn1CQndkT03jat6ckrdRYuwwqLlQw==} markdown-it@13.0.2: resolution: {integrity: sha512-FtwnEuuK+2yVU7goGn/MJ0WBZMM9ZPgU9spqlFs7/A/pDIUNSOQZhUgOqYCficIuR2QaFnrt8LHqBWsbTAoI5w==} @@ -1527,6 +1496,9 @@ packages: resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} engines: {node: '>=6.10'} + tslib@2.3.0: + resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==} + uc.micro@1.0.6: resolution: {integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==} @@ -1640,9 +1612,6 @@ packages: vscode-uri@3.0.8: resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==} - vue3-markdown-it@1.0.10: - resolution: {integrity: sha512-mTvHu0zl7jrh7ojgaZ+tTpCLiS4CVg4bTgTu4KGhw/cRRY5YgIG8QgFAPu6kCzSW6Znc9a52Beb6hFvF4hSMkQ==} - vue@3.5.13: resolution: {integrity: sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==} peerDependencies: @@ -1676,6 +1645,9 @@ packages: resolution: {integrity: sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==} engines: {node: '>=18'} + zrender@5.6.1: + resolution: {integrity: sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==} + snapshots: '@ampproject/remapping@2.3.0': @@ -2021,12 +1993,12 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 + '@kurkle/color@0.3.4': {} + '@mermaid-js/parser@0.3.0': dependencies: langium: 3.0.0 - '@microsoft/fetch-event-source@2.0.1': {} - '@one-ini/wasm@0.1.1': {} '@pkgjs/parseargs@0.11.0': @@ -2224,15 +2196,6 @@ snapshots: '@types/geojson@7946.0.16': {} - '@types/linkify-it@5.0.0': {} - - '@types/markdown-it@14.1.2': - dependencies: - '@types/linkify-it': 5.0.0 - '@types/mdurl': 2.0.0 - - '@types/mdurl@2.0.0': {} - '@types/trusted-types@2.0.7': optional: true @@ -2396,6 +2359,10 @@ snapshots: caniuse-lite@1.0.30001700: {} + chart.js@4.4.8: + dependencies: + '@kurkle/color': 0.3.4 + chevrotain-allstar@0.3.1(chevrotain@11.0.3): dependencies: chevrotain: 11.0.3 @@ -2664,6 +2631,11 @@ snapshots: eastasianwidth@0.2.0: {} + echarts@5.6.0: + dependencies: + tslib: 2.3.0 + zrender: 5.6.1 + editorconfig@1.0.4: dependencies: '@one-ini/wasm': 0.1.1 @@ -2677,8 +2649,6 @@ snapshots: emoji-regex@9.2.2: {} - entities@2.1.0: {} - entities@3.0.1: {} entities@4.5.0: {} @@ -2717,6 +2687,12 @@ snapshots: estree-walker@2.0.2: {} + eventsource-client@1.1.3: + dependencies: + eventsource-parser: 3.0.0 + + eventsource-parser@3.0.0: {} + execa@9.5.2: dependencies: '@sindresorhus/merge-streams': 4.0.0 @@ -2862,9 +2838,7 @@ snapshots: layout-base@2.0.1: {} - linkify-it@3.0.3: - dependencies: - uc.micro: 1.0.6 + licia@1.46.0: {} linkify-it@4.0.1: dependencies: @@ -2881,8 +2855,6 @@ snapshots: lodash-es@4.17.21: {} - lodash.flow@3.5.0: {} - lru-cache@10.4.3: {} lru-cache@5.1.1: @@ -2893,43 +2865,7 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 - markdown-it-abbr@1.0.4: {} - - markdown-it-anchor@8.6.7(@types/markdown-it@14.1.2)(markdown-it@12.3.2): - dependencies: - '@types/markdown-it': 14.1.2 - markdown-it: 12.3.2 - - markdown-it-deflist@2.1.0: {} - - markdown-it-emoji@2.0.2: {} - - markdown-it-footnote@3.0.3: {} - - markdown-it-highlightjs@3.6.0: - dependencies: - highlight.js: 11.11.1 - lodash.flow: 3.5.0 - - markdown-it-ins@3.0.1: {} - - markdown-it-mark@3.0.1: {} - - markdown-it-sub@1.0.0: {} - - markdown-it-sup@1.0.0: {} - - markdown-it-task-lists@2.1.1: {} - - markdown-it-toc-done-right@4.2.0: {} - - markdown-it@12.3.2: - dependencies: - argparse: 2.0.1 - entities: 2.1.0 - linkify-it: 3.0.3 - mdurl: 1.0.1 - uc.micro: 1.0.6 + markdown-it-container@4.0.0: {} markdown-it@13.0.2: dependencies: @@ -3208,6 +3144,8 @@ snapshots: ts-dedent@2.2.0: {} + tslib@2.3.0: {} + uc.micro@1.0.6: {} uc.micro@2.1.0: {} @@ -3303,24 +3241,6 @@ snapshots: vscode-uri@3.0.8: {} - vue3-markdown-it@1.0.10(@types/markdown-it@14.1.2): - dependencies: - markdown-it: 12.3.2 - markdown-it-abbr: 1.0.4 - markdown-it-anchor: 8.6.7(@types/markdown-it@14.1.2)(markdown-it@12.3.2) - markdown-it-deflist: 2.1.0 - markdown-it-emoji: 2.0.2 - markdown-it-footnote: 3.0.3 - markdown-it-highlightjs: 3.6.0 - markdown-it-ins: 3.0.1 - markdown-it-mark: 3.0.1 - markdown-it-sub: 1.0.0 - markdown-it-sup: 1.0.0 - markdown-it-task-lists: 2.1.1 - markdown-it-toc-done-right: 4.2.0 - transitivePeerDependencies: - - '@types/markdown-it' - vue@3.5.13: dependencies: '@vue/compiler-dom': 3.5.13 @@ -3353,3 +3273,7 @@ snapshots: yallist@3.1.1: {} yoctocolors@2.1.1: {} + + zrender@5.6.1: + dependencies: + tslib: 2.3.0 diff --git a/client/src/App.vue b/client/src/App.vue index 1182e71..e3d9478 100644 --- a/client/src/App.vue +++ b/client/src/App.vue @@ -1,73 +1,161 @@ @@ -88,21 +176,176 @@ const handleSubmit = async () => {
- - +
+ + +
+ + + +
+ +
+
+

设置

+
+
+ + + + + +
+
+ +
+ + +
+
+ +
+ +
+ + +
+
+ +
+ +
+ + +
+
+ +
+ +
+ + +
+
+
+
+ +
+
+
+
+ + +
+ {{ toastMessage }} +
@@ -112,13 +355,17 @@ const handleSubmit = async () => { height: 100vh; display: flex; flex-direction: column; - background-color: #fafafa; /* 更柔和的背景色 */ + background-color: #f8fafc; /* 更浅的背景色 */ } .chat-header { padding: 20px 32px; - background: linear-gradient(to right, #2563eb, #3b82f6); /* 渐变背景 */ - box-shadow: 0 2px 12px rgba(37, 99, 235, 0.15); + background: linear-gradient( + to right, + #334155, + #475569 + ); /* 更深沉的渐变背景 */ + box-shadow: 0 2px 12px rgba(51, 65, 85, 0.15); position: sticky; top: 0; z-index: 10; @@ -136,7 +383,11 @@ const handleSubmit = async () => { flex: 1; overflow-y: auto; padding: 32px; - background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%); /* 渐变背景 */ + background: linear-gradient( + 135deg, + #f1f5f9 0%, + #ffffff 100% + ); /* 更柔和的渐变背景 */ scroll-behavior: smooth; } @@ -144,12 +395,13 @@ const handleSubmit = async () => { max-width: 1200px; margin: 0 auto; background-color: #ffffff; - border-radius: 20px; - box-shadow: 0 8px 30px rgba(0, 0, 0, 0.08); + border-radius: 12px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05); padding: 40px; - min-height: 200px; + min-height: 200px; /* 保持最小高度 */ + height: auto; /* 添加自适应高度 */ transition: all 0.3s ease; - border: 1px solid rgba(0, 0, 0, 0.05); + border: 1px solid #e2e8f0; } .placeholder { @@ -162,13 +414,15 @@ const handleSubmit = async () => { } .input-area { - padding: 24px 32px; + padding: 16px 24px; /* 减小内边距 */ background: rgba(255, 255, 255, 0.9); backdrop-filter: blur(20px); border-top: 1px solid rgba(0, 0, 0, 0.06); position: sticky; bottom: 0; box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.05); + display: flex; + flex-direction: column; } .input-container { @@ -176,17 +430,16 @@ const handleSubmit = async () => { margin: 0 auto; width: 100%; display: flex; - gap: 20px; + gap: 12px; /* 减小间距 */ } textarea { flex: 1; - min-height: 64px; max-height: 200px; - padding: 18px 24px; - border: 2px solid #e2e8f0; - border-radius: 16px; - resize: none; /* 禁用调整大小功能 */ + padding: 14px 20px; /* 减小内边距 */ + border: 1px solid #e2e8f0; + border-radius: 12px; + resize: none; font-size: 1rem; line-height: 1.6; transition: all 0.2s ease; @@ -196,8 +449,8 @@ textarea { textarea:focus { outline: none; - border-color: #3b82f6; - box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.15); + border-color: #475569; + box-shadow: 0 0 0 4px rgba(71, 85, 105, 0.15); } textarea::placeholder { @@ -205,31 +458,42 @@ textarea::placeholder { font-weight: 500; } -.send-button { - padding: 0 32px; - background: linear-gradient(135deg, #2563eb 0%, #3b82f6 100%); +.button-group { + display: flex; + flex-direction: column; + gap: 6px; /* 减小按钮之间的间距 */ + justify-content: stretch; +} + +.send-button, +.settings-button { + width: 100px; /* 统一按钮宽度 */ + height: 46px; /* 统一按钮高度 */ + background: linear-gradient(135deg, #334155 0%, #475569 100%); color: white; border: none; - border-radius: 16px; + border-radius: 12px; cursor: pointer; font-weight: 600; font-size: 1rem; display: flex; align-items: center; - gap: 10px; + justify-content: center; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - box-shadow: 0 4px 12px rgba(37, 99, 235, 0.2); + box-shadow: 0 4px 12px rgba(51, 65, 85, 0.2); } -.send-button:hover:not(:disabled) { +.send-button:hover:not(:disabled), +.settings-button:hover { transform: translateY(-2px); - box-shadow: 0 6px 20px rgba(37, 99, 235, 0.25); - background: linear-gradient(135deg, #1d4ed8 0%, #2563eb 100%); + box-shadow: 0 6px 20px rgba(51, 65, 85, 0.25); + background: linear-gradient(135deg, #1e293b 0%, #334155 100%); } -.send-button:active:not(:disabled) { +.send-button:active:not(:disabled), +.settings-button:active { transform: translateY(0); - box-shadow: 0 2px 8px rgba(37, 99, 235, 0.2); + box-shadow: 0 2px 8px rgba(51, 65, 85, 0.2); } .send-button:disabled { @@ -241,7 +505,11 @@ textarea::placeholder { /* Markdown 样式优化 */ .markdown-body { - font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + width: 100%; + height: auto; /* 添加自适应高度 */ + overflow: visible; /* 确保内容不会被截断 */ + font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', + sans-serif; font-size: 1.05rem; line-height: 1.8; color: #334155; @@ -249,20 +517,21 @@ textarea::placeholder { .markdown-body :deep(pre) { background-color: #f8fafc; - border-radius: 16px; - padding: 24px; + border-radius: 8px; /* 减小圆角 */ + padding: 20px; overflow-x: auto; border: 1px solid #e2e8f0; - box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.05); + box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.03); } .markdown-body :deep(code) { - font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; + font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, + Consolas, monospace; font-size: 0.95em; background-color: #f1f5f9; padding: 0.2em 0.4em; - border-radius: 6px; - color: #2563eb; + border-radius: 4px; + color: #475569; } .markdown-body :deep(pre code) { @@ -275,9 +544,9 @@ textarea::placeholder { .markdown-body :deep(blockquote) { margin: 2em 0; padding: 1em 1.5em; - border-left: 4px solid #3b82f6; + border-left: 4px solid #475569; background-color: #f8fafc; - border-radius: 0 16px 16px 0; + border-radius: 0 8px 8px 0; /* 减小圆角 */ color: #475569; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.03); } @@ -290,7 +559,7 @@ textarea::placeholder { border-top-color: transparent; border-radius: 50%; animation: spin 0.8s linear infinite; - display: inline-block; + display: block; /* 改为 block 以确保居中 */ } @keyframes spin { @@ -301,25 +570,17 @@ textarea::placeholder { /* 响应式设计优化 */ @media (max-width: 768px) { - .messages-container { - padding: 20px; - } - - .answer-area { - padding: 24px; - } - .input-area { - padding: 16px 20px; + padding: 12px 16px; /* 移动端进一步减小内边距 */ } - + + .input-container { + gap: 8px; /* 移动端减小间距 */ + } + textarea { - padding: 16px 20px; - font-size: 16px; - } - - .send-button { - padding: 0 24px; + min-height: 56px; /* 移动端减小文本框高度 */ + padding: 12px 16px; /* 移动端减小内边距 */ } } @@ -330,16 +591,346 @@ textarea::placeholder { justify-content: center; gap: 12px; font-size: 1.2rem; - color: #3b82f6; + color: #475569; /* 更改思考中的颜色 */ } .thinking-spinner { width: 16px; height: 16px; - border: 2.5px solid #3b82f6; + border: 2.5px solid #475569; /* 更改加载动画颜色 */ border-top-color: transparent; border-radius: 50%; animation: spin 0.8s linear infinite; display: inline-block; } - \ No newline at end of file + +.settings-modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: flex-start; + justify-content: center; + z-index: 1000; + padding-top: 15vh; +} + +.settings-content { + background: white; + border-radius: 12px; + width: 90%; + max-width: 600px; + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), + 0 10px 10px -5px rgba(0, 0, 0, 0.04); + display: flex; + flex-direction: column; + transform-origin: bottom; +} + +.settings-header { + padding: 5px 20px; + border-bottom: 1px solid #e2e8f0; +} + +.settings-body { + padding: 24px; + flex: 1; + min-height: 200px; +} + +.settings-footer { + padding: 10px 20px; + border-top: 1px solid #e2e8f0; + display: flex; + justify-content: flex-end; + background-color: #f8fafc; + border-radius: 0 0 12px 12px; +} + +.save-button { + background: #334155; + border: none; + cursor: pointer; + padding: 8px 15px; + color: white; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + border-radius: 6px; + font-size: 0.95rem; + font-weight: 500; + transition: all 0.2s ease; +} + +.save-button:hover { + background: #1e293b; + transform: translateY(-1px); +} + +.save-button:active { + transform: translateY(0); +} + +/* 修改数据库设置区域的样式 */ +.database-settings { + margin-top: 12px; + padding: 12px 16px; + background-color: #f8fafc; + border-radius: 8px; + border: 1px solid #e2e8f0; +} + +.input-field { + margin-bottom: 12px; + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; +} + +.input-field:last-child { + margin-bottom: 0; +} + +.input-field label { + display: block; + font-size: 1rem; + color: #334155; + font-weight: 500; + flex: 0 0 80px; +} + +.input-field input { + flex: 1; + padding: 8px 12px; + border: 1px solid #e2e8f0; + border-radius: 6px; + font-size: 0.95rem; + transition: all 0.2s ease; + background-color: white; + padding-right: 32px; /* 为清空按钮留出空间 */ +} + +.input-field input:focus { + outline: none; + border-color: #475569; + box-shadow: 0 0 0 2px rgba(71, 85, 105, 0.1); +} + +.input-field input::placeholder { + color: #94a3b8; +} + +.input-wrapper { + position: relative; + flex: 1; + display: flex; + align-items: center; +} + +.clear-button { + position: absolute; + right: 8px; + top: 50%; + transform: translateY(-50%); + background: none; + border: none; + color: #94a3b8; + cursor: pointer; + padding: 4px; + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; + border-radius: 50%; + width: 20px; + height: 20px; + transition: all 0.2s ease; + z-index: 1; +} + +.clear-button:hover { + background-color: #f1f5f9; + color: #64748b; +} + +.clear-button:active { + background-color: #e2e8f0; +} + +/* 优化开关选项的间距 */ +.switch-wrapper { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 0; +} + +.switch-wrapper + .switch-wrapper { + border-top: 1px solid #e2e8f0; +} + +.switch-label { + font-size: 1rem; + color: #334155; + font-weight: 500; +} + +/* 开关样式 */ +.switch { + position: relative; + display: inline-block; + width: 48px; + height: 24px; +} + +.switch input { + opacity: 0; + width: 0; + height: 0; +} + +.slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #cbd5e1; + transition: 0.4s; + border-radius: 24px; +} + +.slider:before { + position: absolute; + content: ''; + height: 18px; + width: 18px; + left: 3px; + bottom: 3px; + background-color: white; + transition: 0.4s; + border-radius: 50%; +} + +input:checked + .slider { + background-color: #334155; +} + +input:focus + .slider { + box-shadow: 0 0 1px #334155; +} + +input:checked + .slider:before { + transform: translateX(24px); +} + +/* 修改 Toast 样式 */ +.toast-message { + position: fixed; + top: 90px; + left: 50%; + transform: translateX(-50%); + background-color: #334155; + color: white; + padding: 12px 24px; + border-radius: 8px; + font-size: 0.95rem; + font-weight: 500; + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), + 0 2px 4px -1px rgba(0, 0, 0, 0.06); + z-index: 2000; + display: flex; + align-items: center; + gap: 8px; +} + +.toast-message::before { + font-weight: bold; +} + +/* 成功状态的 toast */ +.toast-message[data-type='success']::before { + content: '✓'; + color: #10b981; +} + +/* 错误状态的 toast */ +.toast-message[data-type='error']::before { + content: '!'; + color: #ef4444; +} + +/* 添加响应式样式 */ +@media (max-width: 768px) { + .toast-message { + width: 90%; + max-width: 320px; + text-align: center; + justify-content: center; + } +} + +/* 添加必要的过渡类 */ +.transition { + transition-property: all; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); +} + +.duration-300 { + transition-duration: 300ms; +} + +.duration-200 { + transition-duration: 200ms; +} + +.ease-out { + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); +} + +.ease-in { + transition-timing-function: cubic-bezier(0.4, 0, 1, 1); +} + +.opacity-0 { + opacity: 0; +} + +.opacity-100 { + opacity: 1; +} + +.translate-y-4 { + transform: translateY(1rem); +} + +.translate-y-0 { + transform: translateY(0); +} + +/* 删除这些不需要的类 */ +.translate-y-full { + transform: translateY(100%); +} + +.translate-y-\[-20px\] { + transform: translateY(-20px); +} + +/* 添加图表容器样式 */ +.chart-container { + width: 100%; + max-width: 800px; + margin: 20px auto; + padding: 20px; + background: #ffffff; + border-radius: 8px; + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05); +} + diff --git a/client/src/main.js b/client/src/main.js index c5f2728..c6e14a4 100644 --- a/client/src/main.js +++ b/client/src/main.js @@ -1,7 +1,5 @@ import {createApp} from 'vue' import App from './App.vue' -import Markdown from 'vue3-markdown-it' const app = createApp(App) - .use(Markdown) .mount('#app') \ No newline at end of file diff --git a/pom.xml b/pom.xml index 1f96519..5f8af47 100644 --- a/pom.xml +++ b/pom.xml @@ -22,9 +22,21 @@ org.springframework.boot spring-boot-starter-web - + + + org.springframework.ai + spring-ai-zhipuai-spring-boot-starter + + + + com.mysql + mysql-connector-j org.projectlombok diff --git a/src/main/java/com/lanyuanxiaoyao/ai/analysis/controller/ChatController.java b/src/main/java/com/lanyuanxiaoyao/ai/analysis/controller/ChatController.java index 7728c98..4b13a15 100644 --- a/src/main/java/com/lanyuanxiaoyao/ai/analysis/controller/ChatController.java +++ b/src/main/java/com/lanyuanxiaoyao/ai/analysis/controller/ChatController.java @@ -1,15 +1,23 @@ package com.lanyuanxiaoyao.ai.analysis.controller; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import com.lanyuanxiaoyao.ai.analysis.tools.DatabaseTools; +import com.lanyuanxiaoyao.ai.analysis.tools.DatetimeTools; +import com.lanyuanxiaoyao.ai.analysis.tools.PromptTools; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import lombok.extern.slf4j.Slf4j; import org.springframework.ai.chat.client.ChatClient; import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; -import reactor.core.publisher.Flux; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; @Slf4j @CrossOrigin @@ -23,8 +31,17 @@ public class ChatController { } @ResponseBody - @PostMapping(value = "", produces = MediaType.TEXT_PLAIN_VALUE + ";charset=utf-8") - public String chat(@RequestBody String prompt) { + @PostMapping(value = "", produces = MediaType.TEXT_PLAIN_VALUE) + public String chat( + @RequestParam("prompt") String prompt, + @RequestParam(value = "use_tw", defaultValue = "false") Boolean useTw, + @RequestParam(value = "use_database", defaultValue = "false") Boolean useDatabase, + @RequestParam(value = "database_url", required = false) String databaseUrl, + @RequestParam(value = "database_username", required = false) String databaseUsername, + @RequestParam(value = "database_password", required = false) String databasePassword, + @RequestParam(value = "database_name", required = false) String databaseName + ) { + logParameters(prompt, useTw, useDatabase, databaseUrl, databaseUsername, databasePassword, databaseName); return client.prompt() .system("始终在中文语境下回答") .user(prompt) @@ -32,12 +49,65 @@ public class ChatController { .content(); } - @PostMapping(value = "stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE + ";charset=utf-8") - public Flux chatStream(@RequestBody String prompt) { - return client.prompt() - .system("始终在中文语境下回答") + @PostMapping(value = "stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) + public SseEmitter chatStream( + @RequestParam("prompt") String prompt, + @RequestParam(value = "use_tw", defaultValue = "false") Boolean useTw, + @RequestParam(value = "use_database", defaultValue = "false") Boolean useDatabase, + @RequestParam(value = "database_url", required = false) String databaseUrl, + @RequestParam(value = "database_username", required = false) String databaseUsername, + @RequestParam(value = "database_password", required = false) String databasePassword, + @RequestParam(value = "database_name", required = false) String databaseName + ) { + log.info("chatStream"); + logParameters(prompt, useTw, useDatabase, databaseUrl, databaseUsername, databasePassword, databaseName); + SseEmitter emitter = new SseEmitter(); + StringBuilder systemPrompt = new StringBuilder("始终在中文语境下回答\n\n"); + systemPrompt + .append("当遇到数据对比、占比、趋势等场景相关的提问,优先考虑使用mermaid绘制图表,图文结合来回答") + .append(PromptTools.MERMAID_PROMPT) + .append("\n\n"); + List tools = new ArrayList<>(); + tools.add(new DatetimeTools()); + + if (useDatabase) { + log.info("use database"); + DatabaseTools databaseTools = new DatabaseTools(databaseUrl, databaseUsername, databasePassword); + systemPrompt.append("以下是Hudi数据库中已有表的详细信息,包含表名、表描述、字段名和字段描述,任何与这些表数据相关的问题,都优先查询数据库获取答案\n"); + systemPrompt.append(databaseTools.getTableInformation(databaseName)).append("\n"); + systemPrompt.append("任何使用Hudi数据库的数据的回答都必须从实际的数据中获取,严禁虚构任何下列信息中不存在的表或表字段,没有描述的表或字段可以从表名或字段名中推测,如果找不到答案,就回复从数据库中没有找到相关数据\n"); + tools.add(databaseTools); + } + + client.prompt() + .system(systemPrompt.toString()) .user(prompt) + .tools(tools.toArray(new Object[]{})) .stream() - .content(); + .content() + .subscribe( + text -> { + try { + emitter.send(SseEmitter.event().id(IdUtil.nanoId()).data(StrUtil.format("#/#{}#/#", text)).build()); + } catch (IOException e) { + emitter.completeWithError(e); + } + }, + emitter::completeWithError, + emitter::complete + ); + return emitter; + } + + private void logParameters(String prompt, Boolean useTw, Boolean useDatabase, String databaseUrl, String databaseUsername, String databasePassword, String databaseName) { + log.info("prompt: {}", prompt); + log.info("use_tw: {}", useTw); + log.info("use_database: {}", useDatabase); + if (useDatabase) { + log.info("database_url: {}", databaseUrl); + log.info("database_username: {}", databaseUsername); + log.info("database_password: {}", databasePassword); + log.info("database_name: {}", databaseName); + } } } diff --git a/src/main/java/com/lanyuanxiaoyao/ai/analysis/tools/DatabaseTools.java b/src/main/java/com/lanyuanxiaoyao/ai/analysis/tools/DatabaseTools.java new file mode 100644 index 0000000..795bafc --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/ai/analysis/tools/DatabaseTools.java @@ -0,0 +1,90 @@ +package com.lanyuanxiaoyao.ai.analysis.tools; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.db.DbUtil; +import cn.hutool.db.Entity; +import cn.hutool.db.Session; +import cn.hutool.db.ds.simple.SimpleDataSource; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; +import java.util.Set; +import javax.sql.DataSource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.ai.tool.annotation.Tool; +import org.springframework.ai.tool.annotation.ToolParam; + +/** + * 提供AI调用数据库的能力 + * + * @author lanyuanxiaoyao + * @version 20250225 + */ +@Slf4j +public class DatabaseTools { + private final DataSource dataSource; + + public DatabaseTools(String url, String username, String password) { + this.dataSource = new SimpleDataSource(url, username, password); + } + + public static void main(String[] args) { + DatabaseTools tools = new DatabaseTools( + "jdbc:mysql://localhost:3307/main", + "test", + "test" + ); + log.info(tools.getTableInformation("main")); + } + + @Tool(description = "连接数据库执行SQL查询语句,查询结果对应的数据并以CSV格式返回,调用这个方法必须传如SQL语句作为参数") + public String query(@ToolParam(description = "标准MySQL语句,不允许有除了SQL语句本身之外的任何提示性文字、注释、说明等") String sql) { + log.info("sql: {}", sql); + try (Session session = DbUtil.newSession(dataSource)) { + StringBuilder builder = new StringBuilder(); + List entities = session.query(sql); + for (Entity entity : entities) { + Set fields = entity.getFieldNames(); + for (String field : fields) { + builder.append(entity.get(field)).append(","); + } + builder.append("\n"); + } + return builder.toString(); + } catch (SQLException e) { + return StrUtil.format("查询失败,失败提示:{}", e.getMessage()); + } + } + + public String getTableInformation(String databaseName) { + StringBuilder builder = new StringBuilder(); + try (Session session = DbUtil.newSession(dataSource)) { + try (Connection connection = session.getConnection()) { + DatabaseMetaData metaData = connection.getMetaData(); + ResultSet tables = metaData.getTables(databaseName, null, null, null); + while (tables.next()) { + String tableName = tables.getString("TABLE_NAME"); + String tableComment = tables.getString("REMARKS"); + ; + builder.append(StrUtil.format("以下是{}表的详细信息\n", tableName)); + if (StrUtil.isNotBlank(tableComment)) { + builder.append(StrUtil.format("表描述:{}\n", tableComment)); + } + ResultSet columns = metaData.getColumns(databaseName, null, tableName, null); + builder.append(StrUtil.format("以下是{}表的字段名和字段描述\n", tableName)); + while (columns.next()) { + String columnName = columns.getString("COLUMN_NAME"); + String columnComment = columns.getString("REMARKS"); + builder.append(StrUtil.format("{}:{}", columnName, StrUtil.isBlank(columnComment) ? "无" : columnComment)).append("\n"); + } + builder.append("\n"); + } + } catch (SQLException e) { + return StrUtil.format("获取表信息失败,失败提示:{}", e.getMessage()); + } + } + return builder.toString(); + } +} diff --git a/src/main/java/com/lanyuanxiaoyao/ai/analysis/tools/DatetimeTools.java b/src/main/java/com/lanyuanxiaoyao/ai/analysis/tools/DatetimeTools.java new file mode 100644 index 0000000..2743355 --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/ai/analysis/tools/DatetimeTools.java @@ -0,0 +1,24 @@ +package com.lanyuanxiaoyao.ai.analysis.tools; + +import java.time.LocalDateTime; +import lombok.extern.slf4j.Slf4j; +import org.springframework.ai.tool.annotation.Tool; +import org.springframework.ai.tool.function.FunctionToolCallback; + +/** + * @author lanyuanxiaoyao + * @version 20250225 + */ +@Slf4j +public class DatetimeTools { + public static final FunctionToolCallback call = FunctionToolCallback.builder("current_time", () -> LocalDateTime.now()) + .description("返回当前的日期时间") + .inputType(Void.class) + .build(); + + @Tool(description = "返回当前的日期时间") + public String currentTime() { + return LocalDateTime.now().toString(); + } + +} diff --git a/src/main/java/com/lanyuanxiaoyao/ai/analysis/tools/PromptTools.java b/src/main/java/com/lanyuanxiaoyao/ai/analysis/tools/PromptTools.java new file mode 100644 index 0000000..5ee77ec --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/ai/analysis/tools/PromptTools.java @@ -0,0 +1,211 @@ +package com.lanyuanxiaoyao.ai.analysis.tools; + +/** + * @author lanyuanxiaoyao + * @version 20250225 + */ +public class PromptTools { + public static final String MERMAID_PROMPT = """ + ### 概述 + Mermaid 是一种基于 JavaScript 的工具,使用 Markdown 风格的文本生成各种图表。以下是为内网大模型准备的语法总结,确保模型能理解并生成正确的图表代码。我们将详细介绍每种图表类型的用途和基本语法,并提供示例。 + + ### 详细语法 + 以下是 12 种 Mermaid 图表类型的语法总结,每个类型包括用途、语法规则和示例,方便模型识别和使用。 + + #### 流程图 (Flowchart) + - **用途**: 用于表示算法、工作流或流程。 + - **语法**: + - 以 `graph [direction]` 开始,方向包括 `TB`(从上到下)、`BT`(从下到上)等。 + - 节点用 ID 和标签定义,连接用箭头 `-->` 表示。 + - **示例**: + ```mermaid + graph TD + A[开始] --> B{条件} + B -- 是 --> C[处理] + B -- 否 --> D[结束] + ``` + + #### 序列图 (Sequence Diagram) + - **用途**: 显示对象或参与者之间的交互。 + - **语法**: + - 以 `sequenceDiagram` 开始。 + - 定义参与者用 `participant `。 + - 消息用 `->>: message` 发送。 + - **示例**: + ```mermaid + sequenceDiagram + participant Alice + participant Bob + Alice->>Bob: 你好 + Bob->>Alice: 嗨 + ``` + + #### 类图 (Class Diagram) + - **用途**: 描述系统结构,显示类及其关系。 + - **语法**: + - 以 `classDiagram` 开始。 + - 定义类用 `class { }`。 + - 关系用 ` ` 表示(如 `extends`)。 + - **示例**: + ```mermaid + classDiagram + class Animal + class Dog extends Animal + class Cat extends Animal + ``` + + #### 状态图 (State Diagram) + - **用途**: 通过状态和转换建模系统或对象的行为。 + - **语法**: + - 以 `stateDiagram` 开始。 + - 定义状态用 `state `。 + - 转换用 ` --> : condition` 表示。 + - **示例**: + ```mermaid + stateDiagram + [*] --> Active + Active --> Inactive: 停用时 + Inactive --> Active: 激活时 + Active --> [*]: 销毁时 + ``` + + #### 实体关系图 (ER Diagram) + - **用途**: 可视化数据库中的实体关系。 + - **语法**: + - 以 `erDiagram` 开始。 + - 定义实体用 ` { }`。 + - 关系用 ` || : cardinality` 表示。 + - **示例**: + ```mermaid + erDiagram + Customer { name } + Order { order_id } + Customer ||--o{ Order: 放置 + ``` + + #### 用户旅程图 (User Journey Diagram) + - **用途**: 映射用户完成任务的步骤。 + - **语法**: + - 以 `journey` 开始。 + - 定义部分用 `section `。 + - 定义任务用 `: : `。 + - **示例**: + ```mermaid + journey + title 我的工作日 + section 去上班 + 泡茶: 5: 我 + 上楼: 3: 我 + 工作: 1: 我, 猫 + section 回家 + 下楼: 5: 我 + 坐下: 5: 我 + ``` + + #### 甘特图 (Gantt Chart) + - **用途**: 安排和跟踪项目进度。 + - **语法**: + - 以 `gantt` 开始。 + - 定义部分和任务,包括日期和持续时间。 + - **示例**: + ```mermaid + gantt + dateFormat YYYY-MM-DD + section 部分 + 任务: done, 2020-01-01, 2020-01-02 + 活动任务: active, 2020-01-03, 3d + 未来任务: 2020-01-06, 5d + ``` + + #### 饼图 (Pie Chart) + - **用途**: 显示整体的比例或百分比。 + - **语法**: + - 以 `pie` 开始。 + - 定义切片用 `label: value`。 + - **示例**: + ```mermaid + pie + title 我的饼图 + "标签1": 50 + "标签2": 25 + "标签3": 25 + ``` + + #### 需求图 (Requirement Diagram) + - **用途**: 可视化系统设计中的需求及其关系。 + - **语法**: + - 以 `requirementDiagram` 开始。 + - 定义需求用 ` { id: , text: , risk: , verifymethod: }`。 + - 定义需求间关系。 + - **示例**: + ```mermaid + requirementDiagram + requirement Req1 { id: R1, text: "第一个需求" } + requirement Req2 { id: R2, text: "第二个需求" } + Req1 - satisfies -> Req2 + ``` + + #### Git 图 (Git Graph) + - **用途**: 展示 Git 仓库的历史。 + - **语法**: + - 以 `gitGraph` 开始。 + - 定义提交、分支和合并。 + - **示例**: + ```mermaid + gitGraph + commit + branch develop + commit + commit + merge master + ``` + + #### 时间线 (Timeline) + - **用途**: 显示按时间顺序的事件。 + - **语法**: + - 以 `timeline` 开始。 + - 定义时间段和事件。 + - **示例**: + ```mermaid + timeline + title 互联网历史 + 1969 : ARPANET 诞生 + 1973 : 以太网发明 + 1983 : DNS 创建 + ``` + + #### 思维导图 (Mindmap) + - **用途**: 可视化想法及其关系。 + - **语法**: + - 以 `mindmap` 开始。 + - 用缩进定义层次结构。 + - **示例**: + ```mermaid + mindmap + 根节点 + 子节点 1 + 孙节点 1 + 孙节点 2 + 子节点 2 + ``` + + --- + #### 图表类型与语法详细分析 + 以下是每种图表类型的详细语法说明,包括用途、语法规则和示例。我们使用表格形式组织信息,以提高可读性。 + + | 图表类型 | 用途 | 语法规则 | 示例 | + |----------------|-------------------------------|------------------------------------------------------------------------|--------------------------------------------------------------------| + | 流程图 (Flowchart) | 表示算法、工作流或流程 | 以 `graph [direction]` 开始,方向如 `TB`、`LR`;节点用 ID 和标签定义,连接用 `-->`。 | `graph TD A[开始] --> B{条件} B -- 是 --> C[处理] B -- 否 --> D[结束]` | + | 序列图 (Sequence Diagram) | 显示对象或参与者间的交互 | 以 `sequenceDiagram` 开始,定义参与者用 `participant `,消息用 `->>: message`。 | `sequenceDiagram participant Alice participant Bob Alice->>Bob: 你好 Bob->>Alice: 嗨` | + | 类图 (Class Diagram) | 描述系统结构,显示类及其关系 | 以 `classDiagram` 开始,定义类用 `class { }`,关系用 ` `。 | `classDiagram class Animal class Dog extends Animal class Cat extends Animal` | + | 状态图 (State Diagram) | 通过状态和转换建模系统或对象行为 | 以 `stateDiagram` 开始,定义状态用 `state `,转换用 ` --> : condition`。 | `stateDiagram [*] --> Active Active --> Inactive: 停用时 Inactive --> Active: 激活时 Active --> [*]: 销毁时` | + | 实体关系图 (ER Diagram) | 可视化数据库中的实体关系 | 以 `erDiagram` 开始,定义实体用 ` { }`,关系用 ` || : cardinality`。 | `erDiagram Customer { name } Order { order_id } Customer ||--o{ Order: 放置` | + | 用户旅程图 (User Journey Diagram) | 映射用户完成任务的步骤 | 以 `journey` 开始,定义部分用 `section `,任务用 `: : `。 | `journey title 我的工作日 section 去上班 泡茶: 5: 我 上楼: 3: 我 工作: 1: 我, 猫 section 回家 下楼: 5: 我 坐下: 5: 我` | + | 甘特图 (Gantt Chart) | 安排和跟踪项目进度 | 以 `gantt` 开始,定义部分和任务,包括日期和持续时间。 | `gantt dateFormat YYYY-MM-DD section 部分 任务: done, 2020-01-01, 2020-01-02 Active task: active, 2020-01-03, 3d Future task: 2020-01-06, 5d` | + | 饼图 (Pie Chart) | 显示整体的比例或百分比 | 以 `pie` 开始,定义切片用 `label: value`。 | `pie title 我的饼图 "标签1": 50 "标签2": 25 "标签3": 25` | + | 需求图 (Requirement Diagram) | 可视化系统设计中的需求及其关系 | 以 `requirementDiagram` 开始,定义需求用 ` { id: , text: , risk: , verifymethod: }`,定义关系。 | `requirementDiagram requirement Req1 { id: R1, text: "第一个需求" } requirement Req2 { id: R2, text: "第二个需求" } Req1 - satisfies -> Req2` | + | Git 图 (Git Graph) | 展示 Git 仓库的历史 | 以 `gitGraph` 开始,定义提交、分支和合并。 | `gitGraph commit branch develop commit commit merge master` | + | 时间线 (Timeline) | 显示按时间顺序的事件 | 以 `timeline` 开始,定义时间段和事件。 | `timeline title 互联网历史 1969 : ARPANET 诞生 1973 : 以太网发明 1983 : DNS 创建` | + | 思维导图 (Mindmap) | 可视化想法及其关系 | 以 `mindmap` 开始,用缩进定义层次结构。 | `mindmap 根节点 子节点 1 孙节点 1 孙节点 2 子节点 2` | + """; +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index bb292b1..41532ee 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -5,8 +5,27 @@ spring: name: ai-analysis ai: openai: - base-url: https://api.deepseek.com - api-key: "sk-3e1935e3ffb64ab096384bca071e2841" + base-url: https://api.siliconflow.cn + api-key: "sk-xrguybusoqndpqvgzgvllddzgjamksuecyqdaygdwnrnqfwo" chat: options: - model: "deepseek-chat" \ No newline at end of file + model: "Qwen/Qwen2.5-32B-Instruct" + ollama: + chat: + model: "qwen2.5:7b" + options: + max-tokens: 8092 + zhipuai: + api-key: d1e97306540d12bb2f834be961fcacb1.SNBShlCxWYJCx0qZ + chat: + options: + model: "glm-4-flash" +# openai: +# base-url: http://173.0.59.240:8093 +# api-key: test + mvc: + async: + request-timeout: -1 +logging: + level: + org.springframework.ai: trace \ No newline at end of file diff --git a/test.http b/test.http index 8bafd86..b26a7a6 100644 --- a/test.http +++ b/test.http @@ -1,5 +1,5 @@ ### Chat POST http://localhost:7891/chat/stream -Content-Type: text/plain +Content-Type: application/x-www-form-urlencoded -你好 +prompt=tb_app_collect_table_info中src_schema字段各有多少记录&use_database=true&database_url=jdbc:mysql://localhost:3307/main&database_username=test&database_password=test&database_name=main