Files
simplex-chat/packages/simplex-chat-nodejs/src/util.ts
T
Evgeny @ SimpleX ChatandEvgeny Poberezkin 7810fc4752 core, ui: terminal chat layout for LLM and service bot chats (#7811)
* plan: UI improvements to improve LLM usage via SimpleX

* core, ui: types for chat layout

* core, ui: settings for alternative terminal-like chat layout for managing LLMs or other service bots

* plan: message priority

* fixes

* query plan

* update plans

* remove silent messages plan

* rename migrations

* implement layout

* amend

* fix

* fix

* fix

* improve

* time on the right, sender name in blue

* no avatar in direct chats

* better previews

* update, fixes

* update

* time gap

* changes

* ui: terminal layout line heights, gaps and time placement

* ui: simplify terminal meta layout, correct its docs and tests

* ui: right-to-left emoji edge in terminal rows, time placement tests

* android, desktop: raise Kotlin compiler memory to 4 GB

* ui: terminal captions as follow-up text, classic event separators

* ui: terminal media edge and overlay time, plan decisions

* ui: simplify terminal row separation, correct doc line references

* ui: keep the media time inside terminal rows, restore caption conditions

* ui: terminal heading capitals at large font scales, idle live message time

* ui: rename the classic voice-with-text flag, correct the meta copy docs

* ui: smaller terminal layout diff, sender gap and meta names

* ui: terminal row as tall as the media time

* docs: remove the chat layout section from the bots readme

* docs: shorten the terminal layout section of the chat view

* core: address settings API as on master, /auto_accept reads the stored layout

* ui: smaller terminal headings

* ui: classic sender rows and avatar rule as on master

* ui: classic reply quote text colour as on master

* ios: keep the chat layout setting with other UI settings

* ui: go-to button at the end of terminal rows, no gutter in direct chats

* ui: forwarded arrow on the header line of terminal rows, above the time

* ui: forwarded arrow centred above the time line of terminal rows

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2026-10-25 18:03:11 +00:00

94 lines
3.4 KiB
TypeScript

import {T} from "@simplex-chat/types"
import {BotAddressSettings} from "./api"
export function chatInfoRef(cInfo: T.ChatInfo): T.ChatRef | undefined {
switch (cInfo.type) {
case T.ChatType.Direct: return {chatType: T.ChatType.Direct, chatId: cInfo.contact.contactId}
case T.ChatType.Group: {
const chatScope: T.GroupChatScope | undefined =
cInfo.groupChatScope?.type == "memberSupport"
? {type: "memberSupport", groupMemberId_: cInfo.groupChatScope.groupMember_?.groupMemberId}
: undefined
return {chatType: T.ChatType.Group, chatId: cInfo.groupInfo.groupId, chatScope}
}
default: return undefined
}
}
export function chatInfoName(cInfo: T.ChatInfo): string {
switch (cInfo.type) {
case "direct": return `@${cInfo.contact.profile.displayName}`
case "group": {
const scope = cInfo.groupChatScope
const scopeName = scope?.type === "memberSupport"
? `(support${scope.groupMember_ ? ` ${scope.groupMember_.memberProfile.displayName}` : ""})`
: ""
return `#${cInfo.groupInfo.groupProfile.displayName}${scopeName}`
}
case "local": return "private notes"
case "contactRequest": return `request from @${cInfo.contactRequest.profile.displayName}`
case "contactConnection": {
const alias = cInfo.contactConnection.localAlias
return `pending connection${alias ? ` (@${alias})` : ""}`
}
}
}
export function senderName(cInfo: T.ChatInfo, chatDir: T.CIDirection) {
const sender = chatDir.type === "groupRcv"
? ` @${chatDir.groupMember.memberProfile.displayName}`
: ""
return chatInfoName(cInfo) + sender
}
export function contactAddressStr(link: T.CreatedConnLink): string {
return link.connShortLink || link.connFullLink
}
export function botAddressSettings({addressSettings}: T.UserContactLink): BotAddressSettings {
return {
autoAccept: addressSettings.autoAccept ? true : false,
welcomeMessage: addressSettings.autoReply,
businessAddress: addressSettings.businessAddress,
chatLayout: addressSettings.chatLayout
}
}
export function fromLocalProfile({displayName, fullName, shortDescr, image, contactLink, preferences, peerType, contactDomain}: T.LocalProfile): T.Profile {
const profile = {displayName, fullName, shortDescr, image, contactLink, preferences, peerType, contactDomain: contactDomain && {domain: contactDomain.domain}}
for (const key in profile) {
if (typeof (profile as any)[key] === "undefined") delete (profile as any)[key]
}
return profile
}
export function ciContentText({content}: T.ChatItem): string | undefined {
switch (content.type) {
case "sndMsgContent": return content.msgContent.text;
case "rcvMsgContent": return content.msgContent.text;
default: return undefined;
}
}
export interface BotCommand {
keyword: string
params: string
}
// returns command (without /) and trimmed parameters
export function ciBotCommand(chatItem: T.ChatItem): BotCommand | undefined {
const msg = ciContentText(chatItem)?.trim()
if (msg) {
const r = msg.match(/^\/([^\s]+)([\s\S]*)/)
if (r && r.length >= 3) {
return {keyword: r[1], params: r[2].trim()}
}
}
return undefined
}
export function reactionText(reaction: T.ACIReaction): string {
const r = reaction.chatReaction
return r.reaction.type === "emoji" ? r.reaction.emoji : r.reaction.tag
}