| | |
| | | <template> |
| | | <div class="flex flex-col h-screen"> |
| | | <div class="flex flex-nowrap fixed w-full items-baseline top-0 px-6 py-4 bg-gray-100"> |
| | | <div class="text-2xl font-bold">ChatGPT</div> |
| | | <div class="ml-4 text-sm text-gray-500">基于 OpenAI 的 ChatGPT 自然语言模型人工智能对话</div> |
| | | <div class="ml-auto px-3 py-2 text-sm cursor-pointer hover:bg-white rounded-md" @click="clickConfig()">设置</div> |
| | | </div> |
| | | <div class="flex flex-col h-full"> |
| | | <div class="h-full flex flex-col items-center overflow-y-auto"> |
| | | <div ref="chatListDom" class="h-full"> |
| | | <div |
| | | class="group flex px-4 py-4 hover:bg-slate-100 rounded-lg" |
| | | v-for="(item, index) of messageList.filter((v) => v.role !== 'system')" |
| | | :key="index" |
| | | > |
| | | <img class="rounded-full size-12 mr-4" :src="roleImageMap[item.role]" alt="" srcset="" /> |
| | | |
| | | <div class="flex-1 mx-2 mt-20 mb-2" ref="chatListDom"> |
| | | <div |
| | | class="group flex flex-col px-4 py-3 hover:bg-slate-100 rounded-lg" |
| | | v-for="(item, index) of messageList.filter((v) => v.role !== 'system')" |
| | | :key="index" |
| | | > |
| | | <div class="flex justify-between items-center mb-2"> |
| | | <div class="font-bold">{{ roleAlias[item.role] }}:</div> |
| | | <Copy class="invisible group-hover:visible" :content="item.content" /> |
| | | </div> |
| | | <div> |
| | | <div class="prose text-sm text-slate-600 leading-relaxed" v-if="item.content" v-html="md.render(item.content)"></div> |
| | | <Loding v-else /> |
| | | <div class="flex"> |
| | | <div v-if="item.content"> |
| | | <div |
| | | :class="{ 'bg-[#d8d8ff]': item.role === RoleEnum.assistant, 'bg-white': item.role === RoleEnum.user }" |
| | | class="prose text-sm rounded-[6px] p-4 leading-relaxed max-w-[100ch]" |
| | | v-html="md.render(item.content)" |
| | | ></div> |
| | | <div class=""> |
| | | <SvgIcon name="ele-CopyDocument"/> |
| | | <SvgIcon name="ele-Check"/> |
| | | <SvgIcon name="ywicon icon-dianzan"/> |
| | | <SvgIcon name="ywicon icon-buzan"/> |
| | | |
| | | |
| | | </div> |
| | | </div> |
| | | |
| | | <Loding v-else /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="sticky bottom-0 w-full p-6 pb-8 bg-gray-100"> |
| | | <div class="-mt-2 mb-2 text-sm text-gray-500" v-if="isConfig">请输入 API Key:</div> |
| | | <div class="flex"> |
| | | <input |
| | | class="input" |
| | | :type="isConfig ? 'password' : 'text'" |
| | | :placeholder="isConfig ? 'sk-xxxxxxxxxx' : '请输入'" |
| | | v-model="messageContent" |
| | | @keydown.enter="isTalking || sendOrSave()" |
| | | /> |
| | | <button class="btn" :disabled="isTalking" @click="sendOrSave()"> |
| | | {{ isConfig ? '保存' : '发送' }} |
| | | </button> |
| | | </div> |
| | | <div class="sticky bottom-0 w-full p-6 pb-8 bg-gray-100 flex justify-center"> |
| | | <PlayBar :isTalking="isTalking" v-model="messageContent" @sendClick="sendOrSave" /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import type { ChatMessage } from './types'; |
| | | import { ref, watch, nextTick, onMounted } from 'vue'; |
| | | import cryptoJS from 'crypto-js'; |
| | | import { nextTick, onMounted, ref, watch } from 'vue'; |
| | | import Loding from './components/Loding.vue'; |
| | | import Copy from './components/Copy.vue'; |
| | | import { md } from './libs/markdown'; |
| | | import { RoleEnum, type ChatMessage, roleImageMap } from './types'; |
| | | import PlayBar from '/@/components/chat/components/playBar/PlayBar.vue'; |
| | | import router from '/@/router'; |
| | | |
| | | let apiKey = ''; |
| | | let isConfig = ref(false); |
| | | let isTalking = ref(false); |
| | |
| | | const roleAlias = { user: 'ME', assistant: 'ChatGPT', system: 'System' }; |
| | | const messageList = ref<ChatMessage[]>([ |
| | | { |
| | | role: 'system', |
| | | content: '你是 ChatGPT,OpenAI 训练的大型语言模型,尽可能简洁地回答。', |
| | | }, |
| | | { |
| | | role: 'assistant', |
| | | role: RoleEnum.assistant, |
| | | content: `你好,我是AI语言模型,我可以提供一些常用服务和信息,例如: |
| | | |
| | | 1. 翻译:我可以把中文翻译成英文,英文翻译成中文,还有其他一些语言翻译,比如法语、日语、西班牙语等。 |
| | |
| | | |
| | | 请告诉我你需要哪方面的帮助,我会根据你的需求给你提供相应的信息和建议。`, |
| | | }, |
| | | { |
| | | role: RoleEnum.user, |
| | | content: `你好`, |
| | | }, |
| | | ]); |
| | | |
| | | onMounted(() => { |
| | | if (getAPIKey()) { |
| | | switchConfigStatus(); |
| | | } |
| | | |
| | | const inputValue = history.state.inputValue; |
| | | }); |
| | | |
| | | const sendChatMessage = async (content: string = messageContent.value) => { |
| | |
| | | if (messageList.value.length === 2) { |
| | | messageList.value.pop(); |
| | | } |
| | | messageList.value.push({ role: 'user', content }); |
| | | messageList.value.push({ role: RoleEnum.user, content }); |
| | | clearMessageContent(); |
| | | messageList.value.push({ role: 'assistant', content: '' }); |
| | | messageList.value.push({ role: RoleEnum.assistant, content: '' }); |
| | | |
| | | // const { body, status } = await chat(messageList.value, getAPIKey()); |
| | | // if (body) { |
| | |
| | | // } |
| | | const a = new Promise<string>((resolve) => { |
| | | setTimeout(() => { |
| | | resolve('nihao '); |
| | | resolve('你好 '); |
| | | }, 500); |
| | | }); |
| | | |
| | |
| | | |
| | | const scrollToBottom = () => { |
| | | if (!chatListDom.value) return; |
| | | scrollTo(0, chatListDom.value.scrollHeight); |
| | | chatListDom.value.lastElementChild.scrollIntoView(); |
| | | // scrollTo(0, chatListDom.value.scrollHeight); |
| | | }; |
| | | |
| | | watch(messageList.value, () => nextTick(() => scrollToBottom())); |