Quellcode durchsuchen

fix:优化选择知识库后聊天被遮

ageer vor 1 Monat
Ursprung
Commit
a9fee24ae9
3 geänderte Dateien mit 867 neuen und 857 gelöschten Zeilen
  1. 645 650
      src/views/chat/index.vue
  2. 25 10
      src/views/mj/aiGptInput.vue
  3. 197 197
      src/views/mj/aiModel.vue

+ 645 - 650
src/views/chat/index.vue

@@ -61,372 +61,372 @@ const isChat = computed(() => appStore.isChat)
 
 // 未知原因刷新页面,loading 状态不会重置,手动重置
 dataSources.value.forEach((item, index) => {
-  if (item.loading)
-    updateChatSome(+uuid, index, { loading: false })
+	if (item.loading)
+		updateChatSome(+uuid, index, { loading: false })
 })
 
 function handleSubmit() {
-  //onConversation() //把这个放到aiGpt
-  let message = prompt.value;
-  if (!message || message.trim() === '')
-    return
-   if (loading.value) return;
-   loading.value = true
-  homeStore.setMyData({act:'gpt.submit', actData:{ prompt:prompt.value, uuid } });
-   prompt.value='';
+	//onConversation() //把这个放到aiGpt
+	let message = prompt.value;
+	if (!message || message.trim() === '')
+		return
+	if (loading.value) return;
+	loading.value = true
+	homeStore.setMyData({act:'gpt.submit', actData:{ prompt:prompt.value, uuid } });
+	prompt.value='';
 }
 
 async function onConversation() {
-  let message = prompt.value
-
-  if (loading.value)
-    return
-
-  if (!message || message.trim() === '')
-    return
-
-  controller = new AbortController()
-
-  addChat(
-    +uuid,
-    {
-      dateTime: new Date().toLocaleString(),
-      text: message,
-      inversion: true,
-      error: false,
-      conversationOptions: null,
-      requestOptions: { prompt: message, options: null },
-    },
-  )
-  scrollToBottom()
-
-  loading.value = true
-  prompt.value = ''
-
-  let options: Chat.ConversationRequest = {}
-  const lastContext = conversationList.value[conversationList.value.length - 1]?.conversationOptions
-
-  if (lastContext && usingContext.value)
-    options = { ...lastContext }
-
-  addChat(
-    +uuid,
-    {
-      dateTime: new Date().toLocaleString(),
-      text: '思考中',
-      loading: true,
-      inversion: false,
-      error: false,
-      conversationOptions: null,
-      requestOptions: { prompt: message, options: { ...options } },
-    },
-  )
-  scrollToBottom()
-
-  try {
-    let lastText = ''
-    const fetchChatAPIOnce = async () => {
-      await fetchChatAPIProcess<Chat.ConversationResponse>({
-        prompt: message,
-        options,
-        signal: controller.signal,
-        onDownloadProgress: ({ event }) => {
-          const xhr = event.target
-          const { responseText } = xhr
-          // Always process the final line
-          const lastIndex = responseText.lastIndexOf('\n', responseText.length - 2)
-          let chunk = responseText
-          if (lastIndex !== -1)
-            chunk = responseText.substring(lastIndex)
-          try {
-            const data = JSON.parse(chunk)
-            updateChat(
-              +uuid,
-              dataSources.value.length - 1,
-              {
-                dateTime: new Date().toLocaleString(),
-                text: lastText + (data.text ?? ''),
-                inversion: false,
-                error: false,
-                loading: true,
-                conversationOptions: { conversationId: data.conversationId, parentMessageId: data.id },
-                requestOptions: { prompt: message, options: { ...options } },
-              },
-            )
-
-            if (openLongReply && data.detail.choices[0].finish_reason === 'length') {
-              options.parentMessageId = data.id
-              lastText = data.text
-              message = ''
-              return fetchChatAPIOnce()
-            }
-
-            scrollToBottomIfAtBottom()
-          }
-          catch (error) {
-            //
-          }
-        },
-      })
-      updateChatSome(+uuid, dataSources.value.length - 1, { loading: false })
-    }
-
-    await fetchChatAPIOnce()
-  }
-  catch (error: any) {
-    const errorMessage = error?.message ?? t('common.wrong')
-
-    if (error.message === 'canceled') {
-      updateChatSome(
-        +uuid,
-        dataSources.value.length - 1,
-        {
-          loading: false,
-        },
-      )
-      scrollToBottomIfAtBottom()
-      return
-    }
-
-    const currentChat = getChatByUuidAndIndex(+uuid, dataSources.value.length - 1)
-
-    if (currentChat?.text && currentChat.text !== '') {
-      updateChatSome(
-        +uuid,
-        dataSources.value.length - 1,
-        {
-          text: `${currentChat.text}\n[${errorMessage}]`,
-          error: false,
-          loading: false,
-        },
-      )
-      return
-    }
-
-    updateChat(
-      +uuid,
-      dataSources.value.length - 1,
-      {
-        dateTime: new Date().toLocaleString(),
-        text: errorMessage,
-        inversion: false,
-        error: true,
-        loading: false,
-        conversationOptions: null,
-        requestOptions: { prompt: message, options: { ...options } },
-      },
-    )
-    scrollToBottomIfAtBottom()
-  }
-  finally {
-    loading.value = false
-  }
+	let message = prompt.value
+
+	if (loading.value)
+		return
+
+	if (!message || message.trim() === '')
+		return
+
+	controller = new AbortController()
+
+	addChat(
+		+uuid,
+		{
+			dateTime: new Date().toLocaleString(),
+			text: message,
+			inversion: true,
+			error: false,
+			conversationOptions: null,
+			requestOptions: { prompt: message, options: null },
+		},
+	)
+	scrollToBottom()
+
+	loading.value = true
+	prompt.value = ''
+
+	let options: Chat.ConversationRequest = {}
+	const lastContext = conversationList.value[conversationList.value.length - 1]?.conversationOptions
+
+	if (lastContext && usingContext.value)
+		options = { ...lastContext }
+
+	addChat(
+		+uuid,
+		{
+			dateTime: new Date().toLocaleString(),
+			text: '思考中',
+			loading: true,
+			inversion: false,
+			error: false,
+			conversationOptions: null,
+			requestOptions: { prompt: message, options: { ...options } },
+		},
+	)
+	scrollToBottom()
+
+	try {
+		let lastText = ''
+		const fetchChatAPIOnce = async () => {
+			await fetchChatAPIProcess<Chat.ConversationResponse>({
+				prompt: message,
+				options,
+				signal: controller.signal,
+				onDownloadProgress: ({ event }) => {
+					const xhr = event.target
+					const { responseText } = xhr
+					// Always process the final line
+					const lastIndex = responseText.lastIndexOf('\n', responseText.length - 2)
+					let chunk = responseText
+					if (lastIndex !== -1)
+						chunk = responseText.substring(lastIndex)
+					try {
+						const data = JSON.parse(chunk)
+						updateChat(
+							+uuid,
+							dataSources.value.length - 1,
+							{
+								dateTime: new Date().toLocaleString(),
+								text: lastText + (data.text ?? ''),
+								inversion: false,
+								error: false,
+								loading: true,
+								conversationOptions: { conversationId: data.conversationId, parentMessageId: data.id },
+								requestOptions: { prompt: message, options: { ...options } },
+							},
+						)
+
+						if (openLongReply && data.detail.choices[0].finish_reason === 'length') {
+							options.parentMessageId = data.id
+							lastText = data.text
+							message = ''
+							return fetchChatAPIOnce()
+						}
+
+						scrollToBottomIfAtBottom()
+					}
+					catch (error) {
+						//
+					}
+				},
+			})
+			updateChatSome(+uuid, dataSources.value.length - 1, { loading: false })
+		}
+
+		await fetchChatAPIOnce()
+	}
+	catch (error: any) {
+		const errorMessage = error?.message ?? t('common.wrong')
+
+		if (error.message === 'canceled') {
+			updateChatSome(
+				+uuid,
+				dataSources.value.length - 1,
+				{
+					loading: false,
+				},
+			)
+			scrollToBottomIfAtBottom()
+			return
+		}
+
+		const currentChat = getChatByUuidAndIndex(+uuid, dataSources.value.length - 1)
+
+		if (currentChat?.text && currentChat.text !== '') {
+			updateChatSome(
+				+uuid,
+				dataSources.value.length - 1,
+				{
+					text: `${currentChat.text}\n[${errorMessage}]`,
+					error: false,
+					loading: false,
+				},
+			)
+			return
+		}
+
+		updateChat(
+			+uuid,
+			dataSources.value.length - 1,
+			{
+				dateTime: new Date().toLocaleString(),
+				text: errorMessage,
+				inversion: false,
+				error: true,
+				loading: false,
+				conversationOptions: null,
+				requestOptions: { prompt: message, options: { ...options } },
+			},
+		)
+		scrollToBottomIfAtBottom()
+	}
+	finally {
+		loading.value = false
+	}
 }
 
 async function onRegenerate(index: number) {
-  if (loading.value)
-    return
-
-  controller = new AbortController()
-
-  const { requestOptions } = dataSources.value[index]
-
-  let message = requestOptions?.prompt ?? ''
-
-  let options: Chat.ConversationRequest = {}
-
-  if (requestOptions.options)
-    options = { ...requestOptions.options }
-
-  loading.value = true
-
-  updateChat(
-    +uuid,
-    index,
-    {
-      dateTime: new Date().toLocaleString(),
-      text: '',
-      inversion: false,
-      error: false,
-      loading: true,
-      conversationOptions: null,
-      requestOptions: { prompt: message, options: { ...options } },
-    },
-  )
-
-  try {
-    let lastText = ''
-    const fetchChatAPIOnce = async () => {
-      await fetchChatAPIProcess<Chat.ConversationResponse>({
-        prompt: message,
-        options,
-        signal: controller.signal,
-        onDownloadProgress: ({ event }) => {
-          const xhr = event.target
-          const { responseText } = xhr
-          // Always process the final line
-          const lastIndex = responseText.lastIndexOf('\n', responseText.length - 2)
-          let chunk = responseText
-          if (lastIndex !== -1)
-            chunk = responseText.substring(lastIndex)
-          try {
-            const data = JSON.parse(chunk)
-            updateChat(
-              +uuid,
-              index,
-              {
-                dateTime: new Date().toLocaleString(),
-                text: lastText + (data.text ?? ''),
-                inversion: false,
-                error: false,
-                loading: true,
-                conversationOptions: { conversationId: data.conversationId, parentMessageId: data.id },
-                requestOptions: { prompt: message, options: { ...options } },
-              },
-            )
-
-            if (openLongReply && data.detail.choices[0].finish_reason === 'length') {
-              options.parentMessageId = data.id
-              lastText = data.text
-              message = ''
-              return fetchChatAPIOnce()
-            }
-          }
-          catch (error) {
-            //
-          }
-        },
-      })
-      updateChatSome(+uuid, index, { loading: false })
-    }
-    await fetchChatAPIOnce()
-  }
-  catch (error: any) {
-    if (error.message === 'canceled') {
-      updateChatSome(
-        +uuid,
-        index,
-        {
-          loading: false,
-        },
-      )
-      return
-    }
-
-    const errorMessage = error?.message ?? t('common.wrong')
-
-    updateChat(
-      +uuid,
-      index,
-      {
-        dateTime: new Date().toLocaleString(),
-        text: errorMessage,
-        inversion: false,
-        error: true,
-        loading: false,
-        conversationOptions: null,
-        requestOptions: { prompt: message, options: { ...options } },
-      },
-    )
-  }
-  finally {
-    loading.value = false
-  }
+	if (loading.value)
+		return
+
+	controller = new AbortController()
+
+	const { requestOptions } = dataSources.value[index]
+
+	let message = requestOptions?.prompt ?? ''
+
+	let options: Chat.ConversationRequest = {}
+
+	if (requestOptions.options)
+		options = { ...requestOptions.options }
+
+	loading.value = true
+
+	updateChat(
+		+uuid,
+		index,
+		{
+			dateTime: new Date().toLocaleString(),
+			text: '',
+			inversion: false,
+			error: false,
+			loading: true,
+			conversationOptions: null,
+			requestOptions: { prompt: message, options: { ...options } },
+		},
+	)
+
+	try {
+		let lastText = ''
+		const fetchChatAPIOnce = async () => {
+			await fetchChatAPIProcess<Chat.ConversationResponse>({
+				prompt: message,
+				options,
+				signal: controller.signal,
+				onDownloadProgress: ({ event }) => {
+					const xhr = event.target
+					const { responseText } = xhr
+					// Always process the final line
+					const lastIndex = responseText.lastIndexOf('\n', responseText.length - 2)
+					let chunk = responseText
+					if (lastIndex !== -1)
+						chunk = responseText.substring(lastIndex)
+					try {
+						const data = JSON.parse(chunk)
+						updateChat(
+							+uuid,
+							index,
+							{
+								dateTime: new Date().toLocaleString(),
+								text: lastText + (data.text ?? ''),
+								inversion: false,
+								error: false,
+								loading: true,
+								conversationOptions: { conversationId: data.conversationId, parentMessageId: data.id },
+								requestOptions: { prompt: message, options: { ...options } },
+							},
+						)
+
+						if (openLongReply && data.detail.choices[0].finish_reason === 'length') {
+							options.parentMessageId = data.id
+							lastText = data.text
+							message = ''
+							return fetchChatAPIOnce()
+						}
+					}
+					catch (error) {
+						//
+					}
+				},
+			})
+			updateChatSome(+uuid, index, { loading: false })
+		}
+		await fetchChatAPIOnce()
+	}
+	catch (error: any) {
+		if (error.message === 'canceled') {
+			updateChatSome(
+				+uuid,
+				index,
+				{
+					loading: false,
+				},
+			)
+			return
+		}
+
+		const errorMessage = error?.message ?? t('common.wrong')
+
+		updateChat(
+			+uuid,
+			index,
+			{
+				dateTime: new Date().toLocaleString(),
+				text: errorMessage,
+				inversion: false,
+				error: true,
+				loading: false,
+				conversationOptions: null,
+				requestOptions: { prompt: message, options: { ...options } },
+			},
+		)
+	}
+	finally {
+		loading.value = false
+	}
 }
 
 function handleExport() {
-  if (loading.value)
-    return
-
-  const d = dialog.warning({
-    title: t('chat.exportImage'),
-    content: t('chat.exportImageConfirm'),
-    positiveText: t('common.yes'),
-    negativeText: t('common.no'),
-    onPositiveClick: async () => {
-      try {
-        d.loading = true
-        const ele = document.getElementById('image-wrapper')
-        const canvas = await html2canvas(ele as HTMLDivElement, {
-          useCORS: true,
-        })
-        const imgUrl = canvas.toDataURL('image/png')
-        const tempLink = document.createElement('a')
-        tempLink.style.display = 'none'
-        tempLink.href = imgUrl
-        tempLink.setAttribute('download', 'chat-shot.png')
-        if (typeof tempLink.download === 'undefined')
-          tempLink.setAttribute('target', '_blank')
-
-        document.body.appendChild(tempLink)
-        tempLink.click()
-        document.body.removeChild(tempLink)
-        window.URL.revokeObjectURL(imgUrl)
-        d.loading = false
-        ms.success(t('chat.exportSuccess'))
-        Promise.resolve()
-      }
-      catch (error: any) {
-        ms.error(t('chat.exportFailed'))
-      }
-      finally {
-        d.loading = false
-      }
-    },
-  })
+	if (loading.value)
+		return
+
+	const d = dialog.warning({
+		title: t('chat.exportImage'),
+		content: t('chat.exportImageConfirm'),
+		positiveText: t('common.yes'),
+		negativeText: t('common.no'),
+		onPositiveClick: async () => {
+			try {
+				d.loading = true
+				const ele = document.getElementById('image-wrapper')
+				const canvas = await html2canvas(ele as HTMLDivElement, {
+					useCORS: true,
+				})
+				const imgUrl = canvas.toDataURL('image/png')
+				const tempLink = document.createElement('a')
+				tempLink.style.display = 'none'
+				tempLink.href = imgUrl
+				tempLink.setAttribute('download', 'chat-shot.png')
+				if (typeof tempLink.download === 'undefined')
+					tempLink.setAttribute('target', '_blank')
+
+				document.body.appendChild(tempLink)
+				tempLink.click()
+				document.body.removeChild(tempLink)
+				window.URL.revokeObjectURL(imgUrl)
+				d.loading = false
+				ms.success(t('chat.exportSuccess'))
+				Promise.resolve()
+			}
+			catch (error: any) {
+				ms.error(t('chat.exportFailed'))
+			}
+			finally {
+				d.loading = false
+			}
+		},
+	})
 }
 
 function handleDelete(index: number) {
-  if (loading.value)
-    return
-
-  dialog.warning({
-    title: t('chat.deleteMessage'),
-    content: t('chat.deleteMessageConfirm'),
-    positiveText: t('common.yes'),
-    negativeText: t('common.no'),
-    onPositiveClick: () => {
-      chatStore.deleteChatByUuid(+uuid, index)
-    },
-  })
+	if (loading.value)
+		return
+
+	dialog.warning({
+		title: t('chat.deleteMessage'),
+		content: t('chat.deleteMessageConfirm'),
+		positiveText: t('common.yes'),
+		negativeText: t('common.no'),
+		onPositiveClick: () => {
+			chatStore.deleteChatByUuid(+uuid, index)
+		},
+	})
 }
 
 function handleClear() {
-  if (loading.value)
-    return
-
-  dialog.warning({
-    title: t('chat.clearChat'),
-    content: t('chat.clearChatConfirm'),
-    positiveText: t('common.yes'),
-    negativeText: t('common.no'),
-    onPositiveClick: () => {
-      chatStore.clearChatByUuid(+uuid)
-    },
-  })
+	if (loading.value)
+		return
+
+	dialog.warning({
+		title: t('chat.clearChat'),
+		content: t('chat.clearChatConfirm'),
+		positiveText: t('common.yes'),
+		negativeText: t('common.no'),
+		onPositiveClick: () => {
+			chatStore.clearChatByUuid(+uuid)
+		},
+	})
 }
 
 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()
+		}
+	}
 }
 
 function handleStop() {
-  if (loading.value) {
-    homeStore.setMyData({act:'abort'});
-    controller.abort()
-    loading.value = false
-  }
+	if (loading.value) {
+		homeStore.setMyData({act:'abort'});
+		controller.abort()
+		loading.value = false
+	}
 }
 
 
@@ -436,122 +436,122 @@ function handleStop() {
 // 搜索选项计算,这里使用value作为索引项,所以当出现重复value时渲染异常(多项同时出现选中效果)
 // 理想状态下其实应该是key作为索引项,但官方的renderOption会出现问题,所以就需要value反renderLabel实现
 const searchOptions = computed(() => {
-  if (prompt.value.startsWith('/')) {
-    const abc= promptTemplate.value.filter((item: { key: string }) => item.key.toLowerCase().includes(prompt.value.substring(1).toLowerCase())).map((obj: { value: any }) => {
-      return {
-        label: obj.value,
-        value: obj.value,
-      }
-    })
-    mlog('搜索选项', abc);
-    return abc;
-  }else if(prompt.value=='@'){
-    const abc=  gptsUlistStore.myData.slice(0,10).map( (v:gptsType) => {
-      return {
-        label:v.info,
-        gpts:v,
-        value:v.gid
-      }
-    })
-   return abc ;
-  }else {
-    return []
-  }
+	if (prompt.value.startsWith('/')) {
+		const abc= promptTemplate.value.filter((item: { key: string }) => item.key.toLowerCase().includes(prompt.value.substring(1).toLowerCase())).map((obj: { value: any }) => {
+			return {
+				label: obj.value,
+				value: obj.value,
+			}
+		})
+		mlog('搜索选项', abc);
+		return abc;
+	}else if(prompt.value=='@'){
+		const abc=  gptsUlistStore.myData.slice(0,10).map( (v:gptsType) => {
+			return {
+				label:v.info,
+				gpts:v,
+				value:v.gid
+			}
+		})
+		return abc ;
+	}else {
+		return []
+	}
 })
 
 const goUseGpts= async ( item: gptsType)=>{
-    const saveObj= {model:  `${ item.gid }`   ,gpts:item}
-    gptConfigStore.setMyData(saveObj);
-    if(chatStore.active){ //保存到对话框
-        const chatSet = new chatSetting( chatStore.active );
-        if( chatSet.findIndex()>-1 ) chatSet.save( saveObj )
-    }
-    ms.success(t('mjchat.success2'));
-    // const gptUrl= `https://gpts.ddaiai.com/open/gptsapi/use`;
-    // myFetch(gptUrl,item );
+	const saveObj= {model:  `${ item.gid }`   ,gpts:item}
+	gptConfigStore.setMyData(saveObj);
+	if(chatStore.active){ //保存到对话框
+		const chatSet = new chatSetting( chatStore.active );
+		if( chatSet.findIndex()>-1 ) chatSet.save( saveObj )
+	}
+	ms.success(t('mjchat.success2'));
+	// const gptUrl= `https://gpts.ddaiai.com/open/gptsapi/use`;
+	// myFetch(gptUrl,item );
 
-    mlog('go local ', homeStore.myData.local );
-    if(homeStore.myData.local!=='Chat') router.replace({name:'Chat',params:{uuid:chatStore.active}});
+	mlog('go local ', homeStore.myData.local );
+	if(homeStore.myData.local!=='Chat') router.replace({name:'Chat',params:{uuid:chatStore.active}});
 
-    gptsUlistStore.setMyData( item );
+	gptsUlistStore.setMyData( item );
 
 }
 
 // value反渲染key
 const renderOption = (option: { label: string,gpts?:gptsType }) => {
-  if( prompt.value=='@'){
-    //return [ h( NAvatar,{src:'https://cos.aitutu.cc/gpts/gpt4all.jpg',size:"small",round:true}),option.label ]
-    return [h("div",{class:'flex justify-start items-center'
-    , onclick:()=>{
-      if(option.gpts)   goUseGpts(option.gpts) ;
-      prompt.value='';
-      setTimeout(() =>  prompt.value='', 80);
-    }}
-    ,[h(NAvatar,{src:option.gpts?.logo, "fallback-src" : 'https://cos.aitutu.cc/gpts/3.5net.png',size:"small",round:true, class:"w-8 h-8"})
-    , h('span', { class: 'pl-1' }, option.gpts?.name  )
-    , h('span', { class: 'line-clamp-1 flex-1 pl-1 opacity-50' }, option.label  )
-    ])]
-  }
-  for (const i of promptTemplate.value) {
-    if (i.value === option.label)
-      return [i.key]
-  }
-  return []
+	if( prompt.value=='@'){
+		//return [ h( NAvatar,{src:'https://cos.aitutu.cc/gpts/gpt4all.jpg',size:"small",round:true}),option.label ]
+		return [h("div",{class:'flex justify-start items-center'
+				, onclick:()=>{
+					if(option.gpts)   goUseGpts(option.gpts) ;
+					prompt.value='';
+					setTimeout(() =>  prompt.value='', 80);
+				}}
+			,[h(NAvatar,{src:option.gpts?.logo, "fallback-src" : 'https://cos.aitutu.cc/gpts/3.5net.png',size:"small",round:true, class:"w-8 h-8"})
+				, h('span', { class: 'pl-1' }, option.gpts?.name  )
+				, h('span', { class: 'line-clamp-1 flex-1 pl-1 opacity-50' }, option.label  )
+			])]
+	}
+	for (const i of promptTemplate.value) {
+		if (i.value === option.label)
+			return [i.key]
+	}
+	return []
 }
 
 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 buttonDisabled = computed(() => {
-  return loading.value || !prompt.value || prompt.value.trim() === ''
+	return loading.value || !prompt.value || prompt.value.trim() === ''
 })
 
 const footerClass = computed(() => {
-  let classes = ['p-4']
-  if (isMobile.value)
-    classes = ['sticky', 'left-0', 'bottom-0', 'right-0', 'p-2', 'pr-3'] //, 'overflow-hidden'
-  return classes
+	let classes = ['p-4']
+	if (isMobile.value)
+		classes = ['sticky', 'left-0', 'bottom-0', 'right-0', 'p-2', 'pr-3'] //, 'overflow-hidden'
+	return classes
 })
 
 
 onMounted(() => {
-  scrollToBottom()
-  if (inputRef.value && !isMobile.value)
-    inputRef.value?.focus()
-  // 查询公告信息
-  selectNotice()
-  // 查询通知信息
-  selectInform()
+	scrollToBottom()
+	if (inputRef.value && !isMobile.value)
+		inputRef.value?.focus()
+	// 查询公告信息
+	selectNotice()
+	// 查询通知信息
+	selectInform()
 })
 
 onUnmounted(() => {
 
-  if (loading.value)   controller.abort()
-  homeStore.setMyData({isLoader:false});
+	if (loading.value)   controller.abort()
+	homeStore.setMyData({isLoader:false});
 })
 
 const local= computed(()=>homeStore.myData.local );
 watch(()=>homeStore.myData.act,(n)=>{
-    if(n=='draw')  scrollToBottom();
-    if(n=='scrollToBottom') scrollToBottom();
-    if(n=='scrollToBottomIfAtBottom') scrollToBottomIfAtBottom();
-    if(n=='gpt.submit' || n=='gpt.resubmit'){ loading.value = true;}
-    if(n=='stopLoading'){ loading.value = false;}
+	if(n=='draw')  scrollToBottom();
+	if(n=='scrollToBottom') scrollToBottom();
+	if(n=='scrollToBottomIfAtBottom') scrollToBottomIfAtBottom();
+	if(n=='gpt.submit' || n=='gpt.resubmit'){ loading.value = true;}
+	if(n=='stopLoading'){ loading.value = false;}
 });
 const st =ref({inputme:true});
 
 watch( ()=>loading.value ,(n)=> homeStore.setMyData({isLoader:n }))
 
 const ychat = computed( ()=>{
-  let text= prompt.value
-  if (loading.value) text= "";
-  else {
-    scrollToBottomIfAtBottom();
-  }
-  return { text, dateTime: t('chat.preview')} as Chat.Chat;
+	let text= prompt.value
+	if (loading.value) text= "";
+	else {
+		scrollToBottomIfAtBottom();
+	}
+	return { text, dateTime: t('chat.preview')} as Chat.Chat;
 })
 
 const showModal = ref(false);
@@ -559,259 +559,254 @@ const modalContent = ref('<h2>暂无内容</h2>');
 const informContent = ref([]);
 const noticeId = ref('');
 async function selectNotice() {
-  const [err, result] = await to(getNotice());
-  console.log("result?.data",result?.data)
-  if (result?.data) {
-    showModal.value = true
-    noticeId.value = result.data.noticeId
-    modalContent.value = result.data.noticeContent
-  }
+	const [err, result] = await to(getNotice());
+	console.log("result?.data",result?.data)
+	if (result?.data) {
+		showModal.value = true
+		noticeId.value = result.data.noticeId
+		modalContent.value = result.data.noticeContent
+	}
 }
 async function selectInform() {
-  const [err, result] = await to(getInform());
-  if (result?.rows) {
-    informContent.value = result.rows.length ? result.rows : []
-  }
+	const [err, result] = await to(getInform());
+	if (result?.rows) {
+		informContent.value = result.rows.length ? result.rows : []
+	}
 }
 
 async function handleClose(){
-  await to(readNotice(noticeId.value));
+	await to(readNotice(noticeId.value));
 }
 const gptsList = ref<gptsType[]>([]);
 const gptsFilterList = ref<gptsType[]>([]);
 const getRandowNum = (Min:number, Max: number):number =>{
-  const Range = Max - Min + 1
-  const Rand = Math.random()
-  return Min + Math.floor(Rand * Range)
+	const Range = Max - Min + 1
+	const Rand = Math.random()
+	return Min + Math.floor(Rand * Range)
 }
 const load= async ()=>{
 
-    // const gptUrl= homeStore.myData.session.gptUrl?  homeStore.myData.session.gptUrl :'';
-    // mlog('load',gptUrl );
-    //  let d;
-    // if( homeStore.myData.session.gptUrl ){
-    //    d = await my2Fetch( homeStore.myData.session.gptUrl  );
-    // }else {
-
-    //     d = await myFetch('https://gpts.ddaiai.com/open/gpts');
-    // }
-
-    const params = { pageNum: 1, pageSize: 20 };
-        const [err, result] = await to(getGpts(params));
-        if(err){
-            console.log("err===",err)
-        }else{
-            gptsList.value = result.rows as unknown as gptsType[];
-        }
-
-
-     // gptsList.value = d.gpts as gptsType[];
-    if(gptsList.value.length && gptsList.value.length > 3) {
-      gptsFilterList.value = gptsList.value.slice(0, 4)
-    }
+	// const gptUrl= homeStore.myData.session.gptUrl?  homeStore.myData.session.gptUrl :'';
+	// mlog('load',gptUrl );
+	//  let d;
+	// if( homeStore.myData.session.gptUrl ){
+	//    d = await my2Fetch( homeStore.myData.session.gptUrl  );
+	// }else {
+
+	//     d = await myFetch('https://gpts.ddaiai.com/open/gpts');
+	// }
+
+	const params = { pageNum: 1, pageSize: 20 };
+	const [err, result] = await to(getGpts(params));
+	if(err){
+		console.log("err===",err)
+	}else{
+		gptsList.value = result.rows as unknown as gptsType[];
+	}
+
+
+	// gptsList.value = d.gpts as gptsType[];
+	if(gptsList.value.length && gptsList.value.length > 3) {
+		gptsFilterList.value = gptsList.value.slice(0, 4)
+	}
 }
 const refresh = () => {
-  gptsFilterList.value = []
-  let num = gptsList.value[getRandowNum(0, gptsList.value.length - 1)]
-  let num1 = gptsList.value[getRandowNum(0, gptsList.value.length - 1)]
-  let num2 = gptsList.value[getRandowNum(0, gptsList.value.length - 1)]
-  let num3 = gptsList.value[getRandowNum(0, gptsList.value.length - 1)]
-  let arr = [num, num1, num2, num3]
-  if(Array.from(new Set(arr)).length != 4) {
-    refresh()
-    return
-  }
-  gptsFilterList.value = [num, num1, num2, num3]
+	gptsFilterList.value = []
+	let num = gptsList.value[getRandowNum(0, gptsList.value.length - 1)]
+	let num1 = gptsList.value[getRandowNum(0, gptsList.value.length - 1)]
+	let num2 = gptsList.value[getRandowNum(0, gptsList.value.length - 1)]
+	let num3 = gptsList.value[getRandowNum(0, gptsList.value.length - 1)]
+	let arr = [num, num1, num2, num3]
+	if(Array.from(new Set(arr)).length != 4) {
+		refresh()
+		return
+	}
+	gptsFilterList.value = [num, num1, num2, num3]
 }
 load()
 
 </script>
 
 <template>
-  <NModal
-  v-model:show="showModal"
-   closable @on-after-leave=""
-   :mask-closable="false"
-    preset="dialog"
-    title="公告详情"
-    positive-text="我已知晓"
-    @positive-click="handleClose"
-   >
-   <div v-html="modalContent"></div>
-  </NModal>
-
-  <div class="flex flex-col w-full h-full chat-content" :class="[isMobile? '' : 'chat-content-noMobile']">
-   <!-- v-if="isMobile" -->
-    <!-- <HeaderComponent
-      :haveData="!!dataSources.length"
-      :using-context="usingContext"
-      @export="handleExport"
-      @handle-clear="handleClear"
-    /> -->
-
-    <main class="flex-1 overflow-hidden">
-
-      <template v-if="gptConfigStore.myData.kid">
-        <div class="flex  mt-4  text-neutral-300 chat-header">
-           <SvgIcon icon="material-symbols:book" class="mr-1 text-2xl" ></SvgIcon>
-           <span>{{ gptConfigStore.myData.kName }}</span>
-        </div>
-      </template>
-
-      <div id="scrollRef" ref="scrollRef" class="h-full overflow-hidden overflow-y-auto">
-
-        <div
-          id="image-wrapper"
-          class="w-full max-w-screen-xl m-auto dark:bg-[#101014]"
-          :class="[isMobile ? 'p-2' : 'p-4']"
-        >
-          <template v-if="!dataSources.length">
-            <div v-if="homeStore.myData.session.notify" v-html="homeStore.myData.session.notify" class="text-neutral-300 mt-4">
-
-            </div>
-            <div class="gpts-box" v-else>
-              <h1>{{ href }}</h1>
-              <div class="annou" v-if="informContent.length" :style="{'margin-bottom': isMobile ? '15px' : '30px'}">
-                <div class="ai-icon">
-                  <IconSvg icon="chatGPT" :width="isMobile ? '32px' : '64px'" :height="isMobile ? '32px' : '64px'"></IconSvg>
-                </div>
-                <div class="text" :style="{padding: isMobile? '22px 10px' : '22px 68px'}">
-                  <p class="title">{{ t('chat.annouce') }}</p>
-                  <!-- <p v-for="(item,index) in t('chat.annouceContent').split(';')" :key="index">{{ item }}</p> -->
-                  <div v-for="(item, index) in informContent.slice(0, 1)" :key="index" >
-                    <!-- <p style="margin-top: 10px; font-size: 18px">{{ item.noticeTitle }}</p> -->
-                    <div v-html="item.noticeContent"></div>
-                  </div>
-                </div>
-              </div>
-              <div class="help" v-if="gptsFilterList && gptsFilterList.length">
-                <div class="ai-icon">
-                  <IconSvg icon="chatGPT" :width="isMobile ? '32px' : '64px'" :height="isMobile ? '32px' : '64px'"></IconSvg>
-                </div>
-                <div class="text" :style="{padding: isMobile? '22px 10px' : '22px 68px', 'font-size': isMobile? '14px' : '16px', 'line-height': isMobile? '20px' : '28px'}">
-                  <p class="title">
-                    {{ t('chat.helpTitle') }}
-                  </p>
-                  <p v-for="(item,index) in t('chat.helpcontent').split(';')" :key="index">{{ item }}</p>
-                  <div class="gpts-list">
-                    <div class="refresh" @click="refresh">
-                      <IconSvg icon="refresh"></IconSvg>&nbsp;{{ t('chat.refresh') }}
-                    </div>
-                    <div v-for="v in gptsFilterList" :key="v.name" class="gpts-item" :style="{width: isMobile ? '100%' : 'calc(50% - 20px)', marginRight: '20px', padding: isMobile ? '5px 8px' : '14px 10px', 'margin-bottom': isMobile ? '8px' : '20px'}">
-                      <NImage :src="v.logo" :preview-disabled="true" lazy
-                      class="group-hover:scale-[130%] duration-300 shrink-0 overflow-hidden bg-base object-cover rounded-full bc-avatar w-[80px] h-[80px]" :style="{width: isMobile ? '23px' : '46px', height: isMobile ? '23px' : '46px'}">
-                          <template #placeholder>
-                            <div class="w-full h-full justify-center items-center flex"  >
-                            <SvgIcon icon="line-md:downloading-loop" class="text-[60px] text-green-300"   ></SvgIcon>
-                            </div>
-                          </template>
-                      </NImage>
-                      <div :style="{width: `calc(100% - ${isMobile ? '43px' : '66px'})`, float: 'left', marginLeft: '10px'}">
-                        <p class="info" :title="v.info"> {{ v.info }}</p>
-                        <p @click="goUseGpts(v)" class="name"> {{ t('chat.used') }} {{ v.name }}</p>
-                      </div>
-                    </div>
-                  </div>
-                </div>
-              </div>
-            </div>
-            <!-- <div class="flex items-center justify-center mt-4 text-center text-neutral-300" v-else>
-              <SvgIcon icon="ri:bubble-chart-fill" class="mr-2 text-3xl" />
-              <span>Aha~</span>
-            </div> -->
-          </template>
-
-          <template v-else>
-            <div>
-              <Message
-                v-for="(item, index) of dataSources"
-                :key="index"
-                :date-time="item.dateTime"
-                :text="item.text"
-                :inversion="item.inversion"
-                :error="item.error"
-                :loading="item.loading"
-                @regenerate="onRegenerate(index)"
-                @delete="handleDelete(index)"
-                :chat="item"
-                :index="index"
-              />
-              <Message  v-if="ychat.text && !homeStore.myData.session.isCloseMdPreview"
-              :key="dataSources.length" :inversion="true"
-              :date-time="$t('mj.typing')"
-              :chat="ychat"
-              :text="ychat.text"
-              :index="dataSources.length"
-              />
-              <div class="sticky bottom-0 left-0 flex justify-center">
-                <NButton v-if="loading" type="warning" @click="handleStop">
-                  <template #icon>
-                    <SvgIcon icon="ri:stop-circle-line" />
-                  </template>
-                  {{ t('common.stopResponding') }}
-                </NButton>
-              </div>
-            </div>
-          </template>
-        </div>
-      </div>
-    </main>
-    
-    <footer :class="footerClass" class="footer-content" v-if="local!=='draw'">
-      <div class="w-full max-w-screen-xl m-auto">
-        <aiGptInput @handle-clear="handleClear" @export="handleExport" v-if="['gpt-4o-mini','gpt-3.5-turbo-16k'].indexOf(gptConfigStore.myData.model)>-1 || st.inputme "
-         v-model:modelValue="prompt" :disabled="buttonDisabled"
-         :searchOptions="searchOptions"  :renderOption="renderOption"
-          />
-        <div class="flex items-center justify-between space-x-2" v-else>
-          <!--
-          <HoverButton v-if="!isMobile" @click="handleClear">
-            <span class="text-xl text-[#4f555e] dark:text-white">
-              <SvgIcon icon="ri:delete-bin-line" />
-            </span>
-          </HoverButton>
-          <HoverButton v-if="!isMobile" @click="handleExport">
-            <span class="text-xl text-[#4f555e] dark:text-white">
-              <SvgIcon icon="ri:download-2-line" />
-            </span>
-          </HoverButton>
-          <HoverButton @click="toggleUsingContext">
-            <span class="text-xl" :class="{ 'text-[#4b9e5f]': usingContext, 'text-[#a8071a]': !usingContext }">
-              <SvgIcon icon="ri:chat-history-line" />
-            </span>
-          </HoverButton>
-          -->
-
-          <NAutoComplete v-model:value="prompt" :options="searchOptions" :render-label="renderOption">
-            <template #default="{ handleInput, handleBlur, handleFocus }">
-              <NInput
-                ref="inputRef"
-                v-model:value="prompt"
-                type="textarea"
-                :placeholder="placeholder"
-                :autosize="{ minRows: 1, maxRows: isMobile ? 4 : 8 }"
-                @input="handleInput"
-                @focus="handleFocus"
-                @blur="handleBlur"
-                @keypress="handleEnter"
-              />
-            </template>
-          </NAutoComplete>
-          <NButton type="primary" :disabled="buttonDisabled" @click="handleSubmit">
-            <template #icon>
+	<NModal
+		v-model:show="showModal"
+		closable @on-after-leave=""
+		:mask-closable="false"
+		preset="dialog"
+		title="公告详情"
+		positive-text="我已知晓"
+		@positive-click="handleClose"
+	>
+		<div v-html="modalContent"></div>
+	</NModal>
+
+	<div class="flex flex-col w-full h-full chat-content" :class="[isMobile? '' : 'chat-content-noMobile']">
+		<!-- v-if="isMobile" -->
+		<!-- <HeaderComponent
+			:haveData="!!dataSources.length"
+			:using-context="usingContext"
+			@export="handleExport"
+			@handle-clear="handleClear"
+		/> -->
+
+		<main class="flex-1 overflow-hidden">
+
+			<div id="scrollRef" ref="scrollRef" class="h-full overflow-hidden overflow-y-auto">
+
+				<div
+					id="image-wrapper"
+					class="w-full max-w-screen-xl m-auto dark:bg-[#101014]"
+					:class="[isMobile ? 'p-2' : 'p-4']"
+				>
+					<template v-if="!dataSources.length">
+						<div v-if="homeStore.myData.session.notify" v-html="homeStore.myData.session.notify" class="text-neutral-300 mt-4">
+
+						</div>
+						<div class="gpts-box" v-else>
+							<br>
+							<br>
+<!--							<h1>{{ href }}</h1>-->
+							<div class="annou" v-if="informContent.length" :style="{'margin-bottom': isMobile ? '15px' : '30px'}">
+								<div class="ai-icon">
+									<IconSvg icon="chatGPT" :width="isMobile ? '32px' : '64px'" :height="isMobile ? '32px' : '64px'"></IconSvg>
+								</div>
+								<div class="text" :style="{padding: isMobile? '22px 10px' : '22px 68px'}">
+									<p class="title">{{ t('chat.annouce') }}</p>
+									<!-- <p v-for="(item,index) in t('chat.annouceContent').split(';')" :key="index">{{ item }}</p> -->
+									<div v-for="(item, index) in informContent.slice(0, 1)" :key="index" >
+										<!-- <p style="margin-top: 10px; font-size: 18px">{{ item.noticeTitle }}</p> -->
+										<div v-html="item.noticeContent"></div>
+									</div>
+								</div>
+							</div>
+							<div class="help" v-if="gptsFilterList && gptsFilterList.length">
+								<div class="ai-icon">
+									<IconSvg icon="chatGPT" :width="isMobile ? '32px' : '64px'" :height="isMobile ? '32px' : '64px'"></IconSvg>
+								</div>
+								<div class="text" :style="{padding: isMobile? '22px 10px' : '22px 68px', 'font-size': isMobile? '14px' : '16px', 'line-height': isMobile? '20px' : '28px'}">
+									<p class="title">
+										{{ t('chat.helpTitle') }}
+									</p>
+									<p v-for="(item,index) in t('chat.helpcontent').split(';')" :key="index">{{ item }}</p>
+									<div class="gpts-list">
+										<div class="refresh" @click="refresh">
+											<IconSvg icon="refresh"></IconSvg>&nbsp;{{ t('chat.refresh') }}
+										</div>
+										<div v-for="v in gptsFilterList" :key="v.name" class="gpts-item" :style="{width: isMobile ? '100%' : 'calc(50% - 20px)', marginRight: '20px', padding: isMobile ? '5px 8px' : '14px 10px', 'margin-bottom': isMobile ? '8px' : '20px'}">
+											<NImage :src="v.logo" :preview-disabled="true" lazy
+															class="group-hover:scale-[130%] duration-300 shrink-0 overflow-hidden bg-base object-cover rounded-full bc-avatar w-[80px] h-[80px]" :style="{width: isMobile ? '23px' : '46px', height: isMobile ? '23px' : '46px'}">
+												<template #placeholder>
+													<div class="w-full h-full justify-center items-center flex"  >
+														<SvgIcon icon="line-md:downloading-loop" class="text-[60px] text-green-300"   ></SvgIcon>
+													</div>
+												</template>
+											</NImage>
+											<div :style="{width: `calc(100% - ${isMobile ? '43px' : '66px'})`, float: 'left', marginLeft: '10px'}">
+												<p class="info" :title="v.info"> {{ v.info }}</p>
+												<p @click="goUseGpts(v)" class="name"> {{ t('chat.used') }} {{ v.name }}</p>
+											</div>
+										</div>
+									</div>
+								</div>
+							</div>
+						</div>
+						<!-- <div class="flex items-center justify-center mt-4 text-center text-neutral-300" v-else>
+							<SvgIcon icon="ri:bubble-chart-fill" class="mr-2 text-3xl" />
+							<span>Aha~</span>
+						</div> -->
+					</template>
+
+					<template v-else>
+						<div>
+							<Message
+								v-for="(item, index) of dataSources"
+								:key="index"
+								:date-time="item.dateTime"
+								:text="item.text"
+								:inversion="item.inversion"
+								:error="item.error"
+								:loading="item.loading"
+								@regenerate="onRegenerate(index)"
+								@delete="handleDelete(index)"
+								:chat="item"
+								:index="index"
+							/>
+							<Message  v-if="ychat.text && !homeStore.myData.session.isCloseMdPreview"
+												:key="dataSources.length" :inversion="true"
+												:date-time="$t('mj.typing')"
+												:chat="ychat"
+												:text="ychat.text"
+												:index="dataSources.length"
+							/>
+							<div class="sticky bottom-0 left-0 flex justify-center">
+								<NButton v-if="loading" type="warning" @click="handleStop">
+									<template #icon>
+										<SvgIcon icon="ri:stop-circle-line" />
+									</template>
+									{{ t('common.stopResponding') }}
+								</NButton>
+							</div>
+						</div>
+					</template>
+				</div>
+			</div>
+		</main>
+
+		<footer :class="footerClass" class="footer-content" v-if="local!=='draw'">
+			<div class="w-full max-w-screen-xl m-auto">
+				<aiGptInput @handle-clear="handleClear" @export="handleExport" v-if="['gpt-4o-mini','gpt-3.5-turbo-16k'].indexOf(gptConfigStore.myData.model)>-1 || st.inputme "
+										v-model:modelValue="prompt" :disabled="buttonDisabled"
+										:searchOptions="searchOptions"  :renderOption="renderOption"
+				/>
+				<div class="flex items-center justify-between space-x-2" v-else>
+					<!--
+					<HoverButton v-if="!isMobile" @click="handleClear">
+						<span class="text-xl text-[#4f555e] dark:text-white">
+							<SvgIcon icon="ri:delete-bin-line" />
+						</span>
+					</HoverButton>
+					<HoverButton v-if="!isMobile" @click="handleExport">
+						<span class="text-xl text-[#4f555e] dark:text-white">
+							<SvgIcon icon="ri:download-2-line" />
+						</span>
+					</HoverButton>
+					<HoverButton @click="toggleUsingContext">
+						<span class="text-xl" :class="{ 'text-[#4b9e5f]': usingContext, 'text-[#a8071a]': !usingContext }">
+							<SvgIcon icon="ri:chat-history-line" />
+						</span>
+					</HoverButton>
+					-->
+
+					<NAutoComplete v-model:value="prompt" :options="searchOptions" :render-label="renderOption">
+						<template #default="{ handleInput, handleBlur, handleFocus }">
+							<NInput
+								ref="inputRef"
+								v-model:value="prompt"
+								type="textarea"
+								:placeholder="placeholder"
+								:autosize="{ minRows: 1, maxRows: isMobile ? 4 : 8 }"
+								@input="handleInput"
+								@focus="handleFocus"
+								@blur="handleBlur"
+								@keypress="handleEnter"
+							/>
+						</template>
+					</NAutoComplete>
+					<NButton type="primary" :disabled="buttonDisabled" @click="handleSubmit">
+						<template #icon>
               <span class="dark:text-black">
                 <SvgIcon icon="ri:send-plane-fill" />
               </span>
-            </template>
-          </NButton>
+						</template>
+					</NButton>
 
-        </div>
-      </div>
-    </footer>
-  </div>
+				</div>
+			</div>
+		</footer>
+	</div>
 
-  <drawListVue />
-  <aiGPT @finished="loading = false" />
-  <AiSiderInput v-if="isMobile"  :button-disabled="false" />
+	<drawListVue />
+	<aiGPT @finished="loading = false" />
+	<AiSiderInput v-if="isMobile"  :button-disabled="false" />
 
 </template>

+ 25 - 10
src/views/mj/aiGptInput.vue

@@ -215,7 +215,6 @@ const paste = (e: ClipboardEvent) => {
 	let rz = getFileFromClipboard(e);
 	if (rz.length > 0) upFile(rz[0]);
 };
-
 const sendMic = (e: any) => {
 	mlog("sendMic", e);
 	st.value.showMic = false;
@@ -279,6 +278,22 @@ const handleSelectASR = (key: string | number) => {
 	if (key == "asr") goASR();
 	if (key == "whisper") st.value.showMic = true;
 };
+/**
+ * 校验字符串的大小
+ * @param inputStr 输入的字符
+ * @param maxLength 字符串长度
+ */
+const truncateText = (inputStr:any, maxLength = 20) => {
+	// 处理空值情况
+	if (!inputStr) return ''
+	// 类型安全校验
+	const str = String(inputStr)
+	// 判断并截断
+	return str.length > maxLength
+		? `${str.slice(0, maxLength)}...`
+		: str
+}
+
 const show = ref(false);
 function handleExport() {
 	emit("export");
@@ -335,14 +350,14 @@ function handleClear() {
 						<template v-if="nGptStore.gpts">
 							<SvgIcon icon="ri:apps-fill" />
 							<span class="line-clamp-1 overflow-hidden">{{
-								nGptStore.gpts.name
-							}}</span>
+									nGptStore.gpts.name
+								}}</span>
 						</template>
 						<template v-else>
 							<SvgIcon icon="heroicons:sparkles" />
-							<span>{{
-								nGptStore.modelLabel ? nGptStore.modelLabel : "gpt-4o-mini"
-							}}</span>
+							<span>模型:{{
+									nGptStore.modelLabel ? truncateText(nGptStore.modelLabel,20) : "gpt-4o-mini"
+								}} {{nGptStore.kid?'知识库:'+truncateText(nGptStore.kName,10):''}}</span>
 						</template>
 						<SvgIcon icon="icon-park-outline:right" />
 					</div>
@@ -439,8 +454,8 @@ function handleClear() {
 							<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>
+										$t("setting.setting")
+									}}</NButton>
 							</p>
 						</div>
 					</NPopover>
@@ -555,8 +570,8 @@ function handleClear() {
 						<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>
+									$t("setting.setting")
+								}}</NButton>
 						</p>
 					</div>
 				</NPopover>

+ 197 - 197
src/views/mj/aiModel.vue

@@ -22,228 +22,228 @@ onMounted(() => { fetchData(),fetchDataGetKnowledge() });
 
 const config = ref([])
 const fetchData = async () => {
-    try {
-       // 发起一个请求
-      const [err, result] = await to(modelList());
-
-      if (err) {
-        message.error(err.message)
-        config.value = []; // 设置为空数组,避免迭代错误
-      } else {
-         config.value = result.data;
-      }
-    } catch (error) {
-      console.error('Error fetching data:', error);
-    }
+	try {
+		// 发起一个请求
+		const [err, result] = await to(modelList());
+
+		if (err) {
+			message.error(err.message)
+			config.value = []; // 设置为空数组,避免迭代错误
+		} else {
+			config.value = result.data;
+		}
+	} catch (error) {
+		console.error('Error fetching data:', error);
+	}
 };
 
 const fetchDataGetKnowledge = async () => {
-    if(getToken()){
-        try {
-       // 发起一个请求
-      const [err, result] = await to(getKnowledge());
-      console.log("result===", result.rows)
-      if (err) {
-        ms.error(err.message)
-      } else {
-        options.value = result.rows.map((item: any) => ({
-            label: item.kname, // 假设后台返回的数据有 'name' 字段
-            value: item.id     // 假设每个数据项都有一个唯一的 'id' 字段
-        }));
-
-        // 请求成功
-        options.value.push({ label: 'please select', value: '' });
-      }
-    } catch (error) {
-      console.error('Error fetching data:', error);
-    }
-    }
-  };
+	if(getToken()){
+		try {
+			// 发起一个请求
+			const [err, result] = await to(getKnowledge());
+			console.log("result===", result.rows)
+			if (err) {
+				ms.error(err.message)
+			} else {
+				options.value = result.rows.map((item: any) => ({
+					label: item.kname, // 假设后台返回的数据有 'name' 字段
+					value: item.id     // 假设每个数据项都有一个唯一的 'id' 字段
+				}));
+
+				// 请求成功
+				options.value.push({ label: '暂不配置', value: '' });
+			}
+		} catch (error) {
+			console.error('Error fetching data:', error);
+		}
+	}
+};
 
 const st= ref({openMore:false });
 const voiceList= computed(()=>{
-    let rz=[];
-    for(let o of "alloy,echo,fable,onyx,nova,shimmer".split(/[ ,]+/ig))rz.push({label:o,value:o})
-    return rz;
+	let rz=[];
+	for(let o of "alloy,echo,fable,onyx,nova,shimmer".split(/[ ,]+/ig))rz.push({label:o,value:o})
+	return rz;
 });
 const modellist = computed(() => { //
-    let rz =[ ];
-    for(let o of config.value){
-        rz.push({label:o.modelDescribe,value:o.modelName})
-    }
-
-    if(gptConfigStore.myData.userModel){
-        let arr = gptConfigStore.myData.userModel.split(/[ ,]+/ig);
-        for(let o of arr ){
-             rz.push({label:o,value:o})
-        }
-    }
-    //服务端的 CUSTOM_MODELS 设置
-    if( homeStore.myData.session.cmodels ){
-        let delModel:string[] = [];
-        let addModel:string[]=[];
-        homeStore.myData.session.cmodels.split(/[ ,]+/ig).map( (v:string)=>{
-            if(v.indexOf('-')==0){
-                delModel.push(v.substring(1))
-            }else{
-                addModel.push(v);
-            }
-        });
-        mlog('cmodels',delModel,addModel);
-        rz= rz.filter(v=> delModel.indexOf(v.value)==-1 );
-        addModel.map(o=>rz.push({label:o,value:o}) )
-    }
-
-    let uniqueArray: { label: string, value: string }[] = Array.from(
-        new Map(rz.map(item => [JSON.stringify(item), item]))
-        .values()
-    );
-    return uniqueArray ;
+	let rz =[ ];
+	for(let o of config.value){
+		rz.push({label:o.modelDescribe,value:o.modelName})
+	}
+
+	if(gptConfigStore.myData.userModel){
+		let arr = gptConfigStore.myData.userModel.split(/[ ,]+/ig);
+		for(let o of arr ){
+			rz.push({label:o,value:o})
+		}
+	}
+	//服务端的 CUSTOM_MODELS 设置
+	if( homeStore.myData.session.cmodels ){
+		let delModel:string[] = [];
+		let addModel:string[]=[];
+		homeStore.myData.session.cmodels.split(/[ ,]+/ig).map( (v:string)=>{
+			if(v.indexOf('-')==0){
+				delModel.push(v.substring(1))
+			}else{
+				addModel.push(v);
+			}
+		});
+		mlog('cmodels',delModel,addModel);
+		rz= rz.filter(v=> delModel.indexOf(v.value)==-1 );
+		addModel.map(o=>rz.push({label:o,value:o}) )
+	}
+
+	let uniqueArray: { label: string, value: string }[] = Array.from(
+		new Map(rz.map(item => [JSON.stringify(item), item]))
+			.values()
+	);
+	return uniqueArray ;
 });
 const ms= useMessage();
 
 const saveChat=(type:string)=>{
-     chatSet.save(  nGptStore.value );
-     gptConfigStore.setMyData( nGptStore.value );
-     homeStore.setMyData({act:'saveChat'});
-     if(type!='hide')ms.success( t('common.saveSuccess'));
-     emit('close');
+	chatSet.save(  nGptStore.value );
+	gptConfigStore.setMyData( nGptStore.value );
+	homeStore.setMyData({act:'saveChat'});
+	if(type!='hide')ms.success( t('common.saveSuccess'));
+	emit('close');
 }
 
 // 添加一个空选项
 const options = ref([]);
 
-  const onSelectChange = (newValue: any) => {
-    const option = options.value.find(optionValue => optionValue.value === newValue);
-    nGptStore.value.kName = option.label;
-  };
+const onSelectChange = (newValue: any) => {
+	const option = options.value.find(optionValue => optionValue.value === newValue);
+	nGptStore.value.kName = option.label;
+};
 
-  const onSelectChange1 = (newValue: any) => {
-    const option = modellist.value.find(optionValue => optionValue.value === newValue);
-    nGptStore.value.modelLabel = option.label;
-  };
+const onSelectChange1 = (newValue: any) => {
+	const option = modellist.value.find(optionValue => optionValue.value === newValue);
+	nGptStore.value.modelLabel = option.label;
+};
 
 watch(()=>nGptStore.value.model,(n)=>{
-    nGptStore.value.gpts=undefined;
-    let max=4096;
-    if( n.indexOf('vision')>-1){
-        max=4096;
-    }else if( n.indexOf('gpt-4')>-1 ||  n.indexOf('16k')>-1 ){ //['16k','8k','32k','gpt-4'].indexOf(n)>-1
-        max=4096*2;
-    }else if( n.toLowerCase().includes('claude-3') ){
-         max=4096*2;
-    }
-    config.value.maxToken=max/2;
-    if(nGptStore.value.max_tokens> config.value.maxToken ) nGptStore.value.max_tokens= config.value.maxToken;
+	nGptStore.value.gpts=undefined;
+	let max=4096;
+	if( n.indexOf('vision')>-1){
+		max=4096;
+	}else if( n.indexOf('gpt-4')>-1 ||  n.indexOf('16k')>-1 ){ //['16k','8k','32k','gpt-4'].indexOf(n)>-1
+		max=4096*2;
+	}else if( n.toLowerCase().includes('claude-3') ){
+		max=4096*2;
+	}
+	config.value.maxToken=max/2;
+	if(nGptStore.value.max_tokens> config.value.maxToken ) nGptStore.value.max_tokens= config.value.maxToken;
 })
 
 const reSet=()=>{
-    gptConfigStore.setInit();
-    nGptStore.value= gptConfigStore.myData;
+	gptConfigStore.setInit();
+	nGptStore.value= gptConfigStore.myData;
 }
 
 </script>
 <template>
-<section class="mb-5 justify-between items-center"  >
-     <div style="margin-bottom: 8px;"><span class="text-red-500">*</span>  {{ $t('mjset.model') }}</div>
-    <n-select class="change-select" v-model:value="nGptStore.model" :options="modellist" @update:value="onSelectChange1" size="small"   />
-</section>
-
-<section class="mb-5 flex justify-between items-center"  >
-    <n-input  class="change-select"  :placeholder="$t('mjchat.modlePlaceholder')" v-model:value="nGptStore.userModel">
-      <template #prefix>
-        {{ $t('mjchat.myModle') }}
-      </template>
-    </n-input>
- </section>
-
- <section class="mb-5 justify-between items-center"  >
-     <div  style="margin-bottom: 8px;">{{ $t('mjchat.knowledgeBase') }} </div>
-    <n-select class="change-select" v-model:value="nGptStore.kid" :options="options" @update:value="onSelectChange" size="small"   />
-</section>
-
- <section class=" flex justify-between items-center"  >
-     <div style="margin-bottom: 8px;"> {{ $t('mjchat.historyCnt') }}
-     </div>
-     <div class=" flex justify-end items-center w-[80%] max-w-[240px]">
-        <div class=" w-[200px]"><n-slider class="change-slider" v-model:value="nGptStore.talkCount" :step="1" :max="50" /></div>
-        <div  class="w-[40px] text-right">{{ nGptStore.talkCount }}</div>
-    </div>
-</section>
-<div class="mb-5 text-[12px] text-gray-300 dark:text-gray-300/20">{{ $t('mjchat.historyToken') }}</div>
-
- <section class=" flex justify-between items-center"  >
-     <div> {{ $t('mjchat.historyTCnt') }}
-     </div>
-     <div class=" flex justify-end items-center w-[80%] max-w-[240px]">
-        <div class=" w-[200px]"><n-slider class="change-slider" v-model:value="nGptStore.max_tokens" :step="1" :max="1280000" :min="1" /></div>
-        <div  class="w-[100px] text-right">{{ nGptStore.max_tokens }}</div>
-    </div>
-</section>
-<div class="mb-5 text-[16px] text-gray-300 dark:text-gray-300/20">{{ $t('mjchat.historyTCntInfo') }}  </div>
-
- <section class="mb-5 change-select"  >
-    <div style="margin-bottom: 8px;">{{ $t('mjchat.role') }}</div>
-    <div>
-     <n-input  type="textarea"  :placeholder=" $t('mjchat.rolePlaceholder') "   v-model:value="nGptStore.systemMessage" :autosize="{ minRows: 3 }"
-    />
-    </div>
- </section>
-
-<template v-if="st.openMore">
-    <section class=" flex justify-between items-center "  >
-        <div>{{ $t('mj.temperature') }}</div>
-        <div class=" flex justify-end items-center w-[80%] max-w-[240px]">
-            <div class=" w-[200px]"><n-slider class="change-slider" v-model:value="nGptStore.temperature" :step="0.01" :max="1" /></div>
-            <div  class="w-[40px] text-right">{{ nGptStore.temperature }}</div>
-        </div>
-    </section>
-    <div class="mb-5 text-[12px] text-gray-300 dark:text-gray-300/20"> {{ $t('mj.temperatureInfo') }}</div>
-
-
-    <section class=" flex justify-between items-center "  >
-        <div> {{ $t('mj.top_p') }}</div>
-        <div class=" flex justify-end items-center w-[80%] max-w-[240px]">
-            <div class=" w-[200px]"><n-slider class="change-slider" v-model:value="nGptStore.top_p" :step="0.01" :max="1" /></div>
-            <div  class="w-[40px] text-right">{{ nGptStore.top_p }}</div>
-        </div>
-    </section>
-    <div class="mb-5 text-[12px] text-gray-300 dark:text-gray-300/20">{{ $t('mj.top_pInfo') }}</div>
-
-    <section class=" flex justify-between items-center "  >
-        <div> {{ $t('mj.presence_penalty') }}</div>
-        <div class=" flex justify-end items-center w-[80%] max-w-[240px]">
-            <div class=" w-[200px]"><n-slider class="change-slider" v-model:value="nGptStore.presence_penalty" :step="0.01" :max="1" /></div>
-            <div  class="w-[40px] text-right">{{ nGptStore.presence_penalty }}</div>
-        </div>
-    </section>
-    <div class="mb-5 text-[12px] text-gray-300 dark:text-gray-300/20">{{ $t('mj.presence_penaltyInfo') }} </div>
-
-
-    <section class=" flex justify-between items-center "  >
-        <div>{{ $t('mj.frequency_penalty') }}</div>
-        <div class=" flex justify-end items-center w-[80%] max-w-[240px]">
-            <div class=" w-[200px]"><n-slider class="change-slider" v-model:value="nGptStore.frequency_penalty" :step="0.01" :max="1" /></div>
-            <div  class="w-[40px] text-right">{{ nGptStore.frequency_penalty }}</div>
-        </div>
-    </section>
-    <div class="mb-5 text-[12px] text-gray-300 dark:text-gray-300/20">{{ $t('mj.frequency_penaltyInfo') }}</div>
-
-    <section class="mb-4 justify-between items-center change-select"  >
-        <div style="margin-bottom: 8px;">{{ $t('mj.tts_voice') }}</div>
-        <n-select v-model:value="nGptStore.tts_voice" :options="voiceList" size="small"   />
-    </section>
-
-
-</template>
-<div v-else class="text-right cursor-pointer mb-4" @click="st.openMore=true">
-    <NTag  type="primary" round size="small" :bordered="false" class="!cursor-pointer">More...</NTag>
-</div>
-
-<section class=" text-right flex justify-end space-x-2 model-button"  >
-    <NButton :bordered="false"  @click="reSet()">{{ $t('mj.setBtBack') }}</NButton>
-    <!-- <NButton type="primary" @click="saveChat">{{ $t('mj.setBtSaveChat') }}</NButton>
-    <NButton type="primary" @click="save">{{ $t('mj.setBtSaveSys') }}</NButton> -->
-    <NButton :bordered="false" @click="saveChat('no')">{{ $t('common.save') }}</NButton>
- </section>
+	<section class="mb-5 justify-between items-center"  >
+		<div style="margin-bottom: 8px;"><span class="text-red-500">*</span>  {{ $t('mjset.model') }}</div>
+		<n-select class="change-select" v-model:value="nGptStore.model" :options="modellist" @update:value="onSelectChange1" size="small"   />
+	</section>
+
+	<section class="mb-5 flex justify-between items-center"  >
+		<n-input  class="change-select"  :placeholder="$t('mjchat.modlePlaceholder')" v-model:value="nGptStore.userModel">
+			<template #prefix>
+				{{ $t('mjchat.myModle') }}
+			</template>
+		</n-input>
+	</section>
+
+	<section class="mb-5 justify-between items-center"  >
+		<div  style="margin-bottom: 8px;">{{ $t('mjchat.knowledgeBase') }} </div>
+		<n-select class="change-select" v-model:value="nGptStore.kid" :options="options" @update:value="onSelectChange" size="small"   />
+	</section>
+
+	<section class=" flex justify-between items-center"  >
+		<div style="margin-bottom: 8px;"> {{ $t('mjchat.historyCnt') }}
+		</div>
+		<div class=" flex justify-end items-center w-[80%] max-w-[240px]">
+			<div class=" w-[200px]"><n-slider class="change-slider" v-model:value="nGptStore.talkCount" :step="1" :max="50" /></div>
+			<div  class="w-[40px] text-right">{{ nGptStore.talkCount }}</div>
+		</div>
+	</section>
+	<div class="mb-5 text-[12px] text-gray-300 dark:text-gray-300/20">{{ $t('mjchat.historyToken') }}</div>
+
+	<section class=" flex justify-between items-center"  >
+		<div> {{ $t('mjchat.historyTCnt') }}
+		</div>
+		<div class=" flex justify-end items-center w-[80%] max-w-[240px]">
+			<div class=" w-[200px]"><n-slider class="change-slider" v-model:value="nGptStore.max_tokens" :step="1" :max="1280000" :min="1" /></div>
+			<div  class="w-[100px] text-right">{{ nGptStore.max_tokens }}</div>
+		</div>
+	</section>
+	<div class="mb-5 text-[16px] text-gray-300 dark:text-gray-300/20">{{ $t('mjchat.historyTCntInfo') }}  </div>
+
+	<section class="mb-5 change-select"  >
+		<div style="margin-bottom: 8px;">{{ $t('mjchat.role') }}</div>
+		<div>
+			<n-input  type="textarea"  :placeholder=" $t('mjchat.rolePlaceholder') "   v-model:value="nGptStore.systemMessage" :autosize="{ minRows: 3 }"
+			/>
+		</div>
+	</section>
+
+	<template v-if="st.openMore">
+		<section class=" flex justify-between items-center "  >
+			<div>{{ $t('mj.temperature') }}</div>
+			<div class=" flex justify-end items-center w-[80%] max-w-[240px]">
+				<div class=" w-[200px]"><n-slider class="change-slider" v-model:value="nGptStore.temperature" :step="0.01" :max="1" /></div>
+				<div  class="w-[40px] text-right">{{ nGptStore.temperature }}</div>
+			</div>
+		</section>
+		<div class="mb-5 text-[12px] text-gray-300 dark:text-gray-300/20"> {{ $t('mj.temperatureInfo') }}</div>
+
+
+		<section class=" flex justify-between items-center "  >
+			<div> {{ $t('mj.top_p') }}</div>
+			<div class=" flex justify-end items-center w-[80%] max-w-[240px]">
+				<div class=" w-[200px]"><n-slider class="change-slider" v-model:value="nGptStore.top_p" :step="0.01" :max="1" /></div>
+				<div  class="w-[40px] text-right">{{ nGptStore.top_p }}</div>
+			</div>
+		</section>
+		<div class="mb-5 text-[12px] text-gray-300 dark:text-gray-300/20">{{ $t('mj.top_pInfo') }}</div>
+
+		<section class=" flex justify-between items-center "  >
+			<div> {{ $t('mj.presence_penalty') }}</div>
+			<div class=" flex justify-end items-center w-[80%] max-w-[240px]">
+				<div class=" w-[200px]"><n-slider class="change-slider" v-model:value="nGptStore.presence_penalty" :step="0.01" :max="1" /></div>
+				<div  class="w-[40px] text-right">{{ nGptStore.presence_penalty }}</div>
+			</div>
+		</section>
+		<div class="mb-5 text-[12px] text-gray-300 dark:text-gray-300/20">{{ $t('mj.presence_penaltyInfo') }} </div>
+
+
+		<section class=" flex justify-between items-center "  >
+			<div>{{ $t('mj.frequency_penalty') }}</div>
+			<div class=" flex justify-end items-center w-[80%] max-w-[240px]">
+				<div class=" w-[200px]"><n-slider class="change-slider" v-model:value="nGptStore.frequency_penalty" :step="0.01" :max="1" /></div>
+				<div  class="w-[40px] text-right">{{ nGptStore.frequency_penalty }}</div>
+			</div>
+		</section>
+		<div class="mb-5 text-[12px] text-gray-300 dark:text-gray-300/20">{{ $t('mj.frequency_penaltyInfo') }}</div>
+
+		<section class="mb-4 justify-between items-center change-select"  >
+			<div style="margin-bottom: 8px;">{{ $t('mj.tts_voice') }}</div>
+			<n-select v-model:value="nGptStore.tts_voice" :options="voiceList" size="small"   />
+		</section>
+
+
+	</template>
+	<div v-else class="text-right cursor-pointer mb-4" @click="st.openMore=true">
+		<NTag  type="primary" round size="small" :bordered="false" class="!cursor-pointer">More...</NTag>
+	</div>
+
+	<section class=" text-right flex justify-end space-x-2 model-button"  >
+		<NButton :bordered="false"  @click="reSet()">{{ $t('mj.setBtBack') }}</NButton>
+		<!-- <NButton type="primary" @click="saveChat">{{ $t('mj.setBtSaveChat') }}</NButton>
+		<NButton type="primary" @click="save">{{ $t('mj.setBtSaveSys') }}</NButton> -->
+		<NButton :bordered="false" @click="saveChat('no')">{{ $t('common.save') }}</NButton>
+	</section>
 </template>