@fluxy-chat/create-fluxy-chat 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -30,7 +30,7 @@ function isPackageManager(value) {
30
30
  return PACKAGE_MANAGERS.has(value);
31
31
  }
32
32
  function isAdapterType(value) {
33
- return ["basic", "slack", "telegram", "discord", "web", "react"].includes(value);
33
+ return ["basic", "slack", "telegram", "discord", "web", "react", "hr-feedback"].includes(value);
34
34
  }
35
35
  function detectPackageManagerFromLockfiles(cwd) {
36
36
  if (fs.existsSync(path.join(cwd, "pnpm-lock.yaml"))) {
@@ -131,6 +131,7 @@ async function runPrompts(inputs) {
131
131
  options: [
132
132
  { label: "Minimal chat widget (ui-kit, recommended)", value: "minimal" },
133
133
  { label: "React chat app (Vite + useChat)", value: "react" },
134
+ { label: "HR anonymous feedback (compliance starter)", value: "hr-feedback" },
134
135
  { label: "Basic (Cloudflare Workers bot)", value: "basic" },
135
136
  { label: "Slack", value: "slack" },
136
137
  { label: "Telegram", value: "telegram" },
@@ -825,8 +826,10 @@ function parseArgs(argv) {
825
826
  args.adapter = value;
826
827
  } else if (value === "react") {
827
828
  args.adapter = "react";
829
+ } else if (value === "hr-feedback") {
830
+ args.adapter = "hr-feedback";
828
831
  } else {
829
- console.error(`Invalid template: ${value}. Choose: react, basic, slack, telegram, discord, web`);
832
+ console.error(`Invalid template: ${value}. Choose: react, basic, slack, telegram, discord, web, hr-feedback`);
830
833
  process.exit(1);
831
834
  }
832
835
  } else if (arg === "--adapter" || arg === "-a") {
@@ -834,7 +837,7 @@ function parseArgs(argv) {
834
837
  if (value && isAdapterType(value)) {
835
838
  args.adapter = value;
836
839
  } else {
837
- console.error(`Invalid adapter: ${value}. Choose: react, basic, slack, telegram, discord, web`);
840
+ console.error(`Invalid adapter: ${value}. Choose: react, basic, slack, telegram, discord, web, hr-feedback`);
838
841
  process.exit(1);
839
842
  }
840
843
  } else if (arg === "--pm" || arg === "--package-manager") {
@@ -872,8 +875,8 @@ ${pc.bold("Usage:")}
872
875
  npx create-fluxy-chat [project-name] [options]
873
876
 
874
877
  ${pc.bold("Options:")}
875
- -a, --adapter <type> Adapter: react, basic, slack, telegram, discord, web
876
- -t, --template <type> Alias for --adapter (e.g. react)
878
+ -a, --adapter <type> Adapter: react, basic, slack, telegram, discord, web, hr-feedback
879
+ -t, --template <type> Alias for --adapter (e.g. react, hr-feedback)
877
880
  --pm <manager> Package manager: npm, pnpm, yarn
878
881
  -l, --language <lang> Language: typescript (default) or javascript
879
882
  -y, --yes Skip prompts and accept defaults
@@ -884,6 +887,7 @@ ${pc.bold("Options:")}
884
887
 
885
888
  ${pc.bold("Examples:")}
886
889
  ${pc.cyan("npx create-fluxy-chat my-chat --minimal")}
890
+ ${pc.cyan("npx create-fluxy-chat my-hr-bot --template hr-feedback")}
887
891
  ${pc.cyan("npx create-fluxy-chat my-chat --template react")}
888
892
  ${pc.cyan("npx create-fluxy-chat my-bot --adapter basic")}
889
893
  ${pc.cyan("npx create-fluxy-chat my-bot --adapter slack")}
@@ -942,6 +946,14 @@ async function main() {
942
946
  pkg.name = config.name;
943
947
  writeJson(projectDir, "package.json", pkg);
944
948
  s.stop("React chat app created.");
949
+ } else if (config.adapter === "hr-feedback") {
950
+ const templateRoot = path2.join(templatesDir(), "hr-feedback");
951
+ copyDir(templateRoot, projectDir);
952
+ const pkgPath = path2.join(projectDir, "package.json");
953
+ const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf8"));
954
+ pkg.name = config.name;
955
+ writeJson(projectDir, "package.json", pkg);
956
+ s.stop("HR feedback starter created.");
945
957
  } else {
946
958
  writeJson(projectDir, "package.json", generatePackageJson(config));
947
959
  if (config.language === "typescript") {
@@ -993,6 +1005,7 @@ async function main() {
993
1005
  config.adapter === "react" ? [
994
1006
  `cd ${config.name}`,
995
1007
  "cp .env.example .env",
1008
+ "# Set VITE_FLUXYCHAT_WORKER_URL + VITE_FLUXYCHAT_PUBLIC_ROOM_ID (guest) or MEMBER_JWT",
996
1009
  `${config.packageManager === "npm" ? "npm run" : config.packageManager} dev`
997
1010
  ].join("\n") : [
998
1011
  `cd ${config.name}`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluxy-chat/create-fluxy-chat",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Scaffold a new FluxyChat bot project with a single command",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -38,7 +38,7 @@
38
38
  },
39
39
  "license": "MIT",
40
40
  "scripts": {
41
- "build": "tsup src/index.ts --format esm --dts",
41
+ "build": "tsup src/index.ts --format esm",
42
42
  "dev": "tsup src/index.ts --format esm --watch",
43
43
  "typecheck": "tsc --noEmit"
44
44
  }
package/readme.md CHANGED
@@ -5,10 +5,10 @@ Scaffold a new [FluxyChat](https://github.com/AlessandroFare/fluxychat) bot proj
5
5
  ## Quick start
6
6
 
7
7
  ```bash
8
- # Minimal chat widget (recommended 3 lines in App.tsx)
8
+ # Minimal chat widget (recommended: 3 lines in App.tsx)
9
9
  npx create-fluxy-chat my-chat --minimal
10
10
 
11
- # React + useChat (more control)
11
+ # React + useChat (guest room ~60s or member JWT)
12
12
  npx create-fluxy-chat my-chat --template react
13
13
  ```
14
14
 
@@ -42,7 +42,7 @@ npx create-fluxy-chat my-bot --adapter basic
42
42
  | `--pm <manager>` | | Package manager: `npm`, `pnpm`, `yarn` |
43
43
  | `--language <lang>` | `-l` | Language: `typescript` (default) or `javascript` |
44
44
  | `--yes` | `-y` | Skip prompts and accept defaults |
45
- | `--minimal` | | Chat-only widget (`@fluxy-chat/ui-kit`) no platform modules |
45
+ | `--minimal` | | Chat-only widget (`@fluxy-chat/ui-kit`), no platform modules |
46
46
  | `--skip-install` | | Skip dependency installation |
47
47
  | `--no-git` | | Skip git repository initialization |
48
48
  | `--help` | `-h` | Show help |
@@ -62,14 +62,14 @@ npx create-fluxy-chat my-bot --adapter basic
62
62
 
63
63
  Each generated project includes:
64
64
 
65
- - **`src/index.ts`** Cloudflare Workers entry point with route handling
66
- - **`src/bot.ts`** Bot handler using `@fluxy-chat/sdk`
67
- - **`fluxy.config.ts`** Room authz and publish middleware (basic template)
68
- - **`wrangler.toml`** Cloudflare Workers deployment config
69
- - **`.dev.vars`** Local development environment variables
70
- - **`.env.example`** Example environment variables for your adapter
71
- - **`tsconfig.json`** TypeScript configuration (for TS projects)
72
- - **`README.md`** Project-specific setup instructions
65
+ - **`src/index.ts`**: Cloudflare Workers entry point with route handling
66
+ - **`src/bot.ts`**: Bot handler using `@fluxy-chat/sdk`
67
+ - **`fluxy.config.ts`**: Room authz and publish middleware (basic template)
68
+ - **`wrangler.toml`**: Cloudflare Workers deployment config
69
+ - **`.dev.vars`**: Local development environment variables
70
+ - **`.env.example`**: Example environment variables for your adapter
71
+ - **`tsconfig.json`**: TypeScript configuration (for TS projects)
72
+ - **`README.md`**: Project-specific setup instructions
73
73
 
74
74
  ## Package manager detection
75
75
 
@@ -0,0 +1,9 @@
1
+ # FluxyChat worker URL + service API key (server-side only)
2
+ FLUXY_BASE_URL=https://your-fluxychat-worker.example.com
3
+ FLUXY_API_KEY=your-api-key-here
4
+
5
+ # Optional dedicated room for aggregated summaries
6
+ HR_FEEDBACK_ROOM_ID=hr-anonymous-feedback
7
+
8
+ # Optional webhook for Path B HR escalation (de-identified payload only)
9
+ HR_ESCALATION_WEBHOOK_URL=
@@ -0,0 +1,45 @@
1
+ # HR Anonymous Feedback Starter
2
+
3
+ Production starter for anonymous employee feedback with sensitive classification and privacy-safe audit.
4
+
5
+ ## Features
6
+
7
+ - **Path A** — routine feedback → aggregated anonymous summary room
8
+ - **Path B** — sensitive categories → HR escalation hook (category + confidence only, no identity)
9
+ - Reuses FluxyChat `approvalChain` + room timeline audit when wired to agent tools
10
+ - Classification runs on the worker via `POST /anonymous-feedback`
11
+
12
+ ## Quick start
13
+
14
+ ```bash
15
+ cp .env.example .dev.vars
16
+ # fill FLUXY_BASE_URL + FLUXY_API_KEY
17
+ npm install
18
+ npm run dev
19
+ ```
20
+
21
+ Submit feedback:
22
+
23
+ ```bash
24
+ curl -X POST http://localhost:8787/feedback \
25
+ -H "Content-Type: application/json" \
26
+ -d '{"content":"My manager makes hostile comments in meetings"}'
27
+ ```
28
+
29
+ ## Endpoints
30
+
31
+ | Route | Description |
32
+ | --- | --- |
33
+ | `GET /` | Health check |
34
+ | `POST /feedback` | Anonymous submission (no user id stored) |
35
+
36
+ ## Privacy
37
+
38
+ - Raw message content is **not** persisted by this template
39
+ - Worker audit stores **category + timestamp + path** only
40
+ - Configure `HR_ESCALATION_WEBHOOK_URL` for your HRIS / ticketing integration
41
+
42
+ ## Learn more
43
+
44
+ - [FluxyChat docs](https://github.com/AlessandroFare/fluxychat)
45
+ - Worker route: `POST /anonymous-feedback` (JWT) for in-app widgets
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "hr-feedback-bot",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "private": true,
6
+ "scripts": {
7
+ "dev": "wrangler dev",
8
+ "deploy": "wrangler deploy",
9
+ "type-check": "tsc --noEmit"
10
+ },
11
+ "dependencies": {
12
+ "@fluxy-chat/sdk": "latest"
13
+ },
14
+ "devDependencies": {
15
+ "@cloudflare/workers-types": "^4.0.0",
16
+ "typescript": "^5.6.0",
17
+ "wrangler": "^3.0.0"
18
+ }
19
+ }
@@ -0,0 +1,71 @@
1
+ interface FeedbackEnv {
2
+ FLUXY_BASE_URL: string;
3
+ FLUXY_API_KEY: string;
4
+ HR_FEEDBACK_ROOM_ID?: string;
5
+ HR_ESCALATION_WEBHOOK_URL?: string;
6
+ }
7
+
8
+ interface FeedbackBody {
9
+ content?: string;
10
+ roomId?: string;
11
+ }
12
+
13
+ export interface FeedbackResult {
14
+ ok: boolean;
15
+ path?: string;
16
+ category?: string;
17
+ confidence?: number;
18
+ message?: string;
19
+ error?: string;
20
+ }
21
+
22
+ /**
23
+ * Submit anonymous feedback through the FluxyChat worker classifier.
24
+ * Content is classified server-side; only metadata is returned.
25
+ */
26
+ export async function submitHrFeedback(
27
+ env: FeedbackEnv,
28
+ body: FeedbackBody,
29
+ ): Promise<FeedbackResult> {
30
+ const content = String(body.content ?? "").trim();
31
+ if (!content) return { ok: false, error: "content required" };
32
+ if (content.length > 8000) return { ok: false, error: "content_too_long" };
33
+
34
+ const baseUrl = env.FLUXY_BASE_URL.replace(/\/$/, "");
35
+ const res = await fetch(`${baseUrl}/anonymous-feedback`, {
36
+ method: "POST",
37
+ headers: {
38
+ "Content-Type": "application/json",
39
+ Authorization: `Bearer ${env.FLUXY_API_KEY}`,
40
+ },
41
+ body: JSON.stringify({
42
+ content,
43
+ roomId: body.roomId ?? env.HR_FEEDBACK_ROOM_ID ?? null,
44
+ }),
45
+ });
46
+
47
+ const data = (await res.json().catch(() => ({}))) as FeedbackResult;
48
+ if (!res.ok) {
49
+ return { ok: false, error: data.error ?? `worker_${res.status}` };
50
+ }
51
+
52
+ if (data.path === "hr_escalation" && env.HR_ESCALATION_WEBHOOK_URL) {
53
+ await fetch(env.HR_ESCALATION_WEBHOOK_URL, {
54
+ method: "POST",
55
+ headers: { "Content-Type": "application/json" },
56
+ body: JSON.stringify({
57
+ category: data.category,
58
+ confidence: data.confidence,
59
+ at: new Date().toISOString(),
60
+ }),
61
+ }).catch(() => undefined);
62
+ }
63
+
64
+ return {
65
+ ok: true,
66
+ path: data.path,
67
+ category: data.category,
68
+ confidence: data.confidence,
69
+ message: data.message,
70
+ };
71
+ }
@@ -0,0 +1,22 @@
1
+ import { submitHrFeedback } from "./feedback.js";
2
+
3
+ export default {
4
+ async fetch(request: Request, env: Record<string, string>): Promise<Response> {
5
+ const url = new URL(request.url);
6
+
7
+ if (url.pathname === "/" && request.method === "GET") {
8
+ return new Response("HR anonymous feedback bot is running.", { status: 200 });
9
+ }
10
+
11
+ if (url.pathname === "/feedback" && request.method === "POST") {
12
+ const body = (await request.json().catch(() => ({}))) as {
13
+ content?: string;
14
+ roomId?: string;
15
+ };
16
+ const result = await submitHrFeedback(env, body);
17
+ return Response.json(result, { status: result.ok ? 200 : 400 });
18
+ }
19
+
20
+ return new Response("Not Found", { status: 404 });
21
+ },
22
+ };
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022"],
7
+ "types": ["@cloudflare/workers-types"],
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "resolveJsonModule": true,
13
+ "allowSyntheticDefaultImports": true,
14
+ "noEmit": true
15
+ },
16
+ "include": ["src/**/*"],
17
+ "exclude": ["node_modules", "dist"]
18
+ }
@@ -0,0 +1,7 @@
1
+ name = "hr-feedback-bot"
2
+ main = "src/index.ts"
3
+ compatibility_date = "2024-12-01"
4
+
5
+ [vars]
6
+ FLUXY_BASE_URL = "https://your-fluxychat-worker.example.com"
7
+ HR_FEEDBACK_ROOM_ID = "hr-anonymous-feedback"
@@ -1,6 +1,6 @@
1
1
  # FluxyChat minimal starter
2
2
 
3
- Three-line chat widget no platform modules in the generated project.
3
+ Three-line chat widget with no platform modules in the generated project.
4
4
 
5
5
  ```bash
6
6
  cp .env.example .env
@@ -1,8 +1,9 @@
1
1
  # FluxyChat worker URL (hosted or self-hosted)
2
2
  VITE_FLUXYCHAT_WORKER_URL=https://your-worker.example.workers.dev
3
3
 
4
- # Member JWT from POST /auth/token or onboarding wizard
4
+ # Option A — member JWT (~2 min via onboarding)
5
5
  VITE_FLUXYCHAT_MEMBER_JWT=eyJ...
6
-
7
- # Demo room — create in console or onboarding
8
6
  VITE_FLUXYCHAT_ROOM_ID=demo
7
+
8
+ # Option B — public guest room (~60s, no signup)
9
+ # VITE_FLUXYCHAT_PUBLIC_ROOM_ID=your-public-room-id
@@ -1,22 +1,32 @@
1
1
  # FluxyChat React starter
2
2
 
3
- Minimal Vite + React app with `useChat` first message in minutes.
3
+ Minimal Vite + React app with `useChat`. First message in about 60 seconds via the public guest room.
4
4
 
5
- ## Quick start
5
+ ## Quick start (guest, fastest)
6
6
 
7
7
  ```bash
8
8
  cp .env.example .env
9
- # Edit .env with your Worker URL + member JWT
9
+ # Set VITE_FLUXYCHAT_WORKER_URL + VITE_FLUXYCHAT_PUBLIC_ROOM_ID (public room from console)
10
10
  npm install
11
11
  npm run dev
12
12
  ```
13
13
 
14
- Open http://localhost:5173 and send a message.
14
+ Open http://localhost:5173. A guest JWT is minted automatically via `joinPublicRoomAsGuest`.
15
+
16
+ ## Quick start (member JWT)
17
+
18
+ ```bash
19
+ cp .env.example .env
20
+ # Set VITE_FLUXYCHAT_WORKER_URL + VITE_FLUXYCHAT_MEMBER_JWT + VITE_FLUXYCHAT_ROOM_ID
21
+ npm install
22
+ npm run dev
23
+ ```
15
24
 
16
25
  ## Get credentials
17
26
 
18
- 1. **Hosted:** [fluxychat.com/onboarding](https://fluxychat.com/onboarding) copy Worker URL + JWT
19
- 2. **Local monorepo:** `pnpm run first-message` from the FluxyChat repo
27
+ 1. **Guest:** create a **public** room in console, copy room ID, set `VITE_FLUXYCHAT_PUBLIC_ROOM_ID`
28
+ 2. **Member:** [fluxychat.com/onboarding](https://fluxychat.com/onboarding) for Worker URL and JWT
29
+ 3. **Local monorepo:** `pnpm run first-message` from the FluxyChat repo
20
30
 
21
31
  ## Scripts
22
32
 
@@ -28,6 +38,6 @@ Open http://localhost:5173 and send a message.
28
38
 
29
39
  ## Next steps
30
40
 
31
- - Add `@fluxy-chat/ui` for polished message bubbles
41
+ - Add `@fluxy-chat/ui` themes (`default`, `dark`, `minimal`, `brand`)
32
42
  - Use `useInbox` for unified feed
33
43
  - See [chat-only quickstart](https://docs.fluxychat.com/docs/getting-started/chat-only-quickstart)
@@ -1,10 +1,82 @@
1
- import { useMemo, useState } from "react";
1
+ import { useEffect, useMemo, useState } from "react";
2
2
  import { FluxyChatClient } from "@fluxy-chat/sdk";
3
3
  import { FluxyRealtimeProvider, useChat } from "@fluxy-chat/react";
4
4
 
5
- const workerUrl = import.meta.env.VITE_FLUXYCHAT_WORKER_URL;
6
- const memberJwt = import.meta.env.VITE_FLUXYCHAT_MEMBER_JWT;
7
- const defaultRoomId = import.meta.env.VITE_FLUXYCHAT_ROOM_ID || "demo";
5
+ const workerUrl = import.meta.env.VITE_FLUXYCHAT_WORKER_URL?.trim();
6
+ const memberJwt = import.meta.env.VITE_FLUXYCHAT_MEMBER_JWT?.trim();
7
+ const publicRoomId = import.meta.env.VITE_FLUXYCHAT_PUBLIC_ROOM_ID?.trim();
8
+ const configuredRoomId = import.meta.env.VITE_FLUXYCHAT_ROOM_ID?.trim() || "demo";
9
+
10
+ interface FluxySession {
11
+ client: FluxyChatClient;
12
+ roomId: string;
13
+ mode: "member" | "guest";
14
+ }
15
+
16
+ function useFluxySession(): {
17
+ session: FluxySession | null;
18
+ loading: boolean;
19
+ error: string | null;
20
+ } {
21
+ const [session, setSession] = useState<FluxySession | null>(null);
22
+ const [loading, setLoading] = useState(Boolean(workerUrl));
23
+ const [error, setError] = useState<string | null>(null);
24
+
25
+ useEffect(() => {
26
+ if (!workerUrl) {
27
+ setLoading(false);
28
+ return;
29
+ }
30
+
31
+ if (memberJwt) {
32
+ setSession({
33
+ client: new FluxyChatClient({
34
+ baseUrl: workerUrl,
35
+ userId: "demo-user",
36
+ token: memberJwt,
37
+ }),
38
+ roomId: configuredRoomId,
39
+ mode: "member",
40
+ });
41
+ setLoading(false);
42
+ return;
43
+ }
44
+
45
+ if (publicRoomId) {
46
+ let cancelled = false;
47
+ void FluxyChatClient.joinPublicRoomAsGuest(workerUrl, publicRoomId, {
48
+ displayName: "Guest",
49
+ })
50
+ .then((guest) => {
51
+ if (cancelled) return;
52
+ setSession({
53
+ client: new FluxyChatClient({
54
+ baseUrl: workerUrl,
55
+ userId: guest.userId,
56
+ token: guest.token,
57
+ }),
58
+ roomId: guest.roomId,
59
+ mode: "guest",
60
+ });
61
+ })
62
+ .catch((err) => {
63
+ if (!cancelled) {
64
+ setError(err instanceof Error ? err.message : "Guest session failed");
65
+ }
66
+ })
67
+ .finally(() => {
68
+ if (!cancelled) setLoading(false);
69
+ });
70
+ return () => {
71
+ cancelled = true;
72
+ };
73
+ }
74
+
75
+ setLoading(false);
76
+ }, []);
77
+
78
+ return { session, loading, error };
79
+ }
8
80
 
9
81
  function ChatPanel({ roomId }: { roomId: string }) {
10
82
  const { messages, sendMessage, connectionState } = useChat({
@@ -50,31 +122,49 @@ function ChatPanel({ roomId }: { roomId: string }) {
50
122
  }
51
123
 
52
124
  export function App() {
53
- const [roomId, setRoomId] = useState(defaultRoomId);
54
-
55
- const client = useMemo(() => {
56
- if (!workerUrl?.trim() || !memberJwt?.trim()) return null;
57
- return new FluxyChatClient({
58
- baseUrl: workerUrl.trim(),
59
- userId: "demo-user",
60
- token: memberJwt.trim(),
61
- });
62
- }, []);
125
+ const { session, loading, error } = useFluxySession();
126
+ const [roomId, setRoomId] = useState(configuredRoomId);
127
+
128
+ const activeRoomId = useMemo(() => {
129
+ if (session?.mode === "guest") return session.roomId;
130
+ return roomId.trim() || configuredRoomId;
131
+ }, [session, roomId]);
63
132
 
64
- if (!client) {
133
+ if (!workerUrl) {
65
134
  return (
66
135
  <main className="shell">
67
136
  <h1>FluxyChat — chat-only starter</h1>
68
137
  <p>
69
138
  Copy <code>.env.example</code> to <code>.env</code> and set{" "}
70
- <code>VITE_FLUXYCHAT_WORKER_URL</code> and <code>VITE_FLUXYCHAT_MEMBER_JWT</code>.
139
+ <code>VITE_FLUXYCHAT_WORKER_URL</code>.
140
+ </p>
141
+ <p>
142
+ Then either <code>VITE_FLUXYCHAT_MEMBER_JWT</code> (member) or{" "}
143
+ <code>VITE_FLUXYCHAT_PUBLIC_ROOM_ID</code> (guest, ~60s to first message).
71
144
  </p>
145
+ </main>
146
+ );
147
+ }
148
+
149
+ if (loading) {
150
+ return (
151
+ <main className="shell">
152
+ <p>Connecting…</p>
153
+ </main>
154
+ );
155
+ }
156
+
157
+ if (error || !session) {
158
+ return (
159
+ <main className="shell">
160
+ <h1>FluxyChat</h1>
161
+ <p className="error">{error ?? "Set JWT or public room ID in .env"}</p>
72
162
  <p>
73
163
  Get credentials from{" "}
74
164
  <a href="https://fluxychat.com/onboarding" target="_blank" rel="noreferrer">
75
165
  onboarding
76
166
  </a>{" "}
77
- or <code>pnpm run first-message</code> in the monorepo.
167
+ or use a public room ID for guest mode.
78
168
  </p>
79
169
  </main>
80
170
  );
@@ -83,12 +173,15 @@ export function App() {
83
173
  return (
84
174
  <main className="shell">
85
175
  <h1>FluxyChat</h1>
86
- <label className="room-picker">
87
- Room
88
- <input value={roomId} onChange={(e) => setRoomId(e.target.value)} />
89
- </label>
90
- <FluxyRealtimeProvider client={client}>
91
- <ChatPanel roomId={roomId.trim() || defaultRoomId} />
176
+ <p className="mode-badge">{session.mode === "guest" ? "Guest session" : "Member JWT"}</p>
177
+ {session.mode === "member" ? (
178
+ <label className="room-picker">
179
+ Room
180
+ <input value={roomId} onChange={(e) => setRoomId(e.target.value)} />
181
+ </label>
182
+ ) : null}
183
+ <FluxyRealtimeProvider client={session.client}>
184
+ <ChatPanel roomId={activeRoomId} />
92
185
  </FluxyRealtimeProvider>
93
186
  </main>
94
187
  );
@@ -91,6 +91,16 @@ body {
91
91
  cursor: pointer;
92
92
  }
93
93
 
94
+ .mode-badge {
95
+ font-size: 0.75rem;
96
+ color: #64748b;
97
+ margin: 0 0 0.75rem;
98
+ }
99
+
100
+ .error {
101
+ color: #b91c1c;
102
+ }
103
+
94
104
  .room-picker {
95
105
  display: flex;
96
106
  flex-direction: column;
@@ -2,8 +2,9 @@
2
2
 
3
3
  interface ImportMetaEnv {
4
4
  readonly VITE_FLUXYCHAT_WORKER_URL: string;
5
- readonly VITE_FLUXYCHAT_MEMBER_JWT: string;
6
- readonly VITE_FLUXYCHAT_ROOM_ID: string;
5
+ readonly VITE_FLUXYCHAT_MEMBER_JWT?: string;
6
+ readonly VITE_FLUXYCHAT_ROOM_ID?: string;
7
+ readonly VITE_FLUXYCHAT_PUBLIC_ROOM_ID?: string;
7
8
  }
8
9
 
9
10
  interface ImportMeta {