Skip to content

Commit b0887f7

Browse files
committed
feat: chat room react example
1 parent a368c8d commit b0887f7

File tree

12 files changed

+15812
-4689
lines changed

12 files changed

+15812
-4689
lines changed
+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
description:
3+
globs:
4+
alwaysApply: false
5+
---
6+
7+
# Converting a Snippet to Fullstack Example
8+
9+
Create a new folder for our snippet inside of example folder .
10+
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`)
11+
- Update index.html's github anchor link to point to `https://github.com/rivet-gg/actor-core/tree/main/examples/{insert example name}`
12+
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`: )
13+
```
14+
// We get rid of the default export
15+
// export default ...
16+
17+
// and instead...
18+
19+
// Create and export the app
20+
export const app = setup({
21+
actors: { actor },
22+
});
23+
24+
// Export type for client type checking
25+
export type App = typeof app;
26+
```
27+
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`
28+
4. Ask for user review at this point and see if you should continue. If you continue:
29+
5. Call `yarn install` and fix any type errors. Review to other `examples/*` folders for sample.
30+
31+
# NOTES
32+
1. Everything should be installed wit yarn
33+
2. Environment variables should be used for any LLM calls
34+
- Make sure to use dotenv to put the env to use
35+
3. Feel free to install any missing packages that were present in the original snippet.

examples/chat-room-react/.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.actorcore
2+
node_modules
3+
# React
4+
build/
+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { actor, setup } from "actor-core";
2+
3+
export type Message = { sender: string; text: string; timestamp: number; }
4+
5+
export const chatRoom = actor({
6+
// State is automatically persisted
7+
state: {
8+
messages: [] as Message[]
9+
},
10+
11+
actions: {
12+
sendMessage: (c, sender: string, text: string) => {
13+
const message = { sender, text, timestamp: Date.now() };
14+
15+
// Any changes to state are automatically saved
16+
c.state.messages.push(message);
17+
18+
// Broadcast events trigger real-time updates in connected clients
19+
c.broadcast("newMessage", message);
20+
},
21+
22+
getHistory: (c) => c.state.messages
23+
}
24+
});
25+
26+
// Create and export the app
27+
export const app = setup({
28+
actors: { chatRoom },
29+
});
30+
31+
// Export type for client type checking
32+
export type App = typeof app;

examples/chat-room-react/package.json

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{
2+
"name": "chat-room-react",
3+
"version": "0.8.0",
4+
"private": true,
5+
"type": "module",
6+
"main": "src/index.ts",
7+
"scripts": {
8+
"dev:actors": "npx @actor-core/cli@latest dev actors/app.ts",
9+
"dev:frontend": "react-scripts start",
10+
"build": "react-scripts build",
11+
"check-types": "tsc --noEmit",
12+
"test": "vitest run"
13+
},
14+
"dependencies": {
15+
"@actor-core/react": "workspace:*",
16+
"@types/react": "^19",
17+
"@types/react-dom": "^19",
18+
"actor-core": "workspace:*",
19+
"react": "^19",
20+
"react-dom": "^19",
21+
"react-scripts": "^5.0.1"
22+
},
23+
"devDependencies": {
24+
"@actor-core/cli": "workspace:*",
25+
"actor-core": "workspace:*",
26+
"typescript": "^5.5.2"
27+
},
28+
"example": {
29+
"platforms": [
30+
"*"
31+
]
32+
},
33+
"browserslist": {
34+
"production": [
35+
">0.2%",
36+
"not dead",
37+
"not op_mini all"
38+
],
39+
"development": [
40+
"last 1 chrome version",
41+
"last 1 firefox version",
42+
"last 1 safari version"
43+
]
44+
},
45+
"resolutions": {
46+
"react@^19": "^19.0.0",
47+
"react-dom@^19": "^19.0.0",
48+
"react@^18": "^18.3"
49+
}
50+
}
+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
body, html {
2+
margin: 0;
3+
padding: 0;
4+
padding-top: 24px;
5+
width: 100%;
6+
height: 100%;
7+
}
8+
#example--repo-ref {
9+
position: fixed;
10+
top: 0px;
11+
left: 0px;
12+
cursor: pointer;
13+
background-color: rgb(243, 243, 243);
14+
height: 24px;
15+
width: 100%;
16+
padding: 8px 8px;
17+
}
18+
#example--github-icon {
19+
height: 24px;
20+
float: left;
21+
}
22+
#example--repo-link {
23+
height: 24px;
24+
margin-left: 8px;
25+
color: rgb(45, 50, 55);
26+
font-weight: bold;
27+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
28+
font-size: 15px;
29+
vertical-align: middle;
30+
}
31+
32+
#example--repo-ref:hover #example--repo-link {
33+
color: black;
34+
}
35+
#example--repo-ref:hover svg {
36+
fill: black !important;
37+
}
+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
6+
<meta name="theme-color" content="#000000">
7+
<title>Chat Room</title>
8+
<link rel="stylesheet" href="./github.css">
9+
</head>
10+
<body>
11+
<!-- Github Notch -->
12+
<div id="example--repo-ref">
13+
<a id="example--github-icon" href="https://github.com/rivet-gg/actor-core/tree/main/examples/ai-agent" target="_blank">
14+
<svg height="24" width="24" viewBox="0 0 16 16" style="fill: #24292e;">
15+
<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>
16+
</svg>
17+
</a>
18+
<a id="example--repo-link" href="https://github.com/rivet-gg/actor-core/tree/main/examples/ai-agent">@rivet-gg/actor-core</a>
19+
</div>
20+
<noscript>
21+
You need to enable JavaScript to run this app.
22+
</noscript>
23+
<div id="root"></div>
24+
25+
<link rel="stylesheet" href="./main.css">
26+
</body>
27+
</html>
+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
body {
2+
margin: 0;
3+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
4+
background-color: rgb(180, 216, 255);
5+
}
6+
7+
.chat-container {
8+
max-width: 800px;
9+
margin: 0 auto;
10+
background: white;
11+
border-radius: 12px;
12+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
13+
display: flex;
14+
flex-direction: column;
15+
height: 80vh;
16+
}
17+
18+
.room-header {
19+
padding: 20px;
20+
border-bottom: 1px solid #eee;
21+
}
22+
23+
.room-header h3 {
24+
margin: 0;
25+
color: #333;
26+
}
27+
28+
.messages {
29+
flex: 1;
30+
overflow-y: auto;
31+
padding: 20px;
32+
}
33+
34+
.message {
35+
margin-bottom: 15px;
36+
padding: 10px;
37+
background: #f8f9fa;
38+
border-radius: 8px;
39+
position: relative;
40+
}
41+
42+
.message b {
43+
color: #2c5282;
44+
margin-right: 8px;
45+
}
46+
47+
.timestamp {
48+
font-size: 0.8em;
49+
color: #666;
50+
position: absolute;
51+
right: 10px;
52+
top: 10px;
53+
}
54+
55+
.empty-message {
56+
text-align: center;
57+
color: #666;
58+
padding: 20px;
59+
font-style: italic;
60+
}
61+
62+
.input-area {
63+
padding: 20px;
64+
border-top: 1px solid #eee;
65+
display: flex;
66+
gap: 10px;
67+
}
68+
69+
.input-area input {
70+
flex: 1;
71+
padding: 12px;
72+
border: 1px solid #ddd;
73+
border-radius: 6px;
74+
font-size: 16px;
75+
outline: none;
76+
transition: border-color 0.2s;
77+
}
78+
79+
.input-area input:focus {
80+
border-color: #4299e1;
81+
}
82+
83+
.input-area button {
84+
padding: 12px 24px;
85+
background: #4299e1;
86+
color: white;
87+
border: none;
88+
border-radius: 6px;
89+
font-size: 16px;
90+
cursor: pointer;
91+
transition: background-color 0.2s;
92+
}
93+
94+
.input-area button:hover {
95+
background: #3182ce;
96+
}
97+
98+
.input-area button:active {
99+
background: #2b6cb0;
100+
}

examples/chat-room-react/src/App.tsx

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { createClient } from "actor-core/client";
2+
import { createReactActorCore } from "@actor-core/react";
3+
import { useState, useEffect } from "react";
4+
import type { App, Message } from "../actors/app";
5+
6+
const client = createClient<App>("http://localhost:6420");
7+
const { useActor, useActorEvent } = createReactActorCore(client);
8+
9+
export default function ReactApp({ roomId = "general" }) {
10+
// Connect to specific chat room using tags
11+
const [{ actor }] = useActor("chatRoom", {
12+
tags: { roomId }
13+
});
14+
15+
const [messages, setMessages] = useState<Message[]>([]);
16+
const [input, setInput] = useState("");
17+
18+
// Load initial state
19+
useEffect(() => {
20+
if (actor) {
21+
// Load chat history
22+
actor.getHistory().then(setMessages);
23+
}
24+
}, [actor]);
25+
26+
// Listen for real-time updates from the server
27+
useActorEvent({ actor, event: "newMessage" }, (message) => {
28+
setMessages(prev => [...prev, message as Message]);
29+
});
30+
31+
const sendMessage = () => {
32+
if (actor && input.trim()) {
33+
actor.sendMessage("User", input);
34+
setInput("");
35+
}
36+
};
37+
38+
return (
39+
<div className="chat-container">
40+
<div className="room-header">
41+
<h3>Chat Room: {roomId}</h3>
42+
</div>
43+
44+
<div className="messages">
45+
{messages.length === 0 ? (
46+
<div className="empty-message">No messages yet. Start the conversation!</div>
47+
) : (
48+
messages.map((msg, i) => (
49+
<div key={i} className="message">
50+
<b>{msg.sender}:</b> {msg.text}
51+
<span className="timestamp">
52+
{new Date(msg.timestamp).toLocaleTimeString()}
53+
</span>
54+
</div>
55+
))
56+
)}
57+
</div>
58+
59+
<div className="input-area">
60+
<input
61+
value={input}
62+
onChange={e => setInput(e.target.value)}
63+
onKeyDown={e => e.key === "Enter" && sendMessage()}
64+
placeholder="Type a message..."
65+
/>
66+
<button onClick={sendMessage}>Send</button>
67+
</div>
68+
</div>
69+
);
70+
}
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import ReactDOM from "react-dom/client";
2+
import ReactApp from "./App";
3+
4+
const container = document.getElementById('root')!;
5+
const root = ReactDOM.createRoot(container);
6+
7+
root.render(<ReactApp />);

0 commit comments

Comments
 (0)