fmode-ng 0.0.243 → 0.0.245
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm2022/lib/aigc/chat/chat-message-area/comp-message-area.component.mjs +56 -30
- package/esm2022/lib/aigc/chat/chat-message-card/comp-message-card.component.mjs +3 -3
- package/esm2022/lib/aigc/service-fmai/service-chat/chat.service.mjs +8 -6
- package/esm2022/lib/aigc/service-fmai/service-chat/pipes/chat-content.pipe.mjs +2 -2
- package/esm2022/lib/core/agent/chat/completion/fmode-completion.mjs +6 -2
- package/esm2022/lib/core/agent/chat/fmode-chat.mjs +110 -81
- package/esm2022/lib/core/agent/chat/message/message-manager.mjs +131 -62
- package/esm2022/lib/core/parse/fmode.cloud.mjs +17 -3
- package/esm2022/lib/core/parse/fmode.object.mjs +4 -3
- package/fesm2022/fmode-ng.mjs +329 -183
- package/fesm2022/fmode-ng.mjs.map +1 -1
- package/lib/aigc/chat/chat-message-area/comp-message-area.component.d.ts +9 -3
- package/lib/aigc/service-fmai/service-chat/chat.service.d.ts +3 -2
- package/lib/aigc/service-fmai/service-chat/pipes/chat-content.pipe.d.ts +1 -1
- package/lib/core/agent/chat/fmode-chat.d.ts +12 -5
- package/lib/core/agent/chat/message/message-manager.d.ts +22 -2
- package/package.json +1 -1
package/fesm2022/fmode-ng.mjs
CHANGED
|
@@ -1131,8 +1131,9 @@ class FmodeObject {
|
|
|
1131
1131
|
if (typeof this.updatedAt == "string") {
|
|
1132
1132
|
this.updatedAt = new Date(this.updatedAt);
|
|
1133
1133
|
}
|
|
1134
|
-
|
|
1135
|
-
|
|
1134
|
+
if (this.updatedAt != "Invalid Date") {
|
|
1135
|
+
json.updatedAt = { __type: 'Date', iso: this.updatedAt.toISOString() };
|
|
1136
|
+
}
|
|
1136
1137
|
}
|
|
1137
1138
|
}
|
|
1138
1139
|
catch (err) { }
|
|
@@ -2019,7 +2020,8 @@ class FmodeCloud {
|
|
|
2019
2020
|
}
|
|
2020
2021
|
async function(params = {}, options = {}) {
|
|
2021
2022
|
const config = this.currentConfig;
|
|
2022
|
-
|
|
2023
|
+
let u = new URL(config.serverURL);
|
|
2024
|
+
let url = u.origin + "/api/functions";
|
|
2023
2025
|
const requestBody = {
|
|
2024
2026
|
...params,
|
|
2025
2027
|
_ApplicationId: config.appId,
|
|
@@ -2047,7 +2049,20 @@ class FmodeCloud {
|
|
|
2047
2049
|
if (!response.ok) {
|
|
2048
2050
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
2049
2051
|
}
|
|
2050
|
-
const data = await response.json();
|
|
2052
|
+
// const data = await response.json();
|
|
2053
|
+
// 【关键】直接从 body 获取 reader,只读取一次
|
|
2054
|
+
const reader = response.body?.getReader();
|
|
2055
|
+
if (!reader) {
|
|
2056
|
+
throw new Error('No body reader available');
|
|
2057
|
+
}
|
|
2058
|
+
// 只读取一次!
|
|
2059
|
+
const { value, done } = await reader.read();
|
|
2060
|
+
if (!value) {
|
|
2061
|
+
throw new Error('Empty response body');
|
|
2062
|
+
}
|
|
2063
|
+
const decoder = new TextDecoder();
|
|
2064
|
+
const text = decoder.decode(value);
|
|
2065
|
+
const data = JSON.parse(text);
|
|
2051
2066
|
if (data.error) {
|
|
2052
2067
|
throw new Error(data.error.message || 'Cloud function error');
|
|
2053
2068
|
}
|
|
@@ -4117,10 +4132,14 @@ function chunkToJson(chunk) {
|
|
|
4117
4132
|
function RequestFmodeChatApi(apipath, body, method = "POST") {
|
|
4118
4133
|
return new Observable((observer) => {
|
|
4119
4134
|
let url = API_BASE + apipath;
|
|
4120
|
-
let API_TOKEN = localStorage.getItem("FMODE_AI_TOKEN") || Parse$I.User.current()?.getSessionToken();
|
|
4135
|
+
let API_TOKEN = localStorage.getItem("FMODE_AI_TOKEN") || Parse$I.User.current()?.getSessionToken() || localStorage.getItem("COMPANY_PUBLIC_TOKEN");
|
|
4121
4136
|
// 通过body传递token参数,避免no-cors模式下Authoriztion头部无效
|
|
4122
4137
|
let AUTH_TOKEN = `Bearer ${API_TOKEN}`;
|
|
4123
4138
|
body.token = AUTH_TOKEN;
|
|
4139
|
+
let company = localStorage.getItem("company");
|
|
4140
|
+
if (company) {
|
|
4141
|
+
body.company = body.company || company;
|
|
4142
|
+
}
|
|
4124
4143
|
if (body)
|
|
4125
4144
|
body = JSON.stringify(body);
|
|
4126
4145
|
console.log(url, body);
|
|
@@ -4778,8 +4797,17 @@ class MessageManager {
|
|
|
4778
4797
|
constructor(options) {
|
|
4779
4798
|
this._messageList = []; // 统一的消息列表
|
|
4780
4799
|
this._isLoaded = false;
|
|
4800
|
+
options.limitHistory = options?.limitHistory || 10;
|
|
4781
4801
|
this.options = options;
|
|
4782
4802
|
}
|
|
4803
|
+
getCompanyPointer() {
|
|
4804
|
+
let cid = this.options?.company?.objectId || this.options?.company?.id || localStorage.getItem("company");
|
|
4805
|
+
return {
|
|
4806
|
+
__type: "Pointer",
|
|
4807
|
+
className: "Company",
|
|
4808
|
+
objectId: cid
|
|
4809
|
+
};
|
|
4810
|
+
}
|
|
4783
4811
|
/**
|
|
4784
4812
|
* 获取统一的消息列表
|
|
4785
4813
|
*/
|
|
@@ -4824,43 +4852,34 @@ class MessageManager {
|
|
|
4824
4852
|
messageObj.set("status", "sent");
|
|
4825
4853
|
messageObj.set("createdAt", message.createdAt || new Date());
|
|
4826
4854
|
messageObj.set("complete", message.complete || false);
|
|
4827
|
-
//
|
|
4828
|
-
if (message.content) {
|
|
4829
|
-
if (typeof message.content === "string") {
|
|
4830
|
-
messageObj.set("content", {
|
|
4831
|
-
type: "text",
|
|
4832
|
-
text: message.content
|
|
4833
|
-
});
|
|
4834
|
-
}
|
|
4835
|
-
else if (Array.isArray(message.content)) {
|
|
4836
|
-
messageObj.set("content", {
|
|
4837
|
-
type: "mixed",
|
|
4838
|
-
items: message.content
|
|
4839
|
-
});
|
|
4840
|
-
}
|
|
4841
|
-
else {
|
|
4842
|
-
messageObj.set("content", message.content);
|
|
4843
|
-
}
|
|
4844
|
-
}
|
|
4845
|
-
// 设置角色
|
|
4855
|
+
// 设置消息角色(user/assistant/system)- 这是消息的发送者角色
|
|
4846
4856
|
if (message.role) {
|
|
4847
|
-
messageObj.set("role",
|
|
4848
|
-
}
|
|
4849
|
-
//
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
if (message.voice)
|
|
4855
|
-
messageObj.set("voice", message.voice);
|
|
4856
|
-
if (message.cid)
|
|
4857
|
-
messageObj.set("cid", message.cid);
|
|
4857
|
+
messageObj.set("role", this.options.role.toPointer());
|
|
4858
|
+
}
|
|
4859
|
+
// 设置内容 - 保持原始格式兼容(string 或 Array)
|
|
4860
|
+
// 同时扩展存储格式,支持更多元数据
|
|
4861
|
+
if (message.content !== undefined) {
|
|
4862
|
+
messageObj.set("content", message);
|
|
4863
|
+
}
|
|
4858
4864
|
// 根据场景设置关联对象
|
|
4865
|
+
// avatarRole:指向 AvatarRole 表的指针(智能体角色)
|
|
4859
4866
|
if (targetScene === MessageScene.ROLE && this.options.role) {
|
|
4860
|
-
messageObj.set("
|
|
4867
|
+
messageObj.set("avatarRole", this.options.role.toPointer());
|
|
4861
4868
|
}
|
|
4869
|
+
// company:指向 Company 表的指针
|
|
4862
4870
|
if (this.options.company) {
|
|
4863
|
-
messageObj.set("company", this.
|
|
4871
|
+
messageObj.set("company", this.getCompanyPointer());
|
|
4872
|
+
}
|
|
4873
|
+
// 关键:如果chatSession已存在且有id,立即关联
|
|
4874
|
+
// 如果chatSession还没有id(新会话),标记为待关联
|
|
4875
|
+
if (this.options.chatSession) {
|
|
4876
|
+
if (this.options.chatSession.id) {
|
|
4877
|
+
messageObj.set("session", this.options.chatSession.toPointer());
|
|
4878
|
+
}
|
|
4879
|
+
else {
|
|
4880
|
+
// 新会话,标记待关联,等待session保存后再关联
|
|
4881
|
+
messageObj.set("_pendingSession", true);
|
|
4882
|
+
}
|
|
4864
4883
|
}
|
|
4865
4884
|
// 直接添加到列表末尾
|
|
4866
4885
|
this._messageList.push(messageObj);
|
|
@@ -4873,50 +4892,90 @@ class MessageManager {
|
|
|
4873
4892
|
}
|
|
4874
4893
|
/**
|
|
4875
4894
|
* 异步保存消息对象
|
|
4895
|
+
* 增强版:处理session关联和延迟保存
|
|
4876
4896
|
*/
|
|
4877
4897
|
async saveMessage(messageObj) {
|
|
4878
|
-
if (!messageObj
|
|
4879
|
-
|
|
4880
|
-
|
|
4898
|
+
if (!messageObj)
|
|
4899
|
+
return;
|
|
4900
|
+
try {
|
|
4901
|
+
// 检查是否需要关联session(对于新会话的消息)
|
|
4902
|
+
if (!messageObj.get("session") && this.options.chatSession?.id) {
|
|
4903
|
+
messageObj.set("session", this.options.chatSession.toPointer());
|
|
4904
|
+
}
|
|
4905
|
+
// 确保消息有关联的session才保存到Message表
|
|
4906
|
+
// 如果没有session且不是老消息转换的,先不保存(只存在于内存)
|
|
4907
|
+
if (!messageObj.get("session") && !messageObj.get("isLegacy")) {
|
|
4908
|
+
// 没有session的消息暂时不保存到Message表
|
|
4909
|
+
// 等session创建后会再次调用saveMessage
|
|
4910
|
+
console.log("消息等待session创建后保存");
|
|
4911
|
+
return;
|
|
4881
4912
|
}
|
|
4882
|
-
|
|
4883
|
-
|
|
4913
|
+
await messageObj.save();
|
|
4914
|
+
}
|
|
4915
|
+
catch (error) {
|
|
4916
|
+
console.error("保存消息对象失败:", error);
|
|
4917
|
+
}
|
|
4918
|
+
}
|
|
4919
|
+
/**
|
|
4920
|
+
* 在session创建/更新后,保存所有待关联的消息
|
|
4921
|
+
* 关键方法:在chatSession保存成功后调用
|
|
4922
|
+
*/
|
|
4923
|
+
async savePendingMessages() {
|
|
4924
|
+
if (!this.options.chatSession?.id) {
|
|
4925
|
+
console.warn("session未保存,无法保存待处理消息");
|
|
4926
|
+
return;
|
|
4927
|
+
}
|
|
4928
|
+
const savePromises = [];
|
|
4929
|
+
for (const messageObj of this._messageList) {
|
|
4930
|
+
// 处理待关联的消息
|
|
4931
|
+
// if (messageObj.get("_pendingSession")) {
|
|
4932
|
+
messageObj.set("session", messageObj?.get("session") || this.options.chatSession.toPointer());
|
|
4933
|
+
// messageObj.set("_pendingSession", undefined);
|
|
4934
|
+
// }
|
|
4935
|
+
// 保存所有没有id的消息(新消息)
|
|
4936
|
+
if (!messageObj.id) {
|
|
4937
|
+
if (!messageObj?.get("hidden")) {
|
|
4938
|
+
savePromises.push(this.saveMessage(messageObj));
|
|
4939
|
+
}
|
|
4884
4940
|
}
|
|
4885
4941
|
}
|
|
4942
|
+
if (savePromises.length > 0) {
|
|
4943
|
+
await Promise.all(savePromises);
|
|
4944
|
+
console.log(`保存了 ${savePromises.length} 条待处理消息`);
|
|
4945
|
+
}
|
|
4946
|
+
}
|
|
4947
|
+
/**
|
|
4948
|
+
* 更新chatSession引用(当新会话第一次保存后)
|
|
4949
|
+
*/
|
|
4950
|
+
updateChatSession(chatSession) {
|
|
4951
|
+
this.options.chatSession = chatSession;
|
|
4886
4952
|
}
|
|
4887
4953
|
/**
|
|
4888
4954
|
* 更新消息(优化版)
|
|
4889
|
-
*
|
|
4955
|
+
* 直接通过下标更新消息对象属性,返回是否成功
|
|
4890
4956
|
*/
|
|
4891
4957
|
updateMessage(index, message) {
|
|
4892
4958
|
if (index < 0 || index >= this._messageList.length)
|
|
4893
|
-
return;
|
|
4959
|
+
return null;
|
|
4894
4960
|
const messageObj = this._messageList[index];
|
|
4895
4961
|
if (!messageObj)
|
|
4896
|
-
return;
|
|
4897
|
-
//
|
|
4962
|
+
return null;
|
|
4963
|
+
// 更新消息角色
|
|
4964
|
+
if (message.role) {
|
|
4965
|
+
messageObj.set("role", this.options.role.toPointer());
|
|
4966
|
+
}
|
|
4967
|
+
// 更新内容 - 保持原始格式兼容
|
|
4968
|
+
// 从现有 content 中获取完整的 FmodeChatMessage 对象,然后更新字段
|
|
4898
4969
|
if (message.content !== undefined) {
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
}
|
|
4905
|
-
else if (Array.isArray(message.content)) {
|
|
4906
|
-
messageObj.set("content", {
|
|
4907
|
-
type: "mixed",
|
|
4908
|
-
items: message.content
|
|
4909
|
-
});
|
|
4910
|
-
}
|
|
4911
|
-
else {
|
|
4912
|
-
messageObj.set("content", message.content);
|
|
4913
|
-
}
|
|
4970
|
+
const existingContent = messageObj.get("content") || {};
|
|
4971
|
+
messageObj.set("content", {
|
|
4972
|
+
...existingContent,
|
|
4973
|
+
content: message.content
|
|
4974
|
+
});
|
|
4914
4975
|
}
|
|
4915
4976
|
// 更新其他属性
|
|
4916
4977
|
if (message.complete !== undefined)
|
|
4917
4978
|
messageObj.set("complete", message.complete);
|
|
4918
|
-
if (message.role)
|
|
4919
|
-
messageObj.set("role", message.role);
|
|
4920
4979
|
if (message.json)
|
|
4921
4980
|
messageObj.set("json", message.json);
|
|
4922
4981
|
if (message.hidden)
|
|
@@ -4925,6 +4984,16 @@ class MessageManager {
|
|
|
4925
4984
|
messageObj.set("voice", message.voice);
|
|
4926
4985
|
if (message.cid)
|
|
4927
4986
|
messageObj.set("cid", message.cid);
|
|
4987
|
+
return messageObj;
|
|
4988
|
+
}
|
|
4989
|
+
/**
|
|
4990
|
+
* 更新消息并异步保存
|
|
4991
|
+
*/
|
|
4992
|
+
async updateMessageAndSave(index, message) {
|
|
4993
|
+
const messageObj = this.updateMessage(index, message);
|
|
4994
|
+
if (!messageObj?.get("hidden")) {
|
|
4995
|
+
await this.saveMessage(messageObj);
|
|
4996
|
+
}
|
|
4928
4997
|
}
|
|
4929
4998
|
/**
|
|
4930
4999
|
* 重新加载消息
|
|
@@ -4947,20 +5016,35 @@ class MessageManager {
|
|
|
4947
5016
|
const MessageClass = Parse$G.Object.extend("Message");
|
|
4948
5017
|
const messageObj = new MessageClass();
|
|
4949
5018
|
// 复制老版消息属性
|
|
4950
|
-
messageObj.set("role",
|
|
5019
|
+
messageObj.set("role", this.options.role.toPointer());
|
|
4951
5020
|
messageObj.set("content", legacyMsg.content);
|
|
4952
5021
|
messageObj.set("complete", legacyMsg.complete);
|
|
4953
5022
|
messageObj.set("createdAt", legacyMsg.createdAt);
|
|
4954
5023
|
messageObj.set("voice", legacyMsg.voice);
|
|
4955
5024
|
messageObj.set("cid", legacyMsg.cid);
|
|
4956
5025
|
messageObj.set("isLegacy", true);
|
|
5026
|
+
// 为老消息同步创建 contentData,保持格式统一
|
|
5027
|
+
if (typeof legacyMsg.content === "string") {
|
|
5028
|
+
messageObj.set("contentData", {
|
|
5029
|
+
type: "text",
|
|
5030
|
+
text: legacyMsg.content,
|
|
5031
|
+
role: legacyMsg.role
|
|
5032
|
+
});
|
|
5033
|
+
}
|
|
5034
|
+
else if (Array.isArray(legacyMsg.content)) {
|
|
5035
|
+
messageObj.set("contentData", {
|
|
5036
|
+
type: "mixed",
|
|
5037
|
+
items: legacyMsg.content,
|
|
5038
|
+
role: legacyMsg.role
|
|
5039
|
+
});
|
|
5040
|
+
}
|
|
4957
5041
|
this._messageList.push(messageObj);
|
|
4958
5042
|
}
|
|
4959
5043
|
// 2. 加载新版消息
|
|
4960
5044
|
let newMessages = [];
|
|
4961
5045
|
switch (this.options.scene) {
|
|
4962
5046
|
case MessageScene.ROLE:
|
|
4963
|
-
if (this.options.
|
|
5047
|
+
if (this.options.role) {
|
|
4964
5048
|
newMessages = await this.loadRoleMessages();
|
|
4965
5049
|
}
|
|
4966
5050
|
break;
|
|
@@ -4993,10 +5077,11 @@ class MessageManager {
|
|
|
4993
5077
|
* 加载角色对话消息
|
|
4994
5078
|
*/
|
|
4995
5079
|
async loadRoleMessages() {
|
|
4996
|
-
if (!this.options.
|
|
5080
|
+
if (!this.options.role?.id)
|
|
4997
5081
|
return [];
|
|
4998
5082
|
const query = new Parse$G.Query("Message");
|
|
4999
|
-
query.
|
|
5083
|
+
query.exists("content");
|
|
5084
|
+
query.equalTo("role", this.options.role?.id);
|
|
5000
5085
|
query.addAscending("createdAt");
|
|
5001
5086
|
query.limit(1000); // 限制数量避免性能问题
|
|
5002
5087
|
return await query.find();
|
|
@@ -5008,6 +5093,7 @@ class MessageManager {
|
|
|
5008
5093
|
if (!this.options.group)
|
|
5009
5094
|
return [];
|
|
5010
5095
|
const query = new Parse$G.Query("Message");
|
|
5096
|
+
query.exists("content");
|
|
5011
5097
|
query.equalTo("group", this.options.group);
|
|
5012
5098
|
query.addAscending("createdAt");
|
|
5013
5099
|
query.limit(1000);
|
|
@@ -5020,6 +5106,7 @@ class MessageManager {
|
|
|
5020
5106
|
if (!this.options.userFrom || !this.options.userTo)
|
|
5021
5107
|
return [];
|
|
5022
5108
|
const query = new Parse$G.Query("Message");
|
|
5109
|
+
query.exists("content");
|
|
5023
5110
|
query.containedIn("userFrom", [this.options.userFrom, this.options.userTo]);
|
|
5024
5111
|
query.containedIn("userTo", [this.options.userFrom, this.options.userTo]);
|
|
5025
5112
|
query.addAscending("createdAt");
|
|
@@ -5033,6 +5120,7 @@ class MessageManager {
|
|
|
5033
5120
|
if (!this.options.contact)
|
|
5034
5121
|
return [];
|
|
5035
5122
|
const query = new Parse$G.Query("Message");
|
|
5123
|
+
query.exists("content");
|
|
5036
5124
|
query.equalTo("contact", this.options.contact);
|
|
5037
5125
|
query.addAscending("createdAt");
|
|
5038
5126
|
query.limit(1000);
|
|
@@ -5102,12 +5190,12 @@ function getMessageContentText(content) {
|
|
|
5102
5190
|
if (typeof content == "string")
|
|
5103
5191
|
text = content;
|
|
5104
5192
|
if (typeof content == "object")
|
|
5105
|
-
text = content?.content
|
|
5193
|
+
text = content?.content || "";
|
|
5106
5194
|
return text;
|
|
5107
5195
|
}
|
|
5108
5196
|
function getMessageImageUrl(content) {
|
|
5109
5197
|
if (typeof content == "object")
|
|
5110
|
-
return content?.
|
|
5198
|
+
return content?.image_url?.url || "";
|
|
5111
5199
|
return null;
|
|
5112
5200
|
}
|
|
5113
5201
|
/**
|
|
@@ -5268,20 +5356,17 @@ class FmodeChat {
|
|
|
5268
5356
|
this.navCtrl = navCtrl;
|
|
5269
5357
|
this.ncloud = ncloud;
|
|
5270
5358
|
this.storage = storage;
|
|
5271
|
-
//
|
|
5359
|
+
// 初始化企业信息(异步,不阻塞)
|
|
5272
5360
|
this.initCompany();
|
|
5273
5361
|
if (chatSession?.id) {
|
|
5274
5362
|
this.chatSession = chatSession;
|
|
5275
5363
|
this.sessionId = chatSession?.id;
|
|
5276
|
-
// 初始化消息管理器
|
|
5277
|
-
this.initMessageManager();
|
|
5278
|
-
}
|
|
5279
|
-
else {
|
|
5280
|
-
// 即使没有chatSession,也尝试初始化消息管理器(用于新会话)
|
|
5281
|
-
setTimeout(() => {
|
|
5282
|
-
this.initMessageManager();
|
|
5283
|
-
}, 100);
|
|
5284
5364
|
}
|
|
5365
|
+
// 延迟初始化消息管理器(懒加载模式)
|
|
5366
|
+
// 确保company先初始化完成
|
|
5367
|
+
setTimeout(() => {
|
|
5368
|
+
this.ensureMessageManager();
|
|
5369
|
+
}, 0);
|
|
5285
5370
|
if (this.role?.id) {
|
|
5286
5371
|
this.voiceConfig = this.role?.get("voiceConfig");
|
|
5287
5372
|
if (this.voiceConfig?.autoTalk) {
|
|
@@ -5352,6 +5437,8 @@ class FmodeChat {
|
|
|
5352
5437
|
* 获取消息列表(兼容老版本)
|
|
5353
5438
|
*/
|
|
5354
5439
|
async getMessageList() {
|
|
5440
|
+
// 确保 messageManager 已初始化
|
|
5441
|
+
await this.ensureMessageManager();
|
|
5355
5442
|
if (!this.messageManager) {
|
|
5356
5443
|
return [];
|
|
5357
5444
|
}
|
|
@@ -5386,17 +5473,33 @@ class FmodeChat {
|
|
|
5386
5473
|
}
|
|
5387
5474
|
/**
|
|
5388
5475
|
* 初始化消息管理器
|
|
5476
|
+
* 关键:即使没有chatSession.id也能初始化(新会话场景)
|
|
5389
5477
|
*/
|
|
5390
|
-
initMessageManager() {
|
|
5391
|
-
|
|
5478
|
+
async initMessageManager() {
|
|
5479
|
+
// 确保 company 已初始化
|
|
5480
|
+
if (!this.company) {
|
|
5481
|
+
await this.initCompany();
|
|
5482
|
+
}
|
|
5483
|
+
if (this.role && this.company) {
|
|
5392
5484
|
// 使用智能体对话场景的消息管理器
|
|
5393
|
-
|
|
5485
|
+
// chatSession可能暂时没有id(新会话),但manager仍然可以工作
|
|
5486
|
+
this.messageManager = MessageManager.createForRole(this.chatSession || new Parse$F.Object("ChatSession"), this.role, this.company);
|
|
5487
|
+
}
|
|
5488
|
+
}
|
|
5489
|
+
/**
|
|
5490
|
+
* 确保消息管理器已初始化(懒加载)
|
|
5491
|
+
*/
|
|
5492
|
+
async ensureMessageManager() {
|
|
5493
|
+
if (!this.messageManager) {
|
|
5494
|
+
await this.initMessageManager();
|
|
5394
5495
|
}
|
|
5395
5496
|
}
|
|
5396
5497
|
/**
|
|
5397
5498
|
* 初始化企业信息
|
|
5398
5499
|
*/
|
|
5399
5500
|
async initCompany() {
|
|
5501
|
+
if (this.company)
|
|
5502
|
+
return; // 已经初始化
|
|
5400
5503
|
const companyId = localStorage.getItem("company");
|
|
5401
5504
|
if (companyId) {
|
|
5402
5505
|
this.company = {
|
|
@@ -5411,8 +5514,10 @@ class FmodeChat {
|
|
|
5411
5514
|
* 直接添加到MessageManager的_messageList,延迟保存
|
|
5412
5515
|
*/
|
|
5413
5516
|
async addMessage(content, scene = MessageScene.ROLE) {
|
|
5517
|
+
// 确保 messageManager 已初始化
|
|
5518
|
+
await this.ensureMessageManager();
|
|
5414
5519
|
if (!this.messageManager) {
|
|
5415
|
-
console.warn("MessageManager
|
|
5520
|
+
console.warn("MessageManager初始化失败,无法添加消息");
|
|
5416
5521
|
return;
|
|
5417
5522
|
}
|
|
5418
5523
|
try {
|
|
@@ -5420,8 +5525,9 @@ class FmodeChat {
|
|
|
5420
5525
|
const messageObj = this.messageManager.addMessage(content, scene);
|
|
5421
5526
|
// 异步保存,不阻塞主流程
|
|
5422
5527
|
setTimeout(() => {
|
|
5423
|
-
this.messageManager
|
|
5528
|
+
this.messageManager?.saveMessage(messageObj);
|
|
5424
5529
|
}, 0);
|
|
5530
|
+
return messageObj;
|
|
5425
5531
|
}
|
|
5426
5532
|
catch (error) {
|
|
5427
5533
|
console.error("添加消息失败:", error);
|
|
@@ -5431,16 +5537,18 @@ class FmodeChat {
|
|
|
5431
5537
|
* 更新消息(优化版)
|
|
5432
5538
|
* 直接通过下标更新MessageManager中的消息
|
|
5433
5539
|
*/
|
|
5434
|
-
updateMessage(
|
|
5540
|
+
updateMessage(messageObj, content) {
|
|
5435
5541
|
if (!this.messageManager) {
|
|
5436
5542
|
console.warn("MessageManager未初始化,无法更新消息");
|
|
5437
5543
|
return;
|
|
5438
5544
|
}
|
|
5439
5545
|
try {
|
|
5440
5546
|
// 直接更新MessageManager中的消息
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
|
|
5547
|
+
content = {
|
|
5548
|
+
...messageObj.get("content"),
|
|
5549
|
+
...content
|
|
5550
|
+
};
|
|
5551
|
+
messageObj.set("content", content);
|
|
5444
5552
|
if (messageObj) {
|
|
5445
5553
|
setTimeout(() => {
|
|
5446
5554
|
this.messageManager.saveMessage(messageObj);
|
|
@@ -5555,37 +5663,25 @@ class FmodeChat {
|
|
|
5555
5663
|
// 提示词已经存在
|
|
5556
5664
|
return;
|
|
5557
5665
|
}
|
|
5558
|
-
// 补全提示词
|
|
5559
|
-
|
|
5560
|
-
let insertIndex = systemIndex + 1;
|
|
5561
|
-
let msgObj = new Parse$F.Object("Message");
|
|
5562
|
-
msgObj.set("content", promptMsg);
|
|
5563
|
-
this.messageList.splice(insertIndex, 0, msgObj);
|
|
5666
|
+
// 补全提示词 - 使用 addMessage 确保正确保存
|
|
5667
|
+
this.addMessage(promptMsg, MessageScene.ROLE);
|
|
5564
5668
|
return;
|
|
5565
5669
|
}
|
|
5566
5670
|
/**
|
|
5567
5671
|
* 角色提示词
|
|
5568
5672
|
* @returns
|
|
5569
5673
|
*/
|
|
5570
|
-
loadRolePrompt() {
|
|
5674
|
+
async loadRolePrompt() {
|
|
5675
|
+
// 确保 messageManager 已初始化
|
|
5676
|
+
await this.ensureMessageManager();
|
|
5571
5677
|
// 角色提示
|
|
5572
5678
|
let prompt = this.role?.get("prompt");
|
|
5573
|
-
let promptMsg = { role: "user", content: prompt, hidden: true };
|
|
5574
5679
|
if (!prompt)
|
|
5575
5680
|
return; // 无提示词无需添加
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
return;
|
|
5581
|
-
}
|
|
5582
|
-
// 补全提示词
|
|
5583
|
-
let systemIndex = this.messageList?.findIndex(item => this.getMessageProperty(item, 'role') == "system");
|
|
5584
|
-
let insertIndex = systemIndex + 1;
|
|
5585
|
-
let msgObj = new Parse$F.Object("Message");
|
|
5586
|
-
msgObj.set("content", promptMsg);
|
|
5587
|
-
this.messageList.splice(insertIndex, 0, msgObj);
|
|
5588
|
-
// console.log(this.messageList)
|
|
5681
|
+
this.rolePrompt = prompt;
|
|
5682
|
+
// 使用 addMessage 添加提示词,确保正确保存
|
|
5683
|
+
// let promptMsg:FmodeChatMessage = {role:"user",content:prompt,hidden:true}
|
|
5684
|
+
// await this.addMessage(promptMsg, MessageScene.ROLE);
|
|
5589
5685
|
}
|
|
5590
5686
|
/**
|
|
5591
5687
|
* 发送消息
|
|
@@ -5593,12 +5689,14 @@ class FmodeChat {
|
|
|
5593
5689
|
* @param imageUrl
|
|
5594
5690
|
*/
|
|
5595
5691
|
async sendMessage(message = "FmodeAiTest测试问题", imageUrl, onComplete, eventMap, voice) {
|
|
5692
|
+
// 确保 messageManager 已初始化
|
|
5693
|
+
await this.ensureMessageManager();
|
|
5596
5694
|
// 发消息自动置底
|
|
5597
5695
|
this.scrollToBottom && this.scrollToBottom();
|
|
5598
5696
|
// 为消息列表补全提示词
|
|
5599
5697
|
// await this.loadTalkSystemPrompt(this.role);
|
|
5600
5698
|
this.isPromptMessageAreaShow = false; // 发送第一条消息后,关闭提示看板
|
|
5601
|
-
this.loadRolePrompt();
|
|
5699
|
+
await this.loadRolePrompt();
|
|
5602
5700
|
// 构建用户消息
|
|
5603
5701
|
let userMessage;
|
|
5604
5702
|
if (!imageUrl) { // 纯文本
|
|
@@ -5633,11 +5731,14 @@ class FmodeChat {
|
|
|
5633
5731
|
}
|
|
5634
5732
|
}
|
|
5635
5733
|
// 添加用户消息
|
|
5636
|
-
|
|
5734
|
+
this.addMessage(userMessage, MessageScene.ROLE);
|
|
5637
5735
|
// 获取最新消息列表用于补全
|
|
5638
5736
|
const messages = await this.getMessageList();
|
|
5639
5737
|
// 创建并发起一条新的消息补全
|
|
5640
|
-
let completion = new FmodeChatCompletion(
|
|
5738
|
+
let completion = new FmodeChatCompletion([
|
|
5739
|
+
{ role: "user", content: this.rolePrompt }, // 角色提示词 不在消息列表
|
|
5740
|
+
...this.fixMessageList(this.convertMessages(messages)) // 列表形式,确保灵活的图片消息格式接收
|
|
5741
|
+
], {
|
|
5641
5742
|
model: this.currentModel?.get?.("code") || "fmode-1.6-cn"
|
|
5642
5743
|
});
|
|
5643
5744
|
// 生命周期:消息获取完成
|
|
@@ -5661,9 +5762,7 @@ class FmodeChat {
|
|
|
5661
5762
|
createdAt: new Date()
|
|
5662
5763
|
};
|
|
5663
5764
|
// 添加AI回复消息占位符
|
|
5664
|
-
await this.addMessage(aiMessagePlaceholder, MessageScene.ROLE);
|
|
5665
|
-
const currentMessages = await this.getMessageList();
|
|
5666
|
-
const aiMessageIndex = currentMessages.length - 1;
|
|
5765
|
+
let newAiMessage = await this.addMessage(aiMessagePlaceholder, MessageScene.ROLE);
|
|
5667
5766
|
// 生命周期:用户发送消息回调
|
|
5668
5767
|
if (this.onUserSend) {
|
|
5669
5768
|
let sendResult = await this.onUserSend(this, userMessage);
|
|
@@ -5674,29 +5773,26 @@ class FmodeChat {
|
|
|
5674
5773
|
isDirect: isDirect,
|
|
5675
5774
|
onComplete: onComplete || null
|
|
5676
5775
|
}).pipe(finalize(async () => {
|
|
5677
|
-
let currentMessage = currentMessages[aiMessageIndex];
|
|
5678
5776
|
// 标记消息完成
|
|
5679
|
-
this.
|
|
5680
|
-
await this.updateMessage(aiMessageIndex, currentMessage);
|
|
5777
|
+
this.updateMessage(newAiMessage, { complete: true });
|
|
5681
5778
|
// 在消息完成后生成语音(TalkMode)
|
|
5682
5779
|
if (this.isTalkMode) {
|
|
5683
|
-
let content =
|
|
5780
|
+
let content = newAiMessage?.get("content");
|
|
5684
5781
|
if (this.hasValidContent(content)) {
|
|
5685
5782
|
try {
|
|
5686
5783
|
let voice = await this.getVoiceByContentText(content, eventMap);
|
|
5687
5784
|
if (voice && voice.id) {
|
|
5688
|
-
|
|
5689
|
-
|
|
5785
|
+
// 更新消息的voice属性
|
|
5786
|
+
this.updateMessage(newAiMessage, { voice: voice });
|
|
5690
5787
|
// 通知SSML完成
|
|
5691
5788
|
eventMap?.onSSMLComplete && eventMap?.onSSMLComplete(voice);
|
|
5692
5789
|
// 播放语音
|
|
5693
5790
|
this.playChatVoice(this.voiceMap[voice?.id], {
|
|
5694
5791
|
onResult: (result) => {
|
|
5695
5792
|
if (result?.duration) {
|
|
5696
|
-
|
|
5697
|
-
voice
|
|
5698
|
-
|
|
5699
|
-
this.updateMessage(aiMessageIndex, currentMessage);
|
|
5793
|
+
// 更新语音时长
|
|
5794
|
+
const updatedVoice = { ...voice, duration: result?.duration };
|
|
5795
|
+
this.updateMessage(newAiMessage, { voice: updatedVoice });
|
|
5700
5796
|
}
|
|
5701
5797
|
}
|
|
5702
5798
|
});
|
|
@@ -5708,27 +5804,24 @@ class FmodeChat {
|
|
|
5708
5804
|
}
|
|
5709
5805
|
}
|
|
5710
5806
|
// 生命周期:消息获取完成
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5807
|
+
if (newAiMessage?.id) {
|
|
5808
|
+
this.onMessage && this.onMessage(this, newAiMessage?.get("content"));
|
|
5809
|
+
}
|
|
5810
|
+
// 保存AI消息到数据库(必须在session保存之后)
|
|
5811
|
+
if (newAiMessage) {
|
|
5812
|
+
await this.messageManager?.saveMessage(newAiMessage);
|
|
5714
5813
|
}
|
|
5715
5814
|
})).subscribe(async (message) => {
|
|
5716
|
-
|
|
5717
|
-
await this.updateMessage(aiMessageIndex, message);
|
|
5815
|
+
let content = message.content;
|
|
5718
5816
|
// 更新最新回复内容
|
|
5719
|
-
this.latestAIResponse = this.getContentText(
|
|
5817
|
+
this.latestAIResponse = this.getContentText(content);
|
|
5720
5818
|
// 生命周期:消息更新回调(实时更新)
|
|
5721
5819
|
if (this.onMessage && !message?.complete) {
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
this.onMessage(this, currentMsgs[currentMsgs?.length - 1]?.get("content"));
|
|
5725
|
-
}
|
|
5820
|
+
newAiMessage.set("content", message?.content);
|
|
5821
|
+
this.onMessage(this, message);
|
|
5726
5822
|
}
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
let savedList = this.chatSession?.get("messageList")?.length;
|
|
5730
|
-
if (currentMsgs?.length > savedList) {
|
|
5731
|
-
await this.saveChatSession();
|
|
5823
|
+
if (message?.complete) {
|
|
5824
|
+
this.updateMessage(newAiMessage, content);
|
|
5732
5825
|
}
|
|
5733
5826
|
// 滚动到底部
|
|
5734
5827
|
this.scrollToBottom && this.scrollToBottom();
|
|
@@ -5967,9 +6060,11 @@ class FmodeChat {
|
|
|
5967
6060
|
}
|
|
5968
6061
|
/**
|
|
5969
6062
|
* 保存单次会话
|
|
6063
|
+
* 关键:在session保存后,立即更新消息管理器中的session引用并保存所有待处理消息
|
|
5970
6064
|
*/
|
|
5971
6065
|
async saveChatSession() {
|
|
5972
|
-
|
|
6066
|
+
const isNewSession = this.sessionId == "new";
|
|
6067
|
+
if (isNewSession) {
|
|
5973
6068
|
this.chatSession = new this.ChatSession();
|
|
5974
6069
|
}
|
|
5975
6070
|
this.chatSession.set("title", this.genTitle());
|
|
@@ -5983,8 +6078,18 @@ class FmodeChat {
|
|
|
5983
6078
|
this.chatSession.set("messageList", []);
|
|
5984
6079
|
}
|
|
5985
6080
|
this.chatSession = await this.chatSession.save();
|
|
5986
|
-
|
|
6081
|
+
// 关键:更新sessionId
|
|
6082
|
+
const oldSessionId = this.sessionId;
|
|
5987
6083
|
this.sessionId = this.chatSession?.id;
|
|
6084
|
+
// 关键:更新MessageManager中的session引用
|
|
6085
|
+
if (this.messageManager) {
|
|
6086
|
+
this.messageManager.updateChatSession(this.chatSession);
|
|
6087
|
+
// 保存所有待处理的消息(新会话的消息或之前因缺少session而未保存的消息)
|
|
6088
|
+
// if (this.sessionId) {
|
|
6089
|
+
await this.messageManager.savePendingMessages();
|
|
6090
|
+
// }
|
|
6091
|
+
}
|
|
6092
|
+
this.onChatSaved && this.onChatSaved(this);
|
|
5988
6093
|
if (this.sessionId) {
|
|
5989
6094
|
// 修改URL地址为sessionId,方便分享或切换 角色页面 => 会话页面
|
|
5990
6095
|
let newHref = `${window.location.origin}/chat/pro/chat/${this.sessionId}`;
|
|
@@ -6000,11 +6105,16 @@ class FmodeChat {
|
|
|
6000
6105
|
const messages = await this.getMessageList();
|
|
6001
6106
|
const lastMessage = messages[messages?.length - 1];
|
|
6002
6107
|
let messagePreview = "";
|
|
6003
|
-
|
|
6004
|
-
|
|
6108
|
+
// 处理不同格式的content
|
|
6109
|
+
const content = lastMessage?.get("content");
|
|
6110
|
+
if (typeof content === "string") {
|
|
6111
|
+
messagePreview = content.slice(0, 20);
|
|
6005
6112
|
}
|
|
6006
|
-
else if (
|
|
6007
|
-
|
|
6113
|
+
else if (content?.text) {
|
|
6114
|
+
messagePreview = content.text.slice(0, 20);
|
|
6115
|
+
}
|
|
6116
|
+
else if (Array.isArray(content)) {
|
|
6117
|
+
const textItem = content.find(item => item?.text);
|
|
6008
6118
|
messagePreview = textItem?.text?.slice(0, 20) || "";
|
|
6009
6119
|
}
|
|
6010
6120
|
let newChat = {
|
|
@@ -6016,13 +6126,18 @@ class FmodeChat {
|
|
|
6016
6126
|
};
|
|
6017
6127
|
if (this.chatServ && !this.chatServ?.chatList?.length)
|
|
6018
6128
|
this.chatServ.chatList = [];
|
|
6019
|
-
let index = this.chatServ?.chatList?.
|
|
6129
|
+
let index = this.chatServ?.chatList?.findIndex(item => item?.sid == newChat?.sid);
|
|
6020
6130
|
if (index > -1) {
|
|
6021
6131
|
this.chatServ.chatList[index] = newChat;
|
|
6022
6132
|
}
|
|
6023
6133
|
else {
|
|
6024
6134
|
this.chatServ?.chatList.unshift(newChat);
|
|
6025
6135
|
}
|
|
6136
|
+
// 更新chatServ中的chatMap(将new的key替换为真实sessionId)
|
|
6137
|
+
if (isNewSession && oldSessionId === "new") {
|
|
6138
|
+
this.chatServ.chatMap[this.sessionId] = this;
|
|
6139
|
+
delete this.chatServ.chatMap["new"];
|
|
6140
|
+
}
|
|
6026
6141
|
}
|
|
6027
6142
|
}
|
|
6028
6143
|
getInviteUrl(url) {
|
|
@@ -6046,7 +6161,9 @@ class FmodeChat {
|
|
|
6046
6161
|
return this.title;
|
|
6047
6162
|
}
|
|
6048
6163
|
fixMessageList(messages) {
|
|
6049
|
-
|
|
6164
|
+
let list = messages.filter((msg) => { return typeof msg.content.role == "string" && msg.content.content; }).map((msg) => { return { role: msg.content.role, content: msg.content.content }; });
|
|
6165
|
+
console.log("messages", messages, list);
|
|
6166
|
+
return list;
|
|
6050
6167
|
}
|
|
6051
6168
|
nowStr() {
|
|
6052
6169
|
let now = new Date();
|
|
@@ -11484,14 +11601,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
11484
11601
|
|
|
11485
11602
|
const Parse$w = defaultExport.with("nova");
|
|
11486
11603
|
class ChatService {
|
|
11487
|
-
constructor(router, ncloud, platform, alertCtrl, navCtrl, cross
|
|
11604
|
+
constructor(router, ncloud, platform, alertCtrl, navCtrl, cross) {
|
|
11488
11605
|
this.router = router;
|
|
11489
11606
|
this.ncloud = ncloud;
|
|
11490
11607
|
this.platform = platform;
|
|
11491
11608
|
this.alertCtrl = alertCtrl;
|
|
11492
11609
|
this.navCtrl = navCtrl;
|
|
11493
11610
|
this.cross = cross;
|
|
11494
|
-
this.storage = storage;
|
|
11495
11611
|
// 已加载聊天信息临时存储
|
|
11496
11612
|
this.chatMap = {};
|
|
11497
11613
|
this.isCapacitor = false;
|
|
@@ -11500,6 +11616,10 @@ class ChatService {
|
|
|
11500
11616
|
"mobile": "移动端",
|
|
11501
11617
|
};
|
|
11502
11618
|
this.isCapacitor = this.platform.is("capacitor");
|
|
11619
|
+
this.initStorage();
|
|
11620
|
+
}
|
|
11621
|
+
async initStorage() {
|
|
11622
|
+
this.storage = await NovaStorage.withCid(localStorage.getItem("company"));
|
|
11503
11623
|
}
|
|
11504
11624
|
async doButtonAction(button) {
|
|
11505
11625
|
let type = this.cross.navMenuType;
|
|
@@ -11636,7 +11756,7 @@ class ChatService {
|
|
|
11636
11756
|
type: "phone"
|
|
11637
11757
|
}]);
|
|
11638
11758
|
}
|
|
11639
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ChatService, deps: [{ token: i1$1.Router }, { token: NovaCloudService }, { token: i3.Platform }, { token: i3.AlertController }, { token: i3.NavController }, { token: CrossService }
|
|
11759
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ChatService, deps: [{ token: i1$1.Router }, { token: NovaCloudService }, { token: i3.Platform }, { token: i3.AlertController }, { token: i3.NavController }, { token: CrossService }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
11640
11760
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ChatService, providedIn: 'root' }); }
|
|
11641
11761
|
}
|
|
11642
11762
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ChatService, decorators: [{
|
|
@@ -11644,7 +11764,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
11644
11764
|
args: [{
|
|
11645
11765
|
providedIn: 'root'
|
|
11646
11766
|
}]
|
|
11647
|
-
}], ctorParameters: () => [{ type: i1$1.Router }, { type: NovaCloudService }, { type: i3.Platform }, { type: i3.AlertController }, { type: i3.NavController }, { type: CrossService }
|
|
11767
|
+
}], ctorParameters: () => [{ type: i1$1.Router }, { type: NovaCloudService }, { type: i3.Platform }, { type: i3.AlertController }, { type: i3.NavController }, { type: CrossService }] });
|
|
11648
11768
|
|
|
11649
11769
|
const Parse$v = defaultExport.with("nova");
|
|
11650
11770
|
/**
|
|
@@ -13340,7 +13460,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
13340
13460
|
class ChatContentPipe {
|
|
13341
13461
|
transform(content, ...args) {
|
|
13342
13462
|
// console.log(args);
|
|
13343
|
-
let arg = args?.[0] || "text";
|
|
13463
|
+
let arg = args?.[0] || content?.type || "text";
|
|
13344
13464
|
if (arg == "text") {
|
|
13345
13465
|
return getMessageContentText(content);
|
|
13346
13466
|
}
|
|
@@ -16457,7 +16577,7 @@ class FmChatMessageCard {
|
|
|
16457
16577
|
this.copyServ.copyToClipboard(getMessageContentText(this.message?.get('content')));
|
|
16458
16578
|
}
|
|
16459
16579
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FmChatMessageCard, deps: [{ token: ClipboardService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
16460
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: FmChatMessageCard, isStandalone: true, selector: "fm-chat-message-card", inputs: { index: "index", message: "message", role: "role", chat: "chat" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"message-card\" [class.right]=\"message?.get('
|
|
16580
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: FmChatMessageCard, isStandalone: true, selector: "fm-chat-message-card", inputs: { index: "index", message: "message", role: "role", chat: "chat" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"message-card\" [class.right]=\"message?.get('content')?.role=='user'\" [class.center]=\"message?.get('content')?.role=='system'\">\r\n <!-- \u7528\u6237\u53CA\u64CD\u4F5C\u533A -->\r\n <div class=\"item-row user\" *ngIf=\"message?.get('content')?.role!='system'\"> <!-- \u7CFB\u7EDF\u6D88\u606F\u4E0D\u663E\u793A\u5934\u50CF -->\r\n <div class=\"avatar-row\">\r\n <div class=\"actions\">\r\n <!-- \u5237\u65B0 -->\r\n <!-- <ion-button fill=\"outline\" slot=\"start\">\r\n <ion-icon name=\"refresh-outline\"></ion-icon>\r\n </ion-button> -->\r\n <!-- \u590D\u5236 -->\r\n <ion-button size=\"small\" fill=\"outline\" slot=\"start\" (click)=\"copy()\">\r\n <ion-icon name=\"copy-outline\"></ion-icon>\r\n </ion-button>\r\n <!-- \u7F16\u8F91 -->\r\n <!-- <ion-button fill=\"outline\" slot=\"start\">\r\n <ion-icon name=\"create-outline\"></ion-icon>\r\n </ion-button> -->\r\n </div>\r\n <!-- \u66F4\u65B0\u97F3\u9891\u6D88\u606F\u533A\u57DF -->\r\n <div *ngIf=\"((message?.get('content')?.role=='assistant' && chat?.role?.get('voiceConfig')?.voice) || (message?.get('content')?.role=='user'&&message?.get('voice')))\"\r\n class=\"play-voice\"\r\n (click)=\"!isLoadingText && toggleVoicePlay()\"\r\n [class.loading-voice]=\"chat?.isTalkMode && isLoadingText\">\r\n\r\n <div class=\"voice-button\">\r\n <!-- \u52A0\u8F7D\u65F6\u663E\u793Aspinner\uFF0C\u5426\u5219\u663E\u793Awifi\u56FE\u6807 -->\r\n <ion-spinner *ngIf=\"chat?.isTalkMode && isLoadingText\" name=\"lines\" class=\"loading-spinner\"></ion-spinner>\r\n <ion-icon *ngIf=\"!(chat?.isTalkMode && isLoadingText)\" name=\"wifi-outline\"\r\n [style.transform]=\"message?.get('content')?.role=='user'?'rotate(-90deg)':'rotate(90deg)'\"\r\n class=\"audio-icon\"\r\n [class.play-voice-playing]=\"tts?.isPlaying\"></ion-icon>\r\n </div>\r\n <div class=\"voice-info\">\r\n <span *ngIf=\"message?.get('voice')?.duration && !isLoadingText\">\r\n {{((message?.get('voice')?.duration||0)/1000) | durationStr}}\r\n </span>\r\n </div>\r\n </div>\r\n <!-- \u5934\u50CF\u533A\u57DF -->\r\n <img class=\"avatar\" *ngIf=\"message?.get('content')?.role!='user'\" [src]=\"(chat?.role?.get('avatar') || chat?.role?.get('thumb') || 'https://file-cloud.fmode.cn/E4KpGvTEto/20230930/l413e6090731854.png')+'?'+'x-image-process=image/resize,m_fixed,w_100'+'&imageView2/1/w/100/h/100'\" >\r\n <app-comp-user-avatar [user]=\"user\" *ngIf=\"message?.get('content')?.role=='user'\"></app-comp-user-avatar>\r\n </div>\r\n </div>\r\n <!-- \u9644\u4EF6\uFF1A\u56FE\u7247 -->\r\n <div class=\"item-row images\" *ngIf=\"message?.get('content') | chatContent:'image_url'\">\r\n <img [src]=\"message?.get('content') | chatContent:'image_url'\" alt=\"\">\r\n </div>\r\n <!-- \u804A\u5929\u6C14\u6CE1 -->\r\n <!-- Replace the bubble section with this: -->\r\n <div class=\"item-row bubble\" [style.fontSize]=\"role?.get('uiConfig')?.msg?.bubble?.fontSize || '0.8rem'\">\r\n <!-- \u8BF4\u8BDD\u6A21\u5F0F\uFF1A\u5C55\u793A\u52A0\u8F7D\u72B6\u6001 Show loading state for talk mode when message is not complete -->\r\n\r\n <!-- Show normal content for non-talk mode or when loading is complete -->\r\n <ng-container *ngIf=\"!chat?.isTalkMode || message?.get('content')?.role !== 'assistant' || !isLoadingText\">\r\n <fm-markdown-preview *ngIf=\"!message?.get('complete')\" class=\"content-style\"\r\n [content]=\"message?.get('content') | chatContent\" [render]=\"false\"></fm-markdown-preview>\r\n <fm-markdown-preview *ngIf=\"message?.get('complete')\"\r\n [content]=\"message?.get('content') | chatContent\"></fm-markdown-preview>\r\n </ng-container>\r\n </div>\r\n <!-- \u65F6\u95F4\u663E\u793A -->\r\n <div class=\"item-row loading\" *ngIf=\"message?.get('content')?.role!='system' && !message?.get('complete')\">\r\n \u6B63\u5728\u8F93\u5165<ion-spinner name=\"dots\"></ion-spinner>\r\n </div>\r\n\r\n <div class=\"item-row created\" *ngIf=\"message?.get('createdAt')\">\r\n <span>{{message?.get('createdAt') | date:\"dd/MM/yy HH:mm\"}}</span>\r\n </div>\r\n</div>", styles: ["@charset \"UTF-8\";:host-context(body.dark) .message-card .actions .item-native{background:none!important}:host-context(body.dark) .message-card .bubble{color:#0e101d}:host-context(body.dark) .message-card .bubble .content-style{filter:invert(1)!important}:host-context(body.dark) .message-card .bubble fm-markdown-preview{filter:invert(1)!important}:host-context(body.dark) .message-card .play-voice{background-color:#0e101d}:host-context(body.dark) .message-card .play-voice .voice-info{color:#fff}:host-context(body.dark) .message-card .right .bubble{color:#921f8a!important;background:#921f8a!important}:host-context(body.dark) .message-card .right .play-voice{background:#921f8a!important}:host-context(body.dark) .message-card .created span{color:#fff}@media screen and (max-width: 800px){.message-card:focus .actions{opacity:1!important}}.message-card:hover .actions{opacity:1;transition:opacity .3s ease-in-out}.message-card{display:flex;flex-wrap:wrap;justify-content:start;align-items:flex-start}.message-card .avatar-row{width:300px;height:32px;display:flex;flex-direction:row-reverse;justify-content:start;align-items:center}.message-card .actions{display:flex;opacity:0;padding-left:10px;padding-right:10px}.message-card .item-row{display:flex;flex:100%;justify-content:start;margin-bottom:5px}.message-card .images img{max-width:300px}.message-card .bubble.loading{color:var(--gray-secondary)}.message-card .bubble.loading .content-style{filter:none}.message-card .bubble{display:flex;justify-content:center;max-width:100%;padding:.5rem .5rem 0rem;color:#333;flex:none;border-radius:0 1.5em 1.5em/0em 1.5em 1.5em;color:#fff;background-color:currentColor}.message-card .bubble .content-style{filter:grayscale(1) contrast(999) invert(1)}.message-card .loading{text-align:right;color:#101010}.message-card .created{display:flex}.message-card .created span{font-size:12px;opacity:.4;white-space:nowrap;transition:all .6s ease;color:var(--black);text-align:center;width:100%;box-sizing:border-box;padding-right:10px;pointer-events:none;z-index:1}.right{justify-content:end;align-items:flex-end}.right .avatar-row{flex-direction:row;justify-content:end;width:auto}.right .actions{position:relative;margin-left:0}.right .item-row{justify-content:end}.right .bubble{color:#bbdefb;border-top-left-radius:1.5em;border-top-right-radius:0}.right .play-voice{flex-direction:row-reverse;background-color:#bbdefb}.center{justify-content:center;align-items:center}.center .item-row{justify-content:center}.center .bubble{color:var(--gray-secondary);border-top-left-radius:1.5em;border-top-right-radius:1.5em;font-size:12px;font-weight:100;padding:5px 20px}.play-voice{min-width:100px;height:32px;display:flex;justify-content:space-around;align-items:center;background-color:#fff;border-radius:7px}.play-voice .voice-button{width:32px;height:32px;display:flex;justify-content:center;align-items:center}.play-voice .voice-button span{overflow:hidden;font-size:18px;color:#ff69b4}.play-voice .voice-info{height:32px;display:flex;padding:0 10px;justify-content:end;align-items:center;color:#333}.avatar{border-radius:50%;width:32px;height:32px;object-fit:cover}.audio-icon{color:#ff69b4;font-size:18px}.play-voice-playing{animation:play-voice-animation 1s infinite}@keyframes play-voice-animation{0%{width:0}to{width:32px}}.content-style.loading-text{color:#666;font-style:italic}.play-voice{transition:opacity .3s ease}.play-voice.loading-voice{opacity:.8;cursor:not-allowed}.play-voice.loading-voice .loading-spinner{width:18px;height:18px;color:var(--gray-secondary)}.play-voice.loading-voice .loading-text{font-size:.8em;color:var(--gray-secondary);margin-left:5px}.play-voice .voice-button{display:flex;align-items:center;justify-content:center;width:24px;height:24px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i2.DatePipe, name: "date" }, { kind: "component", type: CompUserAvatarComponent, selector: "app-comp-user-avatar", inputs: ["user"] }, { kind: "ngmodule", type: MarkdownPreviewModule }, { kind: "component", type: MarkdownPreviewComponent, selector: "fm-markdown-preview", inputs: ["content", "render"] }, { kind: "pipe", type: ChatContentPipe, name: "chatContent" }, { kind: "pipe", type: DurationStrPipe, name: "durationStr" }] }); }
|
|
16461
16581
|
}
|
|
16462
16582
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FmChatMessageCard, decorators: [{
|
|
16463
16583
|
type: Component,
|
|
@@ -16468,7 +16588,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
16468
16588
|
ChatContentPipe,
|
|
16469
16589
|
NzSanitizerPipe,
|
|
16470
16590
|
DurationStrPipe
|
|
16471
|
-
], template: "<div class=\"message-card\" [class.right]=\"message?.get('
|
|
16591
|
+
], template: "<div class=\"message-card\" [class.right]=\"message?.get('content')?.role=='user'\" [class.center]=\"message?.get('content')?.role=='system'\">\r\n <!-- \u7528\u6237\u53CA\u64CD\u4F5C\u533A -->\r\n <div class=\"item-row user\" *ngIf=\"message?.get('content')?.role!='system'\"> <!-- \u7CFB\u7EDF\u6D88\u606F\u4E0D\u663E\u793A\u5934\u50CF -->\r\n <div class=\"avatar-row\">\r\n <div class=\"actions\">\r\n <!-- \u5237\u65B0 -->\r\n <!-- <ion-button fill=\"outline\" slot=\"start\">\r\n <ion-icon name=\"refresh-outline\"></ion-icon>\r\n </ion-button> -->\r\n <!-- \u590D\u5236 -->\r\n <ion-button size=\"small\" fill=\"outline\" slot=\"start\" (click)=\"copy()\">\r\n <ion-icon name=\"copy-outline\"></ion-icon>\r\n </ion-button>\r\n <!-- \u7F16\u8F91 -->\r\n <!-- <ion-button fill=\"outline\" slot=\"start\">\r\n <ion-icon name=\"create-outline\"></ion-icon>\r\n </ion-button> -->\r\n </div>\r\n <!-- \u66F4\u65B0\u97F3\u9891\u6D88\u606F\u533A\u57DF -->\r\n <div *ngIf=\"((message?.get('content')?.role=='assistant' && chat?.role?.get('voiceConfig')?.voice) || (message?.get('content')?.role=='user'&&message?.get('voice')))\"\r\n class=\"play-voice\"\r\n (click)=\"!isLoadingText && toggleVoicePlay()\"\r\n [class.loading-voice]=\"chat?.isTalkMode && isLoadingText\">\r\n\r\n <div class=\"voice-button\">\r\n <!-- \u52A0\u8F7D\u65F6\u663E\u793Aspinner\uFF0C\u5426\u5219\u663E\u793Awifi\u56FE\u6807 -->\r\n <ion-spinner *ngIf=\"chat?.isTalkMode && isLoadingText\" name=\"lines\" class=\"loading-spinner\"></ion-spinner>\r\n <ion-icon *ngIf=\"!(chat?.isTalkMode && isLoadingText)\" name=\"wifi-outline\"\r\n [style.transform]=\"message?.get('content')?.role=='user'?'rotate(-90deg)':'rotate(90deg)'\"\r\n class=\"audio-icon\"\r\n [class.play-voice-playing]=\"tts?.isPlaying\"></ion-icon>\r\n </div>\r\n <div class=\"voice-info\">\r\n <span *ngIf=\"message?.get('voice')?.duration && !isLoadingText\">\r\n {{((message?.get('voice')?.duration||0)/1000) | durationStr}}\r\n </span>\r\n </div>\r\n </div>\r\n <!-- \u5934\u50CF\u533A\u57DF -->\r\n <img class=\"avatar\" *ngIf=\"message?.get('content')?.role!='user'\" [src]=\"(chat?.role?.get('avatar') || chat?.role?.get('thumb') || 'https://file-cloud.fmode.cn/E4KpGvTEto/20230930/l413e6090731854.png')+'?'+'x-image-process=image/resize,m_fixed,w_100'+'&imageView2/1/w/100/h/100'\" >\r\n <app-comp-user-avatar [user]=\"user\" *ngIf=\"message?.get('content')?.role=='user'\"></app-comp-user-avatar>\r\n </div>\r\n </div>\r\n <!-- \u9644\u4EF6\uFF1A\u56FE\u7247 -->\r\n <div class=\"item-row images\" *ngIf=\"message?.get('content') | chatContent:'image_url'\">\r\n <img [src]=\"message?.get('content') | chatContent:'image_url'\" alt=\"\">\r\n </div>\r\n <!-- \u804A\u5929\u6C14\u6CE1 -->\r\n <!-- Replace the bubble section with this: -->\r\n <div class=\"item-row bubble\" [style.fontSize]=\"role?.get('uiConfig')?.msg?.bubble?.fontSize || '0.8rem'\">\r\n <!-- \u8BF4\u8BDD\u6A21\u5F0F\uFF1A\u5C55\u793A\u52A0\u8F7D\u72B6\u6001 Show loading state for talk mode when message is not complete -->\r\n\r\n <!-- Show normal content for non-talk mode or when loading is complete -->\r\n <ng-container *ngIf=\"!chat?.isTalkMode || message?.get('content')?.role !== 'assistant' || !isLoadingText\">\r\n <fm-markdown-preview *ngIf=\"!message?.get('complete')\" class=\"content-style\"\r\n [content]=\"message?.get('content') | chatContent\" [render]=\"false\"></fm-markdown-preview>\r\n <fm-markdown-preview *ngIf=\"message?.get('complete')\"\r\n [content]=\"message?.get('content') | chatContent\"></fm-markdown-preview>\r\n </ng-container>\r\n </div>\r\n <!-- \u65F6\u95F4\u663E\u793A -->\r\n <div class=\"item-row loading\" *ngIf=\"message?.get('content')?.role!='system' && !message?.get('complete')\">\r\n \u6B63\u5728\u8F93\u5165<ion-spinner name=\"dots\"></ion-spinner>\r\n </div>\r\n\r\n <div class=\"item-row created\" *ngIf=\"message?.get('createdAt')\">\r\n <span>{{message?.get('createdAt') | date:\"dd/MM/yy HH:mm\"}}</span>\r\n </div>\r\n</div>", styles: ["@charset \"UTF-8\";:host-context(body.dark) .message-card .actions .item-native{background:none!important}:host-context(body.dark) .message-card .bubble{color:#0e101d}:host-context(body.dark) .message-card .bubble .content-style{filter:invert(1)!important}:host-context(body.dark) .message-card .bubble fm-markdown-preview{filter:invert(1)!important}:host-context(body.dark) .message-card .play-voice{background-color:#0e101d}:host-context(body.dark) .message-card .play-voice .voice-info{color:#fff}:host-context(body.dark) .message-card .right .bubble{color:#921f8a!important;background:#921f8a!important}:host-context(body.dark) .message-card .right .play-voice{background:#921f8a!important}:host-context(body.dark) .message-card .created span{color:#fff}@media screen and (max-width: 800px){.message-card:focus .actions{opacity:1!important}}.message-card:hover .actions{opacity:1;transition:opacity .3s ease-in-out}.message-card{display:flex;flex-wrap:wrap;justify-content:start;align-items:flex-start}.message-card .avatar-row{width:300px;height:32px;display:flex;flex-direction:row-reverse;justify-content:start;align-items:center}.message-card .actions{display:flex;opacity:0;padding-left:10px;padding-right:10px}.message-card .item-row{display:flex;flex:100%;justify-content:start;margin-bottom:5px}.message-card .images img{max-width:300px}.message-card .bubble.loading{color:var(--gray-secondary)}.message-card .bubble.loading .content-style{filter:none}.message-card .bubble{display:flex;justify-content:center;max-width:100%;padding:.5rem .5rem 0rem;color:#333;flex:none;border-radius:0 1.5em 1.5em/0em 1.5em 1.5em;color:#fff;background-color:currentColor}.message-card .bubble .content-style{filter:grayscale(1) contrast(999) invert(1)}.message-card .loading{text-align:right;color:#101010}.message-card .created{display:flex}.message-card .created span{font-size:12px;opacity:.4;white-space:nowrap;transition:all .6s ease;color:var(--black);text-align:center;width:100%;box-sizing:border-box;padding-right:10px;pointer-events:none;z-index:1}.right{justify-content:end;align-items:flex-end}.right .avatar-row{flex-direction:row;justify-content:end;width:auto}.right .actions{position:relative;margin-left:0}.right .item-row{justify-content:end}.right .bubble{color:#bbdefb;border-top-left-radius:1.5em;border-top-right-radius:0}.right .play-voice{flex-direction:row-reverse;background-color:#bbdefb}.center{justify-content:center;align-items:center}.center .item-row{justify-content:center}.center .bubble{color:var(--gray-secondary);border-top-left-radius:1.5em;border-top-right-radius:1.5em;font-size:12px;font-weight:100;padding:5px 20px}.play-voice{min-width:100px;height:32px;display:flex;justify-content:space-around;align-items:center;background-color:#fff;border-radius:7px}.play-voice .voice-button{width:32px;height:32px;display:flex;justify-content:center;align-items:center}.play-voice .voice-button span{overflow:hidden;font-size:18px;color:#ff69b4}.play-voice .voice-info{height:32px;display:flex;padding:0 10px;justify-content:end;align-items:center;color:#333}.avatar{border-radius:50%;width:32px;height:32px;object-fit:cover}.audio-icon{color:#ff69b4;font-size:18px}.play-voice-playing{animation:play-voice-animation 1s infinite}@keyframes play-voice-animation{0%{width:0}to{width:32px}}.content-style.loading-text{color:#666;font-style:italic}.play-voice{transition:opacity .3s ease}.play-voice.loading-voice{opacity:.8;cursor:not-allowed}.play-voice.loading-voice .loading-spinner{width:18px;height:18px;color:var(--gray-secondary)}.play-voice.loading-voice .loading-text{font-size:.8em;color:var(--gray-secondary);margin-left:5px}.play-voice .voice-button{display:flex;align-items:center;justify-content:center;width:24px;height:24px}\n"] }]
|
|
16472
16592
|
}], ctorParameters: () => [{ type: ClipboardService }], propDecorators: { index: [{
|
|
16473
16593
|
type: Input
|
|
16474
16594
|
}], message: [{
|
|
@@ -16551,20 +16671,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
16551
16671
|
}] } });
|
|
16552
16672
|
|
|
16553
16673
|
class FmChatMesssageArea {
|
|
16554
|
-
get messageList() {
|
|
16555
|
-
// 优先使用直接传入的chat对象
|
|
16556
|
-
if (this.chat && this.chat.messageList) {
|
|
16557
|
-
return this.chat.messageList;
|
|
16558
|
-
}
|
|
16559
|
-
return [];
|
|
16560
|
-
// 降级到通过chatId获取
|
|
16561
|
-
// else if(this.chatId && this.chatServ.chatMap[this.chatId]){
|
|
16562
|
-
// messages = this.chatServ.chatMap[this.chatId].messageList;
|
|
16563
|
-
// }
|
|
16564
|
-
// else {
|
|
16565
|
-
// return this.cachedMessageList;
|
|
16566
|
-
// }
|
|
16567
|
-
}
|
|
16568
16674
|
/**
|
|
16569
16675
|
* 获取预览消息的FmodeObject
|
|
16570
16676
|
*/
|
|
@@ -16586,23 +16692,44 @@ class FmChatMesssageArea {
|
|
|
16586
16692
|
}
|
|
16587
16693
|
constructor(chatServ) {
|
|
16588
16694
|
this.chatServ = chatServ;
|
|
16589
|
-
//
|
|
16590
|
-
this.
|
|
16695
|
+
// 缓存的消息列表,用于模板绑定
|
|
16696
|
+
this.messageList = [];
|
|
16697
|
+
// 用于变更检测
|
|
16591
16698
|
this.lastMessageCount = 0;
|
|
16699
|
+
this.lastMessageHash = '';
|
|
16700
|
+
this.refreshTimer = null;
|
|
16592
16701
|
}
|
|
16593
16702
|
ngOnChanges(changes) {
|
|
16594
|
-
// 当chat或chatId
|
|
16703
|
+
// 当chat或chatId发生变化时,重置并刷新
|
|
16595
16704
|
if (changes.chat || changes.chatId) {
|
|
16705
|
+
this.lastMessageCount = 0;
|
|
16706
|
+
this.lastMessageHash = '';
|
|
16596
16707
|
this.refreshMessageList();
|
|
16597
16708
|
}
|
|
16598
16709
|
}
|
|
16599
16710
|
ngDoCheck() {
|
|
16600
|
-
//
|
|
16601
|
-
|
|
16602
|
-
|
|
16603
|
-
|
|
16604
|
-
|
|
16605
|
-
|
|
16711
|
+
// 检查chat.messageList是否有变化
|
|
16712
|
+
if (!this.chat)
|
|
16713
|
+
return;
|
|
16714
|
+
const currentMessages = this.chat.messageList;
|
|
16715
|
+
const currentCount = currentMessages?.length || 0;
|
|
16716
|
+
// 简单hash检测内容变化(使用id和createdAt)
|
|
16717
|
+
let currentHash = '';
|
|
16718
|
+
if (currentMessages && currentMessages.length > 0) {
|
|
16719
|
+
const lastMsg = currentMessages[currentMessages.length - 1];
|
|
16720
|
+
currentHash = `${lastMsg.id}_${lastMsg.get?.('content')?.length || 0}_${currentCount}`;
|
|
16721
|
+
}
|
|
16722
|
+
// 如果消息数量变化或hash变化,触发刷新
|
|
16723
|
+
if (currentCount !== this.lastMessageCount || currentHash !== this.lastMessageHash) {
|
|
16724
|
+
this.lastMessageCount = currentCount;
|
|
16725
|
+
this.lastMessageHash = currentHash;
|
|
16726
|
+
// 防抖:避免频繁刷新
|
|
16727
|
+
if (this.refreshTimer) {
|
|
16728
|
+
clearTimeout(this.refreshTimer);
|
|
16729
|
+
}
|
|
16730
|
+
this.refreshTimer = setTimeout(() => {
|
|
16731
|
+
this.syncMessageList();
|
|
16732
|
+
}, 50);
|
|
16606
16733
|
}
|
|
16607
16734
|
}
|
|
16608
16735
|
ngAfterViewInit() {
|
|
@@ -16613,21 +16740,40 @@ class FmChatMesssageArea {
|
|
|
16613
16740
|
this.chat.scrollComp = { nativeElement: document.querySelector('.message-list') };
|
|
16614
16741
|
}
|
|
16615
16742
|
}
|
|
16743
|
+
ngOnDestroy() {
|
|
16744
|
+
if (this.refreshTimer) {
|
|
16745
|
+
clearTimeout(this.refreshTimer);
|
|
16746
|
+
}
|
|
16747
|
+
}
|
|
16748
|
+
/**
|
|
16749
|
+
* 同步消息列表(同步版本,用于DoCheck)
|
|
16750
|
+
*/
|
|
16751
|
+
syncMessageList() {
|
|
16752
|
+
if (!this.chat)
|
|
16753
|
+
return;
|
|
16754
|
+
// 直接获取messageManager中的缓存列表
|
|
16755
|
+
const messages = this.chat.messageList;
|
|
16756
|
+
if (messages && messages.length !== this.messageList.length) {
|
|
16757
|
+
// 创建新数组触发变更检测
|
|
16758
|
+
this.messageList = [...messages];
|
|
16759
|
+
this.scrollToBottom();
|
|
16760
|
+
}
|
|
16761
|
+
}
|
|
16616
16762
|
/**
|
|
16617
|
-
*
|
|
16763
|
+
* 刷新消息列表(异步版本,用于初始加载)
|
|
16618
16764
|
*/
|
|
16619
16765
|
async refreshMessageList() {
|
|
16620
16766
|
try {
|
|
16621
16767
|
let messages = [];
|
|
16622
16768
|
if (this.chat) {
|
|
16623
|
-
//
|
|
16769
|
+
// 触发消息加载(如果是第一次)
|
|
16624
16770
|
messages = await this.chat.getMessageList();
|
|
16625
16771
|
}
|
|
16626
16772
|
else if (this.chatId && this.chatServ.chatMap[this.chatId]) {
|
|
16627
16773
|
messages = await this.chatServ.chatMap[this.chatId].getMessageList();
|
|
16628
16774
|
}
|
|
16629
|
-
//
|
|
16630
|
-
this.
|
|
16775
|
+
// 创建新数组触发变更检测
|
|
16776
|
+
this.messageList = [...messages];
|
|
16631
16777
|
this.lastMessageCount = messages.length;
|
|
16632
16778
|
// 延迟滚动到底部
|
|
16633
16779
|
setTimeout(() => {
|
|
@@ -16654,7 +16800,7 @@ class FmChatMesssageArea {
|
|
|
16654
16800
|
}
|
|
16655
16801
|
}
|
|
16656
16802
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FmChatMesssageArea, deps: [{ token: ChatService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
16657
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: FmChatMesssageArea, isStandalone: true, selector: "fm-chat-message-area", inputs: { chatId: "chatId", chat: "chat" }, usesOnChanges: true, ngImport: i0, template: "\r\n<div class=\"message-list\">\r\n <app-comp-role-prompt [chat]=\"chat\" [role]=\"chat?.role?.id\"></app-comp-role-prompt>\r\n\r\n <!-- \u4E3B\u6D88\u606F\u5217\u8868 -->\r\n <ng-container *ngIf=\"messageList && messageList.length > 0\">\r\n <ng-container *ngFor=\"let message of messageList;let index=index;trackBy: trackByMessageId\">\r\n <!-- \u5185\u5BB9\u683C\u5F0F\u5316\u533A\u57DF -->\r\n <fm-chat-message-card [chat]=\"chat\"
|
|
16803
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: FmChatMesssageArea, isStandalone: true, selector: "fm-chat-message-area", inputs: { chatId: "chatId", chat: "chat" }, usesOnChanges: true, ngImport: i0, template: "\r\n<div class=\"message-list\">\r\n <app-comp-role-prompt [chat]=\"chat\" [role]=\"chat?.role?.id\"></app-comp-role-prompt>\r\n\r\n <!-- \u4E3B\u6D88\u606F\u5217\u8868 -->\r\n <ng-container *ngIf=\"messageList && messageList.length > 0\">\r\n <ng-container *ngFor=\"let message of messageList;let index=index;trackBy: trackByMessageId\">\r\n <!-- \u5185\u5BB9\u683C\u5F0F\u5316\u533A\u57DF -->\r\n @if(!message?.get(\"content\")?.hidden || !message?.get(\"content\")?.content ){\r\n <fm-chat-message-card [chat]=\"chat\" [index]=\"index\" [message]=\"message\" [role]=\"chat?.role\"></fm-chat-message-card>\r\n }\r\n </ng-container>\r\n </ng-container>\r\n\r\n <!-- \u7A7A\u72B6\u6001\u63D0\u793A -->\r\n <div *ngIf=\"!messageList || messageList.length === 0\" class=\"empty-state\">\r\n <p>\u6682\u65E0\u6D88\u606F\u8BB0\u5F55</p>\r\n <p *ngIf=\"chat?.role\">\u5F00\u59CB\u4E0E {{chat?.role?.get('name')}} \u5BF9\u8BDD\u5427\uFF01</p>\r\n </div>\r\n\r\n <!-- \u9884\u89C8\u6D88\u606F -->\r\n @if(!chat?.hideInputPreview && previewMessage){\r\n <fm-chat-message-card [chat]=\"chat\" [message]=\"previewMessage\" [role]=\"chat?.role\"></fm-chat-message-card>\r\n }\r\n</div>", styles: [".message-list{padding:5px 20px;display:flex;flex-direction:column;height:100%;overflow-y:auto}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 20px;text-align:center;color:#666}.empty-state p{margin:8px 0;font-size:14px}.empty-state p:first-child{font-weight:500;color:#999}:host-context(body.dark) .message-list{background-color:#000!important}:host-context(body.dark) .empty-state{color:#ccc}:host-context(body.dark) .empty-state p:first-child{color:#888}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: FmChatMessageCard, selector: "fm-chat-message-card", inputs: ["index", "message", "role", "chat"] }, { kind: "component", type: CompRolePromptComponent, selector: "app-comp-role-prompt", inputs: ["chat", "role"] }] }); }
|
|
16658
16804
|
}
|
|
16659
16805
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FmChatMesssageArea, decorators: [{
|
|
16660
16806
|
type: Component,
|
|
@@ -16662,7 +16808,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
16662
16808
|
CommonModule,
|
|
16663
16809
|
FmChatMessageCard,
|
|
16664
16810
|
CompRolePromptComponent,
|
|
16665
|
-
], template: "\r\n<div class=\"message-list\">\r\n <app-comp-role-prompt [chat]=\"chat\" [role]=\"chat?.role?.id\"></app-comp-role-prompt>\r\n\r\n <!-- \u4E3B\u6D88\u606F\u5217\u8868 -->\r\n <ng-container *ngIf=\"messageList && messageList.length > 0\">\r\n <ng-container *ngFor=\"let message of messageList;let index=index;trackBy: trackByMessageId\">\r\n <!-- \u5185\u5BB9\u683C\u5F0F\u5316\u533A\u57DF -->\r\n <fm-chat-message-card [chat]=\"chat\"
|
|
16811
|
+
], template: "\r\n<div class=\"message-list\">\r\n <app-comp-role-prompt [chat]=\"chat\" [role]=\"chat?.role?.id\"></app-comp-role-prompt>\r\n\r\n <!-- \u4E3B\u6D88\u606F\u5217\u8868 -->\r\n <ng-container *ngIf=\"messageList && messageList.length > 0\">\r\n <ng-container *ngFor=\"let message of messageList;let index=index;trackBy: trackByMessageId\">\r\n <!-- \u5185\u5BB9\u683C\u5F0F\u5316\u533A\u57DF -->\r\n @if(!message?.get(\"content\")?.hidden || !message?.get(\"content\")?.content ){\r\n <fm-chat-message-card [chat]=\"chat\" [index]=\"index\" [message]=\"message\" [role]=\"chat?.role\"></fm-chat-message-card>\r\n }\r\n </ng-container>\r\n </ng-container>\r\n\r\n <!-- \u7A7A\u72B6\u6001\u63D0\u793A -->\r\n <div *ngIf=\"!messageList || messageList.length === 0\" class=\"empty-state\">\r\n <p>\u6682\u65E0\u6D88\u606F\u8BB0\u5F55</p>\r\n <p *ngIf=\"chat?.role\">\u5F00\u59CB\u4E0E {{chat?.role?.get('name')}} \u5BF9\u8BDD\u5427\uFF01</p>\r\n </div>\r\n\r\n <!-- \u9884\u89C8\u6D88\u606F -->\r\n @if(!chat?.hideInputPreview && previewMessage){\r\n <fm-chat-message-card [chat]=\"chat\" [message]=\"previewMessage\" [role]=\"chat?.role\"></fm-chat-message-card>\r\n }\r\n</div>", styles: [".message-list{padding:5px 20px;display:flex;flex-direction:column;height:100%;overflow-y:auto}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 20px;text-align:center;color:#666}.empty-state p{margin:8px 0;font-size:14px}.empty-state p:first-child{font-weight:500;color:#999}:host-context(body.dark) .message-list{background-color:#000!important}:host-context(body.dark) .empty-state{color:#ccc}:host-context(body.dark) .empty-state p:first-child{color:#888}\n"] }]
|
|
16666
16812
|
}], ctorParameters: () => [{ type: ChatService }], propDecorators: { chatId: [{
|
|
16667
16813
|
type: Input
|
|
16668
16814
|
}], chat: [{
|