507 lines
11 KiB
Plaintext
507 lines
11 KiB
Plaintext
<template>
|
||
<view class="msg-root">
|
||
<text v-if="msg.error_msg != null" class="error-msg">
|
||
{{ msg.error_msg }}
|
||
</text>
|
||
<template v-else>
|
||
<!-- AI 思考模式 -->
|
||
<view class="think-container" v-if="!!msg.thinkContent || !!msg.think">
|
||
<view class="think-header">
|
||
<view class="think-header-left" @tap="changeThinkContent">
|
||
<text class="iconfont icon-BulbOutlined thinking-icon"></text>
|
||
<text class="thinking-title">
|
||
{{
|
||
isThinkingFinished
|
||
? t("Mobile.Chat.thought")
|
||
: t("Mobile.Chat.thinking")
|
||
}}
|
||
</text>
|
||
<text
|
||
class="iconfont icon-a-Chevrondown expand-icon"
|
||
:class="{ 'is-expanded': !hideThinkContent }"
|
||
></text>
|
||
</view>
|
||
</view>
|
||
<view
|
||
class="think-content"
|
||
:class="{ 'hide-think-content': hideThinkContent }"
|
||
>
|
||
<view class="text-box">
|
||
<mp-html
|
||
:content="msg.thinkContent || msg.think"
|
||
:markdown="true"
|
||
:selectable="true"
|
||
:container-style="`font-size: 28rpx; color: #888; line-height: 1.6;`"
|
||
/>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- AI 答复内容 -->
|
||
<view class="answer-container">
|
||
<!-- AI校验通过后,需要水平滚动表格时,需要设置 scroll-table="true" -->
|
||
<mp-html
|
||
:content="processedText"
|
||
:processing-list="msg.processingList"
|
||
:conversation-id="msg.chat_id"
|
||
:markdown="true"
|
||
:selectable="true"
|
||
:container-style="`width: 100%;`"
|
||
:tag-style="tagStyle"
|
||
:scroll-table="true"
|
||
/>
|
||
</view>
|
||
</template>
|
||
<image
|
||
v-if="
|
||
isConversationActive && isLastMsg && taskStatus !== TaskStatus.EXECUTING
|
||
"
|
||
class="icon-loading-image"
|
||
src="@/static/assets/icon_loading.svg"
|
||
:show-menu-by-longpress="false"
|
||
@longtap.stop.prevent
|
||
@contextmenu.stop.prevent="true"
|
||
mode="widthFix"
|
||
/>
|
||
<msg-tool-bar
|
||
v-if="
|
||
((!isConversationActive || !isLastMsg) && (msg.body || msg.text)) ||
|
||
msg.state === 3
|
||
"
|
||
:msg="msg"
|
||
/>
|
||
</view>
|
||
</template>
|
||
|
||
<script lang="uts" setup>
|
||
import { MsgItem } from "@/uni_modules/uni-ai-x/sdk";
|
||
import msgToolBar from "@/uni_modules/uni-ai-x/components/msg-tool-bar.uvue";
|
||
import mpHtml from "@/subpackages/uni_modules/mp-html/components/mp-html/mp-html.vue";
|
||
import {
|
||
replaceMathBracket,
|
||
groupMarkdownContainers,
|
||
} from "@/utils/markdown.uts";
|
||
import { TaskStatus } from "@/types/enums/agent";
|
||
import { useI18n } from "@/utils/i18n";
|
||
|
||
const { t } = useI18n();
|
||
|
||
const props = defineProps<{
|
||
msg: MsgItem;
|
||
// 会话是否正在进行中(有消息正在处理)
|
||
isConversationActive?: boolean;
|
||
taskStatus?: TaskStatus;
|
||
// 是否是最后一条消息
|
||
isLastMsg?: boolean;
|
||
}>();
|
||
|
||
const msg = ref<MsgItem>(props.msg as MsgItem); // 转化内部状态为响应式
|
||
const isLastMsg = computed(() => props.isLastMsg);
|
||
const isConversationActive = computed(() => props.isConversationActive);
|
||
|
||
// 思考内容是否隐藏(默认隐藏)
|
||
const hideThinkContent = ref<boolean>(true);
|
||
|
||
// 思考是否已完成:如果正文已经有内容,或者会话已结束,或者不是最后一条消息,则认为思考已完成
|
||
const isThinkingFinished = computed((): boolean => {
|
||
return (
|
||
msg.value.body.trim().length > 0 ||
|
||
!props.isConversationActive ||
|
||
!props.isLastMsg
|
||
);
|
||
});
|
||
|
||
// 标签自定义样式,解决长文本换行问题
|
||
const tagStyle = {
|
||
p: "word-break: break-all; overflow-wrap: break-word;",
|
||
li: "word-break: break-all; overflow-wrap: break-word;",
|
||
div: "word-break: break-all; overflow-wrap: break-word;",
|
||
span: "word-break: break-all; overflow-wrap: break-word;",
|
||
};
|
||
|
||
const emit = defineEmits<{
|
||
changeThinkContent: [hideThinkContent: boolean];
|
||
}>();
|
||
|
||
const getRenderKey = (...args: any[]) => {
|
||
return msg.value.chat_id + "-" + args.join("-");
|
||
};
|
||
|
||
const genImageHtml = (src: string) => {
|
||
return `<img src="${src}" />`;
|
||
};
|
||
|
||
const changeThinkContent = () => {
|
||
hideThinkContent.value = !hideThinkContent.value;
|
||
emit("changeThinkContent", hideThinkContent.value);
|
||
};
|
||
|
||
// 添加updateMessage方法,支持动态更新消息内容
|
||
const updateMessage = (newData: any) => {
|
||
try {
|
||
// 使用响应式数据而不是直接操作props
|
||
if (newData) {
|
||
const updatedMsg = {
|
||
...msg.value,
|
||
...newData,
|
||
};
|
||
msg.value = updatedMsg;
|
||
// console.log('更新消息成功:', newData);
|
||
}
|
||
} catch (error) {
|
||
console.error("updateMessage 执行失败:", error);
|
||
}
|
||
};
|
||
// 处理文本内容,应用数学公式格式转换及组件合并
|
||
const processedText = computed(() => {
|
||
if (!msg.value || (msg.value.body || "").trim().length === 0) {
|
||
return "";
|
||
}
|
||
const groupedText = groupMarkdownContainers(msg.value.body);
|
||
return replaceMathBracket(groupedText);
|
||
});
|
||
|
||
// 暴露方法给父组件
|
||
defineExpose({
|
||
updateMessage,
|
||
});
|
||
</script>
|
||
|
||
<style lang="scss">
|
||
.msg-root {
|
||
flex-direction: row;
|
||
flex-wrap: wrap;
|
||
// #ifdef WEB || MP-WEIXIN
|
||
user-select: text;
|
||
-webkit-user-select: text;
|
||
// #endif
|
||
padding: 0 32rpx;
|
||
min-height: 90rpx;
|
||
|
||
.think-container {
|
||
width: 100%;
|
||
// margin-bottom: 24rpx;
|
||
|
||
.think-header {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
padding: 8rpx 0;
|
||
|
||
.think-header-left {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
|
||
.thinking-icon {
|
||
font-size: 36rpx;
|
||
color: #999;
|
||
margin-right: 12rpx;
|
||
}
|
||
|
||
.thinking-title {
|
||
color: #999;
|
||
font-size: 28rpx;
|
||
}
|
||
|
||
.expand-icon {
|
||
margin-left: 12rpx;
|
||
font-size: 28rpx;
|
||
color: #999;
|
||
transition: transform 0.3s ease;
|
||
|
||
&.is-expanded {
|
||
transform: rotate(180deg);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.think-content {
|
||
overflow: hidden;
|
||
transition: all 0.3s ease;
|
||
padding-top: 8rpx;
|
||
|
||
&.hide-think-content {
|
||
height: 0;
|
||
padding-top: 0;
|
||
opacity: 0;
|
||
}
|
||
|
||
.text-box {
|
||
border-left: 4rpx solid #efefef;
|
||
padding-left: 20rpx;
|
||
background-color: rgba(245, 245, 245, 0.3);
|
||
border-radius: 0 8rpx 8rpx 0;
|
||
padding-top: 8rpx;
|
||
padding-bottom: 8rpx;
|
||
|
||
.text {
|
||
font-size: 28rpx;
|
||
color: #888;
|
||
line-height: 1.6;
|
||
white-space: pre-wrap;
|
||
word-break: break-all;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.answer-container {
|
||
width: 100%;
|
||
}
|
||
|
||
.error-msg {
|
||
color: #ff4d4f;
|
||
font-size: 28rpx;
|
||
line-height: 52rpx;
|
||
}
|
||
|
||
.loging-icon {
|
||
margin-top: 16rpx;
|
||
width: 32rpx;
|
||
height: 32rpx;
|
||
}
|
||
.stop-text {
|
||
color: #aaa;
|
||
font-size: 28rpx;
|
||
padding: 16rpx 0;
|
||
}
|
||
.need-try {
|
||
color: #ff4d4f;
|
||
font-size: 28rpx;
|
||
padding: 16rpx;
|
||
}
|
||
.error-msg,
|
||
.stop-text,
|
||
.need-try {
|
||
text-align: left;
|
||
width: 100%;
|
||
}
|
||
}
|
||
|
||
.text {
|
||
color: #15171f;
|
||
font-weight: 400;
|
||
line-height: 56rpx;
|
||
white-space: pre-wrap;
|
||
word-break: break-all;
|
||
overflow-wrap: anywhere;
|
||
max-width: 100%;
|
||
}
|
||
.strong {
|
||
font-weight: bold;
|
||
}
|
||
|
||
.em {
|
||
font-style: italic;
|
||
}
|
||
|
||
.del {
|
||
text-decoration-line: line-through;
|
||
color: #999;
|
||
}
|
||
|
||
.space {
|
||
height: 10px;
|
||
}
|
||
.link {
|
||
color: #0066cc !important;
|
||
word-break: break-all;
|
||
overflow-wrap: anywhere;
|
||
// #ifdef WEB
|
||
cursor: pointer;
|
||
&:hover {
|
||
opacity: 0.8;
|
||
}
|
||
// #endif
|
||
}
|
||
|
||
.paragraph-box {
|
||
display: block;
|
||
.text {
|
||
font-size: 32rpx;
|
||
font-weight: 400;
|
||
line-height: 56rpx;
|
||
// 自动换行并显示 \n
|
||
white-space: pre-wrap;
|
||
word-break: break-all;
|
||
overflow-wrap: anywhere;
|
||
max-width: 100%;
|
||
/* #ifdef H5 */
|
||
width: auto;
|
||
word-break: break-all;
|
||
/* #endif */
|
||
}
|
||
}
|
||
.image-box {
|
||
width: 100%;
|
||
}
|
||
.blockquote-box {
|
||
padding: 5px;
|
||
background: #f5f5f5;
|
||
border-left: 4px solid #ddd;
|
||
}
|
||
.container-box {
|
||
width: 100%;
|
||
}
|
||
.heading-box {
|
||
.text {
|
||
color: #15171f;
|
||
font-size: 40rpx;
|
||
font-weight: 600;
|
||
line-height: 56rpx;
|
||
}
|
||
.depth-1 {
|
||
font-size: 50rpx;
|
||
line-height: 60rpx;
|
||
}
|
||
|
||
.depth-2 {
|
||
font-size: 42rpx;
|
||
line-height: 56rpx;
|
||
}
|
||
|
||
.depth-3 {
|
||
font-size: 34rpx;
|
||
line-height: 56rpx;
|
||
}
|
||
|
||
.depth-4 {
|
||
font-size: 30rpx;
|
||
line-height: 56rpx;
|
||
}
|
||
|
||
.depth-5 {
|
||
font-size: 26rpx;
|
||
line-height: 56rpx;
|
||
}
|
||
|
||
.depth-6 {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
line-height: 56rpx;
|
||
}
|
||
}
|
||
|
||
.hr {
|
||
height: 2rpx;
|
||
background: #ddd;
|
||
margin: 20rpx 0;
|
||
}
|
||
.br {
|
||
width: 100%;
|
||
display: block;
|
||
}
|
||
|
||
.list-box {
|
||
padding-left: 10rpx;
|
||
.list-content {
|
||
width: 100%;
|
||
}
|
||
.text {
|
||
line-height: 56rpx;
|
||
}
|
||
.list-item-content-box {
|
||
flex: 1;
|
||
.list {
|
||
margin-top: 0;
|
||
}
|
||
}
|
||
.list-item-br {
|
||
width: 100%;
|
||
}
|
||
.list-item-index {
|
||
width: 40rpx;
|
||
&.unordered {
|
||
font-size: 28rpx;
|
||
color: #000;
|
||
}
|
||
&.task {
|
||
font-size: 28rpx;
|
||
color: #666;
|
||
}
|
||
}
|
||
}
|
||
.list-inline-image-box {
|
||
max-width: 100%;
|
||
display: block;
|
||
.list-content {
|
||
width: 100%;
|
||
}
|
||
.text {
|
||
line-height: 56rpx;
|
||
}
|
||
.list-item-content-box {
|
||
flex: 1;
|
||
.list {
|
||
margin-top: 0;
|
||
}
|
||
}
|
||
.list-item-br {
|
||
width: 100%;
|
||
}
|
||
.list-item-index {
|
||
width: 40rpx;
|
||
&.unordered {
|
||
font-size: 28rpx;
|
||
color: #000;
|
||
}
|
||
&.task {
|
||
font-size: 28rpx;
|
||
color: #666;
|
||
}
|
||
}
|
||
}
|
||
|
||
.hr-box {
|
||
background-color: #ddd;
|
||
height: 2rpx;
|
||
}
|
||
|
||
.tool-call-box {
|
||
width: 100%;
|
||
margin: 10rpx 0;
|
||
}
|
||
|
||
.table-box,
|
||
.code-box,
|
||
.hr-box,
|
||
.paragraph-box,
|
||
.blockquote-box,
|
||
.heading-box,
|
||
.list-box,
|
||
.loging-icon,
|
||
.math-box {
|
||
width: 100%;
|
||
margin: 10rpx 0;
|
||
}
|
||
.paragraph-box {
|
||
margin-top: 0;
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.space-box {
|
||
height: 30rpx;
|
||
}
|
||
|
||
.codespan {
|
||
color: #666;
|
||
background-color: #f5f5f5;
|
||
padding: 4rpx 8rpx;
|
||
border-radius: 10rpx;
|
||
font-size: 28rpx;
|
||
word-break: break-all;
|
||
overflow-wrap: anywhere;
|
||
}
|
||
|
||
.icon-loading-image {
|
||
width: 48rpx;
|
||
height: 48rpx;
|
||
margin-right: 10rpx;
|
||
-webkit-touch-callout: none !important;
|
||
-webkit-user-select: none !important;
|
||
user-select: none !important;
|
||
}
|
||
</style>
|