Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit0d5a7ab

Browse files
added chat controller component to expose chat data/methods
1 parentbc6ea20 commit0d5a7ab

File tree

11 files changed

+1059
-351
lines changed

11 files changed

+1059
-351
lines changed

‎client/packages/lowcoder/src/comps/comps/chatBoxComponent/chatBoxComp.tsx‎

Lines changed: 286 additions & 335 deletions
Large diffs are not rendered by default.

‎client/packages/lowcoder/src/comps/comps/chatBoxComponent/chatControllerComp.tsx‎

Lines changed: 487 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import{AutoHeightControl}from"@lowcoder-ee/comps/controls/autoHeightControl";
2+
import{BoolControl}from"@lowcoder-ee/comps/controls/boolControl";
3+
import{StringControl}from"@lowcoder-ee/comps/controls/codeControl";
4+
import{stringExposingStateControl}from"@lowcoder-ee/comps/controls/codeStateControl";
5+
import{dropdownControl}from"@lowcoder-ee/comps/controls/dropdownControl";
6+
import{clickEvent,doubleClickEvent,eventHandlerControl}from"@lowcoder-ee/comps/controls/eventHandlerControl";
7+
import{styleControl}from"@lowcoder-ee/comps/controls/styleControl";
8+
import{AnimationStyle,TextStyle}from"@lowcoder-ee/comps/controls/styleControlConstants";
9+
import{EditorContext}from"@lowcoder-ee/comps/editorState";
10+
import{withDefault}from"@lowcoder-ee/comps/generators/simpleGenerators";
11+
import{NewChildren}from"@lowcoder-ee/comps/generators/uiCompBuilder";
12+
import{hiddenPropertyView}from"@lowcoder-ee/comps/utils/propertyUtils";
13+
import{RecordConstructorToComp}from"lowcoder-core";
14+
import{ScrollBar,Section,sectionNames}from"lowcoder-design";
15+
importReact,{useContext,useMemo}from"react";
16+
import{trans}from"i18n";
17+
18+
// Event options for the chat component
19+
constEventOptions=[clickEvent,doubleClickEvent]asconst;
20+
21+
// Define the component's children map
22+
exportconstchatCompChildrenMap={
23+
chatName:stringExposingStateControl("chatName","Chat Room"),
24+
userId:stringExposingStateControl("userId","user_1"),
25+
userName:stringExposingStateControl("userName","User"),
26+
applicationId:stringExposingStateControl("applicationId","lowcoder_app"),
27+
roomId:stringExposingStateControl("roomId","general"),
28+
mode:dropdownControl([
29+
{label:"🌐 Collaborative (Real-time)",value:"collaborative"},
30+
{label:"🔀 Hybrid (Local + Real-time)",value:"hybrid"},
31+
{label:"📱 Local Only",value:"local"}
32+
],"collaborative"),
33+
34+
// Room Management Configuration
35+
allowRoomCreation:withDefault(BoolControl,true),
36+
allowRoomJoining:withDefault(BoolControl,true),
37+
roomPermissionMode:dropdownControl([
38+
{label:"🌐 Open (Anyone can join public rooms)",value:"open"},
39+
{label:"🔐 Invite Only (Admin invitation required)",value:"invite"},
40+
{label:"👤 Admin Only (Only admins can manage)",value:"admin"}
41+
],"open"),
42+
showAvailableRooms:withDefault(BoolControl,true),
43+
maxRoomsDisplay:withDefault(StringControl,"10"),
44+
45+
// UI Configuration
46+
leftPanelWidth:withDefault(StringControl,"200px"),
47+
showRooms:withDefault(BoolControl,true),
48+
autoHeight:AutoHeightControl,
49+
onEvent:eventHandlerControl(EventOptions),
50+
style:styleControl(TextStyle,'style'),
51+
animationStyle:styleControl(AnimationStyle,'animationStyle'),
52+
};
53+
54+
exporttypeChatCompChildrenType=NewChildren<RecordConstructorToComp<typeofchatCompChildrenMap>>;
55+
56+
// Property view component
57+
exportconstChatPropertyView=React.memo((props:{
58+
children:ChatCompChildrenType
59+
})=>{
60+
consteditorContext=useContext(EditorContext);
61+
consteditorModeStatus=useMemo(()=>editorContext.editorModeStatus,[editorContext.editorModeStatus]);
62+
63+
constbasicSection=useMemo(()=>(
64+
<Sectionname={sectionNames.basic}>
65+
{props.children.chatName.propertyView({
66+
label:"Chat Name",
67+
tooltip:"Name displayed in the chat header"
68+
})}
69+
{props.children.userId.propertyView({
70+
label:"User ID",
71+
tooltip:"Unique identifier for the current user"
72+
})}
73+
{props.children.userName.propertyView({
74+
label:"User Name",
75+
tooltip:"Display name for the current user"
76+
})}
77+
{props.children.applicationId.propertyView({
78+
label:"Application ID",
79+
tooltip:"Unique identifier for this Lowcoder application - all chat components with the same Application ID can discover each other's rooms"
80+
})}
81+
{props.children.roomId.propertyView({
82+
label:"Initial Room",
83+
tooltip:"Default room to join when the component loads (within the application scope)"
84+
})}
85+
{props.children.mode.propertyView({
86+
label:"Sync Mode",
87+
tooltip:"Choose how messages are synchronized: Collaborative (real-time), Hybrid (local + real-time), or Local only"
88+
})}
89+
</Section>
90+
),[props.children]);
91+
92+
constroomManagementSection=useMemo(()=>(
93+
<Sectionname="Room Management">
94+
{props.children.allowRoomCreation.propertyView({
95+
label:"Allow Room Creation",
96+
tooltip:"Allow users to create new chat rooms"
97+
})}
98+
{props.children.allowRoomJoining.propertyView({
99+
label:"Allow Room Joining",
100+
tooltip:"Allow users to join existing rooms"
101+
})}
102+
{props.children.roomPermissionMode.propertyView({
103+
label:"Permission Mode",
104+
tooltip:"Control how users can join rooms"
105+
})}
106+
{props.children.showAvailableRooms.propertyView({
107+
label:"Show Available Rooms",
108+
tooltip:"Display list of available rooms to join"
109+
})}
110+
{props.children.maxRoomsDisplay.propertyView({
111+
label:"Max Rooms to Display",
112+
tooltip:"Maximum number of rooms to show in the list"
113+
})}
114+
</Section>
115+
),[props.children]);
116+
117+
constinteractionSection=useMemo(()=>
118+
["logic","both"].includes(editorModeStatus)&&(
119+
<Sectionname={sectionNames.interaction}>
120+
{hiddenPropertyView(props.children)}
121+
{props.children.onEvent.getPropertyView()}
122+
</Section>
123+
),[editorModeStatus,props.children]);
124+
125+
constlayoutSection=useMemo(()=>
126+
["layout","both"].includes(editorModeStatus)&&(
127+
<>
128+
<Sectionname={sectionNames.layout}>
129+
{props.children.autoHeight.getPropertyView()}
130+
{props.children.leftPanelWidth.propertyView({
131+
label:"Left Panel Width",
132+
tooltip:"Width of the rooms/people panel (e.g., 300px, 25%)"
133+
})}
134+
{props.children.showRooms.propertyView({
135+
label:"Show Rooms"
136+
})}
137+
</Section>
138+
<Sectionname={sectionNames.style}>
139+
{props.children.style.getPropertyView()}
140+
</Section>
141+
<Sectionname={sectionNames.animationStyle}hasTooltip={true}>
142+
{props.children.animationStyle.getPropertyView()}
143+
</Section>
144+
</>
145+
),[editorModeStatus,props.children]);
146+
147+
return(
148+
<>
149+
{basicSection}
150+
{roomManagementSection}
151+
{interactionSection}
152+
{layoutSection}
153+
</>
154+
);
155+
});

‎client/packages/lowcoder/src/comps/comps/chatBoxComponent/hooks/useChatManager.ts‎

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export interface UseChatManagerReturn {
4848
joinRoom:(roomId:string)=>Promise<boolean>;
4949
leaveRoom:(roomId:string)=>Promise<boolean>;
5050
canUserJoinRoom:(roomId:string)=>Promise<boolean>;
51+
getRoomParticipants:(roomId:string)=>Promise<Array<{id:string;name:string}>>;
5152

5253
// Manager access (for advanced use)
5354
manager:HybridChatManager|null;
@@ -95,7 +96,7 @@ export function useChatManager(config: UseChatManagerConfig): UseChatManagerRetu
9596
// 🧪 TEST: Add collaborative config to enable YjsPluvProvider for testing
9697
// This enables testing of the Yjs document structure (Step 1)
9798
collaborative:{
98-
serverUrl:'ws://localhost:3001',// Placeholder - not used in Step 1
99+
serverUrl:'ws://localhost:3005',// Placeholder - not used in Step 1
99100
roomId:config.roomId,
100101
authToken:undefined,
101102
autoConnect:true,
@@ -353,7 +354,7 @@ export function useChatManager(config: UseChatManagerConfig): UseChatManagerRetu
353354
}
354355

355356
try{
356-
constresult=awaitmanager.createRoom({
357+
constresult=awaitmanager.createRoom({
357358
name,
358359
type,
359360
participants:[config.userId],
@@ -558,6 +559,23 @@ export function useChatManager(config: UseChatManagerConfig): UseChatManagerRetu
558559
returnfalse;
559560
}
560561
},[config.userId]);
562+
563+
constgetRoomParticipants=useCallback(async(roomId:string):Promise<Array<{id:string;name:string}>>=>{
564+
constmanager=managerRef.current;
565+
if(!manager)return[];
566+
567+
try{
568+
constresult=awaitmanager.getRoomParticipants(roomId);
569+
if(result.success){
570+
returnresult.data!;
571+
}
572+
setError(result.error||'Failed to get room participants');
573+
return[];
574+
}catch(error){
575+
setError(errorinstanceofError ?error.message :'Failed to get room participants');
576+
return[];
577+
}
578+
},[]);
561579

562580
return{
563581
// Connection state
@@ -588,6 +606,7 @@ export function useChatManager(config: UseChatManagerConfig): UseChatManagerRetu
588606
joinRoom,
589607
leaveRoom,
590608
canUserJoinRoom,
609+
getRoomParticipants,
591610

592611
// Manager access
593612
manager:managerRef.current,

‎client/packages/lowcoder/src/comps/comps/chatBoxComponent/managers/HybridChatManager.ts‎

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,64 @@ export class HybridChatManager {
394394
console.log('[HybridChatManager] 🔐 Checking if user can join room:',{ roomId, userId});
395395
returnthis.getActiveProvider().canUserJoinRoom(roomId,userId);
396396
}
397+
398+
asyncgetRoomParticipants(roomId:string):Promise<OperationResult<Array<{id:string;name:string}>>>{
399+
console.log('[HybridChatManager] 👥 Getting room participants:',{ roomId});
400+
401+
try{
402+
// First get the room to access participants
403+
constroomResult=awaitthis.getRoom(roomId);
404+
if(!roomResult.success||!roomResult.data){
405+
return{
406+
success:false,
407+
error:roomResult.error||'Room not found',
408+
timestamp:Date.now()
409+
};
410+
}
411+
412+
constroom=roomResult.data;
413+
constparticipants=room.participants||[];
414+
415+
// Get participant details by looking at recent messages to extract user names
416+
constmessagesResult=awaitthis.getMessages(roomId,100);// Get recent messages
417+
if(!messagesResult.success){
418+
// If we can't get messages, return participants with just IDs
419+
return{
420+
success:true,
421+
data:participants.map(id=>({ id,name:id})),// Fallback to ID as name
422+
timestamp:Date.now()
423+
};
424+
}
425+
426+
// Create a map of userId -> userName from messages
427+
constuserMap=newMap<string,string>();
428+
messagesResult.data?.forEach(message=>{
429+
if(message.authorId&&message.authorName){
430+
userMap.set(message.authorId,message.authorName);
431+
}
432+
});
433+
434+
// Build participant list with names
435+
constparticipantsWithNames=participants.map(participantId=>({
436+
id:participantId,
437+
name:userMap.get(participantId)||participantId// Fallback to ID if name not found
438+
}));
439+
440+
return{
441+
success:true,
442+
data:participantsWithNames,
443+
timestamp:Date.now()
444+
};
445+
446+
}catch(error){
447+
console.error('[HybridChatManager] Error getting room participants:',error);
448+
return{
449+
success:false,
450+
error:errorinstanceofError ?error.message :'Failed to get room participants',
451+
timestamp:Date.now()
452+
};
453+
}
454+
}
397455

398456
// Message operations (delegated to active provider)
399457
asyncsendMessage(message:Omit<UnifiedMessage,'id'|'timestamp'|'status'>):Promise<OperationResult<UnifiedMessage>>{

‎client/packages/lowcoder/src/comps/comps/chatBoxComponent/providers/YjsPluvProvider.ts‎

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export class YjsPluvProvider extends BaseChatDataProvider implements ChatDataPro
5858
ydoc=newY.Doc();
5959
YjsPluvProvider.globalDocs.set(docId,ydoc);
6060
YjsPluvProvider.docRefCounts.set(docId,1);
61-
constwsUrl=config.realtime.serverUrl||'ws://localhost:3001';
61+
constwsUrl=config.realtime.serverUrl||'ws://localhost:3005';
6262
wsProvider=newWebsocketProvider(wsUrl,docId,ydoc,{
6363
connect:true,
6464
params:{room:docId}
@@ -80,28 +80,29 @@ export class YjsPluvProvider extends BaseChatDataProvider implements ChatDataPro
8080
this.messagesMap.observe(this.messagesObserver);
8181
this.roomsMap.observe(this.roomsObserver);
8282
this.typingMap.observe(this.typingObserver);
83+
84+
// Set connection state immediately to allow local operations
85+
this.setConnectionState('connected');
86+
8387
if(this.wsProvider){
8488
this.wsProvider.off('status',this.handleWSStatus);
8589
this.wsProvider.off('sync',this.handleWSSync);
8690
this.wsProvider.on('status',this.handleWSStatus.bind(this));
8791
this.wsProvider.on('sync',this.handleWSSync.bind(this));
88-
constcurrentStatus=this.wsProvider.wsconnected ?'connected' :
89-
this.wsProvider.wsconnecting ?'connecting' :'disconnected';
90-
this.setConnectionState(currentStatusasConnectionState);
92+
93+
// Update connection state based on WebSocket status
9194
if(this.wsProvider.wsconnected){
9295
this.setConnectionState('connected');
9396
}elseif(this.wsProvider.wsconnecting){
9497
this.setConnectionState('connecting');
95-
}else{
96-
this.setConnectionState('connecting');
9798
}
9899
}
99-
if(this.connectionState!=='connected'){
100-
this.setConnectionState('connected');
101-
}
100+
101+
console.log('[YjsPluvProvider] ✅ Connected successfully with docId:',docId);
102102
returnthis.createSuccessResult(undefined);
103103
}catch(error){
104104
this.setConnectionState('failed');
105+
console.error('[YjsPluvProvider] ❌ Connection failed:',error);
105106
returnthis.handleError(error,'connect');
106107
}
107108
}
@@ -347,9 +348,16 @@ export class YjsPluvProvider extends BaseChatDataProvider implements ChatDataPro
347348

348349
asyncgetAvailableRooms(userId:string,filter?:RoomListFilter):Promise<OperationResult<UnifiedRoom[]>>{
349350
try{
351+
console.log('[YjsPluvProvider] 🔍 Getting available rooms for user:',userId);
352+
console.log('[YjsPluvProvider] 📊 Connection state:',this.connectionState);
353+
console.log('[YjsPluvProvider] 📄 Yjs doc available:',!!this.ydoc);
354+
console.log('[YjsPluvProvider] 🗺️ Rooms map available:',!!this.roomsMap);
355+
350356
awaitthis.ensureConnected();
351357
constallRooms=Array.from(this.roomsMap!.values());
352358

359+
console.log('[YjsPluvProvider] 📋 Total rooms found:',allRooms.length);
360+
353361
letfilteredRooms=allRooms.filter(room=>{
354362
if(!room.isActive)returnfalse;
355363
if(filter?.type&&room.type!==filter.type)returnfalse;
@@ -361,8 +369,10 @@ export class YjsPluvProvider extends BaseChatDataProvider implements ChatDataPro
361369
returntrue;
362370
});
363371

372+
console.log('[YjsPluvProvider] ✅ Filtered rooms:',filteredRooms.length);
364373
returnthis.createSuccessResult(filteredRooms);
365374
}catch(error){
375+
console.error('[YjsPluvProvider] ❌ Error in getAvailableRooms:',error);
366376
returnthis.handleError(error,'getAvailableRooms');
367377
}
368378
}
@@ -881,8 +891,13 @@ export class YjsPluvProvider extends BaseChatDataProvider implements ChatDataPro
881891
}
882892

883893
privateasyncensureConnected():Promise<void>{
884-
if(!this.ydoc||this.connectionState!=='connected'){
885-
thrownewError('YjsPluvProvider is not connected');
894+
if(!this.ydoc){
895+
thrownewError('YjsPluvProvider is not connected - no Yjs document available');
896+
}
897+
898+
// Allow operations even if WebSocket is still connecting, as Yjs works locally
899+
if(this.connectionState==='failed'||this.connectionState==='disconnected'){
900+
thrownewError('YjsPluvProvider is not connected - connection state: '+this.connectionState);
886901
}
887902
}
888-
}
903+
}

‎client/packages/lowcoder/src/comps/hooks/hookComp.tsx‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import { ThemeComp } from "./themeComp";
3737
importUrlParamsHookCompfrom"./UrlParamsHookComp";
3838
import{UtilsComp}from"./utilsComp";
3939
import{ScreenInfoHookComp}from"./screenInfoComp";
40+
import{ChatControllerComp}from"../comps/chatBoxComponent/chatControllerComp";
4041

4142
window._=_;
4243
window.dayjs=dayjs;
@@ -118,6 +119,7 @@ const HookMap: HookCompMapRawType = {
118119
urlParams:UrlParamsHookComp,
119120
drawer:DrawerComp,
120121
theme:ThemeComp,
122+
chatController:ChatControllerComp,
121123
};
122124

123125
exportconstHookTmpComp=withTypeAndChildren(HookMap,"title",{
@@ -155,7 +157,8 @@ function SelectHookView(props: {
155157
if(
156158
(props.compType!=="modal"&&
157159
props.compType!=="drawer"&&
158-
props.compType!=="meeting")||
160+
props.compType!=="meeting"&&
161+
props.compType!=="chatController")||
159162
!selectedComp||
160163
(editorState.selectSource!=="addComp"&&
161164
editorState.selectSource!=="leftPanel")

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp