Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions server/channels/api4/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -1324,6 +1324,12 @@ func autocompleteUsers(c *Context, w http.ResponseWriter, r *http.Request) {
autocomplete.Users = result
}

// Fetch agent users for autocomplete
agentUsers, appErr := c.App.GetUsersForAgents(c.AppContext, c.AppContext.Session().UserId)
if appErr == nil && agentUsers != nil {
autocomplete.Agents = agentUsers
}

if err := json.NewEncoder(w).Encode(autocomplete); err != nil {
c.Logger.Warn("Error while writing response", mlog.Err(err))
}
Expand Down
29 changes: 29 additions & 0 deletions server/channels/app/agents.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,35 @@ func (a *App) GetAgents(rctx request.CTX, userID string) ([]agentclient.BridgeAg
return agents, nil
}

// GetUsersForAgents retrieves the User objects for all available agents
func (a *App) GetUsersForAgents(rctx request.CTX, userID string) ([]*model.User, *model.AppError) {
agents, appErr := a.GetAgents(rctx, userID)
if appErr != nil {
return nil, appErr
}

if len(agents) == 0 {
return []*model.User{}, nil
}

users := make([]*model.User, 0, len(agents))
for _, agent := range agents {
// Agents have a username field that corresponds to the bot user's username
user, err := a.Srv().Store().User().GetByUsername(agent.Username)
if err != nil {
rctx.Logger().Warn("Failed to get user for agent",
mlog.Err(err),
mlog.String("agent_id", agent.ID),
mlog.String("username", agent.Username),
)
continue
}
users = append(users, user)
}

return users, nil
}

// GetLLMServices retrieves all available LLM services from the bridge API
func (a *App) GetLLMServices(rctx request.CTX, userID string) ([]agentclient.BridgeServiceInfo, *model.AppError) {
// Check if the AI plugin is active and supports the bridge API (v1.5.0+)
Expand Down
1 change: 1 addition & 0 deletions server/public/model/user_autocomplete.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ type UserAutocompleteInTeam struct {
type UserAutocomplete struct {
Users []*User `json:"users"`
OutOfChannel []*User `json:"out_of_channel,omitempty"`
Agents []*User `json:"agents,omitempty"`
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

import React from 'react';
import {defineMessage} from 'react-intl';

import {CreationOutlineIcon} from '@mattermost/compass-icons/components';
import type {Group} from '@mattermost/types/groups';
import type {UserProfile} from '@mattermost/types/users';

Expand Down Expand Up @@ -306,6 +308,17 @@ export default class AtMentionProvider extends Provider {
// Combine the local and remote members, sorting to mix the results together.
const localAndRemoteMembers = localMembers.concat(remoteMembers).sort(orderUsers);

// Get agents - these are already User objects from the backend
// Only show agents if bridge is enabled (indicated by presence of agents data)
let agents: CreatedProfile[] = [];
if (this.data && this.data.agents && Array.isArray(this.data.agents) && this.data.agents.length > 0) {
const agentUsers = this.data.agents as UserProfileWithLastViewAt[];
agents = agentUsers.
filter((user: UserProfileWithLastViewAt) => this.filterProfile(user)).
map((user: UserProfileWithLastViewAt) => this.createFromProfile(user)).
sort(orderUsers);
}

// handle groups
const localGroups = this.localGroups();

Expand Down Expand Up @@ -345,6 +358,9 @@ export default class AtMentionProvider extends Provider {
if (priorityProfiles.length > 0 || localAndRemoteMembers.length > 0) {
items.push(membersGroup([...priorityProfiles, ...localAndRemoteMembers]));
}
if (agents.length > 0) {
items.push(agentsGroup(agents));
}
if (localAndRemoteGroups.length > 0) {
items.push(groupsGroup(localAndRemoteGroups));
}
Expand Down Expand Up @@ -453,6 +469,17 @@ export function membersGroup(items: CreatedProfile[]) {
};
}

export function agentsGroup(items: CreatedProfile[]) {
return {
key: 'agents',
label: defineMessage({id: 'suggestion.mention.agents', defaultMessage: 'Agents'}),
icon: <CreationOutlineIcon size={16}/>,
items,
terms: items.map((profile) => '@' + profile.username),
component: AtMentionSuggestion,
};
}

export function groupsGroup(items: Group[]) {
return {
key: 'groups',
Expand Down
1 change: 1 addition & 0 deletions webapp/channels/src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -5990,6 +5990,7 @@
"suggestion.emoji": "Emoji",
"suggestion.group_channel": "Group channel",
"suggestion.group.members": "{member_count} {member_count, plural, one {member} other {members}}",
"suggestion.mention.agents": "Agents",
"suggestion.mention.all": "Notifies everyone in this channel",
"suggestion.mention.channel": "Notifies everyone in this channel",
"suggestion.mention.channels": "My Channels",
Expand Down
Loading