feat: Add Chat UX improvements with notifications and @mention support

- Add ActionBar component with expandable toolbar for mobile
- Add @mention functionality with autocomplete dropdown
- Add browser notification system (push, sound, vibration)
- Add NotificationSettings modal for user preferences
- Add mention badges on room list cards
- Add ReportPreview with Markdown rendering and copy/download
- Add message copy functionality with hover actions
- Add backend mentions field to messages with Alembic migration
- Add lots field to rooms, remove templates
- Optimize WebSocket database session handling
- Various UX polish (animations, accessibility)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
egg
2025-12-08 08:20:37 +08:00
parent 92834dbe0e
commit 599802b818
72 changed files with 6810 additions and 702 deletions

View File

@@ -0,0 +1,58 @@
/**
* Store for tracking unread @mentions across rooms
* Uses localStorage for persistence
*/
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
interface MentionState {
// Map of roomId -> unread mention count
unreadMentions: Record<string, number>
// Add a mention for a room
addMention: (roomId: string) => void
// Clear mentions for a room (when user views the room)
clearMentions: (roomId: string) => void
// Get mention count for a room
getMentionCount: (roomId: string) => number
// Get total unread mentions across all rooms
getTotalMentions: () => number
}
export const useMentionStore = create<MentionState>()(
persist(
(set, get) => ({
unreadMentions: {},
addMention: (roomId: string) => {
set((state) => ({
unreadMentions: {
...state.unreadMentions,
[roomId]: (state.unreadMentions[roomId] || 0) + 1,
},
}))
},
clearMentions: (roomId: string) => {
set((state) => {
const { [roomId]: _, ...rest } = state.unreadMentions
return { unreadMentions: rest }
})
},
getMentionCount: (roomId: string) => {
return get().unreadMentions[roomId] || 0
},
getTotalMentions: () => {
return Object.values(get().unreadMentions).reduce((sum, count) => sum + count, 0)
},
}),
{
name: 'task_reporter_mentions',
}
)
)