@fluxy-chat/create-fluxy-chat 0.3.0 → 0.5.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.
@@ -0,0 +1,242 @@
1
+ :root {
2
+ font-family: system-ui, -apple-system, sans-serif;
3
+ line-height: 1.5;
4
+ color: #0f172a;
5
+ background: #fdfbf9;
6
+ }
7
+
8
+ * {
9
+ box-sizing: border-box;
10
+ }
11
+
12
+ body {
13
+ margin: 0;
14
+ }
15
+
16
+ .shell {
17
+ max-width: 880px;
18
+ margin: 0 auto;
19
+ padding: 1.5rem;
20
+ }
21
+
22
+ .hero {
23
+ margin-bottom: 1rem;
24
+ }
25
+
26
+ .hero h1 {
27
+ font-size: 1.35rem;
28
+ margin: 0 0 0.35rem;
29
+ }
30
+
31
+ .hero p {
32
+ margin: 0;
33
+ font-size: 0.875rem;
34
+ color: #64748b;
35
+ }
36
+
37
+ .hero a {
38
+ color: #c2410c;
39
+ }
40
+
41
+ .layout {
42
+ display: grid;
43
+ gap: 1rem;
44
+ }
45
+
46
+ @media (min-width: 900px) {
47
+ .layout {
48
+ grid-template-columns: 1fr 240px;
49
+ align-items: start;
50
+ }
51
+ }
52
+
53
+ .chat-panel {
54
+ display: flex;
55
+ flex-direction: column;
56
+ min-height: 520px;
57
+ border: 1px solid #e7e5e4;
58
+ border-radius: 16px;
59
+ background: #fff;
60
+ overflow: hidden;
61
+ box-shadow: 0 8px 30px rgb(15 23 42 / 6%);
62
+ }
63
+
64
+ .chat-header {
65
+ display: flex;
66
+ justify-content: space-between;
67
+ align-items: center;
68
+ padding: 0.75rem 1rem;
69
+ border-bottom: 1px solid #e7e5e4;
70
+ background: #0f172a;
71
+ color: #fff;
72
+ }
73
+
74
+ .status {
75
+ font-size: 0.75rem;
76
+ opacity: 0.85;
77
+ }
78
+
79
+ .messages {
80
+ flex: 1;
81
+ overflow-y: auto;
82
+ list-style: none;
83
+ margin: 0;
84
+ padding: 1rem;
85
+ display: flex;
86
+ flex-direction: column;
87
+ gap: 0.5rem;
88
+ }
89
+
90
+ .message {
91
+ display: flex;
92
+ flex-direction: column;
93
+ gap: 0.15rem;
94
+ padding: 0.6rem 0.85rem;
95
+ border-radius: 10px;
96
+ background: #f8fafc;
97
+ max-width: 85%;
98
+ }
99
+
100
+ .message.self {
101
+ align-self: flex-end;
102
+ background: #fff7ed;
103
+ border: 1px solid #fed7aa;
104
+ }
105
+
106
+ .message.agent {
107
+ background: #f1f5f9;
108
+ border: 1px solid #e2e8f0;
109
+ }
110
+
111
+ .author {
112
+ font-size: 0.7rem;
113
+ color: #64748b;
114
+ font-weight: 600;
115
+ }
116
+
117
+ .composer {
118
+ display: flex;
119
+ flex-direction: column;
120
+ gap: 0.5rem;
121
+ padding: 0.75rem;
122
+ border-top: 1px solid #e7e5e4;
123
+ }
124
+
125
+ .composer-row {
126
+ display: flex;
127
+ gap: 0.5rem;
128
+ }
129
+
130
+ .composer input {
131
+ flex: 1;
132
+ border: 1px solid #cbd5e1;
133
+ border-radius: 10px;
134
+ padding: 0.55rem 0.75rem;
135
+ }
136
+
137
+ .composer button {
138
+ border: none;
139
+ border-radius: 10px;
140
+ background: #c2410c;
141
+ color: #fff;
142
+ padding: 0.55rem 1rem;
143
+ cursor: pointer;
144
+ font-weight: 600;
145
+ }
146
+
147
+ .composer button.secondary {
148
+ background: #0f172a;
149
+ }
150
+
151
+ .composer button:disabled {
152
+ opacity: 0.55;
153
+ cursor: not-allowed;
154
+ }
155
+
156
+ .hint {
157
+ font-size: 0.75rem;
158
+ color: #64748b;
159
+ }
160
+
161
+ .tools {
162
+ margin: 0 1rem 1rem;
163
+ padding: 0.75rem;
164
+ border-radius: 10px;
165
+ border: 1px solid #e2e8f0;
166
+ background: #f8fafc;
167
+ }
168
+
169
+ .tools h3 {
170
+ margin: 0 0 0.5rem;
171
+ font-size: 0.7rem;
172
+ letter-spacing: 0.06em;
173
+ text-transform: uppercase;
174
+ color: #64748b;
175
+ }
176
+
177
+ .tools ul {
178
+ margin: 0;
179
+ padding-left: 1rem;
180
+ font-size: 0.8rem;
181
+ }
182
+
183
+ .side {
184
+ display: flex;
185
+ flex-direction: column;
186
+ gap: 0.75rem;
187
+ }
188
+
189
+ .card {
190
+ border: 1px solid #e7e5e4;
191
+ border-radius: 12px;
192
+ padding: 0.85rem 1rem;
193
+ background: #fff;
194
+ font-size: 0.8rem;
195
+ }
196
+
197
+ .card h2 {
198
+ margin: 0 0 0.5rem;
199
+ font-size: 0.75rem;
200
+ text-transform: uppercase;
201
+ letter-spacing: 0.05em;
202
+ color: #64748b;
203
+ }
204
+
205
+ .card dl {
206
+ margin: 0;
207
+ display: grid;
208
+ gap: 0.35rem;
209
+ }
210
+
211
+ .card dt {
212
+ color: #94a3b8;
213
+ font-size: 0.7rem;
214
+ }
215
+
216
+ .card dd {
217
+ margin: 0;
218
+ word-break: break-all;
219
+ }
220
+
221
+ .card a {
222
+ color: #c2410c;
223
+ }
224
+
225
+ .error-box {
226
+ border: 1px solid #fecaca;
227
+ background: #fef2f2;
228
+ color: #991b1b;
229
+ border-radius: 12px;
230
+ padding: 1rem;
231
+ font-size: 0.875rem;
232
+ }
233
+
234
+ .error-box code {
235
+ font-size: 0.8rem;
236
+ }
237
+
238
+ .typing {
239
+ padding: 0 1rem 0.5rem;
240
+ font-size: 0.75rem;
241
+ color: #64748b;
242
+ }
@@ -0,0 +1,10 @@
1
+ import { StrictMode } from "react";
2
+ import { createRoot } from "react-dom/client";
3
+ import { App } from "./App";
4
+ import "./index.css";
5
+
6
+ createRoot(document.getElementById("root")!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>,
10
+ );
@@ -0,0 +1,16 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ interface ImportMetaEnv {
4
+ readonly VITE_FLUXYCHAT_WORKER_URL: string;
5
+ readonly VITE_FLUXYCHAT_MEMBER_JWT: string;
6
+ readonly VITE_FLUXYCHAT_ROOM_ID: string;
7
+ readonly VITE_FLUXYCHAT_AGENT_ID: string;
8
+ readonly VITE_FLUXYCHAT_AGENT_HANDLE: string;
9
+ readonly VITE_FLUXYCHAT_PROJECT_ID: string;
10
+ readonly VITE_FLUXYCHAT_CONSOLE_URL: string;
11
+ readonly VITE_FLUXYCHAT_USER_ID: string;
12
+ }
13
+
14
+ interface ImportMeta {
15
+ readonly env: ImportMetaEnv;
16
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
6
+ "module": "ESNext",
7
+ "skipLibCheck": true,
8
+ "moduleResolution": "bundler",
9
+ "allowImportingTsExtensions": true,
10
+ "isolatedModules": true,
11
+ "moduleDetection": "force",
12
+ "noEmit": true,
13
+ "jsx": "react-jsx",
14
+ "strict": true,
15
+ "noUnusedLocals": true,
16
+ "noUnusedParameters": true,
17
+ "noFallthroughCasesInSwitch": true,
18
+ "noUncheckedSideEffectImports": true
19
+ },
20
+ "include": ["src"]
21
+ }
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from "vite";
2
+ import react from "@vitejs/plugin-react";
3
+
4
+ export default defineConfig({
5
+ plugins: [react()],
6
+ server: { port: 5173, open: true },
7
+ });
@@ -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)