123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815 |
- <script setup lang='ts'>
- import type { Ref } from 'vue'
- import { computed, onMounted, onUnmounted, ref,watch,h } from 'vue'
- import { useRoute, useRouter } from 'vue-router'
- import { storeToRefs } from 'pinia'
- import { NAutoComplete, NButton, NInput, useDialog, useMessage,NAvatar,NModal,NCard,NImage } from 'naive-ui'
- import html2canvas from 'html2canvas'
- import { Message } from './components'
- import { useScroll } from './hooks/useScroll'
- import { useChat } from './hooks/useChat'
- import { useUsingContext } from './hooks/useUsingContext'
- import { getGpts } from '@/api/chatmsg';
- import { SvgIcon } from '@/components/common'
- import { useBasicLayout } from '@/hooks/useBasicLayout'
- import { gptConfigStore, gptsUlistStore, homeStore, useChatStore, usePromptStore,useAppStore } from '@/store'
- import { chatSetting, fetchChatAPIProcess, gptsType, mlog, myFetch, my2Fetch } from '@/api'
- import { t } from '@/locales'
- import drawListVue from '../mj/drawList.vue'
- import aiGPT from '../mj/aiGpt.vue'
- import AiSiderInput from '../mj/aiSiderInput.vue'
- import aiGptInput from '../mj/aiGptInput.vue'
- import { getNotice,readNotice, getInform } from '@/api/notice'
- import to from "await-to-js";
- let controller = new AbortController()
- const openLongReply = import.meta.env.VITE_GLOB_OPEN_LONG_REPLY === 'true'
- const route = useRoute()
- const dialog = useDialog()
- const ms = useMessage()
- const router = useRouter()
- const chatStore = useChatStore()
- // const href = window.location.href.split('#')[0]
- const href = window.location.hostname;
- const { isMobile } = useBasicLayout()
- const { addChat, updateChat, updateChatSome, getChatByUuidAndIndex } = useChat()
- const { scrollRef, scrollToBottom, scrollToBottomIfAtBottom } = useScroll()
- const { usingContext, toggleUsingContext } = useUsingContext()
- const { uuid } = route.params as { uuid: string }
- const dataSources = computed(() => chatStore.getChatByUuid(+uuid))
- const conversationList = computed(() => dataSources.value.filter(item => (!item.inversion && !!item.conversationOptions)))
- const prompt = ref<string>('')
- const loading = ref<boolean>(false)
- const inputRef = ref<Ref | null>(null)
- // 添加PromptStore
- const promptStore = usePromptStore()
- // 使用storeToRefs,保证store修改后,联想部分能够重新渲染
- const { promptList: promptTemplate } = storeToRefs<any>(promptStore)
- const appStore = useAppStore()
- const isChat = computed(() => appStore.isChat)
- // 未知原因刷新页面,loading 状态不会重置,手动重置
- dataSources.value.forEach((item, index) => {
- 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='';
- }
- 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
- }
- }
- 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
- }
- }
- 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
- }
- },
- })
- }
- 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)
- },
- })
- }
- 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)
- },
- })
- }
- 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()
- }
- }
- }
- function handleStop() {
- if (loading.value) {
- homeStore.setMyData({act:'abort'});
- controller.abort()
- loading.value = false
- }
- }
- // 可优化部分
- // 搜索选项计算,这里使用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 []
- }
- })
- const goUseGpts= async ( item: gptsType)=>{
- const saveObj= {model: `${ item.modelName }` ,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}});
- 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 []
- }
- const placeholder = computed(() => {
- if (isMobile.value)
- return t('chat.placeholderMobile')
- return t('chat.placeholder')
- })
- const buttonDisabled = computed(() => {
- 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
- })
- onMounted(() => {
- scrollToBottom()
- if (inputRef.value && !isMobile.value)
- inputRef.value?.focus()
- // 查询公告信息
- selectNotice()
- // 查询通知信息
- selectInform()
- })
- onUnmounted(() => {
- 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;}
- });
- 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;
- })
- const showModal = ref(false);
- 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
- }
- }
- async function selectInform() {
- const [err, result] = await to(getInform());
- if (result?.rows) {
- informContent.value = result.rows.length ? result.rows : []
- }
- }
- async function handleClose(){
- 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 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 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]
- }
- 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">
- <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>
- <div v-if="local!=='draw'">
- <!-- <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> {{ 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>
- <!-- <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>
- </div>
- </div>
- </footer>
- </div>
- <drawListVue />
- <aiGPT @finished="loading = false" />
- <AiSiderInput v-if="isMobile" :button-disabled="false" />
- </template>
|