Changeset 0.24.0 (#156)

This commit is contained in:
2026-03-07 11:27:24 +00:00
parent 2dc4514a57
commit a63728a481
23 changed files with 1850 additions and 385 deletions

View File

@@ -28,6 +28,7 @@ const App = {
collapsedFolders: {},
presence: {}, // { userId: 'online' | 'offline' }
users: [], // v0.23.2: [{id, username, displayName}] for @mention autocomplete
activeParticipants: [], // v0.24.0: user participants of active channel (for scoped autocomplete)
/** Get active conversation ID (shorthand). */
get activeId() { return this.activeConversation?.id || null; },
@@ -38,6 +39,7 @@ const App = {
/** Set the active conversation. Clears if id is null. */
setActive(id, type) {
this.activeConversation = id ? { id, type: type || 'direct' } : null;
this.activeParticipants = [];
},
/** Find the active conversation's chat object (with cached messages). */

View File

@@ -219,16 +219,30 @@ const ChannelModels = {
|| firstName.startsWith(partial);
});
// v0.23.2: Include users in autocomplete (after personas, before models)
const userMatches = (App.users || []).filter(u => {
// v0.24.0: Context-aware user autocomplete.
// direct / dm : no users (1:1 AI or already-defined human pair)
// group : participants only (defined roster)
// channel : full user list (open, ad-hoc)
const activeType = App.activeType || 'direct';
let userPool = [];
if (activeType === 'channel') {
userPool = App.users || [];
} else if (activeType === 'group') {
const pids = App.activeParticipants || [];
userPool = (App.users || []).filter(u => pids.includes(u.id));
}
// direct and dm: userPool stays empty
const userMatches = userPool.filter(u => {
const uhandle = (u.handle || '').toLowerCase();
const uname = u.username.toLowerCase();
const dname = (u.displayName || '').toLowerCase();
if (partial.length === 0) return true;
return uname.startsWith(partial) || dname.startsWith(partial);
return uhandle.startsWith(partial) || uname.startsWith(partial) || dname.startsWith(partial);
}).map(u => ({
id: u.username,
id: u.handle || u.username,
name: u.displayName || u.username,
baseModelId: u.username,
baseModelId: u.handle || u.username,
isPersona: false,
isUser: true,
personaHandle: null,

View File

@@ -1215,7 +1215,7 @@ function normalizeChannel(ch) {
};
}
// v0.23.2: Load user list for @mention autocomplete and DM creation
// v0.24.0: Load user list for @mention autocomplete and DM creation
async function loadUsers() {
try {
const resp = await API._get('/api/v1/users/search?q=');
@@ -1223,6 +1223,7 @@ async function loadUsers() {
id: u.id,
username: u.username,
displayName: u.display_name || u.username,
handle: u.handle || u.username,
}));
} catch (e) {
console.warn('[users] load failed:', e.message);
@@ -1233,6 +1234,7 @@ async function loadUsers() {
async function selectChannel(channelId) {
const ch = (App.channels || []).find(c => c.id === channelId);
App.setActive(channelId, ch?.type || 'channel');
App.activeParticipants = []; // clear until loaded
if (typeof UI !== 'undefined') {
UI.renderChannelsSection();
UI.renderChatList();
@@ -1279,6 +1281,16 @@ async function selectChannel(channelId) {
sessionStorage.setItem('cs-active-conversation', JSON.stringify(App.activeConversation));
} catch (_) {}
// v0.24.0: Load participant list for scoped @mention autocomplete (group only)
if (ch?.type === 'group') {
try {
const resp = await API._get(`/api/v1/channels/${channelId}/participants`);
App.activeParticipants = (resp.participants || [])
.filter(p => p.participant_type === 'user')
.map(p => p.participant_id);
} catch (_) { App.activeParticipants = []; }
}
// v0.23.2: Mark as read and clear unread badge
const selCh = (App.channels || []).find(c => c.id === channelId);
if (selCh) selCh.unread = 0;