|
@@ -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>
|