Skip to content

feat: chat room react example #936

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
35 changes: 35 additions & 0 deletions .cursor/rules/snippet-to-example-rules.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
description:
globs:
alwaysApply: false
---

# Converting a Snippet to Fullstack Example

Create a new folder for our snippet inside of example folder .
1. Copy `tsconfig.json`, `package.json`, `src/index.tsx`, `public/index.html`, `public/github.css`, and `.gitignore` from the `chat-room-react` example folder (1) into the new snippet example folder. (1: `examples/chat-room-react`)
- Update index.html's github anchor link to point to `https://github.com/rivet-gg/actor-core/tree/main/examples/{insert example name}`
2. Copy the `actor-json.ts` file from the snippet folder into the file `actors/app.ts` in the example folder. Make sure its being exported & setup correctly (see example from `chat-room-react/actors/app.ts`: )
```
// We get rid of the default export
// export default ...

// and instead...

// Create and export the app
export const app = setup({
actors: { actor },
});

// Export type for client type checking
export type App = typeof app;
```
3. Copy the `App.tsx` from the snippet folder into `src/App.tsx` file, and then make it export the main app class as `export default function ReactApp`
4. Ask for user review at this point and see if you should continue. If you continue:
5. Call `yarn install` and fix any type errors. Review to other `examples/*` folders for sample.

# NOTES
1. Everything should be installed wit yarn
2. Environment variables should be used for any LLM calls
- Make sure to use dotenv to put the env to use
3. Feel free to install any missing packages that were present in the original snippet.
4 changes: 4 additions & 0 deletions examples/chat-room-react/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.actorcore
node_modules
# React
build/
32 changes: 32 additions & 0 deletions examples/chat-room-react/actors/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { actor, setup } from "actor-core";

export type Message = { sender: string; text: string; timestamp: number; }

export const chatRoom = actor({
// State is automatically persisted
state: {
messages: [] as Message[]
},

actions: {
sendMessage: (c, sender: string, text: string) => {
const message = { sender, text, timestamp: Date.now() };

// Any changes to state are automatically saved
c.state.messages.push(message);

// Broadcast events trigger real-time updates in connected clients
c.broadcast("newMessage", message);
},

getHistory: (c) => c.state.messages
}
});

// Create and export the app
export const app = setup({
actors: { chatRoom },
});

// Export type for client type checking
export type App = typeof app;
50 changes: 50 additions & 0 deletions examples/chat-room-react/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"name": "chat-room-react",
"version": "0.8.0",
"private": true,
"type": "module",
"main": "src/index.ts",
"scripts": {
"dev:actors": "npx @actor-core/cli@latest dev actors/app.ts",
"dev:frontend": "react-scripts start",
"build": "react-scripts build",
"check-types": "tsc --noEmit",
"test": "vitest run"
},
"dependencies": {
"@actor-core/react": "workspace:*",
"@types/react": "^19",
"@types/react-dom": "^19",
"actor-core": "workspace:*",
"react": "^19",
"react-dom": "^19",
"react-scripts": "^5.0.1"
},
"devDependencies": {
"@actor-core/cli": "workspace:*",
"actor-core": "workspace:*",
"typescript": "^5.5.2"
},
"example": {
"platforms": [
"*"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"resolutions": {
"react@^19": "^19.0.0",
"react-dom@^19": "^19.0.0",
"react@^18": "^18.3"
}
}
37 changes: 37 additions & 0 deletions examples/chat-room-react/public/github.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
body, html {
margin: 0;
padding: 0;
padding-top: 24px;
width: 100%;
height: 100%;
}
#example--repo-ref {
position: fixed;
top: 0px;
left: 0px;
cursor: pointer;
background-color: rgb(243, 243, 243);
height: 24px;
width: 100%;
padding: 8px 8px;
}
#example--github-icon {
height: 24px;
float: left;
}
#example--repo-link {
height: 24px;
margin-left: 8px;
color: rgb(45, 50, 55);
font-weight: bold;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
font-size: 15px;
vertical-align: middle;
}

#example--repo-ref:hover #example--repo-link {
color: black;
}
#example--repo-ref:hover svg {
fill: black !important;
}
27 changes: 27 additions & 0 deletions examples/chat-room-react/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<title>Chat Room</title>
<link rel="stylesheet" href="./github.css">
</head>
<body>
<!-- Github Notch -->
<div id="example--repo-ref">
<a id="example--github-icon" href="https://github.com/rivet-gg/actor-core/tree/main/examples/ai-agent" target="_blank">
<svg height="24" width="24" viewBox="0 0 16 16" style="fill: #24292e;">
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path>
</svg>
</a>
<a id="example--repo-link" href="https://github.com/rivet-gg/actor-core/tree/main/examples/ai-agent">@rivet-gg/actor-core</a>
</div>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>

<link rel="stylesheet" href="./main.css">
</body>
</html>
100 changes: 100 additions & 0 deletions examples/chat-room-react/public/main.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background-color: rgb(180, 216, 255);
}

.chat-container {
max-width: 800px;
margin: 0 auto;
background: white;
border-radius: 12px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
height: 80vh;
}

.room-header {
padding: 20px;
border-bottom: 1px solid #eee;
}

.room-header h3 {
margin: 0;
color: #333;
}

.messages {
flex: 1;
overflow-y: auto;
padding: 20px;
}

.message {
margin-bottom: 15px;
padding: 10px;
background: #f8f9fa;
border-radius: 8px;
position: relative;
}

.message b {
color: #2c5282;
margin-right: 8px;
}

.timestamp {
font-size: 0.8em;
color: #666;
position: absolute;
right: 10px;
top: 10px;
}

.empty-message {
text-align: center;
color: #666;
padding: 20px;
font-style: italic;
}

.input-area {
padding: 20px;
border-top: 1px solid #eee;
display: flex;
gap: 10px;
}

.input-area input {
flex: 1;
padding: 12px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 16px;
outline: none;
transition: border-color 0.2s;
}

.input-area input:focus {
border-color: #4299e1;
}

.input-area button {
padding: 12px 24px;
background: #4299e1;
color: white;
border: none;
border-radius: 6px;
font-size: 16px;
cursor: pointer;
transition: background-color 0.2s;
}

.input-area button:hover {
background: #3182ce;
}

.input-area button:active {
background: #2b6cb0;
}
70 changes: 70 additions & 0 deletions examples/chat-room-react/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { createClient } from "actor-core/client";
import { createReactActorCore } from "@actor-core/react";
import { useState, useEffect } from "react";
import type { App, Message } from "../actors/app";

const client = createClient<App>("http://localhost:6420");
const { useActor, useActorEvent } = createReactActorCore(client);

export default function ReactApp({ roomId = "general" }) {
// Connect to specific chat room using tags
const [{ actor }] = useActor("chatRoom", {
tags: { roomId }
});

const [messages, setMessages] = useState<Message[]>([]);
const [input, setInput] = useState("");

// Load initial state
useEffect(() => {
if (actor) {
// Load chat history
actor.getHistory().then(setMessages);
}
}, [actor]);

// Listen for real-time updates from the server
useActorEvent({ actor, event: "newMessage" }, (message) => {
setMessages(prev => [...prev, message as Message]);
});

const sendMessage = () => {
if (actor && input.trim()) {
actor.sendMessage("User", input);
setInput("");
}
};

return (
<div className="chat-container">
<div className="room-header">
<h3>Chat Room: {roomId}</h3>
</div>

<div className="messages">
{messages.length === 0 ? (
<div className="empty-message">No messages yet. Start the conversation!</div>
) : (
messages.map((msg, i) => (
<div key={i} className="message">
<b>{msg.sender}:</b> {msg.text}
<span className="timestamp">
{new Date(msg.timestamp).toLocaleTimeString()}
</span>
</div>
))
)}
</div>

<div className="input-area">
<input
value={input}
onChange={e => setInput(e.target.value)}
onKeyDown={e => e.key === "Enter" && sendMessage()}
placeholder="Type a message..."
/>
<button onClick={sendMessage}>Send</button>
</div>
</div>
);
}
7 changes: 7 additions & 0 deletions examples/chat-room-react/src/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import ReactDOM from "react-dom/client";
import ReactApp from "./App";

const container = document.getElementById('root')!;
const root = ReactDOM.createRoot(container);

root.render(<ReactApp />);
Loading
Loading