Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
e0a18e0
fix conversation history
iamfaran Feb 10, 2026
74f1787
fix height messages window + add height mode (auto/fixed) for chat co…
iamfaran Feb 10, 2026
b4620e5
add style customization
iamfaran Feb 11, 2026
5875702
seperate chat panel component
iamfaran Feb 12, 2026
11bb844
complete chat styles
iamfaran Feb 12, 2026
a12a9b9
refactor ai chat & chat panel component
iamfaran Feb 13, 2026
e3fe298
fix re rendering issue
iamfaran Feb 16, 2026
c970bc2
fix button forward ref + ChatProvider level
iamfaran Feb 16, 2026
64dddc0
fix styling warninings
iamfaran Feb 17, 2026
5ef4403
refactor styles and add storage cleaner for bottom chat panel
iamfaran Feb 17, 2026
a46c1f7
fix attachment file adaptor for chat component
iamfaran Feb 18, 2026
4d22430
add messageInstance for throwing errors in attachments for AI chat co…
iamfaran Feb 18, 2026
7fd74ca
remove unnecessary settings from the AI chat component
iamfaran Feb 19, 2026
0c4e885
fix image attachments preview and remove attachments from the bottom …
iamfaran Feb 20, 2026
57fd468
add chatCompv2 + new chatData store
iamfaran Feb 25, 2026
18abefb
setup basic data structure for chatv2
iamfaran Feb 26, 2026
d8a8423
add yjs support
iamfaran Feb 27, 2026
362e362
fix linter errors
iamfaran Mar 3, 2026
6a1911b
add typing indicators
iamfaran Mar 3, 2026
bf08ee3
refactor chatbox styles, modes and fix registry
iamfaran Mar 4, 2026
6a0ff47
refactor chatbox to multiple files
iamfaran Mar 5, 2026
0a4fe35
remove duplication of modes
iamfaran Mar 5, 2026
7d40041
add testing chat controller
iamfaran Mar 6, 2026
58598dc
add typing state via awareness protocol
iamfaran Mar 6, 2026
9622f2d
add LLM chat room for chatbox
iamfaran Mar 9, 2026
a8a7522
add room invites + pluv integration
iamfaran Mar 10, 2026
33b9efe
fix pluv integration issues
iamfaran Mar 11, 2026
4da29c4
add chatController architecture
iamfaran Mar 12, 2026
c1030f6
complete chatcontroller architecture
iamfaran Mar 13, 2026
c26d9b1
add rooms in the chatbox UI + fix controller issues
iamfaran Mar 13, 2026
6ae7385
fix TS errors + chatbox context
iamfaran Mar 16, 2026
d5b6b62
add online presence rooms
iamfaran Mar 16, 2026
ab37868
fix message styles, autoscroll and pluv server
iamfaran Mar 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion client/packages/lowcoder/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@
"@jsonforms/core": "^3.5.1",
"@lottiefiles/dotlottie-react": "^0.13.0",
"@manaflair/redux-batch": "^1.0.0",
"@pluv/client": "^4.0.1",
"@pluv/crdt-yjs": "^4.0.1",
"@pluv/io": "^4.0.1",
"@pluv/platform-pluv": "^4.0.1",
"@pluv/react": "^4.0.1",
"@radix-ui/react-avatar": "^1.1.10",
"@radix-ui/react-dialog": "^1.1.14",
"@radix-ui/react-slot": "^1.2.3",
Expand All @@ -59,11 +64,13 @@
"coolshapes-react": "lowcoder-org/coolshapes-react",
"copy-to-clipboard": "^3.3.3",
"core-js": "^3.25.2",
"cors": "^2.8.6",
"dayjs": "^1.11.13",
"echarts": "^5.4.3",
"echarts-for-react": "^3.0.2",
"echarts-wordcloud": "^2.1.0",
"eslint4b-prebuilt-2": "^7.32.0",
"express": "^5.2.1",
"file-saver": "^2.0.5",
"github-markdown-css": "^5.1.0",
"hotkeys-js": "^3.8.7",
Expand Down Expand Up @@ -125,13 +132,16 @@
"web-vitals": "^2.1.0",
"ws": "^8.18.3",
"xlsx": "^0.18.5",
"y-indexeddb": "^9.0.12",
"y-protocols": "^1.0.6",
"y-websocket": "^3.0.0",
"yjs": "^13.6.27"
"yjs": "^13.6.27",
"zod": "^3.25.76"
},
"scripts": {
"supportedBrowsers": "yarn dlx browserslist-useragent-regexp --allowHigherVersions '>0.2%,not dead,not op_mini all,chrome >=69'",
"start": "REACT_APP_LOG_LEVEL=debug REACT_APP_ENV=local vite",
"start:pluv": "node pluv-server.js",
"build": "vite build && cp ../../VERSION ./build/VERSION",
"preview": "vite preview",
"prepare": "husky install"
Expand Down
152 changes: 152 additions & 0 deletions client/packages/lowcoder/pluv-server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
#!/usr/bin/env node

/**
* Pluv.io Auth & Webhook Server for ChatBox v2
*
* Replaces yjs-websocket-server.js — pluv.io manages all WebSocket
* infrastructure, so this server only handles:
* 1. Token creation (auth endpoint)
* 2. Webhook ingestion (server-side events like storage persistence)
*
* Required env vars:
* PLUV_PUBLISHABLE_KEY – your pluv.io public/publishable key
* PLUV_SECRET_KEY – your pluv.io secret key
* PLUV_WEBHOOK_SECRET – (optional) webhook signing secret
*
* Usage: node pluv-server.js
*/

import { createIO } from "@pluv/io";
import { platformPluv } from "@pluv/platform-pluv";
import { yjs } from "@pluv/crdt-yjs";
import { z } from "zod";
import express from "express";
import cors from "cors";

const PORT = process.env.PORT || 3006;
const HOST = process.env.HOST || "0.0.0.0";

const PLUV_PUBLISHABLE_KEY = process.env.PLUV_PUBLISHABLE_KEY;
const PLUV_SECRET_KEY = process.env.PLUV_SECRET_KEY;
const PLUV_WEBHOOK_SECRET = process.env.PLUV_WEBHOOK_SECRET;

if (!PLUV_PUBLISHABLE_KEY || !PLUV_SECRET_KEY) {
console.error(
"Missing required env vars: PLUV_PUBLISHABLE_KEY, PLUV_SECRET_KEY",
);
process.exit(1);
}

// ── Pluv IO setup ─────────────────────────────────────────────────────────

const io = createIO(
platformPluv({
authorize: {
user: z.object({
id: z.string(),
name: z.string(),
}),
},
crdt: yjs,
publicKey: PLUV_PUBLISHABLE_KEY,
secretKey: PLUV_SECRET_KEY,
basePath: "/api/pluv",
...(PLUV_WEBHOOK_SECRET ? { webhookSecret: PLUV_WEBHOOK_SECRET } : {}),
}),
);

const ioServer = io.server({
getInitialStorage: async ({ room }) => {
// No persistence yet — rooms start with empty storage.
// To add persistence, load encodedState from a database here.
console.log(`[pluv] getInitialStorage for room: ${room}`);
return null;
},

onRoomDestroyed: async ({ room }) => {
console.log(`[pluv] Room destroyed: ${room}`);
},

onStorageDestroyed: async ({ room, encodedState }) => {
// To persist storage, save encodedState to a database here.
console.log(
`[pluv] Storage destroyed for room: ${room} (${encodedState ? encodedState.length + " bytes" : "empty"})`,
);
},
});

// ── Express app ───────────────────────────────────────────────────────────

const app = express();
app.use(cors());
app.use(express.json());

// Health check
app.get("/health", (_req, res) => {
res.json({
status: "healthy",
server: "pluv-chat",
timestamp: new Date().toISOString(),
});
});

// Webhook endpoint — pluv.io sends server events here
app.post("/api/pluv/webhook", async (req, res) => {
try {
// Convert express req/res to a standard Request for ioServer.fetch
const url = `${req.protocol}://${req.get("host")}${req.originalUrl}`;
const headers = new Headers();
for (const [key, value] of Object.entries(req.headers)) {
if (typeof value === "string") headers.set(key, value);
}

const fetchReq = new Request(url, {
method: req.method,
headers,
body: req.method !== "GET" ? JSON.stringify(req.body) : undefined,
});

const fetchRes = await ioServer.fetch(fetchReq);
const body = await fetchRes.text();
res.status(fetchRes.status).send(body);
} catch (err) {
console.error("[pluv] Webhook error:", err);
res.status(500).json({ error: "Webhook handling failed" });
}
});

// Auth endpoint — creates a JWT token for the requesting user (must be after webhook route)
app.get("/api/auth/pluv", async (req, res) => {
try {
const room = req.query.room;
const userId = req.query.userId;
const userName = req.query.userName;

if (!room || !userId) {
return res.status(400).json({ error: "Missing room or userId" });
}

const token = await ioServer.createToken({
room: String(room),
user: {
id: String(userId),
name: String(userName || userId),
},
});

// pluv expects the token as a plain text response
res.status(200).send(token);
} catch (err) {
console.error("[pluv] Auth error:", err);
res.status(500).json({ error: "Token creation failed" });
}
});

// ── Start server ──────────────────────────────────────────────────────────

app.listen(PORT, HOST, () => {
console.log(`\n Pluv Chat Server running on http://${HOST}:${PORT}`);
console.log(` Auth endpoint: GET /api/auth/pluv?room=...&userId=...`);
console.log(` Webhook endpoint: POST /api/pluv/webhook`);
console.log(` Health check: GET /health\n`);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { createContext, useContext } from "react";
import type { ChatRoom, OnlineUser, PendingRoomInvite } from "./store";

type ChatEventName =
| "messageSent"
| "startTyping"
| "stopTyping"
| "roomSwitch"
| "roomJoin"
| "roomLeave"
| "roomCreate"
| "inviteSend"
| "inviteAccept"
| "inviteDecline";

interface ExposedState {
value: string;
onChange: (v: string) => void;
}

export interface ChatBoxContextValue {
// Data
messages: any[];
rooms: ChatRoom[];
currentRoomId: string;
currentRoom: ChatRoom | null;
currentUserId: string;
currentUserName: string;
typingUsers: any[];
onlineUsers: OnlineUser[];
pendingInvites: PendingRoomInvite[];
isAiThinking: boolean;

// Exposed state
chatTitle: ExposedState;
messageText: ExposedState;
lastSentMessageText: ExposedState;

// UI config
showHeader: boolean;
showRoomsPanel: boolean;
roomsPanelWidth: string;
allowRoomCreation: boolean;
allowRoomSearch: boolean;
style: any;
animationStyle: any;

// Events
onEvent: (event: ChatEventName) => any;

// Room actions
onRoomSwitch: (roomId: string) => void;
onRoomJoin: (roomId: string) => void;
onRoomLeave: (roomId: string) => void;
onRoomCreate: (
name: string,
type: "public" | "private" | "llm",
description?: string,
llmQueryName?: string,
) => void;
onInviteSend: (toUserId: string) => void;
onInviteAccept: (inviteId: string) => void;
onInviteDecline: (inviteId: string) => void;
}

export const ChatBoxContext = createContext<ChatBoxContextValue | null>(null);

export function useChatBox(): ChatBoxContextValue {
const ctx = useContext(ChatBoxContext);
if (!ctx) {
throw new Error("useChatBox must be used within a ChatBoxProvider");
}
return ctx;
}
Loading
Loading