refactor(room): WebSocket queue and message editor improvements
- Enhance ws_universal.rs with queue message support - Add queue types and producer improvements - Simplify MessageBubble.tsx rendering logic - Refactor IMEditor.tsx with improved message handling - Update DiscordChatPanel.tsx with message enhancements
This commit is contained in:
parent
c4fb943e07
commit
a09f66b779
@ -397,7 +397,7 @@ async fn poll_push_streams(
|
||||
if let Some(reactions) = event.reactions.clone() {
|
||||
return Some(WsPushEvent::ReactionUpdated {
|
||||
room_id: event.room_id,
|
||||
message_id: event.id,
|
||||
message_id: event.message_id.unwrap_or(event.id),
|
||||
reactions,
|
||||
});
|
||||
}
|
||||
|
||||
@ -176,7 +176,7 @@ impl MessageProducer {
|
||||
pub async fn publish_reaction_event(
|
||||
&self,
|
||||
room_id: uuid::Uuid,
|
||||
_message_id: uuid::Uuid,
|
||||
message_id: uuid::Uuid,
|
||||
reactions: Vec<ReactionGroup>,
|
||||
) {
|
||||
let Some(pubsub) = &self.pubsub else {
|
||||
@ -196,6 +196,7 @@ impl MessageProducer {
|
||||
seq: 0,
|
||||
display_name: None,
|
||||
reactions: Some(reactions),
|
||||
message_id: Some(message_id),
|
||||
};
|
||||
pubsub.publish_room_message(room_id, &event).await;
|
||||
}
|
||||
|
||||
@ -35,6 +35,9 @@ pub struct RoomMessageEvent {
|
||||
/// Present when this event carries reaction updates for the message.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub reactions: Option<Vec<ReactionGroup>>,
|
||||
/// Target message ID for reaction update events.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub message_id: Option<Uuid>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@ -61,6 +64,7 @@ impl From<RoomMessageEnvelope> for RoomMessageEvent {
|
||||
seq: e.seq,
|
||||
display_name: None,
|
||||
reactions: None,
|
||||
message_id: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1065,6 +1065,7 @@ impl RoomService {
|
||||
display_name: Some(ai_display_name.clone()),
|
||||
in_reply_to: None,
|
||||
reactions: None,
|
||||
message_id: None,
|
||||
};
|
||||
room_manager.broadcast(room_id_inner, msg_event).await;
|
||||
room_manager.metrics.messages_sent.increment(1);
|
||||
@ -1207,6 +1208,7 @@ impl RoomService {
|
||||
display_name: model_display_name,
|
||||
in_reply_to: None,
|
||||
reactions: None,
|
||||
message_id: None,
|
||||
};
|
||||
room_manager.broadcast(room_id, event).await;
|
||||
|
||||
|
||||
@ -79,6 +79,7 @@ export function DiscordChatPanel({ room, isAdmin, onClose, onDelete }: DiscordCh
|
||||
(content: string) => {
|
||||
sendMessage(content, 'text', replyingTo?.id ?? undefined);
|
||||
setReplyingTo(null);
|
||||
messageInputRef.current?.clearContent();
|
||||
},
|
||||
[sendMessage, replyingTo],
|
||||
);
|
||||
|
||||
@ -11,7 +11,6 @@ import { Button } from '@/components/ui/button';
|
||||
import { parseFunctionCalls, type FunctionCall } from '@/lib/functionCallParser';
|
||||
import { formatMessageTime } from '../shared/formatters';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { SmilePlus } from 'lucide-react';
|
||||
import { useUser, useRoom, useTheme } from '@/contexts';
|
||||
import { memo, useMemo, useState, useCallback, useRef } from 'react';
|
||||
import { ModelIcon } from '../icon-match';
|
||||
@ -20,6 +19,7 @@ import { MessageContent } from './MessageContent';
|
||||
import { ThreadIndicator } from '../RoomThreadPanel';
|
||||
import { getSenderDisplayName, getSenderModelId, getAvatarFromUiMessage, getSenderUserUid, isUserSender } from '../sender';
|
||||
import { MessageReactions } from './MessageReactions';
|
||||
import { ReactionPicker } from './ReactionPicker';
|
||||
|
||||
// Sender colors — AI Studio clean palette
|
||||
const SENDER_COLORS: Record<string, string> = {
|
||||
@ -375,14 +375,7 @@ export const MessageBubble = memo(function MessageBubble({
|
||||
className="flex items-center gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity absolute -top-3 right-3"
|
||||
style={{ background: 'var(--card)', border: '1px solid var(--room-border)', borderRadius: 6 }}
|
||||
>
|
||||
<button
|
||||
className="flex h-7 w-7 items-center justify-center rounded-md transition-colors"
|
||||
style={{ color: 'var(--room-text-muted)' }}
|
||||
onClick={() => handleReaction('👍')}
|
||||
title="Add reaction"
|
||||
>
|
||||
<SmilePlus className="size-3.5" />
|
||||
</button>
|
||||
<ReactionPicker onReact={handleReaction} />
|
||||
{onReply && (
|
||||
<button
|
||||
className="flex h-7 w-7 items-center justify-center rounded-md transition-colors"
|
||||
|
||||
@ -13,6 +13,7 @@ import { CustomEmojiNode } from './EmojiNode';
|
||||
import type { MentionItem, MessageAST, MentionType } from './types';
|
||||
import { Paperclip, Smile, Send, X } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { COMMON_EMOJIS } from '../../shared';
|
||||
import { useTheme } from '@/contexts';
|
||||
|
||||
export interface IMEditorProps {
|
||||
@ -92,22 +93,7 @@ type Palette = typeof LIGHT;
|
||||
|
||||
// ─── Emoji Picker ─────────────────────────────────────────────────────────────
|
||||
|
||||
const EMOJIS = [
|
||||
{ name: 'thumbsup', url: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/72x72/1f44d.png' },
|
||||
{ name: 'thumbsdown', url: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/72x72/1f44e.png' },
|
||||
{ name: 'heart', url: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/72x72/2764.png' },
|
||||
{ name: 'laugh', url: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/72x72/1f602.png' },
|
||||
{ name: 'rocket', url: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/72x72/1f680.png' },
|
||||
{ name: 'fire', url: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/72x72/1f525.png' },
|
||||
{ name: 'eyes', url: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/72x72/1f440.png' },
|
||||
{ name: 'check', url: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/72x72/2705.png' },
|
||||
{ name: 'star', url: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/72x72/2b50.png' },
|
||||
{ name: 'clap', url: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/72x72/1f44f.png' },
|
||||
{ name: 'thinking', url: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/72x72/1f914.png' },
|
||||
{ name: 'wave', url: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/72x72/1f44b.png' },
|
||||
];
|
||||
|
||||
function EmojiPicker({ onClose, onSelect, p }: { onClose: () => void; onSelect: (n: string, u: string) => void; p: Palette }) {
|
||||
function EmojiPicker({ onClose, onSelect, p }: { onClose: () => void; onSelect: (emoji: string) => void; p: Palette }) {
|
||||
return (
|
||||
<div
|
||||
className="absolute bottom-full left-0 mb-2 z-50"
|
||||
@ -132,14 +118,14 @@ function EmojiPicker({ onClose, onSelect, p }: { onClose: () => void; onSelect:
|
||||
</button>
|
||||
</div>
|
||||
<div className="grid p-2 gap-0.5" style={{ gridTemplateColumns: 'repeat(6, 1fr)' }}>
|
||||
{EMOJIS.map(e => (
|
||||
{COMMON_EMOJIS.map(emoji => (
|
||||
<button
|
||||
key={e.name}
|
||||
onClick={() => onSelect(e.name, e.url)}
|
||||
className="w-9 h-9 flex items-center justify-center rounded-lg transition-all duration-100 cursor-pointer hover:scale-110"
|
||||
key={emoji}
|
||||
onClick={() => onSelect(emoji)}
|
||||
className="w-9 h-9 flex items-center justify-center rounded-lg transition-all duration-100 cursor-pointer hover:scale-110 text-[18px]"
|
||||
style={{ background: 'transparent' }}
|
||||
>
|
||||
<img src={e.url} alt={e.name} className="w-5 h-5 pointer-events-none" />
|
||||
{emoji}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
@ -448,7 +434,7 @@ export const IMEditor = forwardRef<IMEditorHandle, IMEditorProps>(function IMEdi
|
||||
>
|
||||
<Smile size={18} />
|
||||
</button>
|
||||
{showEmoji && <EmojiPicker onClose={() => setShowEmoji(false)} onSelect={(n, u) => { editor?.chain().focus().insertContent({ type: 'emoji', attrs: { name: n, url: u } }).insertContent(' ').run(); setShowEmoji(false); }} p={p} />}
|
||||
{showEmoji && <EmojiPicker onClose={() => setShowEmoji(false)} onSelect={(emoji) => { editor?.chain().focus().insertContent(emoji).insertContent(' ').run(); setShowEmoji(false); }} p={p} />}
|
||||
</div>
|
||||
|
||||
<label
|
||||
|
||||
Loading…
Reference in New Issue
Block a user