浏览代码

暂时重置之前版本

wanggaokun 1 周之前
父节点
当前提交
8c5518af94

+ 187 - 132
src/views/chat/components/Message/Text.vue

@@ -1,167 +1,222 @@
 <script lang="ts" setup>
-import { computed, onMounted, onUnmounted, onUpdated, ref } from 'vue'
-import MarkdownIt from 'markdown-it'
-import mdKatex from '@traptitech/markdown-it-katex'
-import mila from 'markdown-it-link-attributes'
-import hljs from 'highlight.js'
-import { useBasicLayout } from '@/hooks/useBasicLayout'
-import { t } from '@/locales'
-import { copyToClip } from '@/utils/copy'
-
-import mjText from '@/views/mj/mjText.vue'
-import dallText from '@/views/mj/dallText.vue'
-import ttsText from '@/views/mj/ttsText.vue'
-import whisperText from '@/views/mj/whisperText.vue'
-import MjTextAttr from '@/views/mj/mjTextAttr.vue'
-import aiTextSetting from '@/views/mj/aiTextSetting.vue'
-import aiSetAuth from '@/views/mj/aiSetAuth.vue'
-import { isApikeyError, isAuthSessionError, isTTS, mlog } from '@/api'
+import { computed, onMounted, onUnmounted, onUpdated, ref } from "vue";
+import MarkdownIt from "markdown-it";
+import mdKatex from "@traptitech/markdown-it-katex";
+import mila from "markdown-it-link-attributes";
+import hljs from "highlight.js";
+import { useBasicLayout } from "@/hooks/useBasicLayout";
+import { t } from "@/locales";
+import { copyToClip } from "@/utils/copy";
+
+import mjText from "@/views/mj/mjText.vue";
+import dallText from "@/views/mj/dallText.vue";
+import ttsText from "@/views/mj/ttsText.vue";
+import whisperText from "@/views/mj/whisperText.vue";
+import MjTextAttr from "@/views/mj/mjTextAttr.vue";
+import aiTextSetting from "@/views/mj/aiTextSetting.vue";
+import aiSetAuth from "@/views/mj/aiSetAuth.vue";
+import { isApikeyError, isAuthSessionError, isTTS, mlog } from "@/api";
 
 interface Props {
-  inversion?: boolean
-  error?: boolean
-  text?: string
-  loading?: boolean
-  asRawText?: boolean
-  chat:Chat.Chat
+	inversion?: boolean;
+	error?: boolean;
+	text?: string;
+	loading?: boolean;
+	asRawText?: boolean;
+	chat: Chat.Chat;
 }
 
-const props = defineProps<Props>()
+const props = defineProps<Props>();
 
-const { isMobile } = useBasicLayout()
+const { isMobile } = useBasicLayout();
 
-const textRef = ref<HTMLElement>()
+const textRef = ref<HTMLElement>();
 
 const mdi = new MarkdownIt({
-  html: false,
-  linkify: true,
-  highlight(code, language) {
-    const validLang = !!(language && hljs.getLanguage(language))
-    if (validLang) {
-      const lang = language ?? ''
-      return highlightBlock(hljs.highlight(code, { language: lang }).value, lang)
-    }
-    return highlightBlock(hljs.highlightAuto(code).value, '')
-  },
-})
-
-mdi.use(mila, { attrs: { target: '_blank', rel: 'noopener' } })
-mdi.use(mdKatex, { blockClass: 'katexmath-block rounded-md p-[10px]', errorColor: ' #cc0000' })
+	html: false,
+	linkify: true,
+	highlight(code, language) {
+		const validLang = !!(language && hljs.getLanguage(language));
+		if (validLang) {
+			const lang = language ?? "";
+			return highlightBlock(
+				hljs.highlight(code, { language: lang }).value,
+				lang
+			);
+		}
+		return highlightBlock(hljs.highlightAuto(code).value, "");
+	},
+});
+
+mdi.use(mila, { attrs: { target: "_blank", rel: "noopener" } });
+mdi.use(mdKatex, {
+	blockClass: "katexmath-block rounded-md p-[10px]",
+	errorColor: " #cc0000",
+});
 
 const wrapClass = computed(() => {
-  return [
-    'text-wrap',
-    'min-w-[20px]','max-w-[810px]',
-    'rounded-md',
-    isMobile.value ? 'p-2' : 'px-3 py-2',
-    props.inversion ? 'bg-[#d2f9d1]' : 'bg-[#f4f6f8]',
-    props.inversion ? 'dark:bg-[#a1dc95]' : 'dark:bg-[#1e1e20]',
-    props.inversion ? 'message-request' : 'message-reply',
-    { 'text-red-500': props.error },
-  ]
-})
+	return [
+		"text-wrap",
+		"min-w-[20px]",
+		"max-w-[810px]",
+		"rounded-md",
+		isMobile.value ? "p-2" : "px-3 py-2",
+		props.inversion ? "bg-[#d2f9d1]" : "bg-[#f4f6f8]",
+		props.inversion ? "dark:bg-[#a1dc95]" : "dark:bg-[#1e1e20]",
+		props.inversion ? "message-request" : "message-reply",
+		{ "text-red-500": props.error },
+	];
+});
 
 const text = computed(() => {
-  let value = props.text ?? ''
-  if (!props.asRawText){
-    value = value.replace(/\\\( *(.*?) *\\\)/g, '$$$1$$');
-    //value = value.replace(/\\\((.*?)\\\)/g, '$$$1$$');
-    value = value.replace(/\\\[ *(.*?) *\\\]/g, '$$$$$1$$$$');
-    //
-    value= value.replaceAll('\\[',"$$$$")
-    value= value.replaceAll('\\]',"$$$$")   
-
-    //思考过程处理
-    //value= value.replace(/<think>([\s\S]*?)<\/think>/g, (match: string, content: string) => { 
-    value= value.replace(/<think>([\s\S]*?)(?=<\/think>|$)/g, (match: string, content: string) => { 
-      const processedContent: string = content
-        .split('\n')
-        .map(line => line.trim() ? '>' + line : line)  
-        .join('\n').replace(/(\r?\n)+/g, '\n>\n');
-       
-      return ">Thinking..."+(processedContent) ;
-    });
-    value= value.replaceAll('</think>','')
-    //mlog('replace', value)
-    return mdi.render(value) 
-  }
-  return value
-})
+	let value = props.text ?? "";
+	if (!props.asRawText) {
+		value = value.replace(/\\\( *(.*?) *\\\)/g, "$$$1$$");
+		//value = value.replace(/\\\((.*?)\\\)/g, '$$$1$$');
+		value = value.replace(/\\\[ *(.*?) *\\\]/g, "$$$$$1$$$$");
+		//
+		value = value.replaceAll("\\[", "$$$$");
+		value = value.replaceAll("\\]", "$$$$");
+
+		//思考过程处理
+		//value= value.replace(/<think>([\s\S]*?)<\/think>/g, (match: string, content: string) => {
+		value = value.replace(
+			/<think>([\s\S]*?)(?=<\/think>|$)/g,
+			(match: string, content: string) => {
+				const processedContent: string = content
+					.split("\n")
+					.map((line) => (line.trim() ? ">" + line : line))
+					.join("\n")
+					.replace(/(\r?\n)+/g, "\n>\n");
+
+				return ">Thinking..." + processedContent;
+			}
+		);
+		value = value.replaceAll("</think>", "");
+		//mlog('replace', value)
+		return mdi.render(value);
+	}
+	return value;
+});
 
 function highlightBlock(str: string, lang?: string) {
-  return `<pre class="code-block-wrapper"><div class="code-block-header"><span class="code-block-header__lang">${lang}</span><span class="code-block-header__copy">${t('chat.copyCode')}</span></div><code class="hljs code-block-body ${lang}">${str}</code></pre>`
+	return `<pre class="code-block-wrapper"><div class="code-block-header"><span class="code-block-header__lang">${lang}</span><span class="code-block-header__copy">${t(
+		"chat.copyCode"
+	)}</span></div><code class="hljs code-block-body ${lang}">${str}</code></pre>`;
 }
 
 function addCopyEvents() {
-  if (textRef.value) {
-    const copyBtn = textRef.value.querySelectorAll('.code-block-header__copy')
-    copyBtn.forEach((btn) => {
-      btn.addEventListener('click', () => {
-        const code = btn.parentElement?.nextElementSibling?.textContent
-        if (code) {
-          copyToClip(code).then(() => {
-            btn.textContent = '复制成功'
-            setTimeout(() => {
-              btn.textContent = '复制代码'
-            }, 1000)
-          })
-        }
-      })
-    })
-  }
+	if (textRef.value) {
+		const copyBtn = textRef.value.querySelectorAll(".code-block-header__copy");
+		copyBtn.forEach((btn) => {
+			btn.addEventListener("click", () => {
+				const code = btn.parentElement?.nextElementSibling?.textContent;
+				if (code) {
+					copyToClip(code).then(() => {
+						btn.textContent = "复制成功";
+						setTimeout(() => {
+							btn.textContent = "复制代码";
+						}, 1000);
+					});
+				}
+			});
+		});
+	}
 }
 
 function removeCopyEvents() {
-  if (textRef.value) {
-    const copyBtn = textRef.value.querySelectorAll('.code-block-header__copy')
-    copyBtn.forEach((btn) => {
-      btn.removeEventListener('click', () => {})
-    })
-  }
+	if (textRef.value) {
+		const copyBtn = textRef.value.querySelectorAll(".code-block-header__copy");
+		copyBtn.forEach((btn) => {
+			btn.removeEventListener("click", () => {});
+		});
+	}
 }
 
 onMounted(() => {
-  addCopyEvents()
-})
+	addCopyEvents();
+});
 
 onUpdated(() => {
-  addCopyEvents()
-})
+	addCopyEvents();
+});
 
 onUnmounted(() => {
-  removeCopyEvents()
-})
+	removeCopyEvents();
+});
 </script>
 
 <template>
-  <div class="text-black" :class="wrapClass">
-    <div ref="textRef" class="leading-relaxed break-words">
-      <div v-if="!inversion">
-        <aiTextSetting v-if="!inversion && isApikeyError(text)"/>
-        <aiSetAuth v-if="!inversion && isAuthSessionError(text)" />
-          
-        <dallText :chat="chat" v-if=" chat.model && chat.model?.indexOf('chat') == -1" class="whitespace-pre-wrap" />
-        <mjText v-if="chat.mjID" class="whitespace-pre-wrap" :chat="chat" :mdi="mdi"></mjText>
-        <ttsText v-else-if="chat.model && isTTS(chat.model) && chat.text=='ok'" :chat="chat"/>
-        <template v-else>
-          <div v-if="!asRawText" class="markdown-body" :class="{ 'markdown-body-generate': loading }" v-html="text" />
-          <div v-else class="whitespace-pre-wrap" v-text="text" />
-        </template>
-      </div>
-      <whisperText v-else-if="text=='whisper' && chat.opt?.lkey "  :chat="chat" />
-      <div v-else-if="asRawText" class="whitespace-pre-wrap" v-text="text" />
-      <div v-else class="markdown-body "  style="--color-fg-default:#24292f"  v-html="text" />
-      <!-- <div v-else class="whitespace-pre-wrap" v-text="text" /> -->
-      <MjTextAttr :image="chat.opt?.images[0]" v-if="chat.opt?.images"></MjTextAttr>
-      <whisperText v-if="chat.model && chat.model.indexOf('whisper')>-1 && chat.opt?.lkey " :isW="true"  :chat="chat" class="w-full" />
-      <ttsText v-if="!inversion && chat.opt?.duration && chat.opt?.duration>0 && chat.opt?.lkey " :isW="true"  :chat="chat" class="w-full" />
-
-      
-
-    </div>
-  </div>
+	<div class="text-black" :class="wrapClass">
+		<div ref="textRef" class="leading-relaxed break-words">
+			<div v-if="!inversion">
+				<aiTextSetting v-if="!inversion && isApikeyError(text)" />
+				<aiSetAuth v-if="!inversion && isAuthSessionError(text)" />
+
+				<dallText
+					:chat="chat"
+					v-if="chat.model && chat.model?.indexOf('chat') == -1"
+					class="whitespace-pre-wrap"
+				/>
+				<mjText
+					v-if="chat.mjID"
+					class="whitespace-pre-wrap"
+					:chat="chat"
+					:mdi="mdi"
+				></mjText>
+				<ttsText
+					v-else-if="chat.model && isTTS(chat.model) && chat.text == 'ok'"
+					:chat="chat"
+				/>
+				<template v-else>
+					<div
+						v-if="!asRawText"
+						class="markdown-body"
+						:class="{ 'markdown-body-generate': loading }"
+						v-html="text"
+					/>
+					<div v-else class="whitespace-pre-wrap" v-text="text" />
+				</template>
+			</div>
+			<whisperText
+				v-else-if="text == 'whisper' && chat.opt?.lkey"
+				:chat="chat"
+			/>
+			<div v-else-if="asRawText" class="whitespace-pre-wrap" v-text="text" />
+			<div
+				v-else
+				class="markdown-body"
+				style="--color-fg-default: #24292f"
+				v-html="text"
+			/>
+			<!-- <div v-else class="whitespace-pre-wrap" v-text="text" /> -->
+			<MjTextAttr
+				:image="chat.opt?.images[0]"
+				v-if="chat.opt?.images"
+			></MjTextAttr>
+			<whisperText
+				v-if="
+					chat.model && chat.model.indexOf('whisper') > -1 && chat.opt?.lkey
+				"
+				:isW="true"
+				:chat="chat"
+				class="w-full"
+			/>
+			<ttsText
+				v-if="
+					!inversion &&
+					chat.opt?.duration &&
+					chat.opt?.duration > 0 &&
+					chat.opt?.lkey
+				"
+				:isW="true"
+				:chat="chat"
+				class="w-full"
+			/>
+		</div>
+	</div>
 </template>
 
 <style lang="less">
 @import url(./style.less);
-</style>
+</style>

+ 7 - 49
src/views/chat/components/Message/index.vue

@@ -11,6 +11,7 @@ import { copyToClip } from "@/utils/copy";
 import { homeStore } from "@/store";
 import { getSeed, mlog } from "@/api";
 import { marked } from "marked";
+import { exportDoc } from "../index";
 
 interface Props {
 	dateTime?: string;
@@ -116,58 +117,10 @@ function handleSelect(
 			emit("edit");
 			return;
 		case "download":
-			// exportToWord();
-			exportDoc();
+			exportDoc(props.text);
 	}
 }
 
-// 将 Markdown 转换为 HTML
-function convertMarkdownToHtml(markdown: any) {
-	return marked.parse(markdown);
-}
-
-async function exportDoc() {
-	let title;
-	const htmlContent = convertMarkdownToHtml(props.text) as string;
-	const titleStart = htmlContent.indexOf("<p>");
-	const titleEnd = htmlContent.indexOf("</p>");
-	if (typeof titleStart == "number" && titleEnd) {
-		const data = htmlContent.substring(titleStart + 3, titleEnd);
-		if (data.includes("<strong>")) {
-			const titleS = data.indexOf("<strong>");
-			const titleE = data.indexOf("</strong>");
-			title = data.substring(titleS + 8, titleE);
-		} else {
-			title = data;
-		}
-	}
-	// 将富文本内容拼接为一个完整的html
-	const value = `<!DOCTYPE html>
-  <html lang="zh-CN">
-    <head>
-      <meta charset="UTF-8">
-      <title>文档</title>
-    </head>
-    <body>
-        ${htmlContent}
-    </body>
-  </html>`;
-	// const title= htmlContent
-	const data = new Blob([value]);
-	const a = document.createElement("a");
-	a.href = window.URL.createObjectURL(data);
-	a.setAttribute("download", `${title ? title : "导出内容"}.docx`);
-	// a.setAttribute("download", ".docx");
-	a.click();
-	// 下载后将标签移除
-	a.remove();
-}
-
-function handleRegenerate() {
-	messageRef.value?.scrollIntoView();
-	emit("regenerate");
-}
-
 async function handleCopy(txt?: string) {
 	try {
 		await copyToClip(txt || props.text || "");
@@ -190,6 +143,10 @@ function handleRegenerate2() {
 		actData: { index: props.index, uuid: props.chat.uuid },
 	});
 }
+
+function editHandle() {
+	emit("edit");
+}
 </script>
 
 <template>
@@ -244,6 +201,7 @@ function handleRegenerate2() {
 					:loading="loading"
 					:as-raw-text="asRawText"
 					:chat="chat"
+					@dblclick="editHandle"
 				/>
 				<!-- <div class="flex flex-col" v-if="!chat.mjID && chat.model!='dall-e-3' && chat.model!='dall-e-2' "> -->
 				<div class="flex flex-col" v-if="!chat.mjID">

+ 44 - 1
src/views/chat/components/index.ts

@@ -1,3 +1,46 @@
 import Message from './Message/index.vue'
+import { marked } from "marked";
 
-export { Message }
+async function exportDoc(text:any) {
+	const htmlContent = convertMarkdownToHtml(text) as string;
+  let title;
+			const titleStart = htmlContent.indexOf("<p>");
+			const titleEnd = htmlContent.indexOf("</p>");
+			if (typeof titleStart == "number" && titleEnd) {
+				const data = htmlContent.substring(titleStart + 3, titleEnd);
+				if (data.includes("<strong>")) {
+					const titleS = data.indexOf("<strong>");
+					const titleE = data.indexOf("</strong>");
+					title = data.substring(titleS + 8, titleE);
+				} else {
+					title = data;
+				}
+			}
+	// 将富文本内容拼接为一个完整的html
+	const value = `<!DOCTYPE html>
+  <html lang="zh-CN">
+    <head>
+      <meta charset="UTF-8">
+      <title>文档</title>
+    </head>
+    <body>
+        ${htmlContent}
+    </body>
+  </html>`;
+	// const title= htmlContent
+	const data = new Blob([value]);
+	const a = document.createElement("a");
+	a.href = window.URL.createObjectURL(data);
+	a.setAttribute("download", `${title ? title : "导出内容"}.docx`);
+	// a.setAttribute("download", ".docx");
+	a.click();
+	// 下载后将标签移除
+	a.remove();
+}
+
+// 将 Markdown 转换为 HTML
+function convertMarkdownToHtml(markdown: any) {
+	return marked.parse(markdown);
+}
+
+export { Message,exportDoc }

+ 75 - 12
src/views/chat/index.vue

@@ -43,6 +43,9 @@ 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";
@@ -440,6 +443,7 @@ function handleEdit(index: number) {
 	nextTick(() => {
 		aiEditor = new AiEditor({
 			element: divRef.value as Element,
+			toolbarKeys: [],
 			placeholder: "点击输入内容...",
 			content: editedMessage.value,
 			contentIsMarkdown: true,
@@ -727,6 +731,30 @@ const refresh = () => {
 	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>
@@ -743,20 +771,35 @@ load();
 		<div v-html="modalContent" />
 	</NModal>
 	<NModal
+		class="editModel"
 		v-model:show="showDialog"
-		style="width: 80%"
-		preset="dialog"
-		positive-text="确认"
-		negative-text="算了"
-		title="确认"
-		@positive-click="submitCallback"
-		@negative-click="showDialog = false"
+		style="width: 100vw; height: 100vh; max-width: 100vw"
 	>
-		<div ref="divRef" style="height: 700px; position: relative">
-			<div
-				v-show="aiPanel"
-				:style="`width:500px;height:200px;position: absolute;top: 200px;left: 200px;background-color: aqua`"
-			></div>
+		<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>
 
@@ -1026,3 +1069,23 @@ load();
 	<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>