9dfa06ffee
Docker image / Build (linux/amd64) (push) Has been cancelled
Docker image / Build (linux/arm64) (push) Has been cancelled
Docker image / Merge release multi-arch manifest (push) Has been cancelled
Docker image / Merge debug multi-arch manifest (push) Has been cancelled
Docker image / Build public push gateway (linux/amd64) (push) Has been cancelled
Docker image / Build public push gateway (linux/arm64) (push) Has been cancelled
Docker image / Publish public push gateway image (push) Has been cancelled
Sprig image / Build (linux/amd64) (push) Has been cancelled
Sprig image / Build (linux/arm64) (push) Has been cancelled
Sprig image / Merge multi-arch manifest (push) Has been cancelled
Harbor Buzz Orchestra / Python tests and lint (push) Has been cancelled
CI / Detect Changed Paths (push) Has been cancelled
CI / Rust Lint (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Desktop Core (push) Has been cancelled
CI / Desktop Smoke E2E (1) (push) Has been cancelled
CI / Desktop Smoke E2E (2) (push) Has been cancelled
CI / Desktop Smoke E2E (3) (push) Has been cancelled
CI / Desktop Smoke E2E (4) (push) Has been cancelled
CI / Desktop (push) Has been cancelled
CI / Desktop E2E Relay (push) Has been cancelled
CI / Desktop E2E Integration (1/2) (push) Has been cancelled
CI / Desktop E2E Integration (2/2) (push) Has been cancelled
CI / Desktop E2E Integration (push) Has been cancelled
CI / Backend Integration (relay e2e) (push) Has been cancelled
CI / Relay E2E (push) Has been cancelled
CI / Web (push) Has been cancelled
CI / Mobile (push) Has been cancelled
CI / Security (push) Has been cancelled
CI / Dead Token Reference Guard (push) Has been cancelled
CI / Server Cross-Compile (aarch64-unknown-linux-musl) (push) Has been cancelled
CI / Server Cross-Compile (x86_64-unknown-linux-musl) (push) Has been cancelled
CI / Windows Rust (x86_64-pc-windows-msvc) (push) Has been cancelled
CI / Desktop Build (macOS) (push) Has been cancelled
helm chart / lint + unittest + render matrix (push) Has been cancelled
helm chart / install on kind (gated) (push) Has been cancelled
helm chart / publish chart to GHCR (push) Has been cancelled
Mesh Lifecycle / Relay-Driven Mesh Lifecycle Smoke (push) Has been cancelled
Sprig / Build (aarch64-unknown-linux-musl) (push) Has been cancelled
Sprig / Build (x86_64-unknown-linux-musl) (push) Has been cancelled
Sprig / Publish rolling release (push) Has been cancelled
Sprig / Publish tagged release (push) Has been cancelled
Signed-off-by: cls_宁波本机 <908705107@qq.com>
147 lines
5.1 KiB
Dart
147 lines
5.1 KiB
Dart
import '../../../shared/mentions/agent_identity_provider.dart';
|
|
import '../../profile/user_profile.dart';
|
|
import '../channel_management_provider.dart';
|
|
import 'mention_ranking.dart';
|
|
|
|
/// Whether a non-member relay agent should be mentionable by the current
|
|
/// user. Mirrors desktop's `relayAgentIsSharedWithUser`:
|
|
/// - allowlist mode: user must be on the allowlist
|
|
/// - anyone mode: agent must share at least one channel with the user
|
|
bool agentIsSharedWithUser(
|
|
AgentDirectoryEntry agent,
|
|
Set<String> sharedChannelIds,
|
|
String? currentPubkey,
|
|
) {
|
|
if (agent.respondTo == 'allowlist' && currentPubkey != null) {
|
|
return agent.respondToAllowlist.contains(currentPubkey.toLowerCase());
|
|
}
|
|
return agent.respondTo == 'anyone' &&
|
|
agent.channelIds.any(sharedChannelIds.contains);
|
|
}
|
|
|
|
/// Format the "managed by …" label. Mirrors desktop's `formatOwnerLabel`.
|
|
String? formatOwnerLabel(
|
|
String? ownerPubkey,
|
|
String? currentPubkey,
|
|
Map<String, UserProfile> userCache, {
|
|
String currentUserLabel = 'you',
|
|
}) {
|
|
if (ownerPubkey == null) return null;
|
|
final owner = ownerPubkey.toLowerCase();
|
|
if (currentPubkey != null && owner == currentPubkey.toLowerCase()) {
|
|
return currentUserLabel;
|
|
}
|
|
final profile = userCache[owner];
|
|
final name = profile?.displayName?.trim();
|
|
if (name != null && name.isNotEmpty) return name;
|
|
final handle = profile?.nip05Handle?.trim();
|
|
if (handle != null && handle.isNotEmpty) return handle;
|
|
return '${ownerPubkey.substring(0, 8)}…';
|
|
}
|
|
|
|
/// Assemble the full mention candidate list: channel members first-class,
|
|
/// then eligible non-member relay agents, then global user-search results.
|
|
/// Mirrors desktop's `useMentions` candidate assembly (minus personas and
|
|
/// managed agents, which live in the desktop app's local store; the
|
|
/// owner-check on search results below covers their mention-eligibility
|
|
/// semantics).
|
|
List<MentionCandidate> buildMentionCandidates({
|
|
required List<ChannelMember> members,
|
|
required List<AgentDirectoryEntry> relayAgents,
|
|
required Set<String> sharedChannelIds,
|
|
required Map<String, UserProfile> userCache,
|
|
required Map<String, String> ownerByAgentPubkey,
|
|
List<UserProfile> searchResults = const [],
|
|
String? currentPubkey,
|
|
}) {
|
|
final candidates = <MentionCandidate>[];
|
|
final seen = <String>{};
|
|
|
|
for (final member in members) {
|
|
final pk = member.pubkey.toLowerCase();
|
|
if (!seen.add(pk)) continue;
|
|
final profile = userCache[pk];
|
|
final ownerPubkey = ownerByAgentPubkey[pk] ?? profile?.ownerPubkey;
|
|
final isAgent = member.isBot || ownerPubkey != null;
|
|
candidates.add(
|
|
MentionCandidate(
|
|
pubkey: pk,
|
|
displayName: profile?.displayName?.trim().isNotEmpty == true
|
|
? profile!.displayName!.trim()
|
|
: member.displayName,
|
|
secondaryLabel: profile?.nip05Handle,
|
|
avatarUrl: profile?.avatarUrl,
|
|
isAgent: isAgent,
|
|
isMember: true,
|
|
role: member.role,
|
|
ownerPubkey: ownerPubkey,
|
|
),
|
|
);
|
|
}
|
|
|
|
final directoryPubkeys = <String>{};
|
|
final sharedAgentPubkeys = <String>{};
|
|
for (final agent in relayAgents) {
|
|
directoryPubkeys.add(agent.pubkey);
|
|
if (agentIsSharedWithUser(agent, sharedChannelIds, currentPubkey)) {
|
|
sharedAgentPubkeys.add(agent.pubkey);
|
|
}
|
|
}
|
|
|
|
for (final agent in relayAgents) {
|
|
final pk = agent.pubkey;
|
|
if (seen.contains(pk)) continue;
|
|
if (!sharedAgentPubkeys.contains(pk)) continue;
|
|
seen.add(pk);
|
|
final profile = userCache[pk];
|
|
candidates.add(
|
|
MentionCandidate(
|
|
pubkey: pk,
|
|
displayName: profile?.displayName?.trim().isNotEmpty == true
|
|
? profile!.displayName!.trim()
|
|
: agent.displayName,
|
|
secondaryLabel: profile?.nip05Handle,
|
|
avatarUrl: profile?.avatarUrl,
|
|
isAgent: true,
|
|
isMember: false,
|
|
ownerPubkey: ownerByAgentPubkey[pk] ?? profile?.ownerPubkey,
|
|
),
|
|
);
|
|
}
|
|
|
|
final currentLower = currentPubkey?.toLowerCase();
|
|
for (final profile in searchResults) {
|
|
final pk = profile.pubkey.toLowerCase();
|
|
if (seen.contains(pk)) continue;
|
|
final ownerPubkey = ownerByAgentPubkey[pk] ?? profile.ownerPubkey;
|
|
final isAgent = ownerPubkey != null || directoryPubkeys.contains(pk);
|
|
if (isAgent) {
|
|
// Mirrors desktop's `shouldHideAgentFromMentions` for non-member
|
|
// agents: show only when invocable. Invocable = owned by the current
|
|
// user (desktop's managed-agent semantics, derived here from the
|
|
// verified NIP-OA owner) or shared via the relay agent directory.
|
|
final ownedByCurrentUser =
|
|
currentLower != null && ownerPubkey?.toLowerCase() == currentLower;
|
|
if (!ownedByCurrentUser && !sharedAgentPubkeys.contains(pk)) {
|
|
continue;
|
|
}
|
|
}
|
|
seen.add(pk);
|
|
candidates.add(
|
|
MentionCandidate(
|
|
pubkey: pk,
|
|
displayName: profile.displayName?.trim().isNotEmpty == true
|
|
? profile.displayName!.trim()
|
|
: null,
|
|
secondaryLabel: profile.nip05Handle,
|
|
avatarUrl: profile.avatarUrl,
|
|
isAgent: isAgent,
|
|
isMember: false,
|
|
ownerPubkey: ownerPubkey,
|
|
),
|
|
);
|
|
}
|
|
|
|
return candidates;
|
|
}
|