diff --git a/index.html b/index.html index 3c91bee..ac25c6e 100644 --- a/index.html +++ b/index.html @@ -5,7 +5,7 @@ - Vite + Vue + TS + 优阅工作室 diff --git a/package-lock.json b/package-lock.json index 0f67b88..f0c63ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "company_website", "version": "0.0.0", "dependencies": { + "@tdesign-vue-next/chat": "^0.4.5", "@types/node": "^24.2.1", "@vue/language-core": "^3.0.5", "axios": "^1.11.0", @@ -20,6 +21,8 @@ "devDependencies": { "@vitejs/plugin-vue": "^6.0.1", "@vue/tsconfig": "^0.7.0", + "less": "^4.4.1", + "less-loader": "^12.3.0", "typescript": "~5.8.3", "vite": "^7.1.2", "vue-tsc": "^3.0.5" @@ -815,6 +818,25 @@ "win32" ] }, + "node_modules/@tdesign-vue-next/chat": { + "version": "0.4.5", + "resolved": "https://registry.npmmirror.com/@tdesign-vue-next/chat/-/chat-0.4.5.tgz", + "integrity": "sha512-oM+NaVS2+QAC3tvlKCIm92dYvJVTzjPZip48WicCoWK42XyjKDau+hJiEb8TRXTMOw3kAkK7zrO79N0tWmBQnQ==", + "dependencies": { + "@babel/runtime": "^7.22.6", + "@types/lodash-es": "^4.17.12", + "clipboard": "^2.0.11", + "highlight.js": "^11.9.0", + "lodash-es": "^4.17.21", + "marked": "^12.0.1", + "marked-highlight": "^2.1.1", + "tdesign-icons-vue-next": "^0.3.6", + "tdesign-vue-next": "^1.15.2" + }, + "peerDependencies": { + "vue": ">=3.1.0" + } + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", @@ -1108,6 +1130,16 @@ "node": ">= 0.4" } }, + "node_modules/clipboard": { + "version": "2.0.11", + "resolved": "https://registry.npmmirror.com/clipboard/-/clipboard-2.0.11.tgz", + "integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==", + "dependencies": { + "good-listener": "^1.2.2", + "select": "^1.1.2", + "tiny-emitter": "^2.0.0" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", @@ -1156,6 +1188,11 @@ "node": ">=0.4.0" } }, + "node_modules/delegate": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/delegate/-/delegate-3.2.0.tgz", + "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==" + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -1180,6 +1217,19 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmmirror.com/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dev": true, + "optional": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", @@ -1375,6 +1425,14 @@ "node": ">= 0.4" } }, + "node_modules/good-listener": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/good-listener/-/good-listener-1.2.2.tgz", + "integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==", + "dependencies": { + "delegate": "^3.1.2" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", @@ -1386,6 +1444,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "optional": true + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", @@ -1430,11 +1495,45 @@ "he": "bin/he" } }, + "node_modules/highlight.js": { + "version": "11.11.1", + "resolved": "https://registry.npmmirror.com/highlight.js/-/highlight.js-11.11.1.tgz", + "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/hookable": { "version": "5.5.3", "resolved": "https://registry.npmmirror.com/hookable/-/hookable-5.5.3.tgz", "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==" }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmmirror.com/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", + "dev": true, + "optional": true, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-what": { "version": "4.1.16", "resolved": "https://registry.npmmirror.com/is-what/-/is-what-4.1.16.tgz", @@ -1446,6 +1545,76 @@ "url": "https://github.com/sponsors/mesqueeb" } }, + "node_modules/less": { + "version": "4.4.1", + "resolved": "https://registry.npmmirror.com/less/-/less-4.4.1.tgz", + "integrity": "sha512-X9HKyiXPi0f/ed0XhgUlBeFfxrlDP3xR4M7768Zl+WXLUViuL9AOPPJP4nCV0tgRWvTYvpNmN0SFhZOQzy16PA==", + "dev": true, + "dependencies": { + "copy-anything": "^2.0.1", + "parse-node-version": "^1.0.1", + "tslib": "^2.3.0" + }, + "bin": { + "lessc": "bin/lessc" + }, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "needle": "^3.1.0", + "source-map": "~0.6.0" + } + }, + "node_modules/less-loader": { + "version": "12.3.0", + "resolved": "https://registry.npmmirror.com/less-loader/-/less-loader-12.3.0.tgz", + "integrity": "sha512-0M6+uYulvYIWs52y0LqN4+QM9TqWAohYSNTo4htE8Z7Cn3G/qQMEmktfHmyJT23k+20kU9zHH2wrfFXkxNLtVw==", + "dev": true, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "less": "^3.5.0 || ^4.0.0", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/less/node_modules/copy-anything": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-2.0.6.tgz", + "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", + "dev": true, + "dependencies": { + "is-what": "^3.14.1" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/less/node_modules/is-what": { + "version": "3.14.1", + "resolved": "https://registry.npmmirror.com/is-what/-/is-what-3.14.1.tgz", + "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", + "dev": true + }, "node_modules/lodash-es": { "version": "4.17.21", "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz", @@ -1459,6 +1628,39 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "optional": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/marked": { + "version": "12.0.2", + "resolved": "https://registry.npmmirror.com/marked/-/marked-12.0.2.tgz", + "integrity": "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/marked-highlight": { + "version": "2.2.2", + "resolved": "https://registry.npmmirror.com/marked-highlight/-/marked-highlight-2.2.2.tgz", + "integrity": "sha512-KlHOP31DatbtPPXPaI8nx1KTrG3EW0Z5zewCwpUj65swbtKOTStteK3sNAjBqV75Pgo3fNEVNHeptg18mDuWgw==", + "peerDependencies": { + "marked": ">=4 <17" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -1467,6 +1669,19 @@ "node": ">= 0.4" } }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", @@ -1513,6 +1728,32 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/needle": { + "version": "3.3.1", + "resolved": "https://registry.npmmirror.com/needle/-/needle-3.3.1.tgz", + "integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==", + "dev": true, + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/path-browserify": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz", @@ -1539,6 +1780,16 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "optional": true, + "engines": { + "node": ">=6" + } + }, "node_modules/pinia": { "version": "3.0.3", "resolved": "https://registry.npmmirror.com/pinia/-/pinia-3.0.3.tgz", @@ -1591,6 +1842,13 @@ "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "dev": true, + "optional": true + }, "node_modules/rfdc": { "version": "1.4.1", "resolved": "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz", @@ -1635,11 +1893,50 @@ "fsevents": "~2.3.2" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "optional": true + }, + "node_modules/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "dev": true, + "optional": true + }, + "node_modules/select": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/select/-/select-1.1.2.tgz", + "integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==" + }, + "node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, "node_modules/sortablejs": { "version": "1.15.6", "resolved": "https://registry.npmmirror.com/sortablejs/-/sortablejs-1.15.6.tgz", "integrity": "sha512-aNfiuwMEpfBM/CN6LY0ibyhxPfPbyFeBTYJKCvzkJ2GkUpazIt3H+QIPAMHwqQ7tMKaHz1Qj+rJJCqljnf4p3A==" }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", @@ -1704,6 +2001,11 @@ "vue": ">=3.1.0" } }, + "node_modules/tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" + }, "node_modules/tinycolor2": { "version": "1.6.0", "resolved": "https://registry.npmmirror.com/tinycolor2/-/tinycolor2-1.6.0.tgz", @@ -1725,6 +2027,12 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + }, "node_modules/typescript": { "version": "5.8.3", "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.8.3.tgz", diff --git a/package.json b/package.json index 38e116b..0ef35cd 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "preview": "vite preview" }, "dependencies": { + "@tdesign-vue-next/chat": "^0.4.5", "@types/node": "^24.2.1", "@vue/language-core": "^3.0.5", "axios": "^1.11.0", @@ -21,6 +22,8 @@ "devDependencies": { "@vitejs/plugin-vue": "^6.0.1", "@vue/tsconfig": "^0.7.0", + "less": "^4.4.1", + "less-loader": "^12.3.0", "typescript": "~5.8.3", "vite": "^7.1.2", "vue-tsc": "^3.0.5" diff --git a/src/App.vue b/src/App.vue index 6d57c26..6b00ab5 100644 --- a/src/App.vue +++ b/src/App.vue @@ -34,6 +34,8 @@ import { useLanguageStore } from './store/language' import { provide } from 'vue' import { useRoute } from 'vue-router'; + + // 初始化语言store并提供翻译函数 const languageStore = useLanguageStore() provide('t', (key: string) => languageStore.t(key)) diff --git a/src/assets/ai_img.png b/src/assets/ai_img.png new file mode 100644 index 0000000..0279fbe Binary files /dev/null and b/src/assets/ai_img.png differ diff --git a/src/components/chat/ChatAi.ts b/src/components/chat/ChatAi.ts new file mode 100644 index 0000000..32383c4 --- /dev/null +++ b/src/components/chat/ChatAi.ts @@ -0,0 +1,220 @@ +const Api = { + CHAT_AI_URL: 'http://localhost:8180/chat/generateStreamFlex' +}; +import axios from 'axios' +const getAi=(msg: any):any => { + return axios.get(Api.CHAT_AI_URL, { + params: { message: msg }, + headers: { Accept: 'text/event-stream' }, + responseType: 'stream' + }); +} +export class MockSSEResponse { + private controller!: ReadableStreamDefaultController; + + private encoder = new TextEncoder(); + + private stream: ReadableStream; + + private error: boolean; + + private currentPhase: 'reasoning' | 'content' = 'reasoning'; + + constructor( + private data: { + reasoning: string; // 推理内容 + content: string; // 正式内容 + }, + private delay: number = 100, + error = false, + ) { + this.error = error; + + this.stream = new ReadableStream({ + start: (controller) => { + this.controller = controller; + if (!this.error) { + // 如果不是错误情况,则开始推送数据 + setTimeout(() => this.pushData(), this.delay); // 延迟开始推送数据 + } + }, + cancel() {}, + }); + } + + private pushData() { + try { + if (this.currentPhase === 'reasoning') { + // 推送推理内容 + if (this.data.reasoning.length > 0) { + const chunk = JSON.stringify({ + delta: { + reasoning_content: this.data.reasoning.slice(0, 1), + content: '', + }, + finished: false, + }); + this.controller.enqueue(this.encoder.encode(chunk)); + this.data.reasoning = this.data.reasoning.slice(1); + // 设置下次推送 + setTimeout(() => this.pushData(), this.delay); + } else { + // 推理内容推送完成,切换到正式内容 + this.currentPhase = 'content'; + setTimeout(() => this.pushData(), this.delay); // 立即开始推送正式内容 + return; + } + } + + if (this.currentPhase === 'content') { + // 推送正式内容 + if (this.data.content.length > 0) { + const chunk = JSON.stringify({ + delta: { + reasoning_content: '', + content: this.data.content.slice(0, 1), + }, + finished: this.data.content.length === 1, // 最后一个字符时标记完成 + }); + this.controller.enqueue(this.encoder.encode(chunk)); + this.data.content = this.data.content.slice(1); + + // 设置下次推送 + setTimeout(() => this.pushData(), this.delay); + } else { + // const finalPayload = JSON.stringify({ + // delta: { + // reasoning_content: '', + // content: '', + // }, + // finished: true, + // }); + // this.controller.enqueue(this.encoder.encode(`${finalPayload}`)); + // 全部内容推送完成 + setTimeout(() => this.controller.close(), this.delay); + } + } + } catch {} + } + + getResponse(_msg: String): Promise { + return new Promise((resolve) => { + resolve(getAi(_msg)); + }); + } + + // 封装SSE连接 + connectSSE = (url, params, onMessage, onError) => { + // 构建带参数的URL + const queryString = Object.keys(params) + .map((key, value) => `${encodeURIComponent(key)}=${params[key].message}`) + .join('&'); + + const API_BASE_URL = 'http://localhost:8180'; + const fullUrl = `${API_BASE_URL}${url}?${queryString}`; + + // 创建EventSource + const eventSource = new EventSource(fullUrl); + + eventSource.onmessage = (event) => { + const { data } = event; + + // 检查是否是特殊标记 + if (data === '[DONE]') { + if (onMessage) onMessage('[DONE]'); + } else { + // 处理普通消息 + if (onMessage) onMessage(data); + } + }; + + eventSource.onerror = (error) => { + if (onError) onError(error); + eventSource.close(); + }; + + // 返回eventSource实例,以便后续可以关闭连接 + return eventSource; + }; + + // AI超级智能体聊天 + chatWithManus = (message) => { + return this.connectSSE('/chat/generateStreamFlex', { message }); + }; +} + +export const fetchSSE = async (options: FetchSSEOptions = {}) => { + const { success, fail, complete, url } = options; + // fetch请求流式接口url,需传入接口url和参数 + const responsePromise = fetch(url).catch((e) => { + const msg = e.toString() || '流式接口异常'; + complete?.(false, msg); + return Promise.reject(e); // 确保错误能够被后续的.catch()捕获 + }); + + responsePromise + .then((response) => { + if (!response?.ok) { + complete?.(false, response.statusText); + fail?.(); + throw new Error('Request failed'); // 抛出错误以便链式调用中的下一个.catch()处理 + } + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + if (!reader) throw new Error('No reader available'); + + const bufferArr: string[] = []; + let dataText = ''; // 记录数据 + const event: SSEEvent = { type: null, data: null }; + + async function processText({ done, value }: ReadableStreamReadResult): Promise { + if (done) { + complete?.(true); + return Promise.resolve(); + } + const chunk = decoder.decode(value); + const buffers = chunk.toString().replaceAll(' ', ' ').split(/\r?\n/); + bufferArr.push(...buffers); + const i = 0; + while (i < bufferArr.length) { + const line = bufferArr[i]; + if (line) { + dataText += line; + const response = line.slice(5); + if (response === '[DONE]') { + event.type = 'finish'; + dataText = ''; + } else { + event.type = 'delta'; + event.data = response; + + // const choices = JSON.parse(response.trim())?.choices?.[0]; + // if (choices.finish_reason === 'stop') { + // event.type = 'finish'; + // dataText = ''; + // } else { + // event.type = 'delta'; + // event.data = choices; + // } + } + } + if (event.type && event.data) { + const jsonData = JSON.parse(JSON.stringify(event)); + console.log('流式数据解析结果:', jsonData); + // 回调更新数据 + success(jsonData); + event.type = null; + event.data = null; + } + bufferArr.splice(i, 1); + } + return reader.read().then(processText); + } + + return reader.read().then(processText); + }) + .catch(() => { + // 处理整个链式调用过程中发生的任何错误 + fail?.(); + }); +}; \ No newline at end of file diff --git a/src/components/chat/ChatAi.vue b/src/components/chat/ChatAi.vue new file mode 100644 index 0000000..4108827 --- /dev/null +++ b/src/components/chat/ChatAi.vue @@ -0,0 +1,469 @@ + + + + +// 第二种重写样式 定义一个啥也不带的 style + + diff --git a/src/components/common/BackToTop.vue b/src/components/common/BackToTop.vue index 4d8fd16..419f1e0 100644 --- a/src/components/common/BackToTop.vue +++ b/src/components/common/BackToTop.vue @@ -8,6 +8,7 @@ const isVisible = ref(false) const handleScroll = () => { // 当滚动超过300px时显示按钮 isVisible.value = window.scrollY > 300 + } // 回到顶部函数 diff --git a/src/main.ts b/src/main.ts index bbce151..ae1f9c5 100644 --- a/src/main.ts +++ b/src/main.ts @@ -9,8 +9,10 @@ import router from './router/index.ts' // 确保路径正确 import { createPinia } from 'pinia' // 引入TDesign UI组件库 import TDesign from 'tdesign-vue-next' +import TDesignChat from '@tdesign-vue-next/chat'; // 引入chat组件 import 'tdesign-vue-next/es/style/index.css' + const app = createApp(App) // 必须先 use(router),再挂载 app.use(router) @@ -20,6 +22,7 @@ app.use(createPinia()) // 使用TDesign app.use(TDesign) +app.use(TDesignChat) app.mount('#app') diff --git a/src/views/HomePage.vue b/src/views/HomePage.vue index 76bdd6c..fda6df5 100644 --- a/src/views/HomePage.vue +++ b/src/views/HomePage.vue @@ -27,6 +27,8 @@ + + @@ -37,6 +39,8 @@ import TeamMembers from '@/components/team/TeamMembers.vue' import ProjectShowcase from '@/components/projects/ProjectShowcase.vue' import ContactForm from '@/components/contact/ContactForm.vue' import CompanyTimeline from '@/components/timeline/CompanyTimeline.vue' + +import ChatAi from '@/components/chat/ChatAi.vue' import { inject } from 'vue' // 注入翻译函数(与原App.vue保持一致)