浏览代码

增加对话页面;为上线审核修改:首页面非登录,提示登录等

htc 3 天之前
父节点
当前提交
744f867a33

+ 14 - 14
http/index.js

@@ -24,20 +24,20 @@ export const $http = (url, method, data, json, isloading=true) => {
 			uni.hideLoading()
 		}
 		
-		if (response?.data?.code === 401 || response?.data?.msg.indexOf('未授权') > -1 || response?.data?.msg.indexOf('重新登录') > -1) {
-			return uni.showModal({
-				title: '温馨提示',
-				content:'当前登录已失效,是否返回重新登录',
-				success: (res) => {
-					if (res.confirm) {
-						uni.clearStorageSync();
-						uni.reLaunch({
-							url: '/pages/login'
-						})
-					}
-				}
-			})
-		}
+		// if (response?.data?.code === 401 || response?.data?.msg.indexOf('未授权') > -1 || response?.data?.msg.indexOf('重新登录') > -1) {
+		// 	return uni.showModal({
+		// 		title: '温馨提示',
+		// 		content:'当前登录已失效,是否返回重新登录',
+		// 		success: (res) => {
+		// 			if (res.confirm) {
+		// 				uni.clearStorageSync();
+		// 				uni.reLaunch({
+		// 					url: '/pages/login'
+		// 				})
+		// 			}
+		// 		}
+		// 	})
+		// }
 		
 		// 请根据后端规定的状态码判定
 		if (response.data.code === 300) {//token失效

+ 6 - 1
node_modules/.package-lock.json

@@ -1,5 +1,5 @@
 {
-  "name": "ChuangHengWx",
+  "name": "AiToyCXWx",
   "version": "1.0.1",
   "lockfileVersion": 2,
   "requires": true,
@@ -8,6 +8,11 @@
       "version": "1.0.31",
       "resolved": "https://registry.npmmirror.com/event-source-polyfill/-/event-source-polyfill-1.0.31.tgz",
       "integrity": "sha512-4IJSItgS/41IxN5UVAVuAyczwZF7ZIEsM1XAoUzIHA6A+xzusEZUutdXz2Nr+MQPLxfTiCvqE79/C8HT8fKFvA=="
+    },
+    "node_modules/text-encoding-shim": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmmirror.com/text-encoding-shim/-/text-encoding-shim-1.0.5.tgz",
+      "integrity": "sha512-H7yYW+jRn4yhu60ygZ2f/eMhXPITRt4QSUTKzLm+eCaDsdX8avmgWpmtmHAzesjBVUTAypz9odu5RKUjX5HNYA=="
     }
   }
 }

+ 13 - 0
node_modules/text-encoding-shim/LICENSE

@@ -0,0 +1,13 @@
+The MIT License (MIT)
+Copyright (c) 2016 Till Affeldt
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+documentation files (the "Software"),to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
+and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 64 - 0
node_modules/text-encoding-shim/README.md

@@ -0,0 +1,64 @@
+# Text Encoding Shim
+When I was looking for a simple lightweight polyfill that works great with TypeScript I was not happy with the results.
+While a couple polyfills exist, they either broke in my special setup and lead to unexpected results or they implemented a bunch
+of features that I did not even want to have. So I wrote my own shim.
+
+## Installation
+### npm
+Get it via *npm* by adding `text-encoding-shim` to your `package.json` or run:
+```shell
+npm install text-encoding-shim
+```
+
+### Bower
+Get it via *Bower* by adding it to your `bower.json` or run:
+```shell
+bower install --save text-encoding-shim
+```
+
+### HTML script tag
+Altenatively you can simply download this project folder from [Gitlab](https://gitlab.com/PseudoPsycho/text-encoding-shim)
+and add it to your html script tags like so:
+```shell
+<script type="text/javascript" src="text-encoding-shim/index.js"></script>
+```
+
+### TypeScript definitions
+If you are using TypeScript you do not need to create a definition file. This project already includes one.
+If you are still using *typings* you may need to run this command to copy the file:
+```shell
+typings install --save --global npm:text-encoding-shim
+```
+
+## Importing the polyfill
+This polyfill utilizes the Universal Module Definition (UMD) and be used with either a module loader or standalone.
+If you import it by adding a script tag you do not have to do anything else. It will automatically be bound to the global scope.
+### CommonJS
+```js
+var TextEncodingShim = require('text-encoding-shim');
+var TextEncoder = TextEncodingShim.TextEncoder;
+var TextDecoder = TextEncodingShim.TextDecoder;
+```
+
+### Asynchronous Module Definition (AMD)
+```js
+define([TextEncodingShim], function() {
+	//...
+});
+```
+
+### ES2015 or TypeScript
+```js
+import { TextEncoder, TextDecoder } from 'text-encoding-shim';
+```
+
+## Basic Usage
+```js
+var uint8array = new TextEncoder('utf-8').encode(string);
+var string = new TextDecoder('utf-8').decode(uint8array);
+```
+
+## Limitations
+If a native implementation is available it will always be handled preferred to this polyfill.
+Just like native implementations of Mozilla Firefox or Google Chrome, this library only supports UTF-8 encoding.
+This makes it so fast. If you need additional encodings, check out [this more advanced polyfill](https://github.com/inexorabletash/text-encoding).

+ 27 - 0
node_modules/text-encoding-shim/bower.json

@@ -0,0 +1,27 @@
+{
+	"name": "text-encoding-shim",
+	"version": "1.0.0",
+	"main": "index.js",
+	"authors": ["Till Affeldt <till.affeldt@gmail.com>"],
+	"description": "Simple lightweight polyfill for the Text Encoding API, supporting UTF-8 only",
+	"license": "MIT",
+	"moduleType": [
+		"globals",
+		"amd",
+		"node"
+	],
+	"keywords": [
+		"encoding",
+		"decoding",
+		"w3c",
+		"utf-8"
+	],
+	"ignore": [
+		"*.log"
+	],
+	"repository": {
+		"type": "git",
+		"url": "https://gitlab.com/PseudoPsycho/text-encoding-shim"
+	},
+	"homepage": "https://gitlab.com/PseudoPsycho/text-encoding-shim"
+}

+ 37 - 0
node_modules/text-encoding-shim/index.d.ts

@@ -0,0 +1,37 @@
+declare namespace TextEncodingShim {
+	interface TextEncoder_Static {
+		new(encoding?: string): TextEncoder_Instance;
+	}
+
+	interface TextEncoder_Instance {
+		encoding: string;
+		encode(input?: string): Uint8Array;
+	}
+
+	interface TextDecoderConstructorOptions {
+		fatal?: boolean;
+	}
+
+	interface TextDecoderDecodeOptions {
+		stream?: boolean;
+	}
+
+	interface TextDecoder_Static {
+		new(encoding?: string, options?: TextDecoderConstructorOptions): TextDecoder_Instance;
+	}
+
+	interface TextDecoder_Instance {
+		 encoding:  string;
+		 fatal:     boolean;
+		 ignoreBOM: boolean;
+		 decode(input?: ArrayBufferView, options?: TextDecoderDecodeOptions): string;
+ 	}
+
+	var TextEncoder: TextEncoder_Static;
+	var TextDecoder: TextDecoder_Static;
+
+}
+
+declare module 'text-encoding-shim' {
+	export = TextEncodingShim;
+}

+ 78 - 0
node_modules/text-encoding-shim/index.js

@@ -0,0 +1,78 @@
+(function (root, factory) {
+    if (typeof define === "function" && define.amd) {
+        define([], factory);
+    } else if (typeof exports === "object") {
+        module.exports = factory();
+    } else {
+		var textEncoding = factory();
+        root.TextEncoder = textEncoding.TextEncoder;
+		root.TextDecoder = textEncoding.TextDecoder;
+    }
+}(this, function () {
+	"use strict";
+	// return native implementation if available
+	var g = typeof global !== 'undefined' ? global : self;
+	if (typeof g.TextEncoder !== 'undefined' && typeof g.TextDecoder !== 'undefined') {
+		return {'TextEncoder': g.TextEncoder, 'TextDecoder': g.TextDecoder};
+	}
+
+	// allowed encoding strings for utf-8
+	var utf8Encodings = [
+		'utf8',
+		'utf-8',
+		'unicode-1-1-utf-8'
+	];
+
+	var TextEncoder = function(encoding) {
+		if (utf8Encodings.indexOf(encoding) < 0 && typeof encoding !== 'undefined' && encoding !== null) {
+			throw new RangeError('Invalid encoding type. Only utf-8 is supported');
+		} else {
+			this.encoding = 'utf-8';
+			this.encode = function(str) {
+				if (typeof str !== 'string') {
+					throw new TypeError('passed argument must be of type string');
+				}
+				var binstr = unescape(encodeURIComponent(str)),
+					arr = new Uint8Array(binstr.length);
+				binstr.split('').forEach(function(char, i) {
+					arr[i] = char.charCodeAt(0);
+				});
+				return arr;
+			};
+		}
+	};
+
+	var TextDecoder = function(encoding, options) {
+		if (utf8Encodings.indexOf(encoding) < 0 && typeof encoding !== 'undefined' && encoding !== null) {
+			throw new RangeError('Invalid encoding type. Only utf-8 is supported');
+		}
+		this.encoding = 'utf-8';
+		this.ignoreBOM = false;
+		this.fatal = (typeof options !== 'undefined' && 'fatal' in options) ? options.fatal : false;
+		if (typeof this.fatal !== 'boolean') {
+			throw new TypeError('fatal flag must be boolean');
+		}
+		this.decode = function (view, options) {
+			if (typeof view === 'undefined') {
+				return '';
+			}
+
+			var stream = (typeof options !== 'undefined' && 'stream' in options) ? options.stream : false;
+			if (typeof stream !== 'boolean') {
+				throw new TypeError('stream option must be boolean');
+			}
+
+			if (!ArrayBuffer.isView(view)) {
+				throw new TypeError('passed argument must be an array buffer view');
+			} else {
+				var arr = new Uint8Array(view.buffer, view.byteOffset, view.byteLength),
+					charArr = new Array(arr.length);
+				arr.forEach(function(charcode, i) {
+					charArr[i] = String.fromCharCode(charcode);
+				});
+				return decodeURIComponent(escape(charArr.join('')));
+			}
+		};
+	};
+	return {'TextEncoder': TextEncoder, 'TextDecoder': TextDecoder};
+}));

+ 32 - 0
node_modules/text-encoding-shim/package.json

@@ -0,0 +1,32 @@
+{
+	"name": "text-encoding-shim",
+	"version": "1.0.5",
+	"main": "index.js",
+	"author": "Till Affeldt <affeldt@protonmail.com>",
+	"description": "Simple lightweight polyfill for the Text Encoding API, supporting UTF-8 only",
+	"license": "MIT",
+	"keywords": [
+		"encoding",
+		"decoding",
+		"w3c",
+		"utf-8"
+	],
+	"files": [
+		"bower.json",
+		"index.d.ts",
+		"index.js",
+		"LICENSE",
+		"package.json",
+		"README.md",
+		"typings.json"
+	],
+	"typings": "index.d.ts",
+	"repository": {
+		"type": "git",
+		"url": "https://gitlab.com/PseudoPsycho/text-encoding-shim"
+	},
+	"bugs": {
+		"url": "https://gitlab.com/PseudoPsycho/text-encoding-shim/issues"
+	},
+	"homepage": "https://gitlab.com/PseudoPsycho/text-encoding-shim"
+}

+ 4 - 0
node_modules/text-encoding-shim/typings.json

@@ -0,0 +1,4 @@
+{
+	"name": "text-encoding-shim",
+	"version": "1.0.5"
+}

+ 13 - 2
package-lock.json

@@ -1,18 +1,24 @@
 {
-  "name": "ChuangHengWx",
+  "name": "AiToyCXWx",
   "version": "1.0.1",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "dependencies": {
-        "event-source-polyfill": "^1.0.31"
+        "event-source-polyfill": "^1.0.31",
+        "text-encoding-shim": "^1.0.5"
       }
     },
     "node_modules/event-source-polyfill": {
       "version": "1.0.31",
       "resolved": "https://registry.npmmirror.com/event-source-polyfill/-/event-source-polyfill-1.0.31.tgz",
       "integrity": "sha512-4IJSItgS/41IxN5UVAVuAyczwZF7ZIEsM1XAoUzIHA6A+xzusEZUutdXz2Nr+MQPLxfTiCvqE79/C8HT8fKFvA=="
+    },
+    "node_modules/text-encoding-shim": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmmirror.com/text-encoding-shim/-/text-encoding-shim-1.0.5.tgz",
+      "integrity": "sha512-H7yYW+jRn4yhu60ygZ2f/eMhXPITRt4QSUTKzLm+eCaDsdX8avmgWpmtmHAzesjBVUTAypz9odu5RKUjX5HNYA=="
     }
   },
   "dependencies": {
@@ -20,6 +26,11 @@
       "version": "1.0.31",
       "resolved": "https://registry.npmmirror.com/event-source-polyfill/-/event-source-polyfill-1.0.31.tgz",
       "integrity": "sha512-4IJSItgS/41IxN5UVAVuAyczwZF7ZIEsM1XAoUzIHA6A+xzusEZUutdXz2Nr+MQPLxfTiCvqE79/C8HT8fKFvA=="
+    },
+    "text-encoding-shim": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmmirror.com/text-encoding-shim/-/text-encoding-shim-1.0.5.tgz",
+      "integrity": "sha512-H7yYW+jRn4yhu60ygZ2f/eMhXPITRt4QSUTKzLm+eCaDsdX8avmgWpmtmHAzesjBVUTAypz9odu5RKUjX5HNYA=="
     }
   }
 }

+ 2 - 1
package.json

@@ -1,5 +1,6 @@
 {
   "dependencies": {
-    "event-source-polyfill": "^1.0.31"
+    "event-source-polyfill": "^1.0.31",
+    "text-encoding-shim": "^1.0.5"
   }
 }

+ 3 - 3
pages.json

@@ -1,7 +1,7 @@
 {
 	"pages": [
 		{
-			"path": "pages/launch",
+			"path": "pages/home",
 			"style": {
 				"navigationStyle": "custom"
 			}
@@ -13,13 +13,13 @@
 			}
 		},
 		{
-			"path": "pages/home",
+			"path": "pages/role",
 			"style": {
 				"navigationStyle": "custom"
 			}
 		},
 		{
-			"path": "pages/role",
+			"path": "pages/dialog",
 			"style": {
 				"navigationStyle": "custom"
 			}

+ 473 - 0
pages/dialog.vue

@@ -0,0 +1,473 @@
+<template>
+	<view class="page" :style="{'min-height':h+'px', 'padding-top':mt+'px'}">
+		<u-navbar title="对话" bgColor="transparent" :titleStyle="{'font-size':'32rpx','font-weight':'bold'}">
+			<view class="u-nav-slot" slot="left" style="display: flex;background-color: transparent;">
+				<image src="https://transcend.ringzle.com/xiaozhi-app/profile/2025/09/10/2f7cc90a-848c-4369-b573-4448125591a9.png" style="width: 45rpx;height: 45rpx;" @tap="toBack"></image>
+				<!-- <image src="https://transcend.ringzle.com/xiaozhi-app/profile/2025/09/10/3580b8d2-8ddb-4385-8516-fb6f47ce5fea.png" style="width: 42rpx;height: 42rpx;margin-left: 40rpx;" @tap="startNewDialog"></image> -->
+				<image src="https://transcend.ringzle.com/xiaozhi-app/profile/2025/09/10/cb27247a-8924-4ea7-a750-238ec9debd28.png" style="width: 42rpx;height: 42rpx;margin-left: 40rpx;" @tap="startNewDialog"></image>
+			</view>
+		</u-navbar>
+		<template v-if="dialogList.length===0">
+			<div class="welcome">
+				<image src="https://transcend.ringzle.com/xiaozhi-app/profile/2025/09/10/09ed81ea-ab7c-457b-a128-1d0ed43aaffd.png"></image>
+				<p>Hi,很高兴见到你~</p>
+				<p class="tip">我可以帮你读语文,写作各种创意内容,请把你的任务交给我吧~ </p>
+			</div>
+		</template>
+		<template v-else>
+			<div class="dialogs container" ref="messageContainer">
+				<div class="d_answer init">
+					<div class="da_top adfac">
+						<image src="https://transcend.ringzle.com/xiaozhi-app/profile/2025/09/10/09ed81ea-ab7c-457b-a128-1d0ed43aaffd.png"></image>
+						<text>AI智能终端</text>
+					</div>
+					<div class="da_content">Hi,我可以帮你解答问题,写作各种创意内容,请把你的任务交给我吧~~</div>
+				</div>
+				<div v-for="(item,index) in dialogList" :key="index">
+					<div class="d_question">
+						<div class="dq_text">{{ item.question }}</div>
+					</div>
+					<div class="d_answer">
+						<div class="da_top adfac">
+							<image src="https://transcend.ringzle.com/xiaozhi-app/profile/2025/09/10/09ed81ea-ab7c-457b-a128-1d0ed43aaffd.png"></image>
+							<text>AI智能终端</text>
+						</div>
+						<div class="da_content">
+							<template v-if="item.think">
+								<view class="dac_think adfac">
+									<image src="https://transcend.ringzle.com/xiaozhi-app/profile/2025/07/07/13c5dd16-2032-464a-8b1c-2722d201cfe2.gif"></image>
+									<text>正在思考中</text>
+								</view>
+							</template>
+							<template v-else>
+								<u-parse :content="item.answer"></u-parse>
+							</template>
+							<div class="dc_btns adfacjb" v-if="item.answer">
+								<div class="db_l">
+									<image :src="item.copy?require('@/static/copy_active.png'):require('@/static/copy.png')" @tap="toCopy(item,index)"></image>
+									<image :src="item.upvote?require('@/static/upvote_active.png'):require('@/static/upvote.png')" @tap="toUpvote(item,index)"></image>
+									<image :src="item.comment?require('@/static/comment_active.png'):require('@/static/comment.png')" @tap="toComment(item,index)"></image>
+								</div>
+								<div class="db_r">
+								<!-- 	<image :src="item.share?require('@/static/share_active.png'):require('@/static/share.png')" @tap="toShare(item,index)"></image> -->
+								</div>
+							</div>
+						</div>
+					</div>
+				</div>
+			</div>
+		</template>
+		<div class="ask_box" :style="{'bottom':fixBottom+'px'}">
+			<div class="ask">
+				<div class="a_inp">
+					<div class="ai_l">
+						<u-textarea v-model="question" placeholder="请输入您的问题" autoHeight :showConfirmBar="false" :adjustPosition="false" maxlength="999999"></u-textarea>
+					</div>
+					<div class="ai_r">
+						<image @tap="sendQuestion" src="https://transcend.ringzle.com/xiaozhi-app/profile/2025/09/10/a4a2718d-733e-467a-8bc2-7dc1548d2767.png"></image>
+					</div>
+				</div>
+			</div>
+			<div class="ask_memo">本服务为AI生成内容,结果仅供参考</div>
+		</div>
+		<u-modal :show="commentShow" title="评论" @confirm="commentConfirm" @cancel="commentCancel" @close="commentCancel" :showCancelButton="true">
+			<u-textarea v-model="content" placeholder="对于我们的回答您是否不满意,您有更好的答案建议吗?请在此输入."></u-textarea>
+		</u-modal>
+	</view>
+</template>
+
+<script>
+	var timer = null;
+	let requestTask = null;
+	import { BaseApi } from '../http/baseApi.js'
+	import * as TextEncoding from "text-encoding-shim";
+	export default {
+		data(){
+			return {
+				isDialog:false,
+				retryCount: 3, // 最大重试次数
+				currentRetry: 0, // 当前重试次数
+				isRequesting: false, // 请求状态锁
+				question:'',
+				streamingResponse:'',
+				receivedData:'',
+				dialogList:[],
+				windex:0,
+				commentShow:false,
+				content:'',
+				cindex:'',
+				chzq:false,
+				lwss:false,
+				fixBottom: 0
+			}
+		},
+		onReady() {
+		    uni.onKeyboardHeightChange(res => {
+		      this.fixBottom = res.height||0;
+		    });
+		},
+		onUnload() {
+		    uni.offKeyboardHeightChange();
+			this.fixBottom = 0;
+		},
+		methods:{
+			toBack(){
+				uni.reLaunch({
+					url:'/pages/home'
+				})
+			},
+			startNewDialog(){
+				clearInterval(timer)
+				this.dialogList = [];
+				this.question = '';
+				this.streamingResponse = '';
+			},
+			// 封装带重试机制的请求方法
+			async sendRequestWithRetry() {
+				if (this.isRequesting) return;
+				this.isRequesting = true;
+				this.currentRetry = 0;
+				try {
+					await this._executeRequest();
+				} catch (error) {
+					  this.$showToast('请求失败,请稍后重试')
+				} finally {
+					this.isRequesting = false;
+				}
+			},
+			_executeRequest2(){
+				return new Promise((resolve, reject) => {
+					this.$api.post('/core/chat/sendChatMessageStream',{query:this.question},false).then(res=>{
+						if(res.data.code!==0) return this.$showToast(res.data.msg)
+						let answer = this.dialogList[this.dialogList.length-1].answer+res.data.data;
+						this.$set(this.dialogList[this.dialogList.length-1],'answer',answer);
+						this.$set(this.dialogList[this.dialogList.length-1],'think',false);
+						setTimeout(()=>{
+							this.scrollToBottom();
+						},100)
+						resolve()
+					})
+				})
+			},
+			// 实际执行请求的方法
+			_executeRequest() {
+				return new Promise((resolve, reject) => {
+					requestTask = uni.request({
+						url: `${BaseApi}/core/chat/sendChatMessageStream`,
+						method: 'POST',
+						timeout: 10000,
+						data:{ 
+							query: this.question,
+							identity:'被教练者',
+						},
+						header: {
+						  'Content-Type': 'application/json',
+						  'token': uni.getStorageSync('token') || ''
+						},
+						enableChunked: true, // 启用流式接收
+						responseType:'text',
+						success: (res) => {
+							if (res.statusCode === 200) {
+								this._handleSuccess(res.data);
+								resolve();
+							} else {
+								this._handleError(`状态码异常: ${res.statusCode}`, resolve, reject);
+							}
+						},
+						fail: (err) => {
+							this._handleError(err.errMsg, resolve, reject);
+						},
+						complete: (com) => {
+							console.log('请求完成',com)
+						}
+					});
+					
+					requestTask.onChunkReceived(async (res) => {
+						const uint8Array = new Uint8Array(res.data);
+						const decoder = new TextEncoding.TextDecoder("utf-8");
+						const decodedString = decoder.decode(uint8Array);
+						try {
+							let newtext = decodedString.replaceAll('data:','').replaceAll(':keepAlive','');
+							let ntArr = newtext.split('\n\n');
+							if(ntArr.length){
+								ntArr.forEach(n=>{
+									if(!n.trim()) return
+									let nj = JSON.parse(n);
+									if(nj.event=='message'){
+										let answer = this.dialogList[this.dialogList.length-1].answer+nj.answer?.replace(/(\r\n|\n|\r)+/g, '<br>');
+										this.$set(this.dialogList[this.dialogList.length-1],'answer',answer);
+										this.$set(this.dialogList[this.dialogList.length-1],'id',nj.id);
+										this.$set(this.dialogList[this.dialogList.length-1],'task_id',nj.task_id);
+										this.$set(this.dialogList[this.dialogList.length-1],'message_id',nj.message_id);
+										this.$set(this.dialogList[this.dialogList.length-1],'conversation_id',nj.conversation_id);
+										this.$set(this.dialogList[this.dialogList.length-1],'think',false);
+									}
+								})
+								setTimeout(()=>{
+									this.scrollToBottom();
+								},100)
+							}
+						} catch (e) {
+							console.error('解析失败', e, '原始数据:', decodedString);
+						}
+					});
+				});
+			},
+			// 成功处理
+			_handleSuccess(data) {
+				if (data) {
+				  this.streamingResponse += data;
+				}
+				this.currentRetry = 0; // 重置重试计数器
+			},
+			// 错误处理
+			_handleError(errorMsg, resolve, reject) {
+				if (this._shouldRetry(errorMsg)) {
+					this.currentRetry++;
+					setTimeout(() => {
+						this._executeRequest().then(resolve).catch(reject);
+					}, this._getRetryDelay());
+				} else {
+					reject(errorMsg);
+				}
+			},
+			// 判断是否需要重试
+			_shouldRetry(errorMsg) {
+				const retryableErrors = [
+					'timeout', 
+					'request:fail', 
+					'Network Error'
+				];
+				return this.currentRetry < this.retryCount && retryableErrors.some(e => errorMsg.includes(e));
+			},
+			// 获取指数退避延迟时间
+			_getRetryDelay() {
+				return Math.min(1000 * Math.pow(2, this.currentRetry), 10000);
+			},
+			sendQuestion(){
+				if(!this.question) return this.$showToast('请输入您的问题');
+				if(!this.isLogin()) return
+				let qa = {
+					question:JSON.parse(JSON.stringify(this.question)),
+					answer:'',
+					copy:false,
+					upvote:false,
+					comment:'',
+					share:false,
+					think:true
+				}
+				this.dialogList = [...this.dialogList,...[qa]];
+				this.$nextTick(()=>{
+					this.scrollToBottom();
+					this.sendRequestWithRetry();
+					this.question = '';
+				})
+			},
+			// 滚动到底部
+			scrollToBottom() {
+				this.$nextTick(()=>{
+					this.$nextTick(()=>{
+						uni.pageScrollTo({
+							scrollTop:99999,
+							duration:300
+						})
+					})
+				})
+			},
+			toCopy(item,index){
+				uni.setClipboardData({
+					data:item.answer,
+					success: (res) => {
+						this.$showToast('复制成功');
+					},
+					fail: (err) => {
+						this.$showToast('复制失败');
+					}
+				})
+			},
+			toUpvote(item,index){
+				this.$set(this.dialogList[index],'upvote',!this.dialogList[index].upvote);
+			},
+			toComment(item,index){
+				this.cindex = index;
+				this.commentShow = true;
+			},
+			toShare(item,index){
+				this.$set(this.dialogList[index],'share',!this.dialogList[index].share);
+			},
+			commentConfirm(){
+				this.$set(this.dialogList[this.cindex],'comment',this.content);
+				this.commentShow = false;
+			},
+			commentCancel(){
+				this.content = '';
+				this.commentShow = false;
+			},
+			changeChzq(){
+				this.chzq = !this.chzq;
+			},
+			changeLwss(){
+				this.lwss = !this.lwss;
+			},
+		}
+	}
+</script>
+
+<style scoped lang="scss">
+	::v-deep .u-textarea{
+		border: none !important;
+		padding: 0 !important;
+	}
+	::v-deep .u-textarea textarea{
+		min-height: 64rpx !important;
+	}
+	
+	.page{
+		background: linear-gradient( 227deg, #EEEFF8 0%, #F6ECF4 100%, #F6ECF4 100%);
+		padding: 0 30rpx 200rpx;
+		box-sizing: border-box;
+		
+		.welcome{
+			margin-top: 259rpx;
+			padding: 0 34rpx;
+			image{
+				width: 88rpx;
+				height: 87rpx;
+				margin-left: 20rpx;
+			}
+			p{
+				font-family: PingFang-SC, PingFang-SC;
+				font-weight: bold;
+				font-size: 36rpx;
+				color: #252525;
+				line-height: 36rpx;
+				margin-top: 36rpx;
+				&.tip{
+					font-family: PingFangSC, PingFang SC;
+					font-weight: 400;
+					font-size: 26rpx;
+					color: #646464;
+					line-height: 40rpx;
+					margin-top: 20rpx;
+				}
+			}
+		}
+	
+		.dialogs{
+			width: 100%;
+			padding-top: 34rpx;
+			box-sizing: border-box;
+			overflow-y: auto;
+			.d_answer{
+				margin-top: 40rpx;
+				&.init{
+					margin-top: 0;
+				}
+				.da_top{
+					image{
+						width: 48rpx;
+						height: 48rpx;
+					}
+					text{
+						font-family: PingFang-SC, PingFang-SC;
+						font-weight: bold;
+						font-size: 30rpx;
+						color: #505050;
+						line-height: 48rpx;
+						margin-left: 20rpx;
+					}
+				}
+				.da_content{
+					padding: 30rpx 32rpx;
+					margin-top: 20rpx;
+					background: #FFFFFF;
+					border-radius: 4rpx 24rpx 24rpx 24rpx;
+					.dac_think{
+						image{
+							width: 40rpx;
+							height: 40rpx;
+						}
+						text{
+							font-size: 30rpx;
+							margin-left: 10rpx;
+						}
+					}
+					.dc_btns{
+						margin-top: 44rpx;
+						image{
+							width: 48rpx;
+							height: 48rpx;
+						}
+						.db_l{
+							image{
+								margin-right: 40rpx;
+							}
+						}
+					}
+				}
+			}
+			.d_question{
+				margin-top: 40rpx;
+				display: flex;
+				justify-content: flex-end;
+				.dq_text{
+					background: #833478;
+					border-radius: 24rpx 4rpx 24rpx 24rpx;
+					font-family: PingFangSC, PingFang SC;
+					font-weight: 400;
+					font-size: 30rpx;
+					color: #FFFFFF;
+					line-height: 48rpx;
+					// text-align: right;
+					padding: 26rpx 30rpx;
+				}
+			}
+		}
+		.ask_box{
+			width: 100%;
+			min-height: 176rpx;
+			background: linear-gradient( 227deg, #EEEFF8 0%, #F6ECF4 100%, #F6ECF4 100%);
+			padding: 0 30rpx 60rpx;
+			position: fixed;
+			left: 0;
+			box-sizing: border-box;
+			
+			.ask_memo{
+				font-family: PingFangSC, PingFang SC;
+				font-weight: 400;
+				font-size: 24rpx;
+				color: #b1b1b1;
+				line-height: 34rpx;
+				text-align: center;
+				margin-top: 16rpx;
+			}
+		}
+	
+		.ask{
+			min-height: 116rpx;
+			background: #FFFFFF;
+			border-radius: 24rpx;
+			border: 2rpx solid #F0F2F8;
+			padding: 24rpx;
+			box-sizing: border-box;
+			.a_inp{
+				display: flex;
+				align-items: flex-end;
+				max-height: 300rpx;
+				overflow-y: auto;
+				.ai_l{
+					width: calc(100% - 64rpx);
+					padding-right: 20rpx;
+					box-sizing: border-box;
+				}
+				.ai_r{
+					width: 64rpx;
+					image{
+						width: 64rpx;
+						height: 64rpx;
+					}
+				}
+			}
+		}
+	}
+</style>

+ 4 - 2
pages/home.vue

@@ -39,6 +39,7 @@
 			}
 		},
 		async onShow() {
+			if(!uni.getStorageSync('token')) return
 			await this.getAgentModelList()
 			this.getDeviceList();
 		},
@@ -46,7 +47,7 @@
 			getAgentModelList(){
 				return new Promise((resolve,reject)=>{
 					this.$api.get('/agent/template').then(res=>{
-						if(res.data.code!==0) return this.$showToast(res.data.msg)
+						if(res.data.code!==0) return
 						if(res.data.data.length){
 							let map = new Map();
 							res.data.data.forEach(l=>{
@@ -60,7 +61,7 @@
 			},
 			getDeviceList(){
 				this.$api.get(`/device/bind/${null}`).then(res=>{
-					if(res.data.code!==0) return this.$showToast(res.data.msg)
+					if(res.data.code!==0) return
 					this.list = res.data.data;
 					this.list.forEach(l=>{
 						l.roleModelName = this.modelMap.get(l?.agent?.systemPrompt)?.agentName;
@@ -68,6 +69,7 @@
 				})
 			},
 			addDevice(){
+				if(!this.isLogin()) return
 				this.$refs.bdRef.show = true;
 			},
 			unbindSuccess(){

+ 11 - 3
pages/my.vue

@@ -42,7 +42,8 @@
 				<div class="ir">{{'1.0.0'}}</div>
 			</div>
 		</div>
-		<div class="exit" @tap="exitLogin">退出登录</div>
+		<div class="exit" @tap="toLogin" v-if="!loginStatus">立即登录</div>
+		<div class="exit" @tap="exitLogin" v-else>退出登录</div>
 		<cus-tabbar :tabbarIndex="2"></cus-tabbar>
 	</view>
 </template>
@@ -55,20 +56,27 @@
 		},
 		data(){
 			return {
-				username:''
+				username:'',
+				loginStatus:false
 			}
 		},
 		onShow() {
 			if(uni.getStorageSync('userInfo')){
+				this.loginStatus = true;
 				let username = JSON.parse(uni.getStorageSync('userInfo')).username;
 				this.username = username.replace(/^(.{3})(?:\d+)(.{4})$/, "$1 **** $2");
 			}
 		},
 		methods:{
 			toTurn(url){
-				if(!url) return
+				if(!url||!this.isLogin()) return
 				uni.navigateTo({ url })
 			},
+			toLogin(){
+				uni.reLaunch({
+					url:'/pages/login'
+				})
+			},
 			exitLogin(){
 				uni.showModal({
 					title:'温馨提示',

+ 5 - 2
pages/role.vue

@@ -91,15 +91,17 @@
 			}
 		},
 		onShow() {
+			if(!uni.getStorageSync('token')) return
 			this.getAgentList();
 		},
 		methods:{
 			queryRole(){
+				if(!this.isLogin()) return
 				this.list = this.originList.filter(l=>l.agentName.indexOf(this.keyword)>-1);
 			},
 			getAgentList(){
 				this.$api.get('/agent/list').then(res=>{
-					if(res.data.code!==0) return this.$showToast(res.data.msg)
+					if(res.data.code!==0) return
 					this.list = res.data.data;
 					this.list.forEach((l,i)=>{
 						l.img = this.roleAvatars[i%3];
@@ -109,7 +111,7 @@
 			},
 			getDeviceList(){
 				this.$api.get(`/device/bindOther/${this.agentId}`).then(res=>{
-					if(res.data.code!==0) return this.$showToast(res.data.msg)
+					if(res.data.code!==0) return
 					this.deviceList = [res.data.data];
 					if(res.data.data.length>0) this.show = true;
 					else this.$showToast('暂无可绑定的设备')
@@ -119,6 +121,7 @@
 				this.$refs.bdRef.show = true;
 			},
 			addRole(){
+				if(!this.isLogin()) return
 				uni.navigateTo({
 					url:'/pagesRole/addRole?type=2'
 				})

二进制
static/comment.png


二进制
static/comment_active.png


二进制
static/copy.png


二进制
static/copy_active.png


二进制
static/share.png


二进制
static/share_active.png


二进制
static/upvote.png


二进制
static/upvote_active.png


+ 21 - 0
utils/system.js

@@ -15,5 +15,26 @@ export default {
 				}
 			}
 		})
+	},
+	methods:{
+		isLogin(){
+			if(uni.getStorageSync('token')){
+				return true
+			}else {
+				uni.showModal({
+					title:'温馨提示',
+					content:'当前功能需要登录后使用,是否跳转登录页面?',
+					success: (res) => {
+						if(res.confirm){
+							uni.reLaunch({
+								url:'/pages/login'
+							})
+						}else{
+							return false
+						}
+					}
+				})
+			}
+		}
 	}
 }