12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091 |
- <script setup lang="ts">
- import type { Ref } from "vue";
- import { computed, h, nextTick, onMounted, onUnmounted, ref, watch } from "vue";
- import { useRoute, useRouter } from "vue-router";
- import { storeToRefs } from "pinia";
- import {
- NAutoComplete,
- NAvatar,
- NButton,
- NImage,
- NInput,
- NModal,
- useDialog,
- useMessage,
- } from "naive-ui";
- import html2canvas from "html2canvas";
- import to from "await-to-js";
- import { AiEditor } from "aieditor";
- 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 { 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,
- useAppStore,
- useChatStore,
- usePromptStore,
- } from "@/store";
- import type { gptsType } from "@/api";
- import { chatSetting, fetchChatAPIProcess, mlog } from "@/api";
- import { t } from "@/locales";
- import { getInform, getNotice, readNotice } from "@/api/notice";
- import "aieditor/dist/style.css";
- import extendIcon from "@/assets/icons/extend.svg?raw";
- import abbrIcon from "@/assets/icons/abbr.svg?raw";
- import { getToken } from "@/store/modules/auth/helper";
- import { copyToClip } from "@/utils/copy";
- import { exportDoc } from "./components/index";
- 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 uuid1 = chatStore.active;
- const chatSet = new chatSetting(uuid1 == null ? 1002 : uuid1);
- const nGptStore = ref(chatSet.getGptConfig());
- console.log("nGptStore", nGptStore);
- 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);
- const divRef = ref();
- let aiEditor: AiEditor | null = null;
- const editedMessage = ref();
- const editedMessageIndex = ref();
- const showDialog = ref(false);
- const aiPanel = ref(false);
- // 添加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
- const 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;
- }
- }
- function handleEdit(index: number) {
- if (loading.value) return;
- editedMessageIndex.value = index;
- editedMessage.value = dataSources.value[index].text.slice();
- showDialog.value = true;
- nextTick(() => {
- aiEditor = new AiEditor({
- element: divRef.value as Element,
- toolbarKeys: [],
- placeholder: "点击输入内容...",
- content: editedMessage.value,
- contentIsMarkdown: true,
- textSelectionBubbleMenu: {
- enable: true,
- items: ["ai", "Bold", "Italic", "Underline", "Strike"],
- },
- ai: {
- models: {
- custom: {
- url: "http://127.0.0.1:6039/chat/send",
- headers: () => {
- return {
- "Content-Type": "application/json;charset=UTF-8",
- Authorization: "Bearer " + getToken(),
- Accept: "text/event-stream ",
- };
- },
- wrapPayload: (message: string) => {
- return JSON.stringify({
- messages: [
- {
- role: "user",
- content: message,
- },
- ],
- model: nGptStore.value.modelLabel,
- });
- },
- parseMessage: (message: any) => {
- const data = JSON.parse(message);
- const msg =
- data.choices[0].delta?.content ??
- data.choices[0].delta?.reasoning_content ??
- "";
- return {
- role: "assistant",
- content: msg,
- // index: number,
- // //0 代表首个文本结果;1 代表中间文本结果;2 代表最后一个文本结果。
- // status: 0|1|2,
- };
- },
- protocol: "sse",
- },
- },
- bubblePanelMenus: [
- {
- prompt: `<content>{content}</content>\n请帮我优化一下这段内容,并直接返回优化后的结果。\n注意:你应该先判断一下这句话是中文还是英文,如果是中文,请给我返回中文的内容,如果是英文,请给我返回英文内容,只需要返回内容即可,不需要告知我是中文还是英文。`,
- icon: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M15.1986 9.94447C14.7649 9.5337 14.4859 8.98613 14.4085 8.39384L14.0056 5.31138L11.275 6.79724C10.7503 7.08274 10.1433 7.17888 9.55608 7.06948L6.49998 6.50015L7.06931 9.55625C7.17871 10.1435 7.08257 10.7505 6.79707 11.2751L5.31121 14.0057L8.39367 14.4086C8.98596 14.4861 9.53353 14.7651 9.94431 15.1987L12.0821 17.4557L13.4178 14.6486C13.6745 14.1092 14.109 13.6747 14.6484 13.418L17.4555 12.0823L15.1986 9.94447ZM15.2238 15.5079L13.0111 20.1581C12.8687 20.4573 12.5107 20.5844 12.2115 20.442C12.1448 20.4103 12.0845 20.3665 12.0337 20.3129L8.49229 16.5741C8.39749 16.474 8.27113 16.4096 8.13445 16.3918L3.02816 15.7243C2.69958 15.6814 2.46804 15.3802 2.51099 15.0516C2.52056 14.9784 2.54359 14.9075 2.5789 14.8426L5.04031 10.3192C5.1062 10.1981 5.12839 10.058 5.10314 9.92253L4.16 4.85991C4.09931 4.53414 4.3142 4.22086 4.63997 4.16017C4.7126 4.14664 4.78711 4.14664 4.85974 4.16017L9.92237 5.10331C10.0579 5.12855 10.198 5.10637 10.319 5.04048L14.8424 2.57907C15.1335 2.42068 15.4979 2.52825 15.6562 2.81931C15.6916 2.88421 15.7146 2.95507 15.7241 3.02833L16.3916 8.13462C16.4095 8.2713 16.4739 8.39766 16.5739 8.49245L20.3127 12.0338C20.5533 12.2617 20.5636 12.6415 20.3357 12.8821C20.2849 12.9357 20.2246 12.9795 20.1579 13.0112L15.5078 15.224C15.3833 15.2832 15.283 15.3835 15.2238 15.5079ZM16.0206 17.435L17.4348 16.0208L21.6775 20.2634L20.2633 21.6776L16.0206 17.435Z"></path></svg>`,
- title: "润色",
- },
- {
- prompt: `<content>{content}</content>\n请帮我扩写一下这段内容,并直接返回扩写后的结果。\n注意:你应该先判断一下这句话是中文还是英文,如果是中文,请给我返回中文的内容,如果是英文,请给我返回英文内容,只需要返回内容即可,不需要告知我是中文还是英文。`,
- icon: extendIcon,
- title: "扩写",
- },
- {
- prompt: `<content>{content}</content>\n请帮我缩写一下这段内容,并直接返回缩写后的结果。\n注意:你应该先判断一下这句话是中文还是英文,如果是中文,请给我返回中文的内容,如果是英文,请给我返回英文内容,只需要返回内容即可,不需要告知我是中文还是英文。`,
- icon: abbrIcon,
- title: "缩写",
- },
- ],
- },
- });
- });
- }
- const submitCallback = () => {
- const markdownData = aiEditor?.getMarkdown();
- updateChatSome(+uuid, editedMessageIndex.value, {
- text: markdownData,
- });
- showDialog.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(() => {
- aiEditor && aiEditor.destroy();
- 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 = [];
- const num = gptsList.value[getRandowNum(0, gptsList.value.length - 1)];
- const num1 = gptsList.value[getRandowNum(0, gptsList.value.length - 1)];
- const num2 = gptsList.value[getRandowNum(0, gptsList.value.length - 1)];
- const num3 = gptsList.value[getRandowNum(0, gptsList.value.length - 1)];
- const arr = [num, num1, num2, num3];
- if (Array.from(new Set(arr)).length != 4) {
- refresh();
- return;
- }
- gptsFilterList.value = [num, num1, num2, num3];
- };
- load();
- function closeModel() {
- submitCallback();
- showDialog.value = false;
- }
- async function handleCopy(txt?: string) {
- const markdownData = aiEditor?.getMarkdown();
- console.log("txt", txt);
- console.log("markdownData", markdownData);
- try {
- await copyToClip(txt || markdownData || "");
- ms.success(t("chat.copied"));
- } catch {
- ms.error(t("mj.copyFail"));
- }
- }
- function exportDocHandel() {
- const markdownData = aiEditor?.getMarkdown();
- exportDoc(markdownData);
- }
- </script>
- <template>
- <NModal
- v-model:show="showModal"
- closable
- :mask-closable="false"
- preset="dialog"
- title="公告详情"
- positive-text="我已知晓"
- @on-after-leave=""
- @positive-click="handleClose"
- >
- <div v-html="modalContent" />
- </NModal>
- <NModal
- class="editModel"
- v-model:show="showDialog"
- style="width: 100vw; height: 100vh; max-width: 100vw"
- >
- <div class="flex flex-row w-[100%] h-[100%]">
- <div class="leftContent basis-1/3">111</div>
- <div class="rightContent basis-2/3">
- <div class="top">
- <div class="flex flex-row float-right">
- <div class="tool" @click="handleCopy()">
- <SvgIcon icon="ri-file-copy-line" class="text-xl" />
- <span>复制</span>
- </div>
- <div class="tool ml-5 mr-5" @click="exportDocHandel()">
- <SvgIcon icon="ri-download-line" class="text-xl" />
- <span>下载</span>
- </div>
- |
- <div class="tool">
- <SvgIcon
- icon="ri-close-large-line"
- class="text-xl"
- @click="closeModel"
- />
- </div>
- </div>
- </div>
- <div ref="divRef" style="height: calc(100vh - 50px)"></div>
- </div>
- </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"
- class="text-neutral-300 mt-4"
- v-html="homeStore.myData.session.notify"
- />
- <div v-else class="gpts-box">
- <br />
- <br />
- <div v-if="local !== 'draw'">
- <!-- <h1>{{ href }}</h1> -->
- <div
- v-if="informContent.length"
- class="annou"
- :style="{ 'margin-bottom': isMobile ? '15px' : '30px' }"
- >
- <div class="ai-icon">
- <IconSvg
- icon="chatGPT"
- :width="isMobile ? '32px' : '64px'"
- :height="isMobile ? '32px' : '64px'"
- />
- </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
- v-if="gptsFilterList && gptsFilterList.length"
- class="help"
- >
- <div class="ai-icon">
- <IconSvg
- icon="chatGPT"
- :width="isMobile ? '32px' : '64px'"
- :height="isMobile ? '32px' : '64px'"
- />
- </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" /> {{ 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"
- />
- </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 class="name" @click="goUseGpts(v)">
- {{ 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"
- :chat="item"
- :index="index"
- @regenerate="onRegenerate(index)"
- @delete="handleDelete(index)"
- @edit="handleEdit(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 v-if="local !== 'draw'" :class="footerClass" class="footer-content">
- <div class="w-full max-w-screen-xl m-auto">
- <aiGptInput
- v-if="
- ['gpt-4o-mini', 'gpt-3.5-turbo-16k'].indexOf(
- gptConfigStore.myData.model
- ) > -1 || st.inputme
- "
- v-model:modelValue="prompt"
- :disabled="buttonDisabled"
- :search-options="searchOptions"
- :render-option="renderOption"
- @handle-clear="handleClear"
- @export="handleExport"
- />
- <div v-else class="flex items-center justify-between space-x-2">
- <!--
- <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>
- <style lang="less" scoped>
- .leftContent {
- background-color: #f9fafb;
- box-shadow: inset 0px -4px 15px 0px #e9eced;
- }
- .rightContent {
- .top {
- height: 50px;
- line-height: 50px;
- background-color: #fff;
- .tool {
- display: flex;
- align-items: center;
- justify-content: space-around;
- width: 60px;
- cursor: pointer;
- }
- }
- }
- </style>
|