aI问答支持流式输出,效果非常好
This commit is contained in:
693
unpackage/dist/dev/mp-weixin/pages-subpackage/ai_qa/general_qa/general_qa.js
vendored
Normal file
693
unpackage/dist/dev/mp-weixin/pages-subpackage/ai_qa/general_qa/general_qa.js
vendored
Normal 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
|
||||
6
unpackage/dist/dev/mp-weixin/pages-subpackage/ai_qa/general_qa/general_qa.json
vendored
Normal file
6
unpackage/dist/dev/mp-weixin/pages-subpackage/ai_qa/general_qa/general_qa.json
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"navigationStyle": "custom",
|
||||
"usingComponents": {
|
||||
"uni-icons": "../../../uni_modules/uni-icons/components/uni-icons/uni-icons"
|
||||
}
|
||||
}
|
||||
1
unpackage/dist/dev/mp-weixin/pages-subpackage/ai_qa/general_qa/general_qa.wxml
vendored
Normal file
1
unpackage/dist/dev/mp-weixin/pages-subpackage/ai_qa/general_qa/general_qa.wxml
vendored
Normal 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>
|
||||
307
unpackage/dist/dev/mp-weixin/pages-subpackage/ai_qa/general_qa/general_qa.wxss
vendored
Normal file
307
unpackage/dist/dev/mp-weixin/pages-subpackage/ai_qa/general_qa/general_qa.wxss
vendored
Normal 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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user