chat-frontend: redesign chat entry computation using thread objects
This commit is contained in:
1 parent
9277b5a770
commit
92102a235c
2 files changed
+234
-131
No files matched your search
+232
-129
@@ -22,6 +22,38 @@ function isMessageStale (message) {
|
|||||||
return time == null || (Date.now() - time) > THREAD_STALE_MS;
|
return time == null || (Date.now() - time) > THREAD_STALE_MS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function earliestMessage (...messages) {
|
||||||
|
let minId = null;
|
||||||
|
let minTime = null;
|
||||||
|
let useTime = true;
|
||||||
|
|
||||||
|
for (let msg of messages) {
|
||||||
|
if (!msg) continue;
|
||||||
|
|
||||||
|
if (!minId || msg.id < minId.id) {
|
||||||
|
minId = msg;
|
||||||
|
}
|
||||||
|
let msgTime = messageTime(msg);
|
||||||
|
if (msgTime == null) {
|
||||||
|
useTime = false;
|
||||||
|
} else if (!minTime || msgTime < messageTime(minTime)) {
|
||||||
|
minTime = msg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return useTime ? minTime : minId;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Thread {
|
||||||
|
constructor(obj) {
|
||||||
|
Object.assign(this, {messages: []}, obj || {});
|
||||||
|
}
|
||||||
|
|
||||||
|
getLastMessage () {
|
||||||
|
return this.messages[this.messages.length - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// === Chat Session === //
|
// === Chat Session === //
|
||||||
|
|
||||||
@@ -29,27 +61,29 @@ function isMessageStale (message) {
|
|||||||
// The view creates a session, hooks onRescroll() to its message container,
|
// The view creates a session, hooks onRescroll() to its message container,
|
||||||
// and drives it with load()/unload(). Shared state is exposed as refs.
|
// and drives it with load()/unload(). Shared state is exposed as refs.
|
||||||
export class ChatSession {
|
export class ChatSession {
|
||||||
|
// TODO: This class should not be reused in different chats,
|
||||||
|
// a chat session is created for a single chat id and never changes,
|
||||||
|
// it only ever gets disposed of
|
||||||
constructor () {
|
constructor () {
|
||||||
// Chat state
|
// Chat state
|
||||||
this.chat = ref({});
|
this.chat = ref({});
|
||||||
this.messages = ref([]);
|
this.messages = ref([]);
|
||||||
this.unsentMessages = ref([]);
|
this.unsentMessages = ref([]);
|
||||||
this.currentThreadId = ref(null);
|
|
||||||
this.isNewThread = ref(true); // "New Thread" UI state
|
|
||||||
this.messageRunMap = ref({}); // messageId -> runId of the run it started
|
this.messageRunMap = ref({}); // messageId -> runId of the run it started
|
||||||
|
|
||||||
// Editing state
|
// Input mode and target
|
||||||
|
this.currentThreadId = ref(null);
|
||||||
|
this.creatingThread = ref(true); // "New Thread" UI state
|
||||||
this.editingMessageId = ref(null);
|
this.editingMessageId = ref(null);
|
||||||
this.noteMode = ref(false);
|
this.noteMode = ref(false);
|
||||||
|
|
||||||
this.chatEntries = computed(() => {
|
// this must be recomputed everytime the displayed entries update
|
||||||
return this.computeChatEntries(
|
this.chatEntries = ref([]);
|
||||||
this.messages.value.concat(this.unsentMessages.value)
|
// real chat data
|
||||||
);
|
this.threads = [];
|
||||||
});
|
|
||||||
|
|
||||||
this.editInfo = computed(() => {
|
this.editInfo = computed(() => {
|
||||||
const parts = [this.isNewThread.value ? 'New Thread' : `Thread ${this.currentThreadId.value}`];
|
const parts = [this.creatingThread.value ? 'New Thread' : `Thread ${this.currentThreadId.value}`];
|
||||||
if (this.editingMessageId.value != null) {
|
if (this.editingMessageId.value != null) {
|
||||||
if (this.noteMode.value) {
|
if (this.noteMode.value) {
|
||||||
parts.push(`Editing Note ${this.editingMessageId.value}`);
|
parts.push(`Editing Note ${this.editingMessageId.value}`);
|
||||||
@@ -98,13 +132,16 @@ export class ChatSession {
|
|||||||
this.unsentMessages.value = [];
|
this.unsentMessages.value = [];
|
||||||
this.messageRunMap.value = {};
|
this.messageRunMap.value = {};
|
||||||
this.currentThreadId.value = null;
|
this.currentThreadId.value = null;
|
||||||
this.isNewThread.value = true;
|
this.creatingThread.value = true;
|
||||||
this.editingMessageId.value = null;
|
this.editingMessageId.value = null;
|
||||||
this.noteMode.value = false;
|
this.noteMode.value = false;
|
||||||
|
|
||||||
|
this.chatEntries.value = [];
|
||||||
|
this.threads = [];
|
||||||
|
|
||||||
// Create an agent name dictionary
|
// Create an agent name dictionary
|
||||||
const agents = await backend.getAgents();
|
const agents = await backend.getAgents();
|
||||||
if (this.chatId != chatId) return; // A newer load or unload has won
|
|
||||||
this.agentNames = {};
|
this.agentNames = {};
|
||||||
for (let agent of agents) {
|
for (let agent of agents) {
|
||||||
this.agentNames[agent.id] = agent.name;
|
this.agentNames[agent.id] = agent.name;
|
||||||
@@ -119,29 +156,22 @@ export class ChatSession {
|
|||||||
this._unsubscribeChat = backend.onChatMessages(chatId, it => this.receiveMessage(it));
|
this._unsubscribeChat = backend.onChatMessages(chatId, it => this.receiveMessage(it));
|
||||||
this._unsubscribeRuns = runs.onChatMessage(chatId, event => this.trackRun(event));
|
this._unsubscribeRuns = runs.onChatMessage(chatId, event => this.trackRun(event));
|
||||||
|
|
||||||
// Don't load all messages, only thread headers
|
// Load threads
|
||||||
//const response = await backend.sendAction("get-chat-messages", {chatId});
|
const {messages: heads} = await backend.sendAction("get-chat-thread-heads", {chatId});
|
||||||
const response = await backend.sendAction("get-chat-thread-heads", {chatId});
|
|
||||||
if (this.chatId != chatId) return;
|
|
||||||
|
|
||||||
// Always good to duplicate when assigning to a ref
|
// Always good to duplicate when assigning to a ref
|
||||||
this.messages.value = response.messages.map(it => ({
|
for (const it of heads) {
|
||||||
id: this._messageNonce++,
|
this.getThread(it.threadId).messages.push(it);
|
||||||
type: it.kind,
|
}
|
||||||
subkind: it.subkind,
|
|
||||||
sender: it.sender,
|
|
||||||
content: it.content,
|
|
||||||
threadId: it.threadId,
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Continue the last thread only if it is recent (under 10 minutes old);
|
// Continue the last thread only if it is recent (under 10 minutes old);
|
||||||
// otherwise start in "New Thread" state
|
// otherwise start in "New Thread" state
|
||||||
const lastMessage = response.messages[response.messages.length - 1];
|
const lastMessage = heads[heads.length - 1];
|
||||||
if (lastMessage && lastMessage.threadId != null && !isMessageStale(lastMessage)) {
|
if (lastMessage && lastMessage.threadId != null && !isMessageStale(lastMessage)) {
|
||||||
this.activateThread(lastMessage.threadId);
|
this.activateThread(lastMessage.threadId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Must happen on the same async-thread that sets `messages`
|
this.computeChatEntries();
|
||||||
this.rescroll({forceBottom: true});
|
this.rescroll({forceBottom: true});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,14 +192,18 @@ export class ChatSession {
|
|||||||
receiveMessage (it) {
|
receiveMessage (it) {
|
||||||
if (it.update) {
|
if (it.update) {
|
||||||
// Find message in list. If not found, add it
|
// Find message in list. If not found, add it
|
||||||
for (let entry of this.messages.value) {
|
for (let thread of this.threads) {
|
||||||
if (entry.id == it.id) {
|
for (let entry of thread.messages) {
|
||||||
entry.type = it.kind;
|
if (entry.id == it.id) {
|
||||||
entry.subkind = it.subkind;
|
entry.type = it.kind;
|
||||||
entry.format = it.format;
|
entry.subkind = it.subkind;
|
||||||
entry.content = it.content;
|
entry.format = it.format;
|
||||||
entry.threadId = it.threadId;
|
entry.content = it.content;
|
||||||
return;
|
entry.threadId = it.threadId;
|
||||||
|
|
||||||
|
this.computeChatEntries();
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -189,6 +223,7 @@ export class ChatSession {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// addMessageBulk I think is more correct
|
||||||
this.addMessage({
|
this.addMessage({
|
||||||
id: it.id,
|
id: it.id,
|
||||||
type: it.kind,
|
type: it.kind,
|
||||||
@@ -198,6 +233,9 @@ export class ChatSession {
|
|||||||
content: it.content,
|
content: it.content,
|
||||||
threadId: it.threadId,
|
threadId: it.threadId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.computeChatEntries();
|
||||||
|
this.rescroll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -238,7 +276,8 @@ export class ChatSession {
|
|||||||
message.id = this._messageNonce++;
|
message.id = this._messageNonce++;
|
||||||
this.unsentMessages.value.push(message);
|
this.unsentMessages.value.push(message);
|
||||||
} else {
|
} else {
|
||||||
this.messages.value.push(message);
|
let messages = this.getThread(message.threadId).messages;
|
||||||
|
messages.push(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,12 +289,10 @@ export class ChatSession {
|
|||||||
if (kind == null && this.noteMode.value) kind = 'note';
|
if (kind == null && this.noteMode.value) kind = 'note';
|
||||||
|
|
||||||
const messageId = this.editingMessageId.value;
|
const messageId = this.editingMessageId.value;
|
||||||
|
const threadId = await this.resolveTargetThread();
|
||||||
|
|
||||||
if (messageId == null) {
|
if (messageId == null || this.creatingThread.value) {
|
||||||
// "New Thread" is a UI state: the thread is only created upstream
|
// New message in target thread (maybe fork)
|
||||||
// here, once there is a message to put into it
|
|
||||||
const threadId = await this.resolveSendThread();
|
|
||||||
if (threadId == null) return false;
|
|
||||||
|
|
||||||
backend.sendAction("send-chat-message", {
|
backend.sendAction("send-chat-message", {
|
||||||
chatId: this.chatId, content, threadId, kind, subkind,
|
chatId: this.chatId, content, threadId, kind, subkind,
|
||||||
@@ -272,8 +309,9 @@ export class ChatSession {
|
|||||||
content,
|
content,
|
||||||
threadId,
|
threadId,
|
||||||
}, {unsent: true});
|
}, {unsent: true});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
const messageRef = this.messages.value.find(it => it.id == messageId);
|
// Editing existing message
|
||||||
|
|
||||||
backend.sendAction("update-chat-message", {
|
backend.sendAction("update-chat-message", {
|
||||||
messageId, content, kind,
|
messageId, content, kind,
|
||||||
@@ -283,19 +321,30 @@ export class ChatSession {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
messageRef.type = kind || 'message';
|
// No update event received, local manual update
|
||||||
messageRef.content = content;
|
let thread = await this.getThread(threadId);
|
||||||
|
let existing = thread.messages.find(msg => msg.id == messageId);
|
||||||
|
if (existing) {
|
||||||
|
existing.content = content;
|
||||||
|
existing.type = kind || 'message';
|
||||||
|
} else {
|
||||||
|
console.error("Can't edit locally?", {messageId, threadId, t_m: thread.messages});
|
||||||
|
}
|
||||||
|
|
||||||
this.editingMessageId.value = null;
|
this.editingMessageId.value = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (kind == 'note') this.noteMode.value = false;
|
if (kind == 'note') this.noteMode.value = false;
|
||||||
|
|
||||||
|
this.computeChatEntries();
|
||||||
|
this.rescroll();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the threadId to send into, creating the thread upstream
|
// Returns the threadId to send into, creating the thread upstream
|
||||||
// if we're in "New Thread" state
|
// if we're in "New Thread" state
|
||||||
async resolveSendThread () {
|
async resolveTargetThread () {
|
||||||
if (!this.isNewThread.value) return this.currentThreadId.value;
|
if (!this.creatingThread.value) return this.currentThreadId.value;
|
||||||
|
|
||||||
const res = await backend.sendAction("create-chat-thread", {chatId: this.chatId});
|
const res = await backend.sendAction("create-chat-thread", {chatId: this.chatId});
|
||||||
if (res.threadId == null) {
|
if (res.threadId == null) {
|
||||||
@@ -303,7 +352,7 @@ export class ChatSession {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
this.currentThreadId.value = res.threadId;
|
this.currentThreadId.value = res.threadId;
|
||||||
this.isNewThread.value = false;
|
this.creatingThread.value = false;
|
||||||
return res.threadId;
|
return res.threadId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,14 +361,86 @@ export class ChatSession {
|
|||||||
this.editingMessageId.value = messageId ?? null;
|
this.editingMessageId.value = messageId ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Adds a bulk of messages to the message list, in their correct
|
||||||
|
// chronological position and replacing duplicates
|
||||||
|
addMessageBulk (messages) {
|
||||||
|
while (messages.length) {
|
||||||
|
console.log("bulk iteration", messages.length);
|
||||||
|
|
||||||
|
let thread = this.getThread(messages[0].threadId);
|
||||||
|
|
||||||
|
let skipped = [];
|
||||||
|
for (let msg of messages) {
|
||||||
|
if (msg.threadId != thread.id) {
|
||||||
|
skipped.push(msg);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// rename for the algorithm
|
||||||
|
let id = msg.id;
|
||||||
|
let list = thread.messages;
|
||||||
|
|
||||||
|
// Replace an already-known message in place
|
||||||
|
let existingIndex = list.findIndex(it => it.id == id);
|
||||||
|
if (existingIndex >= 0) {
|
||||||
|
list[existingIndex] = msg;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// An unsent user message echoed back with an id: adopt it
|
||||||
|
if (msg.sender == 'user') {
|
||||||
|
let unsentIndex = this.unsentMessages.value.findIndex(
|
||||||
|
it => it.content == msg.content
|
||||||
|
);
|
||||||
|
if (unsentIndex >= 0) {
|
||||||
|
this.unsentMessages.value.splice(unsentIndex, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert chronologically
|
||||||
|
let msgTime = messageTime(msg);
|
||||||
|
let insertIndex = list.length;
|
||||||
|
while (insertIndex > 0) {
|
||||||
|
// from latest to earliest, skip all "later" messages
|
||||||
|
let elem = list[insertIndex-1];
|
||||||
|
if (messageTime(elem) > msgTime)
|
||||||
|
insertIndex--;
|
||||||
|
else break;
|
||||||
|
}
|
||||||
|
list.splice(insertIndex, 0, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skipped.length && skipped.length != messages.length)
|
||||||
|
messages = skipped;
|
||||||
|
else break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.computeChatEntries();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// === Threads === //
|
// === Threads === //
|
||||||
|
|
||||||
|
getThread (threadId) {
|
||||||
|
// `null` (orphan messages) serializes to the "null" key
|
||||||
|
let thread = this.threads.find(it => it.id ==threadId);
|
||||||
|
if (!thread) {
|
||||||
|
thread = new Thread({
|
||||||
|
id: threadId,
|
||||||
|
loaded: false,
|
||||||
|
time: null, // TODO
|
||||||
|
messages: [],
|
||||||
|
});
|
||||||
|
this.threads.push(thread);
|
||||||
|
}
|
||||||
|
return thread;
|
||||||
|
}
|
||||||
|
|
||||||
activateThread (threadId) {
|
activateThread (threadId) {
|
||||||
if (threadId == null) return; // "New Thread" placeholder isn't clickable
|
if (threadId == null) return; // "New Thread" placeholder isn't clickable
|
||||||
// Continuing a thread: it becomes the one open thread
|
// Continuing a thread: it becomes the one open thread
|
||||||
this.currentThreadId.value = threadId;
|
this.currentThreadId.value = threadId;
|
||||||
this.isNewThread.value = false;
|
this.creatingThread.value = false;
|
||||||
|
|
||||||
// Load thread messages asynchronously
|
// Load thread messages asynchronously
|
||||||
backend.sendAction("get-thread-messages", {
|
backend.sendAction("get-thread-messages", {
|
||||||
@@ -335,59 +456,20 @@ export class ChatSession {
|
|||||||
threadId: it.threadId,
|
threadId: it.threadId,
|
||||||
})));
|
})));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.computeChatEntries();
|
||||||
this.rescroll();
|
this.rescroll();
|
||||||
}
|
}
|
||||||
|
|
||||||
startNewThread () {
|
startNewThread () {
|
||||||
// Pure UI state — nothing is created upstream until a message is sent
|
// Pure UI state — nothing is created upstream until a message is sent
|
||||||
this.isNewThread.value = true;
|
this.creatingThread.value = true;
|
||||||
this.currentThreadId.value = null;
|
this.currentThreadId.value = null;
|
||||||
|
|
||||||
|
this.computeChatEntries();
|
||||||
this.rescroll();
|
this.rescroll();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds a bulk of messages to the message list, in their correct
|
|
||||||
// chronological position and replacing duplicates
|
|
||||||
addMessageBulk (messages) {
|
|
||||||
const list = this.messages.value;
|
|
||||||
|
|
||||||
for (let message of messages) {
|
|
||||||
const id = message.id;
|
|
||||||
|
|
||||||
// Replace an already-known message in place
|
|
||||||
const existingIndex = list.findIndex(it => it.id == id);
|
|
||||||
if (existingIndex >= 0) {
|
|
||||||
list[existingIndex] = message;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// An unsent user message echoed back with an id: adopt it
|
|
||||||
if (message.sender == 'user') {
|
|
||||||
const unsentIndex = this.unsentMessages.value.findIndex(
|
|
||||||
it => it.content == message.content
|
|
||||||
);
|
|
||||||
if (unsentIndex >= 0) {
|
|
||||||
this.unsentMessages.value.splice(unsentIndex, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert after the last message known to belong to the same thread,
|
|
||||||
// keeping each thread's messages contiguous and ordered
|
|
||||||
let insertIndex = -1;
|
|
||||||
for (let i = list.length - 1; i >= 0; i--) {
|
|
||||||
if (list[i].threadId != null && list[i].threadId == message.threadId) {
|
|
||||||
insertIndex = i + 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (insertIndex < 0) insertIndex = list.length;
|
|
||||||
list.splice(insertIndex, 0, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Refs need reassignment to notify watchers; duplicate the list
|
|
||||||
this.messages.value = list.slice();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// === Entry Computation === //
|
// === Entry Computation === //
|
||||||
|
|
||||||
@@ -395,7 +477,6 @@ export class ChatSession {
|
|||||||
// other thread collapsed into a single preview header
|
// other thread collapsed into a single preview header
|
||||||
computeChatEntries (messages) {
|
computeChatEntries (messages) {
|
||||||
let entries = [];
|
let entries = [];
|
||||||
let threadGroups = {}; // Map<int, message[]>
|
|
||||||
|
|
||||||
const senderName = (sender) => {
|
const senderName = (sender) => {
|
||||||
let isInt = !isNaN(parseInt(sender));
|
let isInt = !isNaN(parseInt(sender));
|
||||||
@@ -406,66 +487,88 @@ export class ChatSession {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const addThreadHeader = (threadId, group, options={}) => {
|
const getThreadHeader = (thread, last, options={}) => {
|
||||||
let preview = null;
|
let preview = null;
|
||||||
const last = group[group.length - 1];
|
|
||||||
if (last && !options.active) {
|
if (last && !options.active) {
|
||||||
let content = String(last.content || '').split('\n')[0].trim();
|
let content = String(last.content || '').split('\n')[0].trim();
|
||||||
if (content.length > 100) content = content.slice(0, 100) + '…';
|
if (content.length > 100) content = content.slice(0, 100) + '…';
|
||||||
preview = {sender: senderName(last.sender), content};
|
preview = {sender: senderName(last.sender), content};
|
||||||
}
|
}
|
||||||
entries.push({
|
return {
|
||||||
type: 'thread-header',
|
type: 'thread-header',
|
||||||
threadId,
|
threadId: thread.id,
|
||||||
active: !!options.active,
|
active: !!options.active,
|
||||||
messageCount: group.length,
|
messageCount: thread.messages.length,
|
||||||
preview,
|
preview,
|
||||||
});
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// First pass: Group messages by thread
|
const activeThreadMessages = this.currentThreadId.value == null ?
|
||||||
for (let message of messages) {
|
[] : this.getThread(this.currentThreadId.value).messages;
|
||||||
if (message.threadId != null) {
|
|
||||||
let group = threadGroups[message.threadId] ||
|
|
||||||
(threadGroups[message.threadId] = []);
|
|
||||||
group.push(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only the active thread is ever open; every other thread collapses
|
let activeIndex = 0;
|
||||||
// into a single preview header
|
let threadIndex = 0;
|
||||||
const activeThreadId = this.isNewThread.value ? null : this.currentThreadId.value;
|
|
||||||
let activeHeaderAdded = false;
|
|
||||||
|
|
||||||
// Second pass: Build the display list with correct ordering
|
while (true) {
|
||||||
for (let _msg of messages) {
|
let active = activeThreadMessages[activeIndex];
|
||||||
const message = Object.assign({}, _msg, {sender: senderName(_msg.sender)});
|
let thread = this.threads[threadIndex];
|
||||||
|
|
||||||
if (message.threadId == null) {
|
if (!active && !thread) break;
|
||||||
entries.push(message);
|
|
||||||
continue;
|
// Skip active thread
|
||||||
|
if (thread && thread.id != null && thread.id == this.currentThreadId.value) {
|
||||||
|
thread = null;
|
||||||
|
threadIndex++;
|
||||||
|
|
||||||
|
if (!active) continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let threadId = message.threadId;
|
let threadLast = thread ? thread.getLastMessage() : null;
|
||||||
let group = threadGroups[threadId];
|
let entry = earliestMessage(active, threadLast);
|
||||||
|
|
||||||
if (threadId === activeThreadId) {
|
// None is earliest?
|
||||||
if (!activeHeaderAdded) {
|
if (!entry) break;
|
||||||
addThreadHeader(threadId, group, {active: true});
|
|
||||||
activeHeaderAdded = true;
|
if (entry == active) {
|
||||||
|
// Add thread header before first thread message
|
||||||
|
if (activeIndex == 0) {
|
||||||
|
entries.push({
|
||||||
|
type: 'thread-header',
|
||||||
|
threadId: entry.threadId,
|
||||||
|
active: true,
|
||||||
|
messageCount: activeThreadMessages.length,
|
||||||
|
preview: null,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
entries.push(message);
|
activeIndex++;
|
||||||
} else if (message.id == group[group.length - 1].id) {
|
} else if (entry == threadLast) {
|
||||||
// Collapsed: one preview card in place of the thread's last message
|
// Replace with header
|
||||||
addThreadHeader(threadId, group);
|
entry = getThreadHeader(thread, threadLast);
|
||||||
|
threadIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// never halt
|
||||||
|
else break;
|
||||||
|
|
||||||
|
// Update sender name
|
||||||
|
entry = Object.assign(
|
||||||
|
{type: entry.kind || "message"}, // default
|
||||||
|
entry, // value
|
||||||
|
{sender: senderName(entry.sender)} // override
|
||||||
|
);
|
||||||
|
entries.push(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
// "New Thread" UI state: placeholder where the new thread will start
|
if (this.creatingThread.value) {
|
||||||
if (this.isNewThread.value) {
|
entries.push({
|
||||||
addThreadHeader(null, []);
|
type: 'thread-header',
|
||||||
|
threadId: null,
|
||||||
|
active: true,
|
||||||
|
messageCount: 0,
|
||||||
|
preview: null,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return entries;
|
this.chatEntries.value = entries;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,10 +80,10 @@
|
|||||||
await session.sendMessage(selected, {kind: 'resource', subkind: 'remote'});
|
await session.sendMessage(selected, {kind: 'resource', subkind: 'remote'});
|
||||||
}
|
}
|
||||||
|
|
||||||
function editMessage (message) {
|
function editMessage (message, fork) {
|
||||||
let elem = inputFieldElem.value;
|
let elem = inputFieldElem.value;
|
||||||
elem.innerText = message ? message.content : "";
|
elem.innerText = message ? message.content : "";
|
||||||
session.setEditing(message ? message.id : null);
|
session.setEditing(message ? message.id : null, fork);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteChat (chat) {
|
async function deleteChat (chat) {
|
||||||
|
|||||||
Reference in new issue
Block a user