Files
qiming/qiming-mobile/uni_modules/uni-ai-x/components/uni-ai-x-msg/uni-ai-x-msg.uvue

507 lines
11 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>