完成基本功能
This commit is contained in:
6
.idea/ApifoxUploaderProjectSetting.xml
generated
Normal file
6
.idea/ApifoxUploaderProjectSetting.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ApifoxUploaderProjectSetting">
|
||||
<option name="apiAccessToken" value="APS-0ZZaS4q0gUiFOlbBJMN8hAmS7viQNi4D" />
|
||||
</component>
|
||||
</project>
|
||||
17
.idea/dataSources.xml
generated
Normal file
17
.idea/dataSources.xml
generated
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||
<data-source source="LOCAL" name="main@localhost" uuid="4c84d2e6-c5f7-42bf-929e-5eb5cb744ec5">
|
||||
<driver-ref>mysql.8</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
|
||||
<jdbc-url>jdbc:mysql://localhost:3307/main</jdbc-url>
|
||||
<jdbc-additional-properties>
|
||||
<property name="com.intellij.clouds.kubernetes.db.host.port" />
|
||||
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
|
||||
<property name="com.intellij.clouds.kubernetes.db.container.port" />
|
||||
</jdbc-additional-properties>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/sqldialects.xml
generated
Normal file
6
.idea/sqldialects.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="SqlDialectMappings">
|
||||
<file url="PROJECT" dialect="MySQL" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -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",
|
||||
|
||||
212
client/pnpm-lock.yaml
generated
212
client/pnpm-lock.yaml
generated
@@ -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
|
||||
|
||||
@@ -1,73 +1,161 @@
|
||||
<script setup>
|
||||
import {ref} from 'vue'
|
||||
import {onMounted, ref} from 'vue'
|
||||
import MarkdownIt from 'markdown-it'
|
||||
import MarkDownMermaidPlugin from 'mermaid-it-markdown'
|
||||
import {fetchEventSource} from '@microsoft/fetch-event-source'
|
||||
import {createEventSource} from 'eventsource-client'
|
||||
|
||||
// 初始化 markdown-it 实例
|
||||
const md = new MarkdownIt({
|
||||
html: true, // 启用 HTML 标签
|
||||
breaks: true, // 转换换行符为 <br>
|
||||
linkify: true, // 自动转换 URL 为链接
|
||||
typographer: true // 启用一些语言中性的替换 + 引号美化
|
||||
html: true, // 启用 HTML 标签
|
||||
breaks: true, // 转换换行符为 <br>
|
||||
linkify: true, // 自动转换 URL 为链接
|
||||
typographer: true, // 启用一些语言中性的替换 + 引号美化
|
||||
})
|
||||
md.use(MarkDownMermaidPlugin)
|
||||
|
||||
const question = ref('')
|
||||
const answer = ref('')
|
||||
const loading = ref(false)
|
||||
const useTw = ref(false)
|
||||
|
||||
// 使用计算属性来渲染 markdown
|
||||
const renderedAnswer = ref('')
|
||||
|
||||
answer.value = "```mermaid\ngraph TD;\n A-->B;\n A-->C;\n B-->D;\n C-->D;\n```"
|
||||
renderedAnswer.value = md.render(answer.value)
|
||||
// 在 script setup 中添加新的响应式变量
|
||||
const useDatabase = ref(false)
|
||||
const databaseUrl = ref('')
|
||||
const databaseUsername = ref('')
|
||||
const databasePassword = ref('')
|
||||
const databaseName = ref('')
|
||||
|
||||
// 在 script setup 中添加 Toast 相关的响应式变量
|
||||
const showToast = ref(false)
|
||||
const toastMessage = ref('')
|
||||
const toastMessageType = ref('success')
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!question.value.trim() || loading.value) return
|
||||
|
||||
|
||||
loading.value = true
|
||||
answer.value = ''
|
||||
renderedAnswer.value = ''
|
||||
|
||||
|
||||
try {
|
||||
await fetchEventSource('http://localhost:7891/chat/stream', {
|
||||
const url = 'http://localhost:7891/chat/stream'
|
||||
const formData = new FormData()
|
||||
formData.append('prompt', question.value.trim())
|
||||
formData.append('use_tw', useTw.value)
|
||||
|
||||
// 如果启用数据库,添加相关参数
|
||||
if (useDatabase.value) {
|
||||
formData.append('use_database', useDatabase.value)
|
||||
formData.append('database_url', databaseUrl.value)
|
||||
formData.append('database_username', databaseUsername.value)
|
||||
formData.append('database_password', databasePassword.value)
|
||||
formData.append('database_name', databaseName.value)
|
||||
}
|
||||
|
||||
const eventSource = createEventSource({
|
||||
url: url,
|
||||
method: 'POST',
|
||||
body: question.value.trim(),
|
||||
onmessage(event) {
|
||||
// 处理每个消息事件
|
||||
answer.value += event.data
|
||||
// 实时渲染 markdown
|
||||
renderedAnswer.value = md.render(answer.value)
|
||||
},
|
||||
onclose() {
|
||||
loading.value = false
|
||||
body: formData,
|
||||
onDisconnect: () => {
|
||||
console.log(answer.value)
|
||||
console.log(renderedAnswer.value)
|
||||
eventSource.close()
|
||||
},
|
||||
onerror(err) {
|
||||
console.error('请求出错:', err)
|
||||
loading.value = false
|
||||
return false // 不进行重试
|
||||
}
|
||||
})
|
||||
for await (const { data } of eventSource) {
|
||||
answer.value += data.replaceAll(/#\/#/g, '')
|
||||
renderedAnswer.value = md.render(answer.value)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('请求出错:', error)
|
||||
answer.value = '抱歉,请求出现错误'
|
||||
renderedAnswer.value = answer.value
|
||||
showToastMessage('请求失败,请检查服务器连接', 'error')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 添加设置对话框的状态控制
|
||||
const showSettings = ref(false)
|
||||
|
||||
// 添加保存设置到本地存储的函数
|
||||
const saveSettingsToStorage = () => {
|
||||
const settings = {
|
||||
useTw: useTw.value,
|
||||
useDatabase: useDatabase.value,
|
||||
databaseUrl: databaseUrl.value,
|
||||
databaseUsername: databaseUsername.value,
|
||||
databasePassword: databasePassword.value,
|
||||
databaseName: databaseName.value,
|
||||
}
|
||||
localStorage.setItem('chatSettings', JSON.stringify(settings))
|
||||
}
|
||||
|
||||
// 从本地存储加载设置的函数
|
||||
const loadSettingsFromStorage = () => {
|
||||
const settings = localStorage.getItem('chatSettings')
|
||||
if (settings) {
|
||||
const parsed = JSON.parse(settings)
|
||||
useTw.value = parsed.useTw
|
||||
useDatabase.value = parsed.useDatabase
|
||||
databaseUrl.value = parsed.databaseUrl
|
||||
databaseUsername.value = parsed.databaseUsername
|
||||
databasePassword.value = parsed.databasePassword
|
||||
databaseName.value = parsed.databaseName || ''
|
||||
}
|
||||
}
|
||||
|
||||
// 修改 showToastMessage 函数,添加消息类型参数
|
||||
const showToastMessage = (message, type = 'success') => {
|
||||
toastMessage.value = message
|
||||
toastMessageType.value = type
|
||||
showToast.value = true
|
||||
setTimeout(() => {
|
||||
showToast.value = false
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
// 修改 handleCloseSettings 函数中的调用
|
||||
const handleCloseSettings = () => {
|
||||
if (useDatabase.value) {
|
||||
if (!databaseUrl.value.trim()) {
|
||||
showToastMessage('请输入数据库地址', 'error')
|
||||
return
|
||||
}
|
||||
if (!databaseUsername.value.trim()) {
|
||||
showToastMessage('请输入数据库用户名', 'error')
|
||||
return
|
||||
}
|
||||
if (!databaseName.value.trim()) {
|
||||
showToastMessage('请输入数据库名称', 'error')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
saveSettingsToStorage()
|
||||
showSettings.value = false
|
||||
showToastMessage('设置已保存', 'success')
|
||||
}
|
||||
|
||||
// 在组件挂载时加载设置
|
||||
onMounted(() => {
|
||||
loadSettingsFromStorage()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="chat-container">
|
||||
<div class="chat-header">
|
||||
<h1>AI 助手</h1>
|
||||
<h1>数据库助手</h1>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="messages-container">
|
||||
<div class="answer-area">
|
||||
<div
|
||||
<div
|
||||
v-if="renderedAnswer"
|
||||
class="markdown-body"
|
||||
v-html="renderedAnswer"
|
||||
@@ -80,7 +168,7 @@ const handleSubmit = async () => {
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
在下方输入您的问题,AI 助手会为您解答...
|
||||
你好!很高兴见到你。有什么我可以帮忙的吗?
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
@@ -88,21 +176,176 @@ const handleSubmit = async () => {
|
||||
|
||||
<div class="input-area">
|
||||
<div class="input-container">
|
||||
<textarea
|
||||
<textarea
|
||||
v-model="question"
|
||||
placeholder="请输入您的问题... (Ctrl + Enter 快速发送)"
|
||||
@keyup.ctrl.enter="handleSubmit"
|
||||
></textarea>
|
||||
<button
|
||||
:disabled="loading"
|
||||
class="send-button"
|
||||
@click="handleSubmit"
|
||||
>
|
||||
<span v-if="loading" class="loading-spinner"></span>
|
||||
<span>{{ loading ? '思考中...' : '发送' }}</span>
|
||||
</button>
|
||||
<div class="button-group">
|
||||
<button :disabled="loading" class="send-button" @click="handleSubmit">
|
||||
<template v-if="loading">
|
||||
<span class="loading-spinner"></span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span>发送</span>
|
||||
</template>
|
||||
</button>
|
||||
<button class="settings-button" @click="showSettings = true">
|
||||
<span>设置</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 修改设置对话框的 Transition 组件 -->
|
||||
<Transition
|
||||
enter-active-class="transition duration-300 ease-out"
|
||||
enter-from-class="opacity-0"
|
||||
enter-to-class="opacity-100"
|
||||
leave-active-class="transition duration-200 ease-in"
|
||||
leave-from-class="opacity-100"
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<div v-if="showSettings" class="settings-modal">
|
||||
<Transition
|
||||
enter-active-class="transition duration-300 ease-out"
|
||||
enter-from-class="opacity-0 translate-y-4"
|
||||
enter-to-class="opacity-100 translate-y-0"
|
||||
leave-active-class="transition duration-200 ease-in"
|
||||
leave-from-class="opacity-100 translate-y-0"
|
||||
leave-to-class="opacity-0 translate-y-4"
|
||||
>
|
||||
<div class="settings-content">
|
||||
<div class="settings-header">
|
||||
<h2>设置</h2>
|
||||
</div>
|
||||
<div class="settings-body">
|
||||
<label class="switch-wrapper">
|
||||
<span class="switch-label">图文并茂</span>
|
||||
<div class="switch">
|
||||
<input v-model="useTw" type="checkbox" />
|
||||
<span class="slider"></span>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="switch-wrapper">
|
||||
<span class="switch-label">连接数据库</span>
|
||||
<div class="switch">
|
||||
<input v-model="useDatabase" type="checkbox" />
|
||||
<span class="slider"></span>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<!-- 数据库设置项,仅在 useDatabase 为 true 时显示 -->
|
||||
<div v-if="useDatabase" class="database-settings">
|
||||
<div class="input-field">
|
||||
<label for="database-url">数据库地址</label>
|
||||
<div class="input-wrapper">
|
||||
<input
|
||||
id="database-url"
|
||||
v-model="databaseUrl"
|
||||
placeholder="请输入数据库连接地址"
|
||||
type="text"
|
||||
/>
|
||||
<button
|
||||
v-if="databaseUrl"
|
||||
class="clear-button"
|
||||
@click="databaseUrl = ''"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="input-field">
|
||||
<label for="database-username">用户名</label>
|
||||
<div class="input-wrapper">
|
||||
<input
|
||||
id="database-username"
|
||||
v-model="databaseUsername"
|
||||
placeholder="请输入数据库用户名"
|
||||
type="text"
|
||||
/>
|
||||
<button
|
||||
v-if="databaseUsername"
|
||||
class="clear-button"
|
||||
@click="databaseUsername = ''"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="input-field">
|
||||
<label for="database-password">密码</label>
|
||||
<div class="input-wrapper">
|
||||
<input
|
||||
id="database-password"
|
||||
v-model="databasePassword"
|
||||
placeholder="请输入数据库密码"
|
||||
type="password"
|
||||
/>
|
||||
<button
|
||||
v-if="databasePassword"
|
||||
class="clear-button"
|
||||
@click="databasePassword = ''"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="input-field">
|
||||
<label for="database-name">数据库名</label>
|
||||
<div class="input-wrapper">
|
||||
<input
|
||||
id="database-name"
|
||||
v-model="databaseName"
|
||||
placeholder="请输入数据库名称"
|
||||
type="text"
|
||||
/>
|
||||
<button
|
||||
v-if="databaseName"
|
||||
class="clear-button"
|
||||
@click="databaseName = ''"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-footer">
|
||||
<button class="save-button" @click="handleCloseSettings">
|
||||
<svg
|
||||
fill="none"
|
||||
height="20"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"
|
||||
></path>
|
||||
<polyline points="17 21 17 13 7 13 7 21"></polyline>
|
||||
<polyline points="7 3 7 8 15 8"></polyline>
|
||||
</svg>
|
||||
<span>保存</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<!-- 修改 Toast 组件 -->
|
||||
<div v-if="showToast" :data-type="toastMessageType" class="toast-message">
|
||||
{{ toastMessage }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
</style>
|
||||
|
||||
.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);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -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')
|
||||
14
pom.xml
14
pom.xml
@@ -22,9 +22,21 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<!--<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
|
||||
</dependency>-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-zhipuai-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<!--<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
|
||||
</dependency>-->
|
||||
<dependency>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
|
||||
@@ -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<String> 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<Object> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Entity> entities = session.query(sql);
|
||||
for (Entity entity : entities) {
|
||||
Set<String> 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();
|
||||
}
|
||||
}
|
||||
@@ -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<Void, LocalDateTime> call = FunctionToolCallback.builder("current_time", () -> LocalDateTime.now())
|
||||
.description("返回当前的日期时间")
|
||||
.inputType(Void.class)
|
||||
.build();
|
||||
|
||||
@Tool(description = "返回当前的日期时间")
|
||||
public String currentTime() {
|
||||
return LocalDateTime.now().toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <name>`。
|
||||
- 消息用 `<sender>->><receiver>: message` 发送。
|
||||
- **示例**:
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Alice
|
||||
participant Bob
|
||||
Alice->>Bob: 你好
|
||||
Bob->>Alice: 嗨
|
||||
```
|
||||
|
||||
#### 类图 (Class Diagram)
|
||||
- **用途**: 描述系统结构,显示类及其关系。
|
||||
- **语法**:
|
||||
- 以 `classDiagram` 开始。
|
||||
- 定义类用 `class <ClassName> { <attributes> <methods> }`。
|
||||
- 关系用 `<ClassA> <relationship> <ClassB>` 表示(如 `extends`)。
|
||||
- **示例**:
|
||||
```mermaid
|
||||
classDiagram
|
||||
class Animal
|
||||
class Dog extends Animal
|
||||
class Cat extends Animal
|
||||
```
|
||||
|
||||
#### 状态图 (State Diagram)
|
||||
- **用途**: 通过状态和转换建模系统或对象的行为。
|
||||
- **语法**:
|
||||
- 以 `stateDiagram` 开始。
|
||||
- 定义状态用 `state <StateName>`。
|
||||
- 转换用 `<StateA> --> <StateB>: condition` 表示。
|
||||
- **示例**:
|
||||
```mermaid
|
||||
stateDiagram
|
||||
[*] --> Active
|
||||
Active --> Inactive: 停用时
|
||||
Inactive --> Active: 激活时
|
||||
Active --> [*]: 销毁时
|
||||
```
|
||||
|
||||
#### 实体关系图 (ER Diagram)
|
||||
- **用途**: 可视化数据库中的实体关系。
|
||||
- **语法**:
|
||||
- 以 `erDiagram` 开始。
|
||||
- 定义实体用 `<EntityName> { <attributes> }`。
|
||||
- 关系用 `<EntityA> |<RelationshipType>| <EntityB>: cardinality` 表示。
|
||||
- **示例**:
|
||||
```mermaid
|
||||
erDiagram
|
||||
Customer { name }
|
||||
Order { order_id }
|
||||
Customer ||--o{ Order: 放置
|
||||
```
|
||||
|
||||
#### 用户旅程图 (User Journey Diagram)
|
||||
- **用途**: 映射用户完成任务的步骤。
|
||||
- **语法**:
|
||||
- 以 `journey` 开始。
|
||||
- 定义部分用 `section <SectionName>`。
|
||||
- 定义任务用 `<TaskName>: <score>: <actors>`。
|
||||
- **示例**:
|
||||
```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` 开始。
|
||||
- 定义需求用 `<type> <name> { id: <id>, text: <text>, risk: <risk>, verifymethod: <method> }`。
|
||||
- 定义需求间关系。
|
||||
- **示例**:
|
||||
```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 <name>`,消息用 `<sender>->><receiver>: message`。 | `sequenceDiagram participant Alice participant Bob Alice->>Bob: 你好 Bob->>Alice: 嗨` |
|
||||
| 类图 (Class Diagram) | 描述系统结构,显示类及其关系 | 以 `classDiagram` 开始,定义类用 `class <ClassName> { <attributes> <methods> }`,关系用 `<ClassA> <relationship> <ClassB>`。 | `classDiagram class Animal class Dog extends Animal class Cat extends Animal` |
|
||||
| 状态图 (State Diagram) | 通过状态和转换建模系统或对象行为 | 以 `stateDiagram` 开始,定义状态用 `state <StateName>`,转换用 `<StateA> --> <StateB>: condition`。 | `stateDiagram [*] --> Active Active --> Inactive: 停用时 Inactive --> Active: 激活时 Active --> [*]: 销毁时` |
|
||||
| 实体关系图 (ER Diagram) | 可视化数据库中的实体关系 | 以 `erDiagram` 开始,定义实体用 `<EntityName> { <attributes> }`,关系用 `<EntityA> |<RelationshipType>| <EntityB>: cardinality`。 | `erDiagram Customer { name } Order { order_id } Customer ||--o{ Order: 放置` |
|
||||
| 用户旅程图 (User Journey Diagram) | 映射用户完成任务的步骤 | 以 `journey` 开始,定义部分用 `section <SectionName>`,任务用 `<TaskName>: <score>: <actors>`。 | `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` 开始,定义需求用 `<type> <name> { id: <id>, text: <text>, risk: <risk>, verifymethod: <method> }`,定义关系。 | `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` |
|
||||
""";
|
||||
}
|
||||
@@ -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"
|
||||
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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user