feat: 会话窗口

dev/chat
Lei OT 1 year ago
parent d80b83b1f3
commit 11e4a44de2

@ -17,5 +17,6 @@ module.exports = {
{ allowConstantExport: true },
],
'no-unused-vars': ['warn', { args: 'after-used', vars: 'all' }],
"react/prop-types": "off",
},
}

@ -1,9 +1,6 @@
import {
webSocket,
} from "rxjs/webSocket";
import { filter, buffer, flatMap, merge, map, tap } from "rxjs/operators";
import { webSocket } from 'rxjs/webSocket';
import { filter, buffer, map, tap } from 'rxjs/operators';
// import { v4 as uuid } from "uuid";
import { SHA256 } from "crypto-js";
export class RealTimeAPI {
constructor(param) {
@ -39,7 +36,7 @@ export class RealTimeAPI {
this.getObservable().subscribe({
next: messageHandler,
error: errorHandler,
complete: completionHandler
complete: completionHandler,
});
}
@ -48,108 +45,34 @@ export class RealTimeAPI {
}
getObservableFilteredByMessageType(messageType) {
return this.getObservable().pipe(
filter((message) => message.msg === messageType)
);
return this.getObservable().pipe(filter((message) => message.msg === messageType));
}
getObservableFilteredByID(id) {
return this.getObservable().pipe(
filter((message) => message.id === id)
);
return this.getObservable().pipe(filter((message) => message.id === id));
}
connectToServer() {
this.sendMessage({
msg: "connect",
version: "1",
support: ["1", "pre2", "pre1"]
msg: 'connect',
version: '1',
support: ['1', 'pre2', 'pre1'],
});
return this.getObservableFilteredByMessageType("connected");
return this.getObservableFilteredByMessageType('connected');
}
keepAlive() {
return this.getObservableFilteredByMessageType("ping").pipe(
tap(() => this.sendMessage({ msg: "pong" }))
);
}
login(username, password) {
let id = 'uuid()';
let usernameType = username.indexOf("@") !== -1 ? "email" : "username";
this.sendMessage({
msg: "method",
method: "login",
id: id,
params: [
{
user: { [usernameType]: username },
password: {
digest: SHA256(password).toString(),
algorithm: "sha-256"
}
}
]
});
return this.getLoginObservable(id);
return this.getObservableFilteredByMessageType('ping').pipe(tap(() => this.sendMessage({ msg: 'pong' })));
}
loginWithAuthToken(authToken) {
let id = 'uuid()';
this.sendMessage({
msg: "method",
method: "login",
id: id,
params: [{ resume: authToken }]
});
return this.getLoginObservable(id);
}
loginWithOAuth(credToken, credSecret) {
let id = 'uuid()';
this.sendMessage({
msg: "method",
method: "login",
id: id,
params: [
{
oauth: {
credentialToken: credToken,
credentialSecret: credSecret
}
}
]
});
return this.getLoginObservable(id);
}
getLoginObservable(id) {
let resultObservable = this.getObservableFilteredByID(id);
let resultId;
let addedObservable = this.getObservable().pipe(
buffer(
resultObservable.pipe(
map(({ msg, error, result }) => {
if (msg === "result" && !error) return (resultId = result.id);
})
)
),
flatMap(x => x),
filter(({ id: msgId }) => resultId !== undefined && msgId === resultId),
merge(resultObservable)
);
return addedObservable;
}
callMethod(method, ...params) {
let id = 'uuid()';
this.sendMessage({
msg: "method",
msg: 'method',
method,
id,
params
params,
});
return this.getObservableFilteredByID(id);
}
@ -158,19 +81,16 @@ export class RealTimeAPI {
let id = 'uuid()';
let subscription = this.webSocket.multiplex(
() => ({
msg: "sub",
msg: 'sub',
id: id,
name: streamName,
params: [streamParam, addEvent]
params: [streamParam, addEvent],
}),
() => ({
msg: "unsub",
id: id
msg: 'unsub',
id: id,
}),
(message) =>
typeof message.collection === "string" &&
message.collection === streamName &&
message.fields.eventName === streamParam
(message) => typeof message.collection === 'string' && message.collection === streamName && message.fields.eventName === streamParam
);
return subscription;
}

@ -7,6 +7,7 @@ import {
} from 'react-router-dom'
import { AuthContext } from '@/stores/AuthContext'
import { ThemeContext } from '@/stores/ThemeContext'
import ConversationProvider from '@/stores/ConversationProvider';
import Auth from '@/stores/Auth'
import App from '@/views/App'
import Standlone from '@/views/Standlone'
@ -35,7 +36,7 @@ const router = createBrowserRouter([
path: '/',
element: <App />,
errorElement: <ErrorPage />,
children: [
children: [
{ index: true, element: <OrderFollow /> },
{ path: 'order/follow', element: <OrderFollow />},
{ path: 'chat/history', element: <ChatHistory />},
@ -55,14 +56,16 @@ const router = createBrowserRouter([
])
ReactDOM.createRoot(document.getElementById('root')).render(
// <React.StrictMode>
<ThemeContext.Provider value={{colorPrimary: '#1ba784', borderRadius: 4}}>
<AuthContext.Provider value={{loginUser: {userId: 1, openId: '123456789'}, permissionList: ['view_chat', 'send_msg']}}>
<ConversationProvider>
<RouterProvider
router={router}
fallbackElement={() => <div>Loading...</div>}
/>
</ConversationProvider>
</AuthContext.Provider>
</ThemeContext.Provider>
// </React.StrictMode>

@ -0,0 +1,5 @@
import { createContext, useContext } from 'react';
export const ConversationContext = createContext();
export const useConversations = () => useContext(ConversationContext);

@ -0,0 +1,53 @@
import React, { createContext, useContext, useState } from 'react';
import { ConversationContext } from '@/stores/ConversationContext';
import { RealTimeAPI } from '@/lib/realTimeAPI';
const URL = 'ws://202.103.68.144:8888/whatever/';
// let realtimeAPI = new RealTimeAPI({ url: URL, protocol: 'aaa' });
let realtimeAPI = new RealTimeAPI({ url: URL, protocol: 'WhatsApp' });
export const ConversationProvider = ({ children }) => {
const [errors, setErrors] = useState([]);
const [messages, setMessages] = useState([]);
const addError = (reason) => {
setErrors(prevErrors => [...prevErrors, { reason }]);
}
const addMessage = (message) => {
setMessages(prevMessages => [...prevMessages, message]);
}
const handleMessage = (data) => {
const msg = data.result;
if (!msg) {
return false;
}
if (typeof msg.type === 'string' && msg.type === 'error') {
addError('Error Connecting to Server');
}
addMessage({ ...msg.message, sender: 'other', id: Date.now().toString(16) });
}
const sendMessage = (msg) => {
const msgObj = {
type: 'message',
message: msg,
};
realtimeAPI.sendMessage(msgObj);
addMessage(msgObj.message);
}
realtimeAPI.onError(addError.bind(null, 'Error'));
realtimeAPI.onMessage(handleMessage);
realtimeAPI.onCompletion(addError.bind(null, 'Not Connected to Server'));
realtimeAPI.keepAlive(); // Ping Server
return (
<ConversationContext.Provider value={{ errors, messages, sendMessage }}>
{children}
</ConversationContext.Provider>
);
}
export default ConversationProvider;

@ -1,13 +1,11 @@
import { makeAutoObservable } from 'mobx'
import Auth from './Auth'
import Order from './Order'
import Conversations from './Conversations'
class Root {
constructor() {
this.orderStore = new Order(this)
this.authStore = new Auth(this)
this.conversationsStore = new Conversations(this)
makeAutoObservable(this)
}

@ -1,4 +1,4 @@
import { useEffect, useContext } from 'react';
import { useEffect } from 'react';
import { observer } from 'mobx-react';
import { Layout, List, Avatar } from 'antd';
import Messages from './Components/Messages';
@ -6,16 +6,10 @@ import InputBox from './Components/InputBox';
import Conversations from './Components/ConversationsList';
import CustomerProfile from './Components/CustomerProfile';
import WebSocketLib from '@/lib/websocketLib';
import { useStore } from '@/stores/StoreContext.js';
import { stringToColour } from '@/utils/utils';
import { useConversations } from '@/stores/ConversationContext';
import './Conversations.css';
const customer = { url: 'ws://202.103.68.144:8888/whatever/', authToken: 'customer1Token' };
// Create a WebSocketLib instance for each customer
// const wsConnect = new WebSocketLib(customer.url, customer.authToken, 'WhatApp');
// const wsConnect = new WebSocketLib(customer.url, customer.authToken, 'aaa');
import { useAuthContext } from '@/stores/AuthContext.js'
const { Sider, Content, Header, Footer } = Layout;
@ -25,65 +19,56 @@ const CList = [
{ name: 'Customer_3', label: 'Customer_3', key: 'Customer_3', value: 'Customer_3' },
{ name: 'Customer_4', label: 'Customer_4', key: 'Customer_4', value: 'Customer_4' },
];
const messages = [
{ sender: 'Customer_1', text: 'Hello, how can I help you today?' },
{ sender: 'Customer_2', text: 'Hello, how can I help you today?' },
{ sender: 'Customer_3', text: 'Hello, how can I help you today?' },
{ sender: 'Customer_4', text: 'Hello, how can I help you today?' },
];
/**
*
*/
const ChatWindow = observer(() => {
const { conversationsStore } = useStore();
const { sendMessage, messages } = conversationsStore;
useEffect(() => {
return () => {};
}, []);
const { loginUser: currentUser } = useAuthContext()
const { errors, messages, sendMessage } = useConversations();
return (
<Layout className='full-height' style={{ maxHeight: 'calc(100% - 150px)', height: 'calc(100% - 150px)' }}>
<Sider width={220} theme={'light'} className='scrollable-column' style={{ height: '70vh' }}>
<Conversations conversations={CList} />
</Sider>
<Layout className='full-height' style={{ maxHeight: 'calc(100% - 150px)', height: 'calc(100% - 150px)' }}>
<Sider width={220} theme={'light'} className='scrollable-column' style={{ height: '70vh' }}>
<Conversations conversations={CList} />
</Sider>
<Content className='h70' style={{ maxHeight: '70vh', height: '70vh' }}>
<Layout style={{ height: '100%' }}>
<Header className='ant-layout-sider-light ant-card' style={{paddingLeft: '10px'}}>
<List
dataSource={[{ name: 'Customer_1', label: 'Customer_1', key: 'Customer_1', value: 'Customer_1' }]}
renderItem={(item, ii) => (
<List.Item actions={[<a key='list-loadmore-edit'>mark</a>]}>
<List.Item.Meta
avatar={
<Avatar
style={{
backgroundColor: stringToColour(item.name),
verticalAlign: 'middle',
}}>
{item.name}
</Avatar>
}
title={item.name}
description='{最近的消息}'
/>
</List.Item>
)}
/>
</Header>
<Content style={{ maxHeight: '70vh', height: '70vh' }}>
<div className='scrollable-column' >
<Messages />
</div>
</Content>
<Footer className='ant-layout-sider-light' style={{padding: '10px'}}>
<InputBox onSend={(v) => sendMessage(v)} />
</Footer>
</Layout>
{/* <InputBox onSend={(v) => sendMessage(v)} /> */}
</Content>
<Content className='h70' style={{ maxHeight: '70vh', height: '70vh' }}>
<Layout style={{ height: '100%' }}>
<Header className='ant-layout-sider-light ant-card' style={{ paddingLeft: '10px' }}>
<List
dataSource={[{ name: 'Customer_1', label: 'Customer_1', key: 'Customer_1', value: 'Customer_1' }]}
renderItem={(item, ii) => (
<List.Item actions={[<a key='list-loadmore-edit'>mark</a>]}>
<List.Item.Meta
avatar={
<Avatar src={`https://api.dicebear.com/7.x/avataaars/svg?seed=${item.name}`}
>
{item.name}
</Avatar>
}
title={item.name}
description='{最近的消息}'
/>
</List.Item>
)}
/>
</Header>
<Content style={{ maxHeight: '70vh', height: '70vh' }}>
<div className='scrollable-column'>
<Messages />
</div>
</Content>
<Footer className='ant-layout-sider-light' style={{ padding: '10px' }}>
<InputBox onSend={(v) => sendMessage(v)} />
</Footer>
</Layout>
{/* <InputBox onSend={(v) => sendMessage(v)} /> */}
</Content>
<Sider width={300} theme={'light'} className='scrollable-column' style={{ maxHeight: '70vh', height: '70vh' }}>
<CustomerProfile customer={{}} />
</Sider>
</Layout>
<Sider width={300} theme={'light'} className='scrollable-column' style={{ maxHeight: '70vh', height: '70vh' }}>
<CustomerProfile customer={{}} />
</Sider>
</Layout>
);
});

@ -1,18 +1,5 @@
import { observer } from 'mobx-react';
import { List, Avatar } from 'antd';
import crypto from 'crypto-js';
import { stringToColour } from '@/utils/utils';
const ColorList = []; // ['#f56a00', '#7265e6', '#ffbf00', '#00a2ae'];
// const colors = [];
for (let i = 0; i < 10; i++) {
const red = Math.floor(Math.random() * 256).toString(16);
const green = Math.floor(Math.random() * 256).toString(16);
const blue = Math.floor(Math.random() * 256).toString(16);
// ColorList.push(`rgb(${red}, ${green}, ${blue})`);
ColorList.push(`#${red}${green}${blue}`);
}
// console.log(ColorList);
/**
* []
@ -25,11 +12,7 @@ const Conversations = observer(({ conversations }) => {
<List.Item actions={[<a key='list-loadmore-edit'>mark</a>]}>
<List.Item.Meta
avatar={
<Avatar
style={{
backgroundColor: stringToColour(item.name), // ColorList[ii],
verticalAlign: 'middle',
}}>
<Avatar src={`https://api.dicebear.com/7.x/avataaars/svg?seed=${item.name}`} >
{item.name}
</Avatar>
}

@ -1,22 +1,24 @@
import { useEffect } from 'react';
import { observer } from 'mobx-react';
import { useStore } from '@/stores/StoreContext.js';
import { List, Avatar, Timeline } from 'antd';
import { MessageBox } from 'react-chat-elements';
import { useConversations } from '@/stores/ConversationContext';
const Messages = observer(() => {
const { conversationsStore } = useStore();
const { messages: newMessages } = conversationsStore;
const messagesTemplate = [
{
id: Date.now().toString(16),
sender: 'Customer_1',
type: 'text',
text: { body: 'Hello, how can I help you today?' } ,
}
];
useEffect(() => {
// Load your data here
// For example:
// conversationsStore.loadMessages();
}, [newMessages]);
const Messages = observer(() => {
const { messages } = useConversations()
return (
<>
{conversationsStore.messages.map((message, index) => (
{messages.map((message, index) => (
<MessageBox
key={message.id}
position={ message.sender === 'me' ? 'right' : 'left' }

@ -1,7 +1,7 @@
import { useContext } from 'react';
import { observer } from 'mobx-react';
import { Table } from 'antd';
import WebSocketLib from '@/lib/websocketLib';
import WebSocketLib from '@/lib/websocketLib2';
const CList = [
{ name: 'Customer_1', label: 'Customer_1', key: 'Customer_1', value: 'Customer_1' },

Loading…
Cancel
Save