Compare commits

...

2 Commits

Author SHA1 Message Date
Gaoshunman b171f47545 fix 2025-08-27 17:37:54 +08:00
Gaoshunman 98b10455d1 commit 2025-08-26 17:25:23 +08:00
11 changed files with 1017 additions and 1 deletions

View File

@ -5,7 +5,7 @@
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<link rel="preload" as="image" href="/src/assets/images/hero-bg.jpg" fetchpriority="high">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Vue + TS</title>
<title>优阅工作室</title>
</head>

308
package-lock.json generated
View File

@ -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",

View File

@ -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"

View File

@ -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))

BIN
src/assets/ai_img.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@ -0,0 +1,221 @@
const Api = {
// CHAT_AI_URL: 'http://localhost:8180/chat/generateStreamFlex'
CHAT_AI_URL: 'http://localhost:8180/rag/query_rag'
};
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<Uint8Array>;
private encoder = new TextEncoder();
private stream: ReadableStream<Uint8Array>;
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<Response> {
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('/rag/query_rag', { 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<Uint8Array>): Promise<void> {
if (done) {
complete?.(true);
return Promise.resolve();
}
const chunk = decoder.decode(value);
const buffers = chunk.toString().replaceAll('&nbsp;', ' ').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?.();
});
};

View File

@ -0,0 +1,469 @@
<template>
<t-space v-if="isVisible1" align="center" @click="visibleModelessDrag = true" class="ai_style" >
<img :src="aiImg" alt="勤怠登录页面" width="36px" height="36px">
</t-space>
<t-dialog
v-model:visible="visibleModelessDrag"
:footer="false"
id ="abc"
header="AI助手"
mode="modeless"
showOverlay="true"
draggable
:on-confirm="() => (visibleModelessDrag = false)"
>
<template #body>
<t-chat
layout="both"
style="height: 600px;"
:z-index="3000"
:data="chatList"
:clear-history="chatList.length > 0 && !isStreamLoad"
:text-loading="loading"
:is-stream-load="isStreamLoad"
@on-action="operation"
@clear="clearConfirm"
>
<!-- eslint-disable-next-line vue/no-unused-vars -->
<template #actions="{ item, index }">
<t-chat-action
:content="item.content"
:operation-btn="['good', 'bad', 'replay', 'copy']"
@operation="handleOperation"
/>
</template>
<template #footer>
<t-chat-input v-model="inputValue" :stop-disabled="isStreamLoad" @send="inputEnter" @stop="onStop"> </t-chat-input>
</template>
</t-chat>
</template>
</t-dialog>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import aiImg from '@/assets/ai_img.png'
const visibleModelessDrag = ref(false);
import { MockSSEResponse } from './ChatAi';
const fetchCancel = ref(null);
const loading = ref(false);
const isStreamLoad = ref(false);
const chatRef = ref(null);
const isShowToBottom = ref(false);
const inputValue = ref('');
// /
const isVisible1 = ref(false)
const handleScroll1 = () => {
// console.log(window.scrollY)
// 300px
isVisible1.value = window.scrollY > 300
// console.log("sss:" + isVisible1.value)
};
//
onMounted(() => {
window.addEventListener('scroll', handleScroll1)
})
//
onUnmounted(() => {
window.removeEventListener('scroll', handleScroll)
})
//
const chatList = ref([
{
avatar: 'https://tdesign.gtimg.com/site/chat-avatar.png',
name: 'youyueAI',
datetime: '今天16:38',
content: '我是你的ai助手有什么可以帮你的。',
role: 'assistant',
}
]);
// const chatList = ref([
// {
// content: ` <span>hunyuan</span> <span>GPT4</span>`,
// role: 'model-change',
// },
// {
// avatar: 'https://tdesign.gtimg.com/site/chat-avatar.png',
// name: 'youyueAI',
// datetime: '16:38',
// content: ' McMurdo Station ATM',
// role: 'assistant',
// },
// {
// avatar: 'https://tdesign.gtimg.com/site/avatar.jpg',
// name: '',
// datetime: '16:38',
// content: '',
// role: 'user',
// },
// ]);
const handleOperation = function (type, options) {
console.log('handleOperation', type, options);
};
const operation = function (type, options) {
console.log(type, options);
};
const clearConfirm = function () {
chatList.value = [];
};
const onStop = function () {
if (fetchCancel.value) {
fetchCancel.value.controller.close();
loading.value = false;
}
};
const inputEnter = function (inputValue) {
if (isStreamLoad.value) {
return;
}
if (!inputValue) return;
const params = {
avatar: 'https://tdesign.gtimg.com/site/avatar.jpg',
name: '自己',
datetime: new Date().toDateString(),
content: inputValue,
role: 'user',
};
chatList.value.unshift(params);
//
const params2 = {
avatar: 'https://tdesign.gtimg.com/site/chat-avatar.png',
name: 'youyueAI',
datetime: new Date().toDateString(),
content: '',
role: 'assistant',
};
chatList.value.unshift(params2);
handleData(inputValue);
};
const fetchSSE = async (fetchFn, options) => {
const response = await fetchFn();
const { success, fail, complete } = options;
// ok
if (response.status !== 200) {
complete?.(false, response.statusText);
fail?.();
return;
}
// const reader = response?.body?.getReader();
// const decoder = new TextDecoder();
// if (!reader) return;
// reader.read().then(function processText({ done, value }) {
// if (done) {
// //
// complete?.(true);
// return;
// }
// const chunk = decoder.decode(value, { stream: true });
const buffers = response.data.split(/\r?\n/);
// const jsonData = JSON.parse(buffers);
success(buffers);
// reader.read().then(processText);
};
const displayText = ref('');
const fullText = ref('');
const isLoading = ref(true);
const eventSource = null;
let isProcessing = false;
const messageBuffer = [];
let animationFrameId = null;
//
const config = {
bufferSize: 5, //
flushInterval: 10, // (ms)
typingSpeed: 50, // (ms/)
};
//
const processBuffer = async () => {
isProcessing = true;
// 使requestAnimationFrame
const processNextMessage = () => {
if (messageBuffer.length === 0) {
isProcessing = false;
return;
}
//
const message = messageBuffer.shift();
try {
//
// const parsedData = JSON.parse(message);
// const content = parsedData.choices[0]?.delta?.content || '';
if (message) {
//
typeCharacter(message);
}
} catch (error) {
console.error('解析SSE消息失败:', error);
}
//
animationFrameId = requestAnimationFrame(processNextMessage);
};
//
animationFrameId = requestAnimationFrame(processNextMessage);
};
//
const typeCharacter = (content) => {
const index = 0;
setTimeout(() => {
chatList.value[0].content += content.replaceAll('&nbsp;', ' ');
}, config.typingSpeed);
};
const handleData = async () => {
loading.value = true;
isStreamLoad.value = true;
const lastItem = chatList.value[0];
const mockedData = {
reasoning: '1',
content: '2',
};
const mockResponse = new MockSSEResponse(mockedData);
//
// const messageBuffer = []; // SSE
const message = {
message: inputValue.value,
};
const eventSource = mockResponse.chatWithManus(message);
eventSource.onmessage = (event) => {
const { data } = event;
if (data && data !== '[DONE]') {
// messageBuffer.push(data);
loading.value = false;
//
isStreamLoad.value = false;
// lastItem.reasoning += data.replaceAll('&nbsp;', ' ');
// lastItem.content += data.replaceAll('&nbsp;', ' ');
//
messageBuffer.push(data.replaceAll('&nbsp;', ' '));
//
if (!isProcessing) {
processBuffer();
}
// //
// const combinedText = messageBuffer.join('');
// //
// const lastChar = data.charAt(data.length - 1);
// const hasCompleteSentence = chineseEndPunctuation.includes(lastChar) || data.includes('\n\n');
// const isLongEnough = combinedText.length > 40;
// if (hasCompleteSentence || isLongEnough) {
// createBubble(combinedText);
// }
}
if (data === '[DONE]') {
//
// if (messageBuffer.length > 0) {
// const remainingContent = messageBuffer.join('');
// createBubble(remainingContent, 'ai-final');
// }
//
isStreamLoad.value = false;
loading.value = false;
//
// connectionStatus.value = 'disconnected';
eventSource.close();
}
};
// SSE
eventSource.onerror = (error) => {
// console.error('SSE Error:', error);
// connectionStatus.value = 'error';
// eventSource.close();
// //
// if (messageBuffer.length > 0) {
// const remainingContent = messageBuffer.join('');
// createBubble(remainingContent, 'ai-error');
// }
};
//
eventSource.onclose = () => {
console.log('流式响应已结束');
// EventSource
eventSource.close();
};
// await fetchSSE(
// () => {
// return mockResponse.getResponse(inputValue.value);
// },
// {
// success(result) {
// console.log('success', result);
// for (let i = 0; i < result.length; i++) {
// if (result[i] !== '') {
// lastItem.reasoning += result[i].replace('data:', '');
// lastItem.content += result[i].replace('data:', '');
// }
// }
// loading.value = false;
// //
// isStreamLoad.value = false;
// // lastItem.reasoning += result.delta.reasoning_content;
// // lastItem.content += result.delta.content;
// },
// complete(isOk, msg) {
// if (!isOk) {
// lastItem.role = 'error';
// lastItem.content = msg;
// lastItem.reasoning = msg;
// }
// // xx
// lastItem.duration = 20;
// //
// isStreamLoad.value = false;
// loading.value = false;
// },
// },
// );
};
// const handleData = async () => {
// loading.value = true;
// isStreamLoad.value = true;
// const lastItem = chatList.value[0];
// const mockedData = ``;
// const mockResponse = new MockSSEResponse(mockedData);
// fetchCancel.value = mockResponse;
// await fetchSSE(
// () => {
// return mockResponse.getResponse();
// },
// {
// success(result) {
// loading.value = false;
// const { data } = result;
// lastItem.content += data;
// },
// complete(isOk, msg) {
// if (!isOk || !lastItem.content) {
// lastItem.role = 'error';
// lastItem.content = msg;
// }
// //
// isStreamLoad.value = false;
// loading.value = false;
// },
// },
// );
// };
</script>
<style scoped lang="less">
/* 应用滚动条样式 */
::-webkit-scrollbar-thumb {
background-color: var(--td-scrollbar-color);
}
::-webkit-scrollbar-thumb:horizontal:hover {
background-color: var(--td-scrollbar-hover-color);
}
::-webkit-scrollbar-track {
background-color: var(--td-scroll-track-color);
}
//
:global(.t-dialog__ctx.t-dialog__ctx--modeless .t-dialog) {
background-color: white;
}
:deep(.t-chat__text) {
text-align: left;
}
</style>
// style
<style>
.t-chat__actions-margin {
display: none;
}
.t-dialog__ctx .t-dialog__wrap {
z-index: 3001;
right: 10;
}
.t-dialog__ctx .t-dialog__position {
justify-content: right;
/* left: 86px; */
position: fixed;
right: 100px;
bottom: 52px;
}
.ai_style{
gap: 16px;
position: fixed;
bottom: 100px;
right: 2rem;
background-color: #d7e6ed;
width: 48px;
height: 48px;
border-radius: 50px;
display: flex;
justify-content: center;
align-items: center;
border: 2px solid #fff;
}
.ai_style:hover {
background-color: var(--primary-dark);
transform: translateY(-3px);
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
}
@media (max-width: 768px) {
.t-dialog__ctx .t-dialog__position {
justify-content: center;
/* left: 86px; */
position: unset;
}
}
</style>

View File

@ -8,6 +8,7 @@ const isVisible = ref(false)
const handleScroll = () => {
// 300px
isVisible.value = window.scrollY > 300
}
//

View File

@ -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')

View File

@ -27,6 +27,8 @@
<!-- 联系我们 -->
<ContactForm />
<ChatAi />
</div>
</template>
@ -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

View File

@ -10,4 +10,9 @@ export default defineConfig({
'@': path.resolve(__dirname, 'src'), // 将 @ 指向 src 目录
},
},
server: {
host: '0.0.0.0', // 监听所有网络接口
port: 5173, // 可自定义端口默认5173
open: false // 可选:是否自动打开浏览器
}
})