index.vue 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815
  1. <script setup lang='ts'>
  2. import type { Ref } from 'vue'
  3. import { computed, onMounted, onUnmounted, ref,watch,h } from 'vue'
  4. import { useRoute, useRouter } from 'vue-router'
  5. import { storeToRefs } from 'pinia'
  6. import { NAutoComplete, NButton, NInput, useDialog, useMessage,NAvatar,NModal,NCard,NImage } from 'naive-ui'
  7. import html2canvas from 'html2canvas'
  8. import { Message } from './components'
  9. import { useScroll } from './hooks/useScroll'
  10. import { useChat } from './hooks/useChat'
  11. import { useUsingContext } from './hooks/useUsingContext'
  12. import { getGpts } from '@/api/chatmsg';
  13. import { SvgIcon } from '@/components/common'
  14. import { useBasicLayout } from '@/hooks/useBasicLayout'
  15. import { gptConfigStore, gptsUlistStore, homeStore, useChatStore, usePromptStore,useAppStore } from '@/store'
  16. import { chatSetting, fetchChatAPIProcess, gptsType, mlog, myFetch, my2Fetch } from '@/api'
  17. import { t } from '@/locales'
  18. import drawListVue from '../mj/drawList.vue'
  19. import aiGPT from '../mj/aiGpt.vue'
  20. import AiSiderInput from '../mj/aiSiderInput.vue'
  21. import aiGptInput from '../mj/aiGptInput.vue'
  22. import { getNotice,readNotice, getInform } from '@/api/notice'
  23. import to from "await-to-js";
  24. let controller = new AbortController()
  25. const openLongReply = import.meta.env.VITE_GLOB_OPEN_LONG_REPLY === 'true'
  26. const route = useRoute()
  27. const dialog = useDialog()
  28. const ms = useMessage()
  29. const router = useRouter()
  30. const chatStore = useChatStore()
  31. // const href = window.location.href.split('#')[0]
  32. const href = window.location.hostname;
  33. const { isMobile } = useBasicLayout()
  34. const { addChat, updateChat, updateChatSome, getChatByUuidAndIndex } = useChat()
  35. const { scrollRef, scrollToBottom, scrollToBottomIfAtBottom } = useScroll()
  36. const { usingContext, toggleUsingContext } = useUsingContext()
  37. const { uuid } = route.params as { uuid: string }
  38. const dataSources = computed(() => chatStore.getChatByUuid(+uuid))
  39. const conversationList = computed(() => dataSources.value.filter(item => (!item.inversion && !!item.conversationOptions)))
  40. const prompt = ref<string>('')
  41. const loading = ref<boolean>(false)
  42. const inputRef = ref<Ref | null>(null)
  43. // 添加PromptStore
  44. const promptStore = usePromptStore()
  45. // 使用storeToRefs,保证store修改后,联想部分能够重新渲染
  46. const { promptList: promptTemplate } = storeToRefs<any>(promptStore)
  47. const appStore = useAppStore()
  48. const isChat = computed(() => appStore.isChat)
  49. // 未知原因刷新页面,loading 状态不会重置,手动重置
  50. dataSources.value.forEach((item, index) => {
  51. if (item.loading)
  52. updateChatSome(+uuid, index, { loading: false })
  53. })
  54. function handleSubmit() {
  55. //onConversation() //把这个放到aiGpt
  56. let message = prompt.value;
  57. if (!message || message.trim() === '')
  58. return
  59. if (loading.value) return;
  60. loading.value = true
  61. homeStore.setMyData({act:'gpt.submit', actData:{ prompt:prompt.value, uuid } });
  62. prompt.value='';
  63. }
  64. async function onConversation() {
  65. let message = prompt.value
  66. if (loading.value)
  67. return
  68. if (!message || message.trim() === '')
  69. return
  70. controller = new AbortController()
  71. addChat(
  72. +uuid,
  73. {
  74. dateTime: new Date().toLocaleString(),
  75. text: message,
  76. inversion: true,
  77. error: false,
  78. conversationOptions: null,
  79. requestOptions: { prompt: message, options: null },
  80. },
  81. )
  82. scrollToBottom()
  83. loading.value = true
  84. prompt.value = ''
  85. let options: Chat.ConversationRequest = {}
  86. const lastContext = conversationList.value[conversationList.value.length - 1]?.conversationOptions
  87. if (lastContext && usingContext.value)
  88. options = { ...lastContext }
  89. addChat(
  90. +uuid,
  91. {
  92. dateTime: new Date().toLocaleString(),
  93. text: '思考中',
  94. loading: true,
  95. inversion: false,
  96. error: false,
  97. conversationOptions: null,
  98. requestOptions: { prompt: message, options: { ...options } },
  99. },
  100. )
  101. scrollToBottom()
  102. try {
  103. let lastText = ''
  104. const fetchChatAPIOnce = async () => {
  105. await fetchChatAPIProcess<Chat.ConversationResponse>({
  106. prompt: message,
  107. options,
  108. signal: controller.signal,
  109. onDownloadProgress: ({ event }) => {
  110. const xhr = event.target
  111. const { responseText } = xhr
  112. // Always process the final line
  113. const lastIndex = responseText.lastIndexOf('\n', responseText.length - 2)
  114. let chunk = responseText
  115. if (lastIndex !== -1)
  116. chunk = responseText.substring(lastIndex)
  117. try {
  118. const data = JSON.parse(chunk)
  119. updateChat(
  120. +uuid,
  121. dataSources.value.length - 1,
  122. {
  123. dateTime: new Date().toLocaleString(),
  124. text: lastText + (data.text ?? ''),
  125. inversion: false,
  126. error: false,
  127. loading: true,
  128. conversationOptions: { conversationId: data.conversationId, parentMessageId: data.id },
  129. requestOptions: { prompt: message, options: { ...options } },
  130. },
  131. )
  132. if (openLongReply && data.detail.choices[0].finish_reason === 'length') {
  133. options.parentMessageId = data.id
  134. lastText = data.text
  135. message = ''
  136. return fetchChatAPIOnce()
  137. }
  138. scrollToBottomIfAtBottom()
  139. }
  140. catch (error) {
  141. //
  142. }
  143. },
  144. })
  145. updateChatSome(+uuid, dataSources.value.length - 1, { loading: false })
  146. }
  147. await fetchChatAPIOnce()
  148. }
  149. catch (error: any) {
  150. const errorMessage = error?.message ?? t('common.wrong')
  151. if (error.message === 'canceled') {
  152. updateChatSome(
  153. +uuid,
  154. dataSources.value.length - 1,
  155. {
  156. loading: false,
  157. },
  158. )
  159. scrollToBottomIfAtBottom()
  160. return
  161. }
  162. const currentChat = getChatByUuidAndIndex(+uuid, dataSources.value.length - 1)
  163. if (currentChat?.text && currentChat.text !== '') {
  164. updateChatSome(
  165. +uuid,
  166. dataSources.value.length - 1,
  167. {
  168. text: `${currentChat.text}\n[${errorMessage}]`,
  169. error: false,
  170. loading: false,
  171. },
  172. )
  173. return
  174. }
  175. updateChat(
  176. +uuid,
  177. dataSources.value.length - 1,
  178. {
  179. dateTime: new Date().toLocaleString(),
  180. text: errorMessage,
  181. inversion: false,
  182. error: true,
  183. loading: false,
  184. conversationOptions: null,
  185. requestOptions: { prompt: message, options: { ...options } },
  186. },
  187. )
  188. scrollToBottomIfAtBottom()
  189. }
  190. finally {
  191. loading.value = false
  192. }
  193. }
  194. async function onRegenerate(index: number) {
  195. if (loading.value)
  196. return
  197. controller = new AbortController()
  198. const { requestOptions } = dataSources.value[index]
  199. let message = requestOptions?.prompt ?? ''
  200. let options: Chat.ConversationRequest = {}
  201. if (requestOptions.options)
  202. options = { ...requestOptions.options }
  203. loading.value = true
  204. updateChat(
  205. +uuid,
  206. index,
  207. {
  208. dateTime: new Date().toLocaleString(),
  209. text: '',
  210. inversion: false,
  211. error: false,
  212. loading: true,
  213. conversationOptions: null,
  214. requestOptions: { prompt: message, options: { ...options } },
  215. },
  216. )
  217. try {
  218. let lastText = ''
  219. const fetchChatAPIOnce = async () => {
  220. await fetchChatAPIProcess<Chat.ConversationResponse>({
  221. prompt: message,
  222. options,
  223. signal: controller.signal,
  224. onDownloadProgress: ({ event }) => {
  225. const xhr = event.target
  226. const { responseText } = xhr
  227. // Always process the final line
  228. const lastIndex = responseText.lastIndexOf('\n', responseText.length - 2)
  229. let chunk = responseText
  230. if (lastIndex !== -1)
  231. chunk = responseText.substring(lastIndex)
  232. try {
  233. const data = JSON.parse(chunk)
  234. updateChat(
  235. +uuid,
  236. index,
  237. {
  238. dateTime: new Date().toLocaleString(),
  239. text: lastText + (data.text ?? ''),
  240. inversion: false,
  241. error: false,
  242. loading: true,
  243. conversationOptions: { conversationId: data.conversationId, parentMessageId: data.id },
  244. requestOptions: { prompt: message, options: { ...options } },
  245. },
  246. )
  247. if (openLongReply && data.detail.choices[0].finish_reason === 'length') {
  248. options.parentMessageId = data.id
  249. lastText = data.text
  250. message = ''
  251. return fetchChatAPIOnce()
  252. }
  253. }
  254. catch (error) {
  255. //
  256. }
  257. },
  258. })
  259. updateChatSome(+uuid, index, { loading: false })
  260. }
  261. await fetchChatAPIOnce()
  262. }
  263. catch (error: any) {
  264. if (error.message === 'canceled') {
  265. updateChatSome(
  266. +uuid,
  267. index,
  268. {
  269. loading: false,
  270. },
  271. )
  272. return
  273. }
  274. const errorMessage = error?.message ?? t('common.wrong')
  275. updateChat(
  276. +uuid,
  277. index,
  278. {
  279. dateTime: new Date().toLocaleString(),
  280. text: errorMessage,
  281. inversion: false,
  282. error: true,
  283. loading: false,
  284. conversationOptions: null,
  285. requestOptions: { prompt: message, options: { ...options } },
  286. },
  287. )
  288. }
  289. finally {
  290. loading.value = false
  291. }
  292. }
  293. function handleExport() {
  294. if (loading.value)
  295. return
  296. const d = dialog.warning({
  297. title: t('chat.exportImage'),
  298. content: t('chat.exportImageConfirm'),
  299. positiveText: t('common.yes'),
  300. negativeText: t('common.no'),
  301. onPositiveClick: async () => {
  302. try {
  303. d.loading = true
  304. const ele = document.getElementById('image-wrapper')
  305. const canvas = await html2canvas(ele as HTMLDivElement, {
  306. useCORS: true,
  307. })
  308. const imgUrl = canvas.toDataURL('image/png')
  309. const tempLink = document.createElement('a')
  310. tempLink.style.display = 'none'
  311. tempLink.href = imgUrl
  312. tempLink.setAttribute('download', 'chat-shot.png')
  313. if (typeof tempLink.download === 'undefined')
  314. tempLink.setAttribute('target', '_blank')
  315. document.body.appendChild(tempLink)
  316. tempLink.click()
  317. document.body.removeChild(tempLink)
  318. window.URL.revokeObjectURL(imgUrl)
  319. d.loading = false
  320. ms.success(t('chat.exportSuccess'))
  321. Promise.resolve()
  322. }
  323. catch (error: any) {
  324. ms.error(t('chat.exportFailed'))
  325. }
  326. finally {
  327. d.loading = false
  328. }
  329. },
  330. })
  331. }
  332. function handleDelete(index: number) {
  333. if (loading.value)
  334. return
  335. dialog.warning({
  336. title: t('chat.deleteMessage'),
  337. content: t('chat.deleteMessageConfirm'),
  338. positiveText: t('common.yes'),
  339. negativeText: t('common.no'),
  340. onPositiveClick: () => {
  341. chatStore.deleteChatByUuid(+uuid, index)
  342. },
  343. })
  344. }
  345. function handleClear() {
  346. if (loading.value)
  347. return
  348. dialog.warning({
  349. title: t('chat.clearChat'),
  350. content: t('chat.clearChatConfirm'),
  351. positiveText: t('common.yes'),
  352. negativeText: t('common.no'),
  353. onPositiveClick: () => {
  354. chatStore.clearChatByUuid(+uuid)
  355. },
  356. })
  357. }
  358. function handleEnter(event: KeyboardEvent) {
  359. if (!isMobile.value) {
  360. if (event.key === 'Enter' && !event.shiftKey) {
  361. event.preventDefault()
  362. handleSubmit()
  363. }
  364. }
  365. else {
  366. if (event.key === 'Enter' && event.ctrlKey) {
  367. event.preventDefault()
  368. handleSubmit()
  369. }
  370. }
  371. }
  372. function handleStop() {
  373. if (loading.value) {
  374. homeStore.setMyData({act:'abort'});
  375. controller.abort()
  376. loading.value = false
  377. }
  378. }
  379. // 可优化部分
  380. // 搜索选项计算,这里使用value作为索引项,所以当出现重复value时渲染异常(多项同时出现选中效果)
  381. // 理想状态下其实应该是key作为索引项,但官方的renderOption会出现问题,所以就需要value反renderLabel实现
  382. const searchOptions = computed(() => {
  383. if (prompt.value.startsWith('/')) {
  384. const abc= promptTemplate.value.filter((item: { key: string }) => item.key.toLowerCase().includes(prompt.value.substring(1).toLowerCase())).map((obj: { value: any }) => {
  385. return {
  386. label: obj.value,
  387. value: obj.value,
  388. }
  389. })
  390. mlog('搜索选项', abc);
  391. return abc;
  392. }else if(prompt.value=='@'){
  393. const abc= gptsUlistStore.myData.slice(0,10).map( (v:gptsType) => {
  394. return {
  395. label:v.info,
  396. gpts:v,
  397. value:v.gid
  398. }
  399. })
  400. return abc ;
  401. }else {
  402. return []
  403. }
  404. })
  405. const goUseGpts= async ( item: gptsType)=>{
  406. const saveObj= {model: `${ item.modelName }` ,gpts:item}
  407. gptConfigStore.setMyData(saveObj);
  408. if(chatStore.active){ //保存到对话框
  409. const chatSet = new chatSetting( chatStore.active );
  410. if( chatSet.findIndex()>-1 ) chatSet.save( saveObj )
  411. }
  412. ms.success(t('mjchat.success2'));
  413. // const gptUrl= `https://gpts.ddaiai.com/open/gptsapi/use`;
  414. // myFetch(gptUrl,item );
  415. mlog('go local ', homeStore.myData.local );
  416. if(homeStore.myData.local!=='Chat') router.replace({name:'Chat',params:{uuid:chatStore.active}});
  417. gptsUlistStore.setMyData( item );
  418. }
  419. // value反渲染key
  420. const renderOption = (option: { label: string,gpts?:gptsType }) => {
  421. if( prompt.value=='@'){
  422. //return [ h( NAvatar,{src:'https://cos.aitutu.cc/gpts/gpt4all.jpg',size:"small",round:true}),option.label ]
  423. return [h("div",{class:'flex justify-start items-center'
  424. , onclick:()=>{
  425. if(option.gpts) goUseGpts(option.gpts) ;
  426. prompt.value='';
  427. setTimeout(() => prompt.value='', 80);
  428. }}
  429. ,[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"})
  430. , h('span', { class: 'pl-1' }, option.gpts?.name )
  431. , h('span', { class: 'line-clamp-1 flex-1 pl-1 opacity-50' }, option.label )
  432. ])]
  433. }
  434. for (const i of promptTemplate.value) {
  435. if (i.value === option.label)
  436. return [i.key]
  437. }
  438. return []
  439. }
  440. const placeholder = computed(() => {
  441. if (isMobile.value)
  442. return t('chat.placeholderMobile')
  443. return t('chat.placeholder')
  444. })
  445. const buttonDisabled = computed(() => {
  446. return loading.value || !prompt.value || prompt.value.trim() === ''
  447. })
  448. const footerClass = computed(() => {
  449. let classes = ['p-4']
  450. if (isMobile.value)
  451. classes = ['sticky', 'left-0', 'bottom-0', 'right-0', 'p-2', 'pr-3'] //, 'overflow-hidden'
  452. return classes
  453. })
  454. onMounted(() => {
  455. scrollToBottom()
  456. if (inputRef.value && !isMobile.value)
  457. inputRef.value?.focus()
  458. // 查询公告信息
  459. selectNotice()
  460. // 查询通知信息
  461. selectInform()
  462. })
  463. onUnmounted(() => {
  464. if (loading.value) controller.abort()
  465. homeStore.setMyData({isLoader:false});
  466. })
  467. const local= computed(()=>homeStore.myData.local );
  468. watch(()=>homeStore.myData.act,(n)=>{
  469. if(n=='draw') scrollToBottom();
  470. if(n=='scrollToBottom') scrollToBottom();
  471. if(n=='scrollToBottomIfAtBottom') scrollToBottomIfAtBottom();
  472. if(n=='gpt.submit' || n=='gpt.resubmit'){ loading.value = true;}
  473. if(n=='stopLoading'){ loading.value = false;}
  474. });
  475. const st =ref({inputme:true});
  476. watch( ()=>loading.value ,(n)=> homeStore.setMyData({isLoader:n }))
  477. const ychat = computed( ()=>{
  478. let text= prompt.value
  479. if (loading.value) text= "";
  480. else {
  481. scrollToBottomIfAtBottom();
  482. }
  483. return { text, dateTime: t('chat.preview')} as Chat.Chat;
  484. })
  485. const showModal = ref(false);
  486. const modalContent = ref('<h2>暂无内容</h2>');
  487. const informContent = ref([]);
  488. const noticeId = ref('');
  489. async function selectNotice() {
  490. const [err, result] = await to(getNotice());
  491. console.log("result?.data",result?.data)
  492. if (result?.data) {
  493. showModal.value = true
  494. noticeId.value = result.data.noticeId
  495. modalContent.value = result.data.noticeContent
  496. }
  497. }
  498. async function selectInform() {
  499. const [err, result] = await to(getInform());
  500. if (result?.rows) {
  501. informContent.value = result.rows.length ? result.rows : []
  502. }
  503. }
  504. async function handleClose(){
  505. await to(readNotice(noticeId.value));
  506. }
  507. const gptsList = ref<gptsType[]>([]);
  508. const gptsFilterList = ref<gptsType[]>([]);
  509. const getRandowNum = (Min:number, Max: number):number =>{
  510. const Range = Max - Min + 1
  511. const Rand = Math.random()
  512. return Min + Math.floor(Rand * Range)
  513. }
  514. const load= async ()=>{
  515. // const gptUrl= homeStore.myData.session.gptUrl? homeStore.myData.session.gptUrl :'';
  516. // mlog('load',gptUrl );
  517. // let d;
  518. // if( homeStore.myData.session.gptUrl ){
  519. // d = await my2Fetch( homeStore.myData.session.gptUrl );
  520. // }else {
  521. // d = await myFetch('https://gpts.ddaiai.com/open/gpts');
  522. // }
  523. const params = { pageNum: 1, pageSize: 20 };
  524. const [err, result] = await to(getGpts(params));
  525. if(err){
  526. console.log("err===",err)
  527. }else{
  528. gptsList.value = result.rows as unknown as gptsType[];
  529. }
  530. // gptsList.value = d.gpts as gptsType[];
  531. if(gptsList.value.length && gptsList.value.length > 3) {
  532. gptsFilterList.value = gptsList.value.slice(0, 4)
  533. }
  534. }
  535. const refresh = () => {
  536. gptsFilterList.value = []
  537. let num = gptsList.value[getRandowNum(0, gptsList.value.length - 1)]
  538. let num1 = gptsList.value[getRandowNum(0, gptsList.value.length - 1)]
  539. let num2 = gptsList.value[getRandowNum(0, gptsList.value.length - 1)]
  540. let num3 = gptsList.value[getRandowNum(0, gptsList.value.length - 1)]
  541. let arr = [num, num1, num2, num3]
  542. if(Array.from(new Set(arr)).length != 4) {
  543. refresh()
  544. return
  545. }
  546. gptsFilterList.value = [num, num1, num2, num3]
  547. }
  548. load()
  549. </script>
  550. <template>
  551. <NModal
  552. v-model:show="showModal"
  553. closable @on-after-leave=""
  554. :mask-closable="false"
  555. preset="dialog"
  556. title="公告详情"
  557. positive-text="我已知晓"
  558. @positive-click="handleClose"
  559. >
  560. <div v-html="modalContent"></div>
  561. </NModal>
  562. <div class="flex flex-col w-full h-full chat-content" :class="[isMobile? '' : 'chat-content-noMobile']">
  563. <!-- v-if="isMobile" -->
  564. <!-- <HeaderComponent
  565. :haveData="!!dataSources.length"
  566. :using-context="usingContext"
  567. @export="handleExport"
  568. @handle-clear="handleClear"
  569. /> -->
  570. <main class="flex-1 overflow-hidden">
  571. <div id="scrollRef" ref="scrollRef" class="h-full overflow-hidden overflow-y-auto">
  572. <div
  573. id="image-wrapper"
  574. class="w-full max-w-screen-xl m-auto dark:bg-[#101014]"
  575. :class="[isMobile ? 'p-2' : 'p-4']"
  576. >
  577. <template v-if="!dataSources.length">
  578. <div v-if="homeStore.myData.session.notify" v-html="homeStore.myData.session.notify" class="text-neutral-300 mt-4">
  579. </div>
  580. <div class="gpts-box" v-else>
  581. <br>
  582. <br>
  583. <div v-if="local!=='draw'">
  584. <!-- <h1>{{ href }}</h1>-->
  585. <div class="annou" v-if="informContent.length" :style="{'margin-bottom': isMobile ? '15px' : '30px'}">
  586. <div class="ai-icon">
  587. <IconSvg icon="chatGPT" :width="isMobile ? '32px' : '64px'" :height="isMobile ? '32px' : '64px'"></IconSvg>
  588. </div>
  589. <div class="text" :style="{padding: isMobile? '22px 10px' : '22px 68px'}">
  590. <p class="title">{{ t('chat.annouce') }}</p>
  591. <!-- <p v-for="(item,index) in t('chat.annouceContent').split(';')" :key="index">{{ item }}</p> -->
  592. <div v-for="(item, index) in informContent.slice(0, 1)" :key="index" >
  593. <!-- <p style="margin-top: 10px; font-size: 18px">{{ item.noticeTitle }}</p> -->
  594. <div v-html="item.noticeContent"></div>
  595. </div>
  596. </div>
  597. </div>
  598. <div class="help" v-if="gptsFilterList && gptsFilterList.length">
  599. <div class="ai-icon">
  600. <IconSvg icon="chatGPT" :width="isMobile ? '32px' : '64px'" :height="isMobile ? '32px' : '64px'"></IconSvg>
  601. </div>
  602. <div class="text" :style="{padding: isMobile? '22px 10px' : '22px 68px', 'font-size': isMobile? '14px' : '16px', 'line-height': isMobile? '20px' : '28px'}">
  603. <p class="title">
  604. {{ t('chat.helpTitle') }}
  605. </p>
  606. <p v-for="(item,index) in t('chat.helpcontent').split(';')" :key="index">{{ item }}</p>
  607. <div class="gpts-list">
  608. <div class="refresh" @click="refresh">
  609. <IconSvg icon="refresh"></IconSvg>&nbsp;{{ t('chat.refresh') }}
  610. </div>
  611. <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'}">
  612. <NImage :src="v.logo" :preview-disabled="true" lazy
  613. 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'}">
  614. <template #placeholder>
  615. <div class="w-full h-full justify-center items-center flex" >
  616. <SvgIcon icon="line-md:downloading-loop" class="text-[60px] text-green-300" ></SvgIcon>
  617. </div>
  618. </template>
  619. </NImage>
  620. <div :style="{width: `calc(100% - ${isMobile ? '43px' : '66px'})`, float: 'left', marginLeft: '10px'}">
  621. <p class="info" :title="v.info"> {{ v.info }}</p>
  622. <p @click="goUseGpts(v)" class="name"> {{ t('chat.used') }} {{ v.name }}</p>
  623. </div>
  624. </div>
  625. </div>
  626. </div>
  627. </div>
  628. </div>
  629. </div>
  630. <!-- <div class="flex items-center justify-center mt-4 text-center text-neutral-300" v-else>
  631. <SvgIcon icon="ri:bubble-chart-fill" class="mr-2 text-3xl" />
  632. <span>Aha~</span>
  633. </div> -->
  634. </template>
  635. <template v-else>
  636. <div>
  637. <Message
  638. v-for="(item, index) of dataSources"
  639. :key="index"
  640. :date-time="item.dateTime"
  641. :text="item.text"
  642. :inversion="item.inversion"
  643. :error="item.error"
  644. :loading="item.loading"
  645. @regenerate="onRegenerate(index)"
  646. @delete="handleDelete(index)"
  647. :chat="item"
  648. :index="index"
  649. />
  650. <Message v-if="ychat.text && !homeStore.myData.session.isCloseMdPreview"
  651. :key="dataSources.length" :inversion="true"
  652. :date-time="$t('mj.typing')"
  653. :chat="ychat"
  654. :text="ychat.text"
  655. :index="dataSources.length"
  656. />
  657. <div class="sticky bottom-0 left-0 flex justify-center">
  658. <NButton v-if="loading" type="warning" @click="handleStop">
  659. <template #icon>
  660. <SvgIcon icon="ri:stop-circle-line" />
  661. </template>
  662. {{ t('common.stopResponding') }}
  663. </NButton>
  664. </div>
  665. </div>
  666. </template>
  667. </div>
  668. </div>
  669. </main>
  670. <footer :class="footerClass" class="footer-content" v-if="local!=='draw'">
  671. <div class="w-full max-w-screen-xl m-auto">
  672. <aiGptInput @handle-clear="handleClear" @export="handleExport" v-if="['gpt-4o-mini','gpt-3.5-turbo-16k'].indexOf(gptConfigStore.myData.model)>-1 || st.inputme "
  673. v-model:modelValue="prompt" :disabled="buttonDisabled"
  674. :searchOptions="searchOptions" :renderOption="renderOption"
  675. />
  676. <div class="flex items-center justify-between space-x-2" v-else>
  677. <!--
  678. <HoverButton v-if="!isMobile" @click="handleClear">
  679. <span class="text-xl text-[#4f555e] dark:text-white">
  680. <SvgIcon icon="ri:delete-bin-line" />
  681. </span>
  682. </HoverButton>
  683. <HoverButton v-if="!isMobile" @click="handleExport">
  684. <span class="text-xl text-[#4f555e] dark:text-white">
  685. <SvgIcon icon="ri:download-2-line" />
  686. </span>
  687. </HoverButton>
  688. <HoverButton @click="toggleUsingContext">
  689. <span class="text-xl" :class="{ 'text-[#4b9e5f]': usingContext, 'text-[#a8071a]': !usingContext }">
  690. <SvgIcon icon="ri:chat-history-line" />
  691. </span>
  692. </HoverButton>
  693. -->
  694. <NAutoComplete v-model:value="prompt" :options="searchOptions" :render-label="renderOption">
  695. <template #default="{ handleInput, handleBlur, handleFocus }">
  696. <NInput
  697. ref="inputRef"
  698. v-model:value="prompt"
  699. type="textarea"
  700. :placeholder="placeholder"
  701. :autosize="{ minRows: 1, maxRows: isMobile ? 4 : 8 }"
  702. @input="handleInput"
  703. @focus="handleFocus"
  704. @blur="handleBlur"
  705. @keypress="handleEnter"
  706. />
  707. </template>
  708. </NAutoComplete>
  709. <NButton type="primary" :disabled="buttonDisabled" @click="handleSubmit">
  710. <template #icon>
  711. <span class="dark:text-black">
  712. <SvgIcon icon="ri:send-plane-fill" />
  713. </span>
  714. </template>
  715. </NButton>
  716. </div>
  717. </div>
  718. </footer>
  719. </div>
  720. <drawListVue />
  721. <aiGPT @finished="loading = false" />
  722. <AiSiderInput v-if="isMobile" :button-disabled="false" />
  723. </template>