支持markdown格式显示,效果

This commit is contained in:
zhonghua.li
2026-03-31 21:45:41 +08:00
parent 22f951b514
commit 99a71cdc69
5 changed files with 400 additions and 18 deletions

View File

@@ -74,7 +74,21 @@
<text class="qa-copy-btn-icon"></text>
</view>
</view>
<text class="qa-msg-text" selectable="true">{{ m.text }}</text>
<!-- 用户消息仍按纯文本展示 -->
<text
v-if="m.role === 'user'"
class="qa-msg-text"
selectable="true"
>{{ m.text }}</text>
<!-- AI 消息支持 Markdown 渲染保留纯文本兜底 -->
<view
v-else
class="qa-msg-text qa-msg-text-markdown"
>
<rich-text
:nodes="renderMarkdownNodes(m.text)"
></rich-text>
</view>
</view>
</view>
</scroll-view>
@@ -601,7 +615,16 @@ export default {
parsed.content ??
parsed.answer ??
parsed.text ??
parsed.data ??
parsed.result ??
parsed.message ??
parsed.output ??
parsed.token ??
'';
// 若对象结构未知,至少回退到原始行,避免“有返回但前端无内容”
if ((textToAppend === undefined || textToAppend === null || String(textToAppend) === '') && raw) {
textToAppend = raw;
}
}
} catch (e) {
// 非 JSON 分片按纯文本处理
@@ -850,6 +873,151 @@ export default {
return String(value);
}
},
/**
* 非常轻量的 Markdown 转换,仅支持:
* - 换行 => <br/>
* - **加粗** => <strong>
* - *斜体* => <em>
* - 无序列表行 "- " => <ul><li>
* - 代码块 ``` => <pre><code>
* 复杂 Markdown 语法暂不完全覆盖,但足够保证结构清晰。
*/
parseInlineMarkdownToNodes(line) {
const text = String(line || '');
const nodes = [];
const tokenRe = /(\*\*[^*]+\*\*|`[^`\n]+`|\*[^*\n]+\*)/g;
let lastIndex = 0;
let match = null;
while ((match = tokenRe.exec(text)) !== null) {
const idx = match.index;
if (idx > lastIndex) {
nodes.push({ type: 'text', text: text.slice(lastIndex, idx) });
}
const token = match[0];
if (token.startsWith('**') && token.endsWith('**')) {
nodes.push({
name: 'strong',
attrs: { style: 'font-weight:600;' },
children: [{ type: 'text', text: token.slice(2, -2) }]
});
} else if (token.startsWith('`') && token.endsWith('`')) {
nodes.push({
name: 'span',
attrs: {
style: 'font-family:monospace;background:#f2f2f4;padding:1px 4px;border-radius:3px;'
},
children: [{ type: 'text', text: token.slice(1, -1) }]
});
} else if (token.startsWith('*') && token.endsWith('*')) {
nodes.push({
name: 'em',
attrs: { style: 'font-style:italic;' },
children: [{ type: 'text', text: token.slice(1, -1) }]
});
} else {
nodes.push({ type: 'text', text: token });
}
lastIndex = idx + token.length;
}
if (lastIndex < text.length) {
nodes.push({ type: 'text', text: text.slice(lastIndex) });
}
if (nodes.length === 0) {
nodes.push({ type: 'text', text: '' });
}
return nodes;
},
renderMarkdownNodes(text) {
try {
const raw = String(text || '');
const lines = raw.replace(/\r\n/g, '\n').split('\n');
const nodes = [];
let i = 0;
let inCode = false;
let codeLines = [];
while (i < lines.length) {
const line = lines[i];
// 代码块围栏
if (line.trim().startsWith('```')) {
if (!inCode) {
inCode = true;
codeLines = [];
} else {
inCode = false;
nodes.push({
name: 'div',
attrs: {
style: 'background:#f7f7f9;border-radius:6px;padding:8px 10px;font-family:monospace;white-space:pre-wrap;word-break:break-word;'
},
children: [{ type: 'text', text: codeLines.join('\n') }]
});
}
i += 1;
continue;
}
if (inCode) {
codeLines.push(line);
i += 1;
continue;
}
// 无序列表
if (/^\s*-\s+/.test(line)) {
const ulChildren = [];
while (i < lines.length && /^\s*-\s+/.test(lines[i])) {
const content = lines[i].replace(/^\s*-\s+/, '');
ulChildren.push({
name: 'li',
attrs: { style: 'margin:4px 0;' },
children: this.parseInlineMarkdownToNodes(content)
});
i += 1;
}
nodes.push({
name: 'ul',
attrs: { style: 'padding-left:18px;margin:6px 0;' },
children: ulChildren
});
continue;
}
// 空行
if (!line.trim()) {
nodes.push({ name: 'div', children: [{ type: 'text', text: ' ' }] });
i += 1;
continue;
}
// 普通段落
nodes.push({
name: 'div',
attrs: { style: 'line-height:1.6;margin:2px 0;word-break:break-word;' },
children: this.parseInlineMarkdownToNodes(line)
});
i += 1;
}
// 兜底:如果代码块未闭合,也照样展示
if (inCode && codeLines.length > 0) {
nodes.push({
name: 'div',
attrs: {
style: 'background:#f7f7f9;border-radius:6px;padding:8px 10px;font-family:monospace;white-space:pre-wrap;word-break:break-word;'
},
children: [{ type: 'text', text: codeLines.join('\n') }]
});
}
return nodes;
} catch (e) {
return [{ type: 'text', text: String(text || '') }];
}
},
onSend() {
const text = (this.message || '').trim();
if (!text) {
@@ -1060,6 +1228,48 @@ export default {
user-select: text;
}
.qa-msg-text-markdown {
padding-top: 6rpx;
}
.md-root {
font-size: 26rpx;
line-height: 1.6;
color: #333333;
}
.md-root strong {
font-weight: 600;
}
.md-root em {
font-style: italic;
}
.md-root ul {
padding-left: 32rpx;
margin: 8rpx 0;
}
.md-root li {
margin: 4rpx 0;
}
.md-code-block {
background-color: #f7f7f9;
border-radius: 8rpx;
padding: 10rpx 14rpx;
font-family: monospace;
overflow-x: auto;
}
.md-inline-code {
font-family: monospace;
background-color: #f2f2f4;
padding: 2rpx 6rpx;
border-radius: 4rpx;
}
/* 复制按钮在角上绝对定位;气泡已有左右 padding再给正文加一点边距避免与 44rpx 按钮重叠 */
.qa-msg--ai .qa-msg-text {
padding-left: 40rpx;

File diff suppressed because one or more lines are too long

View File

@@ -189,7 +189,7 @@ const _sfc_main = {
}
this.aiQaMainList = body.data || [];
} catch (e) {
common_vendor.index.__f__("error", "at pages-subpackage/ai_qa/general_qa/general_qa.vue:351", "[general_qa] fetchAiQaMainList error:", e);
common_vendor.index.__f__("error", "at pages-subpackage/ai_qa/general_qa/general_qa.vue:365", "[general_qa] fetchAiQaMainList error:", e);
common_vendor.index.showToast({ title: "加载失败", icon: "none" });
this.aiQaMainList = [];
} finally {
@@ -276,7 +276,7 @@ const _sfc_main = {
this.chatMessages = restored;
} catch (e) {
common_vendor.index.hideLoading();
common_vendor.index.__f__("error", "at pages-subpackage/ai_qa/general_qa/general_qa.vue:448", "[general_qa] listByParentId error:", e);
common_vendor.index.__f__("error", "at pages-subpackage/ai_qa/general_qa/general_qa.vue:462", "[general_qa] listByParentId error:", e);
common_vendor.index.showToast({ title: "加载失败,请重试", icon: "none" });
}
},
@@ -425,7 +425,10 @@ const _sfc_main = {
if (parentId) {
this.sessionParentId = String(parentId);
}
textToAppend = parsed.delta ?? parsed.content ?? parsed.answer ?? parsed.text ?? "";
textToAppend = parsed.delta ?? parsed.content ?? parsed.answer ?? parsed.text ?? parsed.data ?? parsed.result ?? parsed.message ?? parsed.output ?? parsed.token ?? "";
if ((textToAppend === void 0 || textToAppend === null || String(textToAppend) === "") && raw) {
textToAppend = raw;
}
}
} catch (e) {
}
@@ -483,7 +486,7 @@ const _sfc_main = {
this.chatMessages.push({ role: "ai", text: this.normalizeText(rawAnswer) });
}).catch((err) => {
common_vendor.index.hideLoading();
common_vendor.index.__f__("error", "at pages-subpackage/ai_qa/general_qa/general_qa.vue:673", "[general_qa] askAI error:", err);
common_vendor.index.__f__("error", "at pages-subpackage/ai_qa/general_qa/general_qa.vue:696", "[general_qa] askAI error:", err);
this.chatMessages.push({ role: "ai", text: (err == null ? void 0 : err.errMsg) || "调用失败,请重试" });
}).finally(() => {
this.sending = false;
@@ -511,7 +514,7 @@ const _sfc_main = {
}
},
fail: (err) => {
common_vendor.index.__f__("error", "at pages-subpackage/ai_qa/general_qa/general_qa.vue:770", "[general_qa] askAIStream error:", err);
common_vendor.index.__f__("error", "at pages-subpackage/ai_qa/general_qa/general_qa.vue:793", "[general_qa] askAIStream error:", err);
const idx = this.streamAiMsgIndex;
if (idx >= 0 && this.chatMessages[idx]) {
const raw = String(this.chatMessages[idx].text || "").trim();
@@ -544,7 +547,7 @@ const _sfc_main = {
this.streamBuffer += chunk;
this.flushStreamBuffer(false);
} catch (e) {
common_vendor.index.__f__("error", "at pages-subpackage/ai_qa/general_qa/general_qa.vue:811", "[general_qa] stream chunk parse error:", e);
common_vendor.index.__f__("error", "at pages-subpackage/ai_qa/general_qa/general_qa.vue:834", "[general_qa] stream chunk parse error:", e);
}
});
} else {
@@ -587,6 +590,136 @@ const _sfc_main = {
return String(value);
}
},
/**
* 非常轻量的 Markdown 转换,仅支持:
* - 换行 => <br/>
* - **加粗** => <strong>
* - *斜体* => <em>
* - 无序列表行 "- " => <ul><li>
* - 代码块 ``` => <pre><code>
* 复杂 Markdown 语法暂不完全覆盖,但足够保证结构清晰。
*/
parseInlineMarkdownToNodes(line) {
const text = String(line || "");
const nodes = [];
const tokenRe = /(\*\*[^*]+\*\*|`[^`\n]+`|\*[^*\n]+\*)/g;
let lastIndex = 0;
let match = null;
while ((match = tokenRe.exec(text)) !== null) {
const idx = match.index;
if (idx > lastIndex) {
nodes.push({ type: "text", text: text.slice(lastIndex, idx) });
}
const token = match[0];
if (token.startsWith("**") && token.endsWith("**")) {
nodes.push({
name: "strong",
attrs: { style: "font-weight:600;" },
children: [{ type: "text", text: token.slice(2, -2) }]
});
} else if (token.startsWith("`") && token.endsWith("`")) {
nodes.push({
name: "span",
attrs: {
style: "font-family:monospace;background:#f2f2f4;padding:1px 4px;border-radius:3px;"
},
children: [{ type: "text", text: token.slice(1, -1) }]
});
} else if (token.startsWith("*") && token.endsWith("*")) {
nodes.push({
name: "em",
attrs: { style: "font-style:italic;" },
children: [{ type: "text", text: token.slice(1, -1) }]
});
} else {
nodes.push({ type: "text", text: token });
}
lastIndex = idx + token.length;
}
if (lastIndex < text.length) {
nodes.push({ type: "text", text: text.slice(lastIndex) });
}
if (nodes.length === 0) {
nodes.push({ type: "text", text: "" });
}
return nodes;
},
renderMarkdownNodes(text) {
try {
const raw = String(text || "");
const lines = raw.replace(/\r\n/g, "\n").split("\n");
const nodes = [];
let i = 0;
let inCode = false;
let codeLines = [];
while (i < lines.length) {
const line = lines[i];
if (line.trim().startsWith("```")) {
if (!inCode) {
inCode = true;
codeLines = [];
} else {
inCode = false;
nodes.push({
name: "div",
attrs: {
style: "background:#f7f7f9;border-radius:6px;padding:8px 10px;font-family:monospace;white-space:pre-wrap;word-break:break-word;"
},
children: [{ type: "text", text: codeLines.join("\n") }]
});
}
i += 1;
continue;
}
if (inCode) {
codeLines.push(line);
i += 1;
continue;
}
if (/^\s*-\s+/.test(line)) {
const ulChildren = [];
while (i < lines.length && /^\s*-\s+/.test(lines[i])) {
const content = lines[i].replace(/^\s*-\s+/, "");
ulChildren.push({
name: "li",
attrs: { style: "margin:4px 0;" },
children: this.parseInlineMarkdownToNodes(content)
});
i += 1;
}
nodes.push({
name: "ul",
attrs: { style: "padding-left:18px;margin:6px 0;" },
children: ulChildren
});
continue;
}
if (!line.trim()) {
nodes.push({ name: "div", children: [{ type: "text", text: " " }] });
i += 1;
continue;
}
nodes.push({
name: "div",
attrs: { style: "line-height:1.6;margin:2px 0;word-break:break-word;" },
children: this.parseInlineMarkdownToNodes(line)
});
i += 1;
}
if (inCode && codeLines.length > 0) {
nodes.push({
name: "div",
attrs: {
style: "background:#f7f7f9;border-radius:6px;padding:8px 10px;font-family:monospace;white-space:pre-wrap;word-break:break-word;"
},
children: [{ type: "text", text: codeLines.join("\n") }]
});
}
return nodes;
} catch (e) {
return [{ type: "text", text: String(text || "") }];
}
},
onSend() {
const text = (this.message || "").trim();
if (!text) {
@@ -659,32 +792,37 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
k: $data.chatMessages.length === 0
}, $data.chatMessages.length === 0 ? {} : {
l: common_vendor.f($data.chatMessages, (m, i, i0) => {
return {
return common_vendor.e({
a: common_vendor.o(($event) => $options.copyText(m.text), i),
b: common_vendor.n(m.role === "ai" ? "qa-msg-actions--left" : "qa-msg-actions--right"),
c: common_vendor.t(m.text),
d: i,
e: common_vendor.n(m.role === "user" ? "qa-msg--user" : "qa-msg--ai")
};
c: m.role === "user"
}, m.role === "user" ? {
d: common_vendor.t(m.text)
} : {
e: $options.renderMarkdownNodes(m.text)
}, {
f: i,
g: common_vendor.n(m.role === "user" ? "qa-msg--user" : "qa-msg--ai")
});
})
}, {
m: -1,
n: $data.message,
o: common_vendor.o(($event) => $data.message = $event.detail.value, "a5"),
o: common_vendor.o(($event) => $data.message = $event.detail.value, "ee"),
p: common_vendor.p({
type: "top",
size: "18",
color: "#fff"
}),
q: common_vendor.o((...args) => $options.onSend && $options.onSend(...args), "f0"),
q: common_vendor.o((...args) => $options.onSend && $options.onSend(...args), "bc"),
r: common_vendor.n($data.useStreamOutput ? "qa-chip-stream--active" : ""),
s: common_vendor.o((...args) => $options.toggleStreamOutput && $options.toggleStreamOutput(...args), "c2"),
s: common_vendor.o((...args) => $options.toggleStreamOutput && $options.toggleStreamOutput(...args), "35"),
t: common_vendor.p({
type: "left",
size: "18",
color: "#666"
}),
v: common_vendor.o((...args) => $options.goBack && $options.goBack(...args), "d7"),
v: common_vendor.o((...args) => $options.goBack && $options.goBack(...args), "bd"),
w: $data.safeBottom + "px"
});
}

View File

@@ -1 +1 @@
<view class="qa-page data-v-135ac533"><view class="qa-header data-v-135ac533" style="{{'padding-top:' + c}}"><view class="qa-header-bar data-v-135ac533"><view class="qa-header-left data-v-135ac533" bindtap="{{a}}"><view class="qa-header-menu-line data-v-135ac533"></view><view class="qa-header-menu-line qa-header-menu-line-short data-v-135ac533"></view></view><text class="qa-header-title data-v-135ac533">新对话</text><view class="qa-header-right data-v-135ac533" bindtap="{{b}}"><text class="qa-header-plus data-v-135ac533"></text></view></view></view><view wx:if="{{d}}" class="main-list-mask data-v-135ac533" bindtap="{{e}}"></view><view wx:if="{{f}}" class="main-list-drawer data-v-135ac533"><view class="main-list-drawer-header data-v-135ac533"><text class="main-list-drawer-title data-v-135ac533">对话列表</text><view class="main-list-drawer-close data-v-135ac533" catchtap="{{g}}"><text class="data-v-135ac533">关闭</text></view></view><scroll-view class="main-list-scroll data-v-135ac533" scroll-y="true"><view wx:if="{{h}}" class="main-list-loading data-v-135ac533">加载中...</view><view wx:else class="data-v-135ac533"><view wx:for="{{i}}" wx:for-item="g" wx:key="c" class="main-list-group data-v-135ac533"><view class="main-list-group-title data-v-135ac533">{{g.a}}</view><view wx:for="{{g.b}}" wx:for-item="item" wx:key="b" class="main-list-item data-v-135ac533" catchtap="{{item.c}}"><text class="main-list-item-text data-v-135ac533">{{item.a}}</text></view></view><view wx:if="{{j}}" class="main-list-empty data-v-135ac533"> 暂无数据 </view></view></scroll-view></view><view wx:if="{{k}}" class="qa-main data-v-135ac533"><view class="qa-main-icon-wrapper data-v-135ac533"><text class="qa-main-icon data-v-135ac533">Q</text></view><text class="qa-main-text data-v-135ac533" selectable="true">今天有什么可以帮到你?</text></view><scroll-view wx:else class="qa-chat data-v-135ac533" scroll-y="true"><view class="qa-chat-inner data-v-135ac533"><view wx:for="{{l}}" wx:for-item="m" wx:key="d" class="{{['qa-msg', 'data-v-135ac533', m.e]}}"><view class="{{['qa-msg-actions', 'data-v-135ac533', m.b]}}"><view class="qa-copy-btn data-v-135ac533" catchtap="{{m.a}}"><text class="qa-copy-btn-icon data-v-135ac533">⧉</text></view></view><text class="qa-msg-text data-v-135ac533" selectable="true">{{m.c}}</text></view></view></scroll-view><view class="qa-input-wrapper data-v-135ac533" style="{{'padding-bottom:' + w}}"><view class="qa-input-row data-v-135ac533"><view class="qa-input-box data-v-135ac533"><block wx:if="{{r0}}"><textarea class="qa-textarea data-v-135ac533" placeholder="发送消息或按住说话" placeholder-class="qa-input-placeholder" auto-height="{{true}}" maxlength="{{m}}" value="{{n}}" bindinput="{{o}}"></textarea></block></view><view class="qa-input-send data-v-135ac533" bindtap="{{q}}"><uni-icons wx:if="{{p}}" class="data-v-135ac533" u-i="135ac533-0" bind:__l="__l" u-p="{{p}}"></uni-icons></view></view><view class="qa-input-actions data-v-135ac533"><view class="{{['qa-chip', 'qa-chip-stream', 'data-v-135ac533', r]}}" bindtap="{{s}}"><text class="qa-chip-stream-icon data-v-135ac533">∿</text><text class="qa-chip-stream-text data-v-135ac533">流式</text></view><view class="qa-chip data-v-135ac533" bindtap="{{v}}"><uni-icons wx:if="{{t}}" class="data-v-135ac533" u-i="135ac533-1" bind:__l="__l" u-p="{{t}}"></uni-icons></view></view></view></view>
<view class="qa-page data-v-135ac533"><view class="qa-header data-v-135ac533" style="{{'padding-top:' + c}}"><view class="qa-header-bar data-v-135ac533"><view class="qa-header-left data-v-135ac533" bindtap="{{a}}"><view class="qa-header-menu-line data-v-135ac533"></view><view class="qa-header-menu-line qa-header-menu-line-short data-v-135ac533"></view></view><text class="qa-header-title data-v-135ac533">新对话</text><view class="qa-header-right data-v-135ac533" bindtap="{{b}}"><text class="qa-header-plus data-v-135ac533"></text></view></view></view><view wx:if="{{d}}" class="main-list-mask data-v-135ac533" bindtap="{{e}}"></view><view wx:if="{{f}}" class="main-list-drawer data-v-135ac533"><view class="main-list-drawer-header data-v-135ac533"><text class="main-list-drawer-title data-v-135ac533">对话列表</text><view class="main-list-drawer-close data-v-135ac533" catchtap="{{g}}"><text class="data-v-135ac533">关闭</text></view></view><scroll-view class="main-list-scroll data-v-135ac533" scroll-y="true"><view wx:if="{{h}}" class="main-list-loading data-v-135ac533">加载中...</view><view wx:else class="data-v-135ac533"><view wx:for="{{i}}" wx:for-item="g" wx:key="c" class="main-list-group data-v-135ac533"><view class="main-list-group-title data-v-135ac533">{{g.a}}</view><view wx:for="{{g.b}}" wx:for-item="item" wx:key="b" class="main-list-item data-v-135ac533" catchtap="{{item.c}}"><text class="main-list-item-text data-v-135ac533">{{item.a}}</text></view></view><view wx:if="{{j}}" class="main-list-empty data-v-135ac533"> 暂无数据 </view></view></scroll-view></view><view wx:if="{{k}}" class="qa-main data-v-135ac533"><view class="qa-main-icon-wrapper data-v-135ac533"><text class="qa-main-icon data-v-135ac533">Q</text></view><text class="qa-main-text data-v-135ac533" selectable="true">今天有什么可以帮到你?</text></view><scroll-view wx:else class="qa-chat data-v-135ac533" scroll-y="true"><view class="qa-chat-inner data-v-135ac533"><view wx:for="{{l}}" wx:for-item="m" wx:key="f" class="{{['qa-msg', 'data-v-135ac533', m.g]}}"><view class="{{['qa-msg-actions', 'data-v-135ac533', m.b]}}"><view class="qa-copy-btn data-v-135ac533" catchtap="{{m.a}}"><text class="qa-copy-btn-icon data-v-135ac533">⧉</text></view></view><text wx:if="{{m.c}}" class="qa-msg-text data-v-135ac533" selectable="true">{{m.d}}</text><view wx:else class="qa-msg-text qa-msg-text-markdown data-v-135ac533"><rich-text class="data-v-135ac533" nodes="{{m.e}}"></rich-text></view></view></view></scroll-view><view class="qa-input-wrapper data-v-135ac533" style="{{'padding-bottom:' + w}}"><view class="qa-input-row data-v-135ac533"><view class="qa-input-box data-v-135ac533"><block wx:if="{{r0}}"><textarea class="qa-textarea data-v-135ac533" placeholder="发送消息或按住说话" placeholder-class="qa-input-placeholder" auto-height="{{true}}" maxlength="{{m}}" value="{{n}}" bindinput="{{o}}"></textarea></block></view><view class="qa-input-send data-v-135ac533" bindtap="{{q}}"><uni-icons wx:if="{{p}}" class="data-v-135ac533" u-i="135ac533-0" bind:__l="__l" u-p="{{p}}"></uni-icons></view></view><view class="qa-input-actions data-v-135ac533"><view class="{{['qa-chip', 'qa-chip-stream', 'data-v-135ac533', r]}}" bindtap="{{s}}"><text class="qa-chip-stream-icon data-v-135ac533">∿</text><text class="qa-chip-stream-text data-v-135ac533">流式</text></view><view class="qa-chip data-v-135ac533" bindtap="{{v}}"><uni-icons wx:if="{{t}}" class="data-v-135ac533" u-i="135ac533-1" bind:__l="__l" u-p="{{t}}"></uni-icons></view></view></view></view>

View File

@@ -142,6 +142,40 @@
-webkit-user-select: text;
user-select: text;
}
.qa-msg-text-markdown.data-v-135ac533 {
padding-top: 6rpx;
}
.md-root.data-v-135ac533 {
font-size: 26rpx;
line-height: 1.6;
color: #333333;
}
.md-root strong.data-v-135ac533 {
font-weight: 600;
}
.md-root em.data-v-135ac533 {
font-style: italic;
}
.md-root ul.data-v-135ac533 {
padding-left: 32rpx;
margin: 8rpx 0;
}
.md-root li.data-v-135ac533 {
margin: 4rpx 0;
}
.md-code-block.data-v-135ac533 {
background-color: #f7f7f9;
border-radius: 8rpx;
padding: 10rpx 14rpx;
font-family: monospace;
overflow-x: auto;
}
.md-inline-code.data-v-135ac533 {
font-family: monospace;
background-color: #f2f2f4;
padding: 2rpx 6rpx;
border-radius: 4rpx;
}
/* 复制按钮在角上绝对定位;气泡已有左右 padding再给正文加一点边距避免与 44rpx 按钮重叠 */
.qa-msg--ai .qa-msg-text.data-v-135ac533 {