✨ Auto completion in chat
This commit is contained in:
15
DysonNetwork.Shared/Models/Autocompletion.cs
Normal file
15
DysonNetwork.Shared/Models/Autocompletion.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public class AutocompletionRequest
|
||||
{
|
||||
[Required] public string Content { get; set; } = null!;
|
||||
}
|
||||
|
||||
public class Autocompletion
|
||||
{
|
||||
public string Type { get; set; } = null!;
|
||||
public string Keyword { get; set; } = null!;
|
||||
public object Data { get; set; } = null!;
|
||||
}
|
121
DysonNetwork.Sphere/Autocompletion/AutocompletionService.cs
Normal file
121
DysonNetwork.Sphere/Autocompletion/AutocompletionService.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace DysonNetwork.Sphere.Autocompletion;
|
||||
|
||||
public class AutocompletionService(AppDatabase db)
|
||||
{
|
||||
public async Task<List<DysonNetwork.Shared.Models.Autocompletion>> GetAutocompletion(string content, int limit = 10)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(content))
|
||||
return [];
|
||||
|
||||
if (content.StartsWith('@'))
|
||||
{
|
||||
var afterAt = content[1..];
|
||||
string type;
|
||||
string query;
|
||||
if (afterAt.Contains('/'))
|
||||
{
|
||||
var parts = afterAt.Split('/', 2);
|
||||
type = parts[0];
|
||||
query = parts.Length > 1 ? parts[1] : "";
|
||||
}
|
||||
else
|
||||
{
|
||||
type = "u";
|
||||
query = afterAt;
|
||||
}
|
||||
return await AutocompleteAt(type, query, limit);
|
||||
}
|
||||
|
||||
if (!content.StartsWith(':')) return [];
|
||||
{
|
||||
var query = content[1..];
|
||||
return await AutocompleteSticker(query, limit);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private async Task<List<DysonNetwork.Shared.Models.Autocompletion>> AutocompleteAt(string type, string query, int limit)
|
||||
{
|
||||
var results = new List<DysonNetwork.Shared.Models.Autocompletion>();
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case "p":
|
||||
var publishers = await db.Publishers
|
||||
.Where(p => EF.Functions.Like(p.Name, $"{query}%") || EF.Functions.Like(p.Nick, $"{query}%"))
|
||||
.Take(limit)
|
||||
.Select(p => new DysonNetwork.Shared.Models.Autocompletion
|
||||
{
|
||||
Type = "publisher",
|
||||
Keyword = p.Name,
|
||||
Data = p
|
||||
})
|
||||
.ToListAsync();
|
||||
results.AddRange(publishers);
|
||||
break;
|
||||
|
||||
case "r":
|
||||
var realms = await db.Realms
|
||||
.Where(r => EF.Functions.Like(r.Slug, $"{query}%") || EF.Functions.Like(r.Name, $"{query}%"))
|
||||
.Take(limit)
|
||||
.Select(r => new DysonNetwork.Shared.Models.Autocompletion
|
||||
{
|
||||
Type = "realm",
|
||||
Keyword = r.Slug,
|
||||
Data = r
|
||||
})
|
||||
.ToListAsync();
|
||||
results.AddRange(realms);
|
||||
break;
|
||||
|
||||
case "c":
|
||||
var chats = await db.ChatRooms
|
||||
.Where(c => c.Name != null && EF.Functions.Like(c.Name, $"{query}%"))
|
||||
.Take(limit)
|
||||
.Select(c => new DysonNetwork.Shared.Models.Autocompletion
|
||||
{
|
||||
Type = "chat",
|
||||
Keyword = c.Name!,
|
||||
Data = c
|
||||
})
|
||||
.ToListAsync();
|
||||
results.AddRange(chats);
|
||||
break;
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private async Task<List<DysonNetwork.Shared.Models.Autocompletion>> AutocompleteSticker(string query, int limit)
|
||||
{
|
||||
var stickers = await db.Stickers
|
||||
.Include(s => s.Pack)
|
||||
.Where(s => EF.Functions.Like(s.Slug, $"{query}%"))
|
||||
.Take(limit)
|
||||
.Select(s => new DysonNetwork.Shared.Models.Autocompletion
|
||||
{
|
||||
Type = "sticker",
|
||||
Keyword = s.Slug,
|
||||
Data = s
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
// Also possibly search by pack prefix? But user said slug after :
|
||||
// Perhaps combine or search packs
|
||||
var packs = await db.StickerPacks
|
||||
.Where(p => EF.Functions.Like(p.Prefix, $"{query}%"))
|
||||
.Take(limit)
|
||||
.Select(p => new DysonNetwork.Shared.Models.Autocompletion
|
||||
{
|
||||
Type = "sticker_pack",
|
||||
Keyword = p.Prefix,
|
||||
Data = p
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
var results = stickers.Concat(packs).Take(limit).ToList();
|
||||
return results;
|
||||
}
|
||||
}
|
@@ -4,6 +4,7 @@ using DysonNetwork.Shared.Auth;
|
||||
using DysonNetwork.Shared.Data;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using DysonNetwork.Sphere.Autocompletion;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@@ -17,7 +18,8 @@ public partial class ChatController(
|
||||
ChatService cs,
|
||||
ChatRoomService crs,
|
||||
FileService.FileServiceClient files,
|
||||
AccountService.AccountServiceClient accounts
|
||||
AccountService.AccountServiceClient accounts,
|
||||
AutocompletionService aus
|
||||
) : ControllerBase
|
||||
{
|
||||
public class MarkMessageReadRequest
|
||||
@@ -329,4 +331,20 @@ public partial class ChatController(
|
||||
var response = await cs.GetSyncDataAsync(roomId, request.LastSyncTimestamp);
|
||||
return Ok(response);
|
||||
}
|
||||
|
||||
[HttpGet("{roomId:guid}/autocomplete")]
|
||||
public async Task<ActionResult<List<DysonNetwork.Shared.Models.Autocompletion>>> ChatAutoComplete([FromBody] AutocompletionRequest request, Guid roomId)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser)
|
||||
return Unauthorized();
|
||||
|
||||
var accountId = Guid.Parse(currentUser.Id);
|
||||
var isMember = await db.ChatMembers
|
||||
.AnyAsync(m => m.AccountId == accountId && m.ChatRoomId == roomId && m.JoinedAt != null && m.LeaveAt == null);
|
||||
if (!isMember)
|
||||
return StatusCode(403, "You are not a member of this chat room.");
|
||||
|
||||
var result = await aus.GetAutocompletion(request.Content, limit: 10);
|
||||
return Ok(result);
|
||||
}
|
||||
}
|
@@ -15,6 +15,7 @@ using System.Text.Json.Serialization;
|
||||
using System.Threading.RateLimiting;
|
||||
using DysonNetwork.Shared.Cache;
|
||||
using DysonNetwork.Shared.GeoIp;
|
||||
using DysonNetwork.Sphere.Autocompletion;
|
||||
using DysonNetwork.Sphere.WebReader;
|
||||
using DysonNetwork.Sphere.Discovery;
|
||||
using DysonNetwork.Sphere.Poll;
|
||||
@@ -118,6 +119,7 @@ public static class ServiceCollectionExtensions
|
||||
services.AddScoped<WebFeedService>();
|
||||
services.AddScoped<DiscoveryService>();
|
||||
services.AddScoped<PollService>();
|
||||
services.AddScoped<AutocompletionService>();
|
||||
|
||||
var translationProvider = configuration["Translation:Provider"]?.ToLower();
|
||||
switch (translationProvider)
|
||||
|
Reference in New Issue
Block a user