aI问答支持流式输出,效果非常好

This commit is contained in:
zhonghua.li
2026-03-31 21:04:53 +08:00
parent 60da0e2dd5
commit 22f951b514
163 changed files with 4646 additions and 8010 deletions

View File

@@ -0,0 +1,693 @@
"use strict";
const common_vendor = require("../../../common/vendor.js");
const common_request = require("../../../common/request.js");
const common_config = require("../../../common/config.js");
const common_store = require("../../../common/store.js");
function firstNonEmptyTrimmed(...parts) {
for (const p of parts) {
if (p === void 0 || p === null)
continue;
const s = String(p).trim();
if (s)
return s;
}
return "";
}
function getSubmitUserContext() {
try {
const login = common_vendor.index.getStorageSync("backend-login-response") || {};
const ui = common_store.store.userInfo || {};
const loginAccount = firstNonEmptyTrimmed(login.userName, ui.username);
const userName = firstNonEmptyTrimmed(
login.realName,
login.name,
login.nickName,
ui.nickname,
login.email,
ui.email
);
return { loginAccount, userName };
} catch (e) {
return { loginAccount: "", userName: "" };
}
}
function getSystemInfo() {
try {
const info = common_vendor.index.getSystemInfoSync();
return {
statusBarHeight: info.statusBarHeight || 20,
safeAreaInsets: info.safeAreaInsets || { bottom: 0 }
};
} catch (e) {
return {
statusBarHeight: 20,
safeAreaInsets: { bottom: 0 }
};
}
}
function extractAnswerFromAnswerText(answerText) {
if (answerText === void 0 || answerText === null)
return "";
if (typeof answerText === "string") {
const s = answerText.trim();
if (!s)
return "";
if (s.startsWith("{") && s.endsWith("}") || s.startsWith("[") && s.endsWith("]")) {
try {
const parsed = JSON.parse(s);
return extractAnswerFromAnswerText(parsed);
} catch (e) {
}
}
return s;
}
if (typeof answerText === "object") {
return answerText.answer ?? answerText.content ?? answerText.response ?? answerText.result ?? answerText.message ?? answerText;
}
return String(answerText);
}
function stripQuestionTypePrefix(text) {
const raw = text === void 0 || text === null ? "" : String(text);
let s = raw.trim();
if (!s)
return "";
s = s.replace(/^\s*问题类型\s*[:]\s*[^\r\n]*\s*/g, "");
s = s.replace(/^\s*用户问题\s*[:]\s*/g, "");
return s.trim();
}
const _sfc_main = {
data() {
const systemInfo = getSystemInfo();
return {
statusBarHeight: systemInfo.statusBarHeight,
safeBottom: systemInfo.safeAreaInsets.bottom || 0,
message: "",
sending: false,
useStreamOutput: false,
chatMessages: [],
sessionParentId: "",
streamRequestTask: null,
streamAiMsgIndex: -1,
streamBuffer: "",
streamTextDecoder: null,
streamPendingBytes: [],
// AiQaMain 列表面板状态
showMainList: false,
mainListLoading: false,
aiQaMainList: []
};
},
computed: {
groupedAiQaMainList() {
const groups = [
{ key: "today", label: "今天", items: [] },
{ key: "yesterday", label: "昨天", items: [] },
{ key: "seven", label: "7天内", items: [] },
{ key: "thirty", label: "30天内", items: [] },
{ key: "older", label: "更早", items: [] }
];
const MS_DAY = 24 * 60 * 60 * 1e3;
const now = /* @__PURE__ */ new Date();
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();
const parseDateToMs = (v) => {
if (v === void 0 || v === null)
return null;
const raw = String(v).trim();
if (!raw)
return null;
if (/^\d+$/.test(raw)) {
const n = Number(raw);
if (!n)
return null;
const ms = raw.length === 10 ? n * 1e3 : n;
if (ms > 0)
return ms;
}
const m = raw.match(/^(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2})(?::(\d{2}))?)?/);
if (m) {
const y = Number(m[1]);
const mo = Number(m[2]) - 1;
const d = Number(m[3]);
const hh = m[4] ? Number(m[4]) : 0;
const mi = m[5] ? Number(m[5]) : 0;
const ss = m[6] ? Number(m[6]) : 0;
return new Date(y, mo, d, hh, mi, ss).getTime();
}
const dt = new Date(raw);
const t = dt.getTime();
return Number.isNaN(t) ? null : t;
};
const list = Array.isArray(this.aiQaMainList) ? this.aiQaMainList : [];
list.forEach((item) => {
const timeMs = parseDateToMs(item.createTime) || parseDateToMs(item.gmtCreate) || parseDateToMs(item.create_time) || parseDateToMs(item.time) || null;
if (!timeMs)
return;
const diffDays = Math.floor((todayStart - timeMs) / MS_DAY);
const idx = diffDays === 0 ? 0 : diffDays === 1 ? 1 : diffDays <= 6 ? 2 : diffDays <= 29 ? 3 : 4;
groups[idx].items.push(item);
});
return groups.filter((g) => g.items.length > 0);
}
},
methods: {
goBack() {
common_vendor.index.navigateBack();
},
openMainList() {
this.showMainList = true;
this.fetchAiQaMainList(true);
},
closeMainList() {
this.showMainList = false;
},
async fetchAiQaMainList(force = false) {
if (this.mainListLoading)
return;
if (!force && Array.isArray(this.aiQaMainList) && this.aiQaMainList.length > 0)
return;
this.mainListLoading = true;
try {
const login = common_vendor.index.getStorageSync("backend-login-response") || {};
const salesId = login.userId ? String(login.userId).trim() : "";
const salesPhone = firstNonEmptyTrimmed(login.userName, login.phone);
const salesName = "";
const url = common_config.getApiUrl("/api/aiQaMain/list");
const res = await common_request.get(url, {
current: 1,
size: 50,
salesId,
salesPhone,
salesName
});
const body = res && res.data ? res.data : null;
const success = body && body.success === true;
if (!success) {
const msg = (body == null ? void 0 : body.message) || "请求失败";
common_vendor.index.showToast({ title: msg, icon: "none" });
this.aiQaMainList = [];
return;
}
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.showToast({ title: "加载失败", icon: "none" });
this.aiQaMainList = [];
} finally {
this.mainListLoading = false;
}
},
getMainItemKey(item) {
const key = firstNonEmptyTrimmed(
item.id,
item.mainId,
item.parentId,
item.qaMainId,
item.questionId
);
return key || JSON.stringify(item);
},
getMainItemTitle(item) {
return firstNonEmptyTrimmed(
item.questionTitle,
item.title,
item.questionContent,
item.question,
item.content,
item.summary,
item.name,
item.id
);
},
async selectMainItem(item) {
const parentId = firstNonEmptyTrimmed(
item.id,
// AiQaMain.id优先
item.parentId,
// 兜底:如果后端把字段直接展开了
item.mainId,
item.qaMainId,
item.sessionParentId
);
common_vendor.index.hideLoading();
this.mainListLoading = false;
this.showMainList = false;
this.chatMessages = [];
this.message = "";
this.sending = false;
this.sessionParentId = parentId ? String(parentId) : "";
if (!this.sessionParentId)
return;
try {
common_vendor.index.showLoading({ title: "加载历史...", mask: true });
const url = common_config.getApiUrl("/api/aiQaItem/listByParentId");
const res = await common_request.get(url, { parentId: this.sessionParentId });
common_vendor.index.hideLoading();
const body = res && res.data ? res.data : null;
const success = body && body.success === true;
if (!success) {
const msg = (body == null ? void 0 : body.message) || "查询失败";
common_vendor.index.showToast({ title: msg, icon: "none" });
return;
}
const list = Array.isArray(body.data) ? body.data : [];
const ordered = list.slice().reverse();
const restored = [];
ordered.forEach((qItem) => {
const questionText = firstNonEmptyTrimmed(
qItem.questSrc,
qItem.quest_src,
qItem.questionContent,
qItem.question,
qItem.title
);
if (questionText) {
const cleaned = stripQuestionTypePrefix(this.normalizeText(questionText));
if (cleaned) {
restored.push({ role: "user", text: cleaned });
}
}
const answerText = extractAnswerFromAnswerText(
qItem.answerText ?? qItem.answer_text
);
if (answerText !== void 0 && answerText !== null && String(answerText).trim()) {
restored.push({ role: "ai", text: this.normalizeText(answerText) });
}
});
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.showToast({ title: "加载失败,请重试", icon: "none" });
}
},
startNewSession() {
this.abortStreamRequest();
common_vendor.index.hideLoading();
this.chatMessages = [];
this.sessionParentId = "";
this.message = "";
this.sending = false;
},
toggleStreamOutput() {
this.useStreamOutput = !this.useStreamOutput;
common_vendor.index.showToast({
title: this.useStreamOutput ? "已开启流式输出" : "已关闭流式输出",
icon: "none"
});
},
getRequestHeaders() {
const header = {
"Content-Type": "application/json"
};
const token = common_vendor.index.getStorageSync("backend-token") || "";
if (token) {
header.Authorization = `Bearer ${token}`;
}
const tenantId = common_vendor.index.getStorageSync("backend-tenant-id") || "";
if (tenantId) {
header["X-Tenant-Id"] = tenantId;
}
const roleName = common_vendor.index.getStorageSync("backend-role-name") || "";
if (roleName) {
header["X-Role-Name"] = roleName;
}
const scenario = common_vendor.index.getStorageSync("backend-scenario") || "";
if (scenario) {
header["X-Scenario"] = scenario;
}
return header;
},
abortStreamRequest() {
if (this.streamRequestTask && typeof this.streamRequestTask.abort === "function") {
try {
this.streamRequestTask.abort();
} catch (e) {
}
}
this.streamRequestTask = null;
this.streamAiMsgIndex = -1;
this.streamBuffer = "";
this.streamTextDecoder = null;
this.streamPendingBytes = [];
},
decodeUtf8Bytes(bytes) {
if (!bytes || !bytes.length)
return "";
if (typeof TextDecoder !== "undefined") {
if (!this.streamTextDecoder) {
this.streamTextDecoder = new TextDecoder("utf-8");
}
return this.streamTextDecoder.decode(bytes, { stream: true });
}
const pending = Array.isArray(this.streamPendingBytes) ? this.streamPendingBytes : [];
const merged = pending.concat(Array.from(bytes));
this.streamPendingBytes = [];
let i = 0;
let out = "";
while (i < merged.length) {
const b1 = merged[i];
if ((b1 & 128) === 0) {
out += String.fromCharCode(b1);
i += 1;
continue;
}
if ((b1 & 224) === 192) {
if (i + 1 >= merged.length)
break;
const b2 = merged[i + 1];
out += String.fromCharCode((b1 & 31) << 6 | b2 & 63);
i += 2;
continue;
}
if ((b1 & 240) === 224) {
if (i + 2 >= merged.length)
break;
const b2 = merged[i + 1];
const b3 = merged[i + 2];
out += String.fromCharCode(
(b1 & 15) << 12 | (b2 & 63) << 6 | b3 & 63
);
i += 3;
continue;
}
if ((b1 & 248) === 240) {
if (i + 3 >= merged.length)
break;
const b2 = merged[i + 1];
const b3 = merged[i + 2];
const b4 = merged[i + 3];
const codePoint = (b1 & 7) << 18 | (b2 & 63) << 12 | (b3 & 63) << 6 | b4 & 63;
out += String.fromCodePoint(codePoint);
i += 4;
continue;
}
i += 1;
}
if (i < merged.length) {
this.streamPendingBytes = merged.slice(i);
}
return out;
},
decodeChunkData(raw) {
if (raw === void 0 || raw === null)
return "";
if (typeof raw === "string")
return raw;
try {
const u8 = raw instanceof Uint8Array ? raw : new Uint8Array(raw);
return this.decodeUtf8Bytes(u8);
} catch (e) {
return String(raw);
}
},
appendStreamTextChunk(text) {
if (text === void 0 || text === null)
return;
const chunk = String(text);
if (!chunk)
return;
const idx = this.streamAiMsgIndex;
if (idx < 0 || !this.chatMessages[idx] || this.chatMessages[idx].role !== "ai")
return;
this.chatMessages[idx].text += chunk;
},
handleStreamDataLine(line) {
const raw = String(line || "").trim();
if (!raw)
return;
if (raw === "[DONE]")
return;
let textToAppend = raw;
try {
const parsed = JSON.parse(raw);
if (parsed && typeof parsed === "object") {
const parentId = firstNonEmptyTrimmed(parsed.parentId, parsed.parent_id);
if (parentId) {
this.sessionParentId = String(parentId);
}
textToAppend = parsed.delta ?? parsed.content ?? parsed.answer ?? parsed.text ?? "";
}
} catch (e) {
}
if (textToAppend) {
this.appendStreamTextChunk(this.normalizeText(textToAppend));
}
},
flushStreamBuffer(force = false) {
if (!this.streamBuffer)
return;
let data = this.streamBuffer;
if (!force) {
const lastLf = data.lastIndexOf("\n");
if (lastLf < 0)
return;
this.streamBuffer = data.slice(lastLf + 1);
data = data.slice(0, lastLf);
} else {
this.streamBuffer = "";
}
if (!data)
return;
const lines = data.split(/\r?\n/);
lines.forEach((line) => {
const s = String(line || "").trim();
if (!s)
return;
if (s.startsWith("data:")) {
this.handleStreamDataLine(s.slice(5));
return;
}
if (s.startsWith("event:") || s.startsWith("id:") || s.startsWith(":")) {
return;
}
this.handleStreamDataLine(s);
});
},
sendByNormal(payload) {
const url = common_config.getApiUrl("/api/aiQaItem/askAI");
common_request.post(url, payload).then((res) => {
common_vendor.index.hideLoading();
const body = res && res.data ? res.data : null;
const success = body && body.success === true;
if (!success) {
const msg = (body == null ? void 0 : body.message) || "请求失败";
this.chatMessages.push({ role: "ai", text: msg });
return;
}
const aiData = body.data || {};
const returnedParentId = aiData.parentId;
if (returnedParentId !== void 0 && returnedParentId !== null && String(returnedParentId).trim()) {
this.sessionParentId = String(returnedParentId).trim();
}
const rawAnswer = aiData.answer ?? aiData.content ?? aiData.response ?? aiData.result ?? aiData.message ?? aiData;
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);
this.chatMessages.push({ role: "ai", text: (err == null ? void 0 : err.errMsg) || "调用失败,请重试" });
}).finally(() => {
this.sending = false;
});
},
async sendByStream(payload) {
const url = common_config.getApiUrl("/api/aiQaItem/askAIStream");
this.chatMessages.push({ role: "ai", text: "" });
this.streamAiMsgIndex = this.chatMessages.length - 1;
this.streamBuffer = "";
this.streamTextDecoder = null;
this.streamPendingBytes = [];
const requestOptions = {
url,
method: "POST",
data: payload,
header: this.getRequestHeaders(),
enableChunked: true,
responseType: "text",
success: () => {
this.flushStreamBuffer(true);
const idx = this.streamAiMsgIndex;
if (idx >= 0 && this.chatMessages[idx] && !String(this.chatMessages[idx].text || "").trim()) {
this.chatMessages[idx].text = "未获取到流式内容";
}
},
fail: (err) => {
common_vendor.index.__f__("error", "at pages-subpackage/ai_qa/general_qa/general_qa.vue:770", "[general_qa] askAIStream error:", err);
const idx = this.streamAiMsgIndex;
if (idx >= 0 && this.chatMessages[idx]) {
const raw = String(this.chatMessages[idx].text || "").trim();
this.chatMessages[idx].text = raw || ((err == null ? void 0 : err.errMsg) || "调用失败,请重试");
} else {
this.chatMessages.push({ role: "ai", text: (err == null ? void 0 : err.errMsg) || "调用失败,请重试" });
}
},
complete: () => {
common_vendor.index.hideLoading();
this.sending = false;
this.streamRequestTask = null;
this.streamAiMsgIndex = -1;
this.streamBuffer = "";
this.streamTextDecoder = null;
this.streamPendingBytes = [];
}
};
let task = null;
if (typeof common_vendor.wx$1 !== "undefined" && common_vendor.wx$1 && typeof common_vendor.wx$1.request === "function") {
task = common_vendor.wx$1.request(requestOptions);
} else {
task = common_vendor.index.request(requestOptions);
}
this.streamRequestTask = task;
if (task && typeof task.onChunkReceived === "function") {
task.onChunkReceived((res) => {
try {
const chunk = this.decodeChunkData(res.data);
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);
}
});
} else {
const idx = this.streamAiMsgIndex;
common_vendor.index.hideLoading();
this.sending = false;
this.streamRequestTask = null;
this.streamAiMsgIndex = -1;
this.streamBuffer = "";
if (idx >= 0 && this.chatMessages[idx]) {
this.chatMessages[idx].text = "当前运行环境不支持流式输出,请关闭流式后重试";
} else {
this.chatMessages.push({ role: "ai", text: "当前运行环境不支持流式输出,请关闭流式后重试" });
}
}
},
copyText(text) {
const content = (text || "").toString();
if (!content.trim())
return;
common_vendor.index.setClipboardData({
data: content,
success: () => {
common_vendor.index.showToast({ title: "已复制", icon: "success" });
},
fail: () => {
common_vendor.index.showToast({ title: "复制失败", icon: "none" });
}
});
},
normalizeText(value) {
if (value === void 0 || value === null)
return "";
if (typeof value === "string") {
return value.replace(/\\n/g, "\n");
}
try {
return JSON.stringify(value, null, 2);
} catch (e) {
return String(value);
}
},
onSend() {
const text = (this.message || "").trim();
if (!text) {
common_vendor.index.showToast({ title: "请输入消息", icon: "none" });
return;
}
if (this.sending)
return;
this.sending = true;
const userMsg = { role: "user", text };
this.chatMessages.push(userMsg);
this.message = "";
common_vendor.index.showLoading({ title: "生成中...", mask: true });
const questionType = "general_qa";
const { loginAccount, userName } = getSubmitUserContext();
const payload = {
questionType,
questionContent: text,
loginAccount,
userName
};
if (this.sessionParentId) {
payload.parentId = this.sessionParentId;
}
if (this.useStreamOutput) {
this.sendByStream(payload);
} else {
this.sendByNormal(payload);
}
}
}
};
if (!Array) {
const _easycom_uni_icons2 = common_vendor.resolveComponent("uni-icons");
_easycom_uni_icons2();
}
const _easycom_uni_icons = () => "../../../uni_modules/uni-icons/components/uni-icons/uni-icons.js";
if (!Math) {
_easycom_uni_icons();
}
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return common_vendor.e({
a: common_vendor.o((...args) => $options.openMainList && $options.openMainList(...args), "2e"),
b: common_vendor.o((...args) => $options.startNewSession && $options.startNewSession(...args), "3a"),
c: $data.statusBarHeight + "px",
d: $data.showMainList
}, $data.showMainList ? {
e: common_vendor.o((...args) => $options.closeMainList && $options.closeMainList(...args), "cb")
} : {}, {
f: $data.showMainList
}, $data.showMainList ? common_vendor.e({
g: common_vendor.o((...args) => $options.closeMainList && $options.closeMainList(...args), "c3"),
h: $data.mainListLoading
}, $data.mainListLoading ? {} : common_vendor.e({
i: common_vendor.f($options.groupedAiQaMainList, (g, k0, i0) => {
return {
a: common_vendor.t(g.label),
b: common_vendor.f(g.items, (item, k1, i1) => {
return {
a: common_vendor.t($options.getMainItemTitle(item)),
b: $options.getMainItemKey(item),
c: common_vendor.o(($event) => $options.selectMainItem(item), $options.getMainItemKey(item))
};
}),
c: g.key
};
}),
j: !$data.aiQaMainList || $data.aiQaMainList.length === 0
}, !$data.aiQaMainList || $data.aiQaMainList.length === 0 ? {} : {})) : {}, {
k: $data.chatMessages.length === 0
}, $data.chatMessages.length === 0 ? {} : {
l: common_vendor.f($data.chatMessages, (m, i, i0) => {
return {
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")
};
})
}, {
m: -1,
n: $data.message,
o: common_vendor.o(($event) => $data.message = $event.detail.value, "a5"),
p: common_vendor.p({
type: "top",
size: "18",
color: "#fff"
}),
q: common_vendor.o((...args) => $options.onSend && $options.onSend(...args), "f0"),
r: common_vendor.n($data.useStreamOutput ? "qa-chip-stream--active" : ""),
s: common_vendor.o((...args) => $options.toggleStreamOutput && $options.toggleStreamOutput(...args), "c2"),
t: common_vendor.p({
type: "left",
size: "18",
color: "#666"
}),
v: common_vendor.o((...args) => $options.goBack && $options.goBack(...args), "d7"),
w: $data.safeBottom + "px"
});
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-135ac533"]]);
wx.createPage(MiniProgramPage);
//# sourceMappingURL=../../../../.sourcemap/mp-weixin/pages-subpackage/ai_qa/general_qa/general_qa.js.map

View File

@@ -0,0 +1,6 @@
{
"navigationStyle": "custom",
"usingComponents": {
"uni-icons": "../../../uni_modules/uni-icons/components/uni-icons/uni-icons"
}
}

View File

@@ -0,0 +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 File

@@ -0,0 +1,307 @@
.qa-page.data-v-135ac533 {
flex: 1;
min-height: 100vh;
background-color: #F5F5F5;
display: flex;
flex-direction: column;
}
.qa-header.data-v-135ac533 {
background-color: #FFFFFF;
border-bottom: 1rpx solid #EDEDED;
}
.qa-header-bar.data-v-135ac533 {
height: 88rpx;
padding: 0 32rpx;
display: flex;
align-items: center;
justify-content: space-between;
}
.qa-header-left.data-v-135ac533 {
width: 60rpx;
height: 60rpx;
justify-content: center;
align-items: flex-start;
display: flex;
flex-direction: column;
}
.qa-header-menu-line.data-v-135ac533 {
width: 26rpx;
height: 4rpx;
border-radius: 4rpx;
background-color: #333333;
margin-bottom: 6rpx;
}
.qa-header-menu-line-short.data-v-135ac533 {
width: 16rpx;
}
.qa-header-title.data-v-135ac533 {
flex: 1;
text-align: center;
font-size: 30rpx;
font-weight: 500;
color: #333333;
}
.qa-header-right.data-v-135ac533 {
width: 60rpx;
height: 60rpx;
align-items: center;
justify-content: center;
display: flex;
}
.qa-header-plus.data-v-135ac533 {
font-size: 38rpx;
color: #333333;
}
.qa-main.data-v-135ac533 {
flex: 1;
align-items: center;
justify-content: center;
display: flex;
flex-direction: column;
padding-bottom: 200rpx; /* 给底部固定输入框留空间 */
}
.qa-main-icon-wrapper.data-v-135ac533 {
width: 120rpx;
height: 120rpx;
border-radius: 60rpx;
background-color: #FFFFFF;
align-items: center;
justify-content: center;
display: flex;
margin-bottom: 32rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.06);
}
.qa-main-icon.data-v-135ac533 {
font-size: 60rpx;
color: #2A68FF;
}
.qa-main-text.data-v-135ac533 {
font-size: 30rpx;
color: #333333;
}
.qa-chat.data-v-135ac533 {
flex: 1;
width: 100%;
padding-bottom: 200rpx; /* 给底部固定输入框留空间 */
}
.qa-chat-inner.data-v-135ac533 {
padding: 24rpx 24rpx 12rpx;
}
.qa-msg.data-v-135ac533 {
margin-bottom: 20rpx;
max-width: 88%;
position: relative;
}
.qa-msg-actions.data-v-135ac533 {
position: absolute;
top: 8rpx;
z-index: 2;
}
.qa-msg-actions--left.data-v-135ac533 {
left: 8rpx;
}
.qa-msg-actions--right.data-v-135ac533 {
right: 8rpx;
}
.qa-copy-btn.data-v-135ac533 {
width: 44rpx;
height: 44rpx;
border-radius: 22rpx;
background-color: #F4F6FA;
border: 1rpx solid #E5EAF3;
display: flex;
align-items: center;
justify-content: center;
}
.qa-copy-btn-icon.data-v-135ac533 {
font-size: 22rpx;
color: #8a8a8a;
line-height: 1;
}
.qa-msg--user.data-v-135ac533 {
margin-left: auto;
background-color: #EAF2FF;
border: 1rpx solid #D7E6FF;
}
.qa-msg--ai.data-v-135ac533 {
margin-right: auto;
background-color: #FFFFFF;
border: 1rpx solid #F0F0F0;
}
.qa-msg--user.data-v-135ac533,
.qa-msg--ai.data-v-135ac533 {
padding: 18rpx 20rpx;
border-radius: 16rpx;
}
.qa-msg-text.data-v-135ac533 {
font-size: 26rpx;
color: #333333;
line-height: 1.6;
word-break: break-word;
-webkit-user-select: text;
user-select: text;
}
/* 复制按钮在角上绝对定位;气泡已有左右 padding再给正文加一点边距避免与 44rpx 按钮重叠 */
.qa-msg--ai .qa-msg-text.data-v-135ac533 {
padding-left: 40rpx;
}
.qa-msg--user .qa-msg-text.data-v-135ac533 {
padding-right: 40rpx;
}
.qa-input-wrapper.data-v-135ac533 {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 1000;
padding: 12rpx 20rpx 12rpx;
background-color: #FFFFFF;
box-shadow: 0 -4rpx 12rpx rgba(0, 0, 0, 0.04);
box-sizing: border-box;
}
/* AiQaMain 列表面板 */
.main-list-mask.data-v-135ac533 {
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.35);
z-index: 1999;
}
.main-list-drawer.data-v-135ac533 {
position: fixed;
left: 0;
top: 0;
bottom: 0;
width: 560rpx;
background-color: #FFFFFF;
z-index: 2000;
border-right: 1rpx solid #F0F0F0;
display: flex;
flex-direction: column;
}
.main-list-drawer-header.data-v-135ac533 {
padding: 22rpx 24rpx;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1rpx solid #F0F0F0;
}
.main-list-drawer-title.data-v-135ac533 {
font-size: 32rpx;
font-weight: 600;
color: #333333;
}
.main-list-drawer-close.data-v-135ac533 {
font-size: 28rpx;
color: #666666;
}
.main-list-scroll.data-v-135ac533 {
flex: 1;
}
.main-list-loading.data-v-135ac533,
.main-list-empty.data-v-135ac533 {
padding: 30rpx 24rpx;
font-size: 28rpx;
color: #888888;
}
.main-list-group.data-v-135ac533 {
padding: 18rpx 24rpx;
}
.main-list-group-title.data-v-135ac533 {
font-size: 28rpx;
color: #333333;
font-weight: 600;
margin-bottom: 10rpx;
}
.main-list-item.data-v-135ac533 {
background-color: #F7F7FA;
border: 1rpx solid #EFEFF4;
border-radius: 12rpx;
padding: 18rpx 16rpx;
margin-bottom: 14rpx;
}
.main-list-item-text.data-v-135ac533 {
font-size: 26rpx;
color: #333333;
line-height: 1.4;
word-break: break-word;
}
.qa-input-row.data-v-135ac533 {
flex-direction: row;
display: flex;
align-items: center;
}
.qa-input-box.data-v-135ac533 {
flex: 1;
min-height: 80rpx;
border-radius: 40rpx;
background-color: #F5F5F5;
padding: 0 32rpx;
display: flex;
align-items: center;
}
.qa-textarea.data-v-135ac533 {
width: 100%;
min-height: 88rpx; /* 默认两行2*44rpx */
max-height: 176rpx; /* 最大四行4*44rpx */
line-height: 44rpx;
font-size: 26rpx;
color: #333333;
padding: 18rpx 0;
box-sizing: border-box;
overflow-y: auto;
}
.qa-input-placeholder.data-v-135ac533 {
font-size: 26rpx;
color: #999999;
}
.qa-input-send.data-v-135ac533 {
width: 80rpx;
height: 80rpx;
border-radius: 40rpx;
margin-left: 16rpx;
background-color: #2A68FF;
align-items: center;
justify-content: center;
display: flex;
}
.qa-input-actions.data-v-135ac533 {
margin-top: 12rpx;
flex-direction: row;
display: flex;
}
.qa-chip.data-v-135ac533 {
padding: 8rpx 20rpx;
border-radius: 999rpx;
background-color: #F5F5F5;
margin-right: 16rpx;
display: flex;
align-items: center;
justify-content: center;
}
.qa-chip-stream.data-v-135ac533 {
display: flex;
flex-direction: row;
align-items: center;
background-color: #F1F4FA;
}
.qa-chip-stream--active.data-v-135ac533 {
background-color: #E7F0FF;
}
.qa-chip-stream-icon.data-v-135ac533 {
font-size: 24rpx;
color: #2A68FF;
line-height: 1;
margin-right: 8rpx;
}
.qa-chip-stream-text.data-v-135ac533 {
font-size: 24rpx;
color: #2A68FF;
line-height: 1;
}