Explorar el Código

feat: 增加知识库配置属性

ageerle hace 1 mes
padre
commit
3a7a88050d

+ 33 - 62
src/api/Recognition.ts

@@ -9,102 +9,82 @@ export interface recType{
 }
 export class Recognition {
   private recognition: any;
-
   private listener?: (result: string) => void;
-
   private isStop = false;
-
-  //选项
-  private recOpt:recType={timeOut:2000} 
-  
-  //
-  private handleTime: any ;
-
-  private hTime:Date | undefined;
-  //语言
-  private asrLanguage = 'cmn-Hans-CN'
-  //
+  private recOpt: recType = { timeOut: 2000 };
+  private handleTime: any;
+  private hTime: Date | undefined;
+  private asrLanguage = "cmn-Hans-CN";
   private onEnd?: () => void;
   private onStart?: () => void;
+
   public setListener(fn: (result: string) => void) {
     this.listener = fn;
     return this;
   }
-  public setOnEnd( fn: ( ) => void){
+
+  public setOnEnd(fn: () => void) {
     this.onEnd = fn;
     return this;
   }
-  public setOpt( opt:recType ){
-    this.recOpt= opt;
-
-    if(opt.listener)  this.setListener(opt.listener)
-    if(opt.onEnd)  this.setListener(opt.onEnd)
-    if(opt.asrLanguage)  this.setLang(opt.asrLanguage);
-    if(opt.onStart) this.onStart= opt.onStart;
 
+  public setOpt(opt: recType) {
+    this.recOpt = opt;
+    if (opt.listener) this.setListener(opt.listener);
+    if (opt.onEnd) this.setOnEnd(opt.onEnd);
+    if (opt.asrLanguage) this.setLang(opt.asrLanguage);
+    if (opt.onStart) this.onStart = opt.onStart;
     return this;
   }
-  
-  public setLang( lang:string ){
+
+  public setLang(lang: string) {
     this.asrLanguage = lang;
     return this;
   }
 
   public start() {
     this.isStop = false;
-    // @ts-ignore
-    if (!window.SpeechRecognition && !window.webkitSpeechRecognition) return;
+    if (typeof window === "undefined" || (!window.SpeechRecognition && !window.webkitSpeechRecognition)) {
+      console.warn("当前浏览器不支持 SpeechRecognition,请使用 Chrome 或 Edge");
+      return;
+    }
+
     if (!this.recognition) {
-      // @ts-ignore
       const recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
       this.recognition = recognition;
     }
     const recognition = this.recognition;
 
-    // 返回实时识别结果
     recognition.interimResults = true;
-    // 设置语言
-    const lang = this.asrLanguage;
-    recognition.lang = lang;
-
-    // 设置是否连续识别
+    recognition.lang = this.asrLanguage;
     recognition.continuous = true;
 
     this.hTime = new Date();
-    this.handleTime = setInterval( ()=>this.check( this ), this.recOpt.timeOut ) 
+    this.handleTime = setInterval(() => this.check(this), this.recOpt.timeOut);
 
-    // 当识别到语音时触发该事件
-    recognition.addEventListener('result', (event: any) => { 
-
-      let transcript = '';
+    recognition.addEventListener("result", (event: any) => {
+      let transcript = "";
       for (let index = 0; index < event.results.length; index++) {
         const item = event.results[index];
-        // 中文添加逗号
-        if (transcript && lang?.includes('Han')) transcript += ',';
-
+        if (transcript && this.asrLanguage.includes("Han")) transcript += ",";
         transcript += (item as unknown as SpeechRecognitionAlternative[])[0]?.transcript;
       }
-      if (!transcript) return; 
+      if (!transcript) return;
       this.hTime = new Date();
       this.listener?.(transcript);
     });
 
-    // 当识别结束时触发该事件
-    recognition.addEventListener('end', () => {
-      mlog('recognition onEnd',  this.isStop );
+    recognition.addEventListener("end", () => {
       if (this.isStop) {
         this.onEnd?.();
-        this.handleTime && clearInterval( this.handleTime )
+        clearInterval(this.handleTime);
         return;
       }
-      // 继续监听
-      recognition.start();
+      setTimeout(() => recognition.start(), 1000); // 避免 Mac 触发安全限制
     });
 
-    // 启动语音识别
     recognition.start();
     this.onStart?.();
-
     return this;
   }
 
@@ -114,19 +94,10 @@ export class Recognition {
     return this;
   }
 
-  private check( that:Recognition ){
-     if( !that.hTime ) {
-         mlog('mcheck 未定义');
-        return ;
-     }  
-     const nTime =  new Date();
-     
-     const dt =  nTime.getTime()- that.hTime.getTime();
-     mlog('mcheck', dt,that.recOpt.timeOut );
-     if( dt> that.recOpt.timeOut ){
-        that.stop();
-     }
-     return this;
+  private check(that: Recognition) {
+    if (!that.hTime) return;
+    const dt = new Date().getTime() - that.hTime.getTime();
+    if (dt > that.recOpt.timeOut) that.stop();
   }
 }
 

+ 163 - 72
src/views/fanyi/components/textComponent.vue

@@ -1,29 +1,76 @@
 <template>
 	<div class="name-textBox">
 		<div class="name-textBox-left">
+			<n-input
+				v-model:value="prompt"
+				type="textarea"
+				rows="15"
+				placeholder="请输入文本"
+			/>
 			<div class="name-selectBox">
-				<div>
-					<n-space vertical>
-						<n-select v-model:value="sourceLanguage" :options="options" />
-					</n-space>
+				<div
+					style="
+						display: flex;
+						justify-content: space-between;
+						align-items: center;
+					"
+				>
+					<span style="width: 100px; font-size: 15px; font-weight: bold"
+						>语音录入:</span
+					>
+					<div>
+						<IconSvg
+							icon="voice"
+							width="22px"
+							height="22px"
+							@click="goASR"
+						></IconSvg>
+					</div>
 				</div>
-				<div>
-					<n-space vertical>
+				<div
+					style="
+						display: flex;
+						justify-content: space-between;
+						align-items: center;
+					"
+				>
+					<span style="width: 100px; font-size: 15px; font-weight: bold"
+						>目标语言:</span
+					>
+					<n-space vertical style="width: 100%">
 						<n-select v-model:value="targetLanguage" :options="options1" />
 					</n-space>
 				</div>
 
-				<div style="display: flex; align-items: center;">
-					<n-space vertical>
-						<n-select v-model:value="model" :options="modelListData" value-field="modelDescribe"
-							label-field="modelName" />
+				<div
+					style="
+						display: flex;
+						justify-content: space-between;
+						align-items: center;
+					"
+				>
+					<span style="width: 100px; font-size: 15px; font-weight: bold"
+						>模型:</span
+					>
+					<n-space vertical style="width: 100%">
+						<n-select
+							v-model:value="model"
+							:options="modelListData"
+							value-field="modelDescribe"
+							label-field="modelName"
+						/>
 					</n-space>
 				</div>
 
-				<n-button @click="handleTranslation" type="primary">翻译</n-button>
+				<div>
+					<n-button
+						style="width: 100%"
+						@click="handleTranslation"
+						type="primary"
+						>翻译</n-button
+					>
+				</div>
 			</div>
-			<n-input v-model:value="prompt" type="textarea" placeholder="请输入文本" :resizable="false" />
-
 		</div>
 		<div class="name-textBox-right">
 			<div class="name-textBox-right-content">
@@ -34,37 +81,63 @@
 </template>
 
 <script setup>
-import { ref, onMounted } from 'vue'
-import { NInput, NSelect, NButton } from 'naive-ui'
-import { modelList } from '@/api/model'
-import { translation } from '@/api/fanyi'
-const modelListData = ref([])
-
-
-
-const prompt = ref('')
-const sourceLanguage = ref('英文')
-const targetLanguage = ref('中文')
-const translationResult = ref('')
-const model = ref('')
+import { t } from "@/locales";
+import { SvgIcon, PromptStore } from "@/components/common";
+import { ref, onMounted } from "vue";
+import { NInput, NSelect, NButton, useMessage, NDropdown } from "naive-ui";
+import { Recognition } from "@/api";
+import { modelList } from "@/api/model";
+import { translation } from "@/api/fanyi";
+const modelListData = ref([]);
+import { useIconRender } from "@/hooks/useIconRender";
+const { iconRender } = useIconRender();
+const st = ref({
+	fileBase64: [],
+	isLoad: 0,
+	isShow: false,
+	showMic: false,
+	micStart: false,
+});
+const drOption = [
+	{
+		label: t("mj.micWhisper"),
+		key: "whisper",
+		icon: iconRender({ icon: "ri:openai-fill" }),
+	},
+	{
+		label: t("mj.micAsr"),
+		icon: iconRender({ icon: "ri:chrome-line" }),
+		key: "asr",
+	},
+];
+
+const handleSelectASR = (value) => {
+	console.log(value);
+};
+
+const prompt = ref("");
+// const sourceLanguage = ref("英文");
+const targetLanguage = ref("中文");
+const translationResult = ref("");
+const model = ref("");
 const options = ref([
 	// { label: '自动设别', value: '' },
-	{ label: '中文', value: '中文' },
-	{ label: '英文', value: '英文' },
-])
+	{ label: "中文", value: "中文" },
+	{ label: "英文", value: "英文" },
+]);
 const options1 = ref([
-	{ label: '中文', value: '中文' },
-	{ label: '英文', value: '英文' },
-])
+	{ label: "中文", value: "中文" },
+	{ label: "英文", value: "英文" },
+]);
 
 onMounted(() => {
-	getModelList()
-})
+	getModelList();
+});
 
 async function getModelList() {
-	const res = await modelList()
-	modelListData.value = res.data
-	model.value = modelListData.value[0].modelDescribe
+	const res = await modelList();
+	modelListData.value = res.data;
+	model.value = modelListData.value[0].modelDescribe;
 }
 
 async function handleTranslation() {
@@ -73,72 +146,90 @@ async function handleTranslation() {
 	}
 	const res = await translation({
 		prompt: prompt.value,
-		sourceLanguage: sourceLanguage.value,
+		// sourceLanguage: sourceLanguage.value,
 		targetLanguage: targetLanguage.value,
 		model: model.value,
-	})
-	translationResult.value = res
+	});
+	translationResult.value = res;
 }
+
+//语音识别ASR
+// let mvalue = ref("");
+const ms = useMessage();
+const goASR = () => {
+	console.log("触发语音识别");
+
+	const olod = prompt.value;
+	const rec = new Recognition();
+	let rz = "";
+	rec
+		.setListener((r) => {
+			//mlog('result ', r  );
+			rz = r;
+			prompt.value = r;
+			console.log("识别结果222", prompt.value);
+
+			st.value.micStart = true;
+		})
+		.setOnEnd(() => {
+			//mlog('rec end');
+			prompt.value = olod + rz;
+			console.log("识别结果1111", prompt.value);
+			ms.info(t("mj.micRecEnd"));
+			st.value.micStart = false;
+		})
+		.setOpt({
+			timeOut: 2000,
+			onStart: () => {
+				ms.info(t("mj.micRec"));
+				st.value.micStart = true;
+			},
+		})
+		.start();
+};
 </script>
 
 <style scoped lang="less">
 .name-textBox {
 	display: flex;
-
-	:deep(.n-input) {
-
-		.n-input__border,
-		.n-input__state-border {
-			border: none !important;
-		}
-
-		textarea {
-			min-height: 500px !important;
-		}
-	}
+	justify-content: center;
 
 	.name-textBox-left {
-		flex: 1;
-		height: calc(100vh - 100px);
+		width: 49%;
+		height: calc(100vh - 50px);
+		overflow: hidden;
+		overflow-y: scroll;
 		border-radius: 10px;
-
+		border: 2px solid #f0f0f0;
 		padding: 10px;
+		margin-right: 20px;
 
 		.name-selectBox {
-			display: flex;
-			justify-content: space-between;
-			margin-bottom: 10px;
+			// display: flex;
+			// justify-content: space-between;
+			// margin-bottom: 10px;
 
 			div {
 				flex: 1;
+				margin-top: 10px;
 			}
 		}
 	}
 
 	.name-textBox-right {
-		flex: 1;
-		min-height: 500px;
+		width: 49%;
+		height: calc(100vh - 50px);
 		border-radius: 10px;
-		padding: 10px;
-
 
 		.name-selectBox {
 			margin-bottom: 10px;
 		}
 
 		.name-textBox-right-content {
-			min-height: 500px;
-			border: 1px solid #ccc;
+			height: calc(100vh - 50px);
+			border: 2px solid #f0f0f0;
 			border-radius: 10px;
-			padding: 10px;
-			margin-top: 40px;
 		}
 	}
-
-
-
-	div {
-		margin-right: 20px;
-	}
 }
 </style>

+ 11 - 11
src/views/fanyi/index.vue

@@ -1,26 +1,26 @@
 <template>
 	<div class="fanyi-container">
 		<n-card>
-			<n-tabs animated :value="activeTab" @update:value="activeTab = $event">
+			<textComponent />
+			<!-- <n-tabs animated :value="activeTab" @update:value="activeTab = $event">
 				<n-tab-pane name="1" tab="文本">
 					<textComponent />
 				</n-tab-pane>
-				<!-- <n-tab-pane name="2" tab="文档">
-                    <documentComponent />
-                </n-tab-pane> -->
-			</n-tabs>
+				<n-tab-pane name="2" tab="文档">
+					<documentComponent />
+				</n-tab-pane>
+			</n-tabs> -->
 		</n-card>
 	</div>
 </template>
 
 <script setup>
-import { ref } from 'vue'
-import documentComponent from '@/views/fanyi/components/documentComponent.vue'
-import textComponent from '@/views/fanyi/components/textComponent.vue'
-import { NCard, NTabs, NTabPane } from 'naive-ui'
+import { ref } from "vue";
+import documentComponent from "@/views/fanyi/components/documentComponent.vue";
+import textComponent from "@/views/fanyi/components/textComponent.vue";
+import { NCard, NTabs, NTabPane } from "naive-ui";
 
-const activeTab = ref('1')
+const activeTab = ref("1");
 </script>
 
-
 <style scoped lang="less"></style>

+ 50 - 23
src/views/knowledge/index.vue

@@ -1,9 +1,9 @@
 <script setup lang="ts">
-import { h, onMounted, ref } from 'vue';
+import { h, onMounted, reactive, ref } from 'vue';
 import {
 	NButton, NDataTable, DrawerPlacement, NDrawer,
 	NDrawerContent, NForm, NFormItem, NInput, NDivider,
-	NSpace, useMessage, NGrid, NGi,NSwitch,NInputNumber,NSelect,NSlider
+	NSpace, useMessage, NGrid, NGi,NSwitch,NInputNumber,NSelect,NSlider 
 } from 'naive-ui';
 import { createKnowledgeReq, getKnowledge, delKnowledge } from '@/api/knowledge';
 import to from 'await-to-js';
@@ -23,6 +23,13 @@ const formValue = ref({
 	kname: '', // 知识库名称
 	share: '0', // 是否分享
 	description: '', // 知识库描述
+	knowledgeSeparator: '', // 知识分隔符
+	questionSeparator: '', // 提问分隔符
+	overlapChar: 50, // 重叠字符数
+	retrieveLimit: 3, // 知识库中检索的条数
+	textBlockSize: 500, // 文本块大小
+	vector: '', //  向量库
+	vectorModel: '', //  向量模型
 });
 
 async function submitForm() {
@@ -76,6 +83,15 @@ const activate = (place: DrawerPlacement) => {
 const active = ref(false);
 const placement = ref<DrawerPlacement>('right');
 
+const getVector = reactive([
+      { label: 'weaviate', value: 'weaviate' },
+      { label: 'milvus', value: 'milvus' }
+]);
+
+const getVectorModel = reactive([
+      { label: 'text-embedding-3-small', value: 'text-embedding-3-small' }
+]);
+
 const createColumns = () => {
 	return [
 		...(false
@@ -175,64 +191,75 @@ const columns = ref(createColumns());
 								<n-input v-model:value="formValue.kname" placeholder="请输入知识库名称" />
 							</n-form-item>
 						</n-gi>
-
+				
 						<n-gi>
 							<n-form-item label="分隔符" >
-								<n-input  placeholder="请输入知识分隔符" />
+								<n-input v-model:value="formValue.knowledgeSeparator" placeholder="请输入知识分隔符" />
 							</n-form-item>
 						</n-gi>
 
 						<n-gi >
 							<n-form-item label="知识库中检索的条数" >
-								<n-input-number
+								<n-input-number  v-model:value="formValue.retrieveLimit"
 									placeholder="请输入检索条数" />
 							</n-form-item>
 						</n-gi>
 
 						<n-gi >
 							<n-form-item label="文本块大小" path="phone">
-								<n-input-number  placeholder="请输入文本块大小"/>
-
+								<n-input-number v-model:value="formValue.textBlockSize"  placeholder="请输入文本块大小"/>
+					
 							</n-form-item>
 						</n-gi>
 
 
-						<n-gi >
-							<n-form-item label="是否公开">
-								<n-switch size="large" checked-value="1" unchecked-value="0"
-									@update:value="handleUpdateValue" />
-							</n-form-item>
-						</n-gi>
 
 						<n-gi>
-							<n-form-item label="重叠字符" path="formValue.kname">
-								<n-input-number
+							<n-form-item label="重叠字符" >
+								<n-input-number  v-model:value="formValue.overlapChar"  
 									placeholder="请输入重叠字符数" />
 							</n-form-item>
 						</n-gi>
 
+						<n-gi>
+							<n-form-item label="向量库" >
+							<n-select :options="getVector" v-model:value="formValue.vector"  
+								placeholder="请选择向量库" 
+							></n-select>
+						    </n-form-item>
+						</n-gi>
 
-						<n-gi :span="24">
-							<n-form-item :label="$t('knowledge.knowledgeDescription')" path="formValue.description">
-								<n-input maxlength="1000" type="textarea" v-model:value="formValue.description"
-									:placeholder="$t('knowledge.enterKnowledgeDescription')" />
+					
+						<n-gi >
+							<n-form-item label="提问分割符" path="phone">
+								<n-input placeholder="请输入提问分割符"/>
 							</n-form-item>
 						</n-gi>
 
 						<n-gi>
 							<n-form-item label="向量模型" path="formValue.description">
-							<n-select
+							<n-select :options="getVectorModel" v-model:value="formValue.vectorModel"  
 								placeholder="请选择向量模型"
 							></n-select>
 						    </n-form-item>
 						</n-gi>
 
-						<n-gi >
-							<n-form-item label="提问分割符" path="phone">
-								<n-input placeholder="请输入提问分割符"/>
+						
+						<n-gi :span="24">
+							<n-form-item :label="$t('knowledge.knowledgeDescription')" >
+								<n-input maxlength="1000" type="textarea" v-model:value="formValue.description"
+									:placeholder="$t('knowledge.enterKnowledgeDescription')" />
 							</n-form-item>
 						</n-gi>
 
+						
+						<n-gi :span="24">
+							<n-form-item label="是否公开">
+								<n-switch size="large" checked-value="1" unchecked-value="0"
+									@update:value="handleUpdateValue" />
+							</n-form-item>
+						</n-gi>
+			
 						<n-gi  :span="24">
 							<div style="display: flex; justify-content: flex-end">
 								<n-button @click="submitForm" :bordered="false" type="primary" class="draw-button">

+ 483 - 476
src/views/mj/aiGptInput.vue

@@ -3,29 +3,29 @@ import { ref, computed, watch } from "vue";
 import { useBasicLayout } from "@/hooks/useBasicLayout";
 import { t } from "@/locales";
 import {
-  NInput,
-  NButton,
-  useMessage,
-  NImage,
-  NTooltip,
-  NAutoComplete,
-  NTag,
-  NPopover,
-  NModal,
-  NDropdown,
+	NInput,
+	NButton,
+	useMessage,
+	NImage,
+	NTooltip,
+	NAutoComplete,
+	NTag,
+	NPopover,
+	NModal,
+	NDropdown,
 } from "naive-ui";
 import { SvgIcon, PromptStore } from "@/components/common";
 import {
-  canVisionModel,
-  GptUploader,
-  mlog,
-  upImg,
-  getFileFromClipboard,
-  isFileMp3,
-  countTokens,
-  checkDisableGpt4,
-  Recognition,
-  chatSetting,
+	canVisionModel,
+	GptUploader,
+	mlog,
+	upImg,
+	getFileFromClipboard,
+	isFileMp3,
+	countTokens,
+	checkDisableGpt4,
+	Recognition,
+	chatSetting,
 } from "@/api";
 import { gptConfigStore, homeStore, useChatStore } from "@/store";
 import { AutoCompleteOptions } from "naive-ui/es/auto-complete/src/interface";
@@ -41,29 +41,29 @@ const route = useRoute();
 const chatStore = useChatStore();
 const emit = defineEmits(["update:modelValue", "export", "handleClear"]);
 const props = defineProps<{
-  modelValue: string;
-  disabled?: boolean;
-  searchOptions?: AutoCompleteOptions;
-  renderOption?: RenderLabel;
+	modelValue: string;
+	disabled?: boolean;
+	searchOptions?: AutoCompleteOptions;
+	renderOption?: RenderLabel;
 }>();
 const fsRef = ref();
 const st = ref<{
-  fileBase64: string[];
-  isLoad: number;
-  isShow: boolean;
-  showMic: boolean;
-  micStart: boolean;
+	fileBase64: string[];
+	isLoad: number;
+	isShow: boolean;
+	showMic: boolean;
+	micStart: boolean;
 }>({
-  fileBase64: [],
-  isLoad: 0,
-  isShow: false,
-  showMic: false,
-  micStart: false,
+	fileBase64: [],
+	isLoad: 0,
+	isShow: false,
+	showMic: false,
+	micStart: false,
 });
 const { isMobile } = useBasicLayout();
 const placeholder = computed(() => {
-  if (isMobile.value) return t("chat.placeholderMobile");
-  return t("chat.placeholder"); //可输入说点什么,也可贴截图或拖拽文件
+	if (isMobile.value) return t("chat.placeholderMobile");
+	return t("chat.placeholder"); //可输入说点什么,也可贴截图或拖拽文件
 });
 
 const { uuid } = route.params as { uuid: string };
@@ -73,56 +73,56 @@ const nGptStore = ref(chatSet.getGptConfig());
 const dataSources = computed(() => chatStore.getChatByUuid(+uuid));
 
 watch(
-  () => gptConfigStore.myData,
-  () => (nGptStore.value = chatSet.getGptConfig()),
-  { deep: true }
+	() => gptConfigStore.myData,
+	() => (nGptStore.value = chatSet.getGptConfig()),
+	{ deep: true }
 );
 watch(
-  () => homeStore.myData.act,
-  (n) => n == "saveChat" && (nGptStore.value = chatSet.getGptConfig()),
-  { deep: true }
+	() => homeStore.myData.act,
+	(n) => n == "saveChat" && (nGptStore.value = chatSet.getGptConfig()),
+	{ deep: true }
 );
 const handleSubmit = () => {
-  if (mvalue.value == "") return;
-  if (checkDisableGpt4(gptConfigStore.myData.model)) {
-    ms.error(t("mj.disableGpt4"));
-    return false;
-  }
-  if (homeStore.myData.isLoader) {
-    return;
-  }
-  let obj = {
-    prompt: mvalue.value,
-    fileBase64: st.value.fileBase64,
-  };
-  homeStore.setMyData({ act: "gpt.submit", actData: obj });
-  mvalue.value = "";
-  st.value.fileBase64 = [];
-  return false;
+	if (mvalue.value == "") return;
+	if (checkDisableGpt4(gptConfigStore.myData.model)) {
+		ms.error(t("mj.disableGpt4"));
+		return false;
+	}
+	if (homeStore.myData.isLoader) {
+		return;
+	}
+	let obj = {
+		prompt: mvalue.value,
+		fileBase64: st.value.fileBase64,
+	};
+	homeStore.setMyData({ act: "gpt.submit", actData: obj });
+	mvalue.value = "";
+	st.value.fileBase64 = [];
+	return false;
 };
 const ms = useMessage();
 const mvalue = computed({
-  get() {
-    return props.modelValue;
-  },
-  set(value) {
-    emit("update:modelValue", value);
-  },
+	get() {
+		return props.modelValue;
+	},
+	set(value) {
+		emit("update:modelValue", value);
+	},
 });
 function selectFile(input: any) {
-  const file = input.target.files[0];
-  upFile(file);
+	const file = input.target.files[0];
+	upFile(file);
 }
 
 const myToken = ref({ remain: 0, modelTokens: "4k" });
 const funt = async () => {
-  const d = await countTokens(
-    dataSources.value,
-    mvalue.value,
-    chatStore.active ?? 1002
-  );
-  myToken.value = d;
-  return d;
+	const d = await countTokens(
+		dataSources.value,
+		mvalue.value,
+		chatStore.active ?? 1002
+	);
+	myToken.value = d;
+	return d;
 };
 watch(() => mvalue.value, funt);
 watch(() => dataSources.value, funt);
@@ -131,452 +131,459 @@ watch(() => homeStore.myData.isLoader, funt, { deep: true });
 funt();
 
 const upFile = (file: any) => {
-  if (!canVisionModel(gptConfigStore.myData.model)) {
-    if (isFileMp3(file.name)) {
-      mlog("mp3", file);
-      //  const formData = new FormData( );
-      // formData.append('file', file);
-      // formData.append('model', 'whisper-1');
+	if (!canVisionModel(gptConfigStore.myData.model)) {
+		if (isFileMp3(file.name)) {
+			mlog("mp3", file);
+			//  const formData = new FormData( );
+			// formData.append('file', file);
+			// formData.append('model', 'whisper-1');
 
-      // GptUploader('/v1/audio/transcriptions',formData).then(r=>{
-      //     mlog('语音识别成功', r );
-      // }).catch(e=>ms.error('上传失败:'+ ( e.message?? JSON.stringify(e)) ));
-      homeStore.setMyData({
-        act: "gpt.whisper",
-        actData: { file, prompt: "whisper" },
-      });
-      return;
-    } else {
-      upImg(file)
-        .then((d) => {
-          fsRef.value.value = "";
-          if (st.value.fileBase64.findIndex((v) => v == d) > -1) {
-            ms.error(t("mj.noReUpload")); //'不能重复上传'
-            return;
-          }
-          st.value.fileBase64.push(d);
-        })
-        .catch((e) => ms.error(e));
-    }
-  } else {
-    const formData = new FormData();
-    //const file = input.target.files[0];
-    formData.append("file", file);
-    ms.info(t("mj.uploading"));
-    st.value.isLoad = 1;
-    GptUploader("/v1/upload", formData)
-      .then((r) => {
-        //mlog('上传成功', r);
-        st.value.isLoad = 0;
-        if (r.url) {
-          ms.info(t("mj.uploadSuccess"));
-          if (r.url.indexOf("http") > -1) {
-            st.value.fileBase64.push(r.url);
-          } else {
-            st.value.fileBase64.push(location.origin + r.url);
-          }
-        } else if (r.error) ms.error(r.error);
-      })
-      .catch((e) => {
-        st.value.isLoad = 0;
-        ms.error(t("mj.uploadFail") + (e.message ?? JSON.stringify(e)));
-      });
-  }
+			// GptUploader('/v1/audio/transcriptions',formData).then(r=>{
+			//     mlog('语音识别成功', r );
+			// }).catch(e=>ms.error('上传失败:'+ ( e.message?? JSON.stringify(e)) ));
+			homeStore.setMyData({
+				act: "gpt.whisper",
+				actData: { file, prompt: "whisper" },
+			});
+			return;
+		} else {
+			upImg(file)
+				.then((d) => {
+					fsRef.value.value = "";
+					if (st.value.fileBase64.findIndex((v) => v == d) > -1) {
+						ms.error(t("mj.noReUpload")); //'不能重复上传'
+						return;
+					}
+					st.value.fileBase64.push(d);
+				})
+				.catch((e) => ms.error(e));
+		}
+	} else {
+		const formData = new FormData();
+		//const file = input.target.files[0];
+		formData.append("file", file);
+		ms.info(t("mj.uploading"));
+		st.value.isLoad = 1;
+		GptUploader("/v1/upload", formData)
+			.then((r) => {
+				//mlog('上传成功', r);
+				st.value.isLoad = 0;
+				if (r.url) {
+					ms.info(t("mj.uploadSuccess"));
+					if (r.url.indexOf("http") > -1) {
+						st.value.fileBase64.push(r.url);
+					} else {
+						st.value.fileBase64.push(location.origin + r.url);
+					}
+				} else if (r.error) ms.error(r.error);
+			})
+			.catch((e) => {
+				st.value.isLoad = 0;
+				ms.error(t("mj.uploadFail") + (e.message ?? JSON.stringify(e)));
+			});
+	}
 };
 
 function handleEnter(event: KeyboardEvent) {
-  if (!isMobile.value) {
-    if (event.key === "Enter" && !event.shiftKey) {
-      event.preventDefault();
-      handleSubmit();
-    }
-  } else {
-    if (event.key === "Enter" && event.ctrlKey) {
-      event.preventDefault();
-      handleSubmit();
-    }
-  }
+	if (!isMobile.value) {
+		if (event.key === "Enter" && !event.shiftKey) {
+			event.preventDefault();
+			handleSubmit();
+		}
+	} else {
+		if (event.key === "Enter" && event.ctrlKey) {
+			event.preventDefault();
+			handleSubmit();
+		}
+	}
 }
 
 const acceptData = computed(() => {
-  if (canVisionModel(gptConfigStore.myData.model)) return "*/*";
-  return "image/jpeg, image/jpg, image/png, image/gif, .mp3, .mp4, .mpeg, .mpga, .m4a, .wav, .webm";
+	if (canVisionModel(gptConfigStore.myData.model)) return "*/*";
+	return "image/jpeg, image/jpg, image/png, image/gif, .mp3, .mp4, .mpeg, .mpga, .m4a, .wav, .webm";
 });
 
 const drop = (e: DragEvent) => {
-  e.preventDefault();
-  e.stopPropagation();
-  if (!e.dataTransfer || e.dataTransfer.files.length == 0) return;
-  const files = e.dataTransfer.files;
-  upFile(files[0]);
-  //mlog('drop', files);
+	e.preventDefault();
+	e.stopPropagation();
+	if (!e.dataTransfer || e.dataTransfer.files.length == 0) return;
+	const files = e.dataTransfer.files;
+	upFile(files[0]);
+	//mlog('drop', files);
 };
 const paste = (e: ClipboardEvent) => {
-  let rz = getFileFromClipboard(e);
-  if (rz.length > 0) upFile(rz[0]);
+	let rz = getFileFromClipboard(e);
+	if (rz.length > 0) upFile(rz[0]);
 };
 
 const sendMic = (e: any) => {
-  mlog("sendMic", e);
-  st.value.showMic = false;
-  let du = "whisper.wav"; // (e.stat && e.stat.duration)?(e.stat.duration.toFixed(2)+'s'):'whisper.wav';
-  const file = new File([e.blob], du, { type: "audio/wav" });
-  homeStore.setMyData({
-    act: "gpt.whisper",
-    actData: { file, prompt: "whisper", duration: e.stat?.duration },
-  });
+	mlog("sendMic", e);
+	st.value.showMic = false;
+	let du = "whisper.wav"; // (e.stat && e.stat.duration)?(e.stat.duration.toFixed(2)+'s'):'whisper.wav';
+	const file = new File([e.blob], du, { type: "audio/wav" });
+	homeStore.setMyData({
+		act: "gpt.whisper",
+		actData: { file, prompt: "whisper", duration: e.stat?.duration },
+	});
 };
 
 //语音识别ASR
 const goASR = () => {
-  const olod = mvalue.value;
-  const rec = new Recognition();
-  let rz = "";
-  rec
-    .setListener((r: string) => {
-      //mlog('result ', r  );
-      rz = r;
-      mvalue.value = r;
-      st.value.micStart = true;
-    })
-    .setOnEnd(() => {
-      //mlog('rec end');
-      mvalue.value = olod + rz;
-      ms.info(t("mj.micRecEnd"));
-      st.value.micStart = false;
-    })
-    .setOpt({
-      timeOut: 2000,
-      onStart: () => {
-        ms.info(t("mj.micRec"));
-        st.value.micStart = true;
-      },
-    })
-    .start();
+	console.log("触发语音识别");
+
+	const olod = mvalue.value;
+	const rec = new Recognition();
+	console.log("🚀 ~ goASR ~ rec:", rec);
+	let rz = "";
+	rec
+		.setListener((r: string) => {
+			//mlog('result ', r  );
+			rz = r;
+			mvalue.value = r;
+			console.log("mvalue.value1111", mvalue.value);
+			st.value.micStart = true;
+		})
+		.setOnEnd(() => {
+			//mlog('rec end');
+			mvalue.value = olod + rz;
+			console.log("mvalue.value", mvalue.value);
+
+			ms.info(t("mj.micRecEnd"));
+			st.value.micStart = false;
+		})
+		.setOpt({
+			timeOut: 3000,
+			onStart: () => {
+				ms.info(t("mj.micRec"));
+				st.value.micStart = true;
+			},
+		})
+		.start();
 };
 
 const drOption = [
-  {
-    label: t("mj.micWhisper"),
-    key: "whisper",
-    icon: iconRender({ icon: "ri:openai-fill" }),
-  },
-  {
-    label: t("mj.micAsr"),
-    icon: iconRender({ icon: "ri:chrome-line" }),
-    key: "asr",
-  },
+	{
+		label: t("mj.micWhisper"),
+		key: "whisper",
+		icon: iconRender({ icon: "ri:openai-fill" }),
+	},
+	{
+		label: t("mj.micAsr"),
+		icon: iconRender({ icon: "ri:chrome-line" }),
+		key: "asr",
+	},
 ];
 const handleSelectASR = (key: string | number) => {
-  if (key == "asr") goASR();
-  if (key == "whisper") st.value.showMic = true;
+	console.log("*********");
+
+	if (key == "asr") goASR();
+	if (key == "whisper") st.value.showMic = true;
 };
 const show = ref(false);
 function handleExport() {
-  emit("export");
+	emit("export");
 }
 function handleClear() {
-  emit("handleClear");
+	emit("handleClear");
 }
 </script>
 <template>
-  <div v-if="st.showMic" class="myinputs flex justify-center items-center">
-    <AiMic @cancel="st.showMic = false" @send="sendMic" />
-  </div>
-  <div v-else>
-      <div
-        class="flex items-base justify-start pb-1 flex-wrap-reverse"
-        v-if="st.fileBase64.length > 0"
-        style="margin: 0 40px;"
-      >
-        <div
-          class="w-[60px] h-[60px] rounded-sm bg-slate-50 mr-1 mt-1 text-red-300 relative group"
-          v-for="(v, ii) in st.fileBase64"
-        >
-          <NImage :src="v" object-fit="cover" class="w-full h-full">
-            <template #placeholder>
-              <a
-                class="w-full h-full flex items-center justify-center text-neutral-500"
-                :href="v"
-                target="_blank"
-              >
-                <SvgIcon icon="mdi:download" />{{ $t("mj.attr1") }} {{ ii + 1 }}
-              </a>
-            </template>
-          </NImage>
-          <SvgIcon
-            icon="mdi:close"
-            class="hidden group-hover:block absolute top-[-5px] right-[-5px] rounded-full bg-red-300 text-white cursor-pointer"
-            @click="st.fileBase64.splice(st.fileBase64.indexOf(v), 1)"
-          ></SvgIcon>
-        </div>
-      </div>
-    <div
-      class="myinputs"
-      :class="[!isMobile ? 'chat-footer' : '']"
-      @drop="drop"
-      @paste="paste"
-    >
-      <div class="top-bar" v-if="!isMobile">
-        <div class="left" v-if="st">
-          <div
-            v-if="homeStore.myData.local != 'draw'"
-            class="chage-model-select"
-            @click="st.isShow = true"
-          >
-            <template v-if="nGptStore.gpts">
-              <SvgIcon icon="ri:apps-fill" />
-              <span class="line-clamp-1 overflow-hidden">{{
-                nGptStore.gpts.name
-              }}</span>
-            </template>
-            <template v-else>
-              <SvgIcon icon="heroicons:sparkles" />
-              <span>{{
-                nGptStore.modelLabel ? nGptStore.modelLabel : "gpt-4o-mini"
-              }}</span>
-            </template>
-            <SvgIcon icon="icon-park-outline:right" />
-          </div>
-          <n-dropdown
-            trigger="hover"
-            :options="drOption"
-            @select="handleSelectASR"
-          >
-            <div class="relative; w-[22px]" style="margin: 0 25px">
-              <div
-                class="absolute bottom-[14px] left-[31px]"
-                v-if="st.micStart"
-              >
-                <span class="relative flex h-3 w-3">
-                  <span
-                    class="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-500 opacity-75"
-                  ></span>
-                  <span
-                    class="relative inline-flex rounded-full h-3 w-3 bg-red-400"
-                  ></span>
-                </span>
-              </div>
-              <!-- <SvgIcon icon="bi:mic"  class="absolute bottom-[10px] left-[55px] cursor-pointer" @click="goASR()"></SvgIcon> -->
-              <IconSvg icon="voice" width="22px" height="22px"></IconSvg>
-            </div>
-          </n-dropdown>
-          <n-tooltip trigger="hover">
-            <template #trigger>
-              <SvgIcon
-                icon="line-md:uploading-loop"
-                class="absolute bottom-[10px] left-[8px] cursor-pointer"
-                v-if="st.isLoad == 1"
-              ></SvgIcon>
-              <IconSvg
-                icon="upload"
-                @click="fsRef.click()"
-                v-else
-                width="22px"
-                height="22px"
-              ></IconSvg>
-            </template>
-            <div
-              v-if="canVisionModel(gptConfigStore.myData.model)"
-              v-html="$t('mj.upPdf')"
-            ></div>
-            <div v-else v-html="$t('mj.upImg')"></div>
-          </n-tooltip>
-          <IconSvg
-            @click="handleExport"
-            icon="screenshot"
-            width="22px"
-            height="22px"
-          ></IconSvg>
-        </div>
-        <IconSvg
-          @click="handleClear"
-          class="right"
-          icon="clear"
-          width="28px"
-          height="22px"
-        ></IconSvg>
-        <!-- <div @click="show = true">
+	<div v-if="st.showMic" class="myinputs flex justify-center items-center">
+		<AiMic @cancel="st.showMic = false" @send="sendMic" />
+	</div>
+	<div v-else>
+		<div
+			class="flex items-base justify-start pb-1 flex-wrap-reverse"
+			v-if="st.fileBase64.length > 0"
+			style="margin: 0 40px"
+		>
+			<div
+				class="w-[60px] h-[60px] rounded-sm bg-slate-50 mr-1 mt-1 text-red-300 relative group"
+				v-for="(v, ii) in st.fileBase64"
+			>
+				<NImage :src="v" object-fit="cover" class="w-full h-full">
+					<template #placeholder>
+						<a
+							class="w-full h-full flex items-center justify-center text-neutral-500"
+							:href="v"
+							target="_blank"
+						>
+							<SvgIcon icon="mdi:download" />{{ $t("mj.attr1") }} {{ ii + 1 }}
+						</a>
+					</template>
+				</NImage>
+				<SvgIcon
+					icon="mdi:close"
+					class="hidden group-hover:block absolute top-[-5px] right-[-5px] rounded-full bg-red-300 text-white cursor-pointer"
+					@click="st.fileBase64.splice(st.fileBase64.indexOf(v), 1)"
+				></SvgIcon>
+			</div>
+		</div>
+		<div
+			class="myinputs"
+			:class="[!isMobile ? 'chat-footer' : '']"
+			@drop="drop"
+			@paste="paste"
+		>
+			<div class="top-bar" v-if="!isMobile">
+				<div class="left" v-if="st">
+					<div
+						v-if="homeStore.myData.local != 'draw'"
+						class="chage-model-select"
+						@click="st.isShow = true"
+					>
+						<template v-if="nGptStore.gpts">
+							<SvgIcon icon="ri:apps-fill" />
+							<span class="line-clamp-1 overflow-hidden">{{
+								nGptStore.gpts.name
+							}}</span>
+						</template>
+						<template v-else>
+							<SvgIcon icon="heroicons:sparkles" />
+							<span>{{
+								nGptStore.modelLabel ? nGptStore.modelLabel : "gpt-4o-mini"
+							}}</span>
+						</template>
+						<SvgIcon icon="icon-park-outline:right" />
+					</div>
+					<n-dropdown
+						trigger="hover"
+						:options="drOption"
+						@select="handleSelectASR"
+					>
+						<div class="relative; w-[22px]" style="margin: 0 25px">
+							<div
+								class="absolute bottom-[14px] left-[31px]"
+								v-if="st.micStart"
+							>
+								<span class="relative flex h-3 w-3">
+									<span
+										class="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-500 opacity-75"
+									></span>
+									<span
+										class="relative inline-flex rounded-full h-3 w-3 bg-red-400"
+									></span>
+								</span>
+							</div>
+							<IconSvg icon="voice" width="22px" height="22px"></IconSvg>
+						</div>
+					</n-dropdown>
+					<n-tooltip trigger="hover">
+						<template #trigger>
+							<SvgIcon
+								icon="line-md:uploading-loop"
+								class="absolute bottom-[10px] left-[8px] cursor-pointer"
+								v-if="st.isLoad == 1"
+							></SvgIcon>
+							<IconSvg
+								icon="upload"
+								@click="fsRef.click()"
+								v-else
+								width="22px"
+								height="22px"
+							></IconSvg>
+						</template>
+						<div
+							v-if="canVisionModel(gptConfigStore.myData.model)"
+							v-html="$t('mj.upPdf')"
+						></div>
+						<div v-else v-html="$t('mj.upImg')"></div>
+					</n-tooltip>
+					<IconSvg
+						@click="handleExport"
+						icon="screenshot"
+						width="22px"
+						height="22px"
+					></IconSvg>
+				</div>
+				<IconSvg
+					@click="handleClear"
+					class="right"
+					icon="clear"
+					width="28px"
+					height="22px"
+				></IconSvg>
+				<!-- <div @click="show = true">
             {{ $t('store.siderButton') }}
         </div> -->
-      </div>
-      <input
-        type="file"
-        id="fileInput"
-        @change="selectFile"
-        class="hidden"
-        ref="fsRef"
-        :accept="acceptData"
-      />
-      <div class="w-full relative">
-        <div class="absolute bottom-0 right-0 z-1" v-if="isMobile">
-          <NPopover trigger="hover">
-            <template #trigger>
-              <NTag
-                type="info"
-                round
-                size="small"
-                style="cursor: pointer"
-                :bordered="false"
-              >
-                <div class="opacity-60 flex">
-                  <SvgIcon icon="material-symbols:token-outline" />
-                  {{ $t("mj.remain") }}{{ myToken.remain }}/{{
-                    myToken.modelTokens
-                  }}
-                </div>
-              </NTag>
-            </template>
-            <div class="w-[300px]">
-              {{ $t("mj.tokenInfo1") }}
-              <p class="py-1" v-text="$t('mj.tokenInfo2')"></p>
-              <p class="text-right">
-                <NButton @click="st.isShow = true" type="info" size="small">{{
-                  $t("setting.setting")
-                }}</NButton>
-              </p>
-            </div>
-          </NPopover>
-        </div>
-      </div>
-      <NAutoComplete
-        v-model:value="mvalue"
-        :options="searchOptions"
-        :render-label="renderOption"
-        :class="[!isMobile ? 'chat-input' : '']"
-      >
-        <template #default="{ handleInput, handleBlur, handleFocus }">
-          <NInput
-            ref="inputRef"
-            v-model:value="mvalue"
-            type="textarea"
-            :placeholder="placeholder"
-            rows="3"
-            :autosize="{ minRows: 3, maxRows: 3 }"
-            @input="handleInput"
-            @focus="handleFocus"
-            @blur="handleBlur"
-            @keypress="handleEnter"
-          >
-            <template #prefix v-if="isMobile">
-              <div class="relative; w-[22px]">
-                <n-tooltip trigger="hover">
-                  <template #trigger>
-                    <SvgIcon
-                      icon="line-md:uploading-loop"
-                      class="absolute bottom-[10px] left-[8px] cursor-pointer"
-                      v-if="st.isLoad == 1"
-                    ></SvgIcon>
-                    <SvgIcon
-                      icon="ri:attachment-line"
-                      class="absolute bottom-[10px] left-[8px] cursor-pointer"
-                      @click="fsRef.click()"
-                      v-else
-                    ></SvgIcon>
-                  </template>
-                  <div
-                    v-if="canVisionModel(gptConfigStore.myData.model)"
-                    v-html="$t('mj.upPdf')"
-                  ></div>
-                  <div v-else v-html="$t('mj.upImg')"></div>
-                </n-tooltip>
-              </div>
-              <!-- <div  class=" relative; w-[22px]">
+			</div>
+			<input
+				type="file"
+				id="fileInput"
+				@change="selectFile"
+				class="hidden"
+				ref="fsRef"
+				:accept="acceptData"
+			/>
+			<div class="w-full relative">
+				<div class="absolute bottom-0 right-0 z-1" v-if="isMobile">
+					<NPopover trigger="hover">
+						<template #trigger>
+							<NTag
+								type="info"
+								round
+								size="small"
+								style="cursor: pointer"
+								:bordered="false"
+							>
+								<div class="opacity-60 flex">
+									<SvgIcon icon="material-symbols:token-outline" />
+									{{ $t("mj.remain") }}{{ myToken.remain }}/{{
+										myToken.modelTokens
+									}}
+								</div>
+							</NTag>
+						</template>
+						<div class="w-[300px]">
+							{{ $t("mj.tokenInfo1") }}
+							<p class="py-1" v-text="$t('mj.tokenInfo2')"></p>
+							<p class="text-right">
+								<NButton @click="st.isShow = true" type="info" size="small">{{
+									$t("setting.setting")
+								}}</NButton>
+							</p>
+						</div>
+					</NPopover>
+				</div>
+			</div>
+			<NAutoComplete
+				v-model:value="mvalue"
+				:options="searchOptions"
+				:render-label="renderOption"
+				:class="[!isMobile ? 'chat-input' : '']"
+			>
+				<template #default="{ handleInput, handleBlur, handleFocus }">
+					<NInput
+						ref="inputRef"
+						v-model:value="mvalue"
+						type="textarea"
+						:placeholder="placeholder"
+						rows="3"
+						:autosize="{ minRows: 3, maxRows: 3 }"
+						@input="handleInput"
+						@focus="handleFocus"
+						@blur="handleBlur"
+						@keypress="handleEnter"
+					>
+						<template #prefix v-if="isMobile">
+							<div class="relative; w-[22px]">
+								<n-tooltip trigger="hover">
+									<template #trigger>
+										<SvgIcon
+											icon="line-md:uploading-loop"
+											class="absolute bottom-[10px] left-[8px] cursor-pointer"
+											v-if="st.isLoad == 1"
+										></SvgIcon>
+										<SvgIcon
+											icon="ri:attachment-line"
+											class="absolute bottom-[10px] left-[8px] cursor-pointer"
+											@click="fsRef.click()"
+											v-else
+										></SvgIcon>
+									</template>
+									<div
+										v-if="canVisionModel(gptConfigStore.myData.model)"
+										v-html="$t('mj.upPdf')"
+									></div>
+									<div v-else v-html="$t('mj.upImg')"></div>
+								</n-tooltip>
+							</div>
+							<!-- <div  class=" relative; w-[22px]">
                     <SvgIcon icon="bi:mic"  class="absolute bottom-[10px] left-[30px] cursor-pointer" @click="st.showMic=true"></SvgIcon>
                 </div> -->
-              <n-dropdown
-                trigger="hover"
-                :options="drOption"
-                @select="handleSelectASR"
-              >
-                <div class="relative; w-[22px]">
-                  <div
-                    class="absolute bottom-[14px] left-[31px]"
-                    v-if="st.micStart"
-                  >
-                    <span class="relative flex h-3 w-3">
-                      <span
-                        class="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-500 opacity-75"
-                      ></span>
-                      <span
-                        class="relative inline-flex rounded-full h-3 w-3 bg-red-400"
-                      ></span>
-                    </span>
-                  </div>
-                  <!-- <SvgIcon icon="bi:mic"  class="absolute bottom-[10px] left-[55px] cursor-pointer" @click="goASR()"></SvgIcon> -->
-                  <SvgIcon
-                    icon="bi:mic"
-                    class="absolute bottom-[10px] left-[30px] cursor-pointer"
-                  ></SvgIcon>
-                </div>
-              </n-dropdown>
-            </template>
-            <template #suffix v-if="isMobile">
-              <div class="relative; w-[40px]">
-                <div class="absolute bottom-[-3px] right-[0px]">
-                  <NButton
-                    type="primary"
-                    :disabled="disabled || homeStore.myData.isLoader"
-                    @click="handleSubmit"
-                  >
-                    <template #icon>
-                      <span class="dark:text-black">
-                        <SvgIcon
-                          icon="ri:stop-circle-line"
-                          v-if="homeStore.myData.isLoader"
-                        />
-                        <SvgIcon icon="ri:send-plane-fill" v-else />
-                      </span>
-                    </template>
-                  </NButton>
-                </div>
-              </div>
-            </template>
-          </NInput>
-        </template>
-      </NAutoComplete>
-      <div class="send" @click="handleSubmit" v-if="!isMobile">
-        <IconSvg icon="send" width="16px" height="15px"></IconSvg>
-        |
-        <IconSvg icon="money" width="14px" height="24px"></IconSvg>
-        <NPopover trigger="hover">
-          <template #trigger>
-            {{ myToken.modelTokens }}
-          </template>
-          <div class="w-[300px]">
-            {{ $t("mj.tokenInfo1") }}
-            <p class="py-1" v-text="$t('mj.tokenInfo2')"></p>
-            <p class="text-right">
-              <NButton @click="st.isShow = true" type="info" size="small">{{
-                $t("setting.setting")
-              }}</NButton>
-            </p>
-          </div>
-        </NPopover>
-      </div>
-      <!-- translate-y-[-8px]       -->
-    </div>
-  </div>
+							<n-dropdown
+								trigger="hover"
+								:options="drOption"
+								@select="handleSelectASR"
+							>
+								<div class="relative; w-[22px]">
+									<div
+										class="absolute bottom-[14px] left-[31px]"
+										v-if="st.micStart"
+									>
+										<span class="relative flex h-3 w-3">
+											<span
+												class="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-500 opacity-75"
+											></span>
+											<span
+												class="relative inline-flex rounded-full h-3 w-3 bg-red-400"
+											></span>
+										</span>
+									</div>
+									<!-- <SvgIcon icon="bi:mic"  class="absolute bottom-[10px] left-[55px] cursor-pointer" @click="goASR()"></SvgIcon> -->
+									<SvgIcon
+										icon="bi:mic"
+										class="absolute bottom-[10px] left-[30px] cursor-pointer"
+									></SvgIcon>
+								</div>
+							</n-dropdown>
+						</template>
+						<template #suffix v-if="isMobile">
+							<div class="relative; w-[40px]">
+								<div class="absolute bottom-[-3px] right-[0px]">
+									<NButton
+										type="primary"
+										:disabled="disabled || homeStore.myData.isLoader"
+										@click="handleSubmit"
+									>
+										<template #icon>
+											<span class="dark:text-black">
+												<SvgIcon
+													icon="ri:stop-circle-line"
+													v-if="homeStore.myData.isLoader"
+												/>
+												<SvgIcon icon="ri:send-plane-fill" v-else />
+											</span>
+										</template>
+									</NButton>
+								</div>
+							</div>
+						</template>
+					</NInput>
+				</template>
+			</NAutoComplete>
+			<div class="send" @click="handleSubmit" v-if="!isMobile">
+				<IconSvg icon="send" width="16px" height="15px"></IconSvg>
+				|
+				<IconSvg icon="money" width="14px" height="24px"></IconSvg>
+				<NPopover trigger="hover">
+					<template #trigger>
+						{{ myToken.modelTokens }}
+					</template>
+					<div class="w-[300px]">
+						{{ $t("mj.tokenInfo1") }}
+						<p class="py-1" v-text="$t('mj.tokenInfo2')"></p>
+						<p class="text-right">
+							<NButton @click="st.isShow = true" type="info" size="small">{{
+								$t("setting.setting")
+							}}</NButton>
+						</p>
+					</div>
+				</NPopover>
+			</div>
+			<!-- translate-y-[-8px]       -->
+		</div>
+	</div>
 
-  <NModal
-    v-model:show="st.isShow"
-    preset="card"
-    :title="$t('mjchat.modelChange')"
-    class="!max-w-[620px]"
-    @close="st.isShow = false"
-  >
-    <aiModel @close="st.isShow = false" />
-  </NModal>
+	<NModal
+		v-model:show="st.isShow"
+		preset="card"
+		:title="$t('mjchat.modelChange')"
+		class="!max-w-[620px]"
+		@close="st.isShow = false"
+	>
+		<aiModel @close="st.isShow = false" />
+	</NModal>
 
-  <PromptStore v-model:visible="show"></PromptStore>
-  <!-- <n-drawer v-model:show="st.showMic" :width="420" :on-update:show="onShowFun">
+	<PromptStore v-model:visible="show"></PromptStore>
+	<!-- <n-drawer v-model:show="st.showMic" :width="420" :on-update:show="onShowFun">
     <n-drawer-content title="录音" closable>
         <AiMic />
     </n-drawer-content>
 </n-drawer> -->
 </template>
-<style    >
+<style>
 .myinputs .n-input .n-input-wrapper {
-  @apply items-stretch;
+	@apply items-stretch;
 }
 </style>