@fluxy-chat/create-fluxy-chat 0.2.0 → 0.3.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 +88 -22
- package/package.json +1 -1
- package/readme.md +13 -3
- package/templates/basic/package.json +1 -1
- package/templates/discord/package.json +1 -1
- package/templates/minimal/.env.example +3 -0
- package/templates/minimal/README.md +14 -0
- package/templates/minimal/index.html +12 -0
- package/templates/minimal/package.json +26 -0
- package/templates/minimal/src/App.tsx +24 -0
- package/templates/minimal/src/index.css +13 -0
- package/templates/minimal/src/main.tsx +10 -0
- package/templates/minimal/src/vite-env.d.ts +7 -0
- package/templates/minimal/tsconfig.json +17 -0
- package/templates/minimal/vite.config.ts +7 -0
- package/templates/react/.env.example +8 -0
- package/templates/react/README.md +33 -0
- package/templates/react/index.html +12 -0
- package/templates/react/package.json +25 -0
- package/templates/react/src/App.tsx +95 -0
- package/templates/react/src/index.css +106 -0
- package/templates/react/src/main.tsx +10 -0
- package/templates/react/src/vite-env.d.ts +11 -0
- package/templates/react/tsconfig.json +21 -0
- package/templates/react/vite.config.ts +7 -0
- package/templates/slack/package.json +1 -1
- package/templates/telegram/package.json +1 -1
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"].includes(value);
|
|
33
|
+
return ["basic", "slack", "telegram", "discord", "web", "react"].includes(value);
|
|
34
34
|
}
|
|
35
35
|
function detectPackageManagerFromLockfiles(cwd) {
|
|
36
36
|
if (fs.existsSync(path.join(cwd, "pnpm-lock.yaml"))) {
|
|
@@ -79,6 +79,25 @@ function writeJson(projectDir, filePath, value) {
|
|
|
79
79
|
writeFile(projectDir, filePath, `${JSON.stringify(value, null, 2)}
|
|
80
80
|
`);
|
|
81
81
|
}
|
|
82
|
+
function copyDir(source, destination) {
|
|
83
|
+
fs.mkdirSync(destination, { recursive: true });
|
|
84
|
+
for (const entry of fs.readdirSync(source, { withFileTypes: true })) {
|
|
85
|
+
const srcPath = path.join(source, entry.name);
|
|
86
|
+
const destPath = path.join(destination, entry.name);
|
|
87
|
+
if (entry.isDirectory()) {
|
|
88
|
+
copyDir(srcPath, destPath);
|
|
89
|
+
} else {
|
|
90
|
+
fs.copyFileSync(srcPath, destPath);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function templatesDir() {
|
|
95
|
+
return path.resolve(
|
|
96
|
+
path.dirname(new URL(import.meta.url).pathname.replace(/^\//, "")),
|
|
97
|
+
"..",
|
|
98
|
+
"templates"
|
|
99
|
+
);
|
|
100
|
+
}
|
|
82
101
|
|
|
83
102
|
// src/prompts.ts
|
|
84
103
|
var DEFAULT_PROJECT_NAME = "my-fluxy-bot";
|
|
@@ -101,15 +120,18 @@ async function runPrompts(inputs) {
|
|
|
101
120
|
if (nameError) {
|
|
102
121
|
throw new Error(nameError);
|
|
103
122
|
}
|
|
123
|
+
const minimal = inputs.minimal ?? false;
|
|
104
124
|
let adapter = inputs.adapter;
|
|
105
|
-
if (!adapter) {
|
|
125
|
+
if (!minimal && !adapter) {
|
|
106
126
|
if (inputs.yes) {
|
|
107
|
-
adapter = "
|
|
127
|
+
adapter = "react";
|
|
108
128
|
} else {
|
|
109
129
|
const result = await select({
|
|
110
130
|
message: "Select an adapter:",
|
|
111
131
|
options: [
|
|
112
|
-
{ label: "
|
|
132
|
+
{ label: "Minimal chat widget (ui-kit, recommended)", value: "minimal" },
|
|
133
|
+
{ label: "React chat app (Vite + useChat)", value: "react" },
|
|
134
|
+
{ label: "Basic (Cloudflare Workers bot)", value: "basic" },
|
|
113
135
|
{ label: "Slack", value: "slack" },
|
|
114
136
|
{ label: "Telegram", value: "telegram" },
|
|
115
137
|
{ label: "Discord", value: "discord" },
|
|
@@ -117,6 +139,9 @@ async function runPrompts(inputs) {
|
|
|
117
139
|
]
|
|
118
140
|
});
|
|
119
141
|
if (isCancel(result)) return null;
|
|
142
|
+
if (result === "minimal") {
|
|
143
|
+
return runPrompts({ ...inputs, minimal: true, adapter: "react" });
|
|
144
|
+
}
|
|
120
145
|
adapter = result;
|
|
121
146
|
}
|
|
122
147
|
}
|
|
@@ -167,11 +192,12 @@ async function runPrompts(inputs) {
|
|
|
167
192
|
if (isCancel(shouldInitGit)) return null;
|
|
168
193
|
return {
|
|
169
194
|
name,
|
|
170
|
-
adapter,
|
|
195
|
+
adapter: adapter ?? "react",
|
|
171
196
|
packageManager,
|
|
172
197
|
language,
|
|
173
198
|
shouldInstall,
|
|
174
|
-
shouldInitGit
|
|
199
|
+
shouldInitGit,
|
|
200
|
+
minimal: minimal || inputs.minimal === true
|
|
175
201
|
};
|
|
176
202
|
}
|
|
177
203
|
|
|
@@ -775,6 +801,7 @@ var execAsync = promisify(exec);
|
|
|
775
801
|
function parseArgs(argv) {
|
|
776
802
|
const args = {
|
|
777
803
|
yes: false,
|
|
804
|
+
minimal: false,
|
|
778
805
|
skipInstall: false,
|
|
779
806
|
noGit: false,
|
|
780
807
|
help: false
|
|
@@ -786,16 +813,28 @@ function parseArgs(argv) {
|
|
|
786
813
|
args.help = true;
|
|
787
814
|
} else if (arg === "-y" || arg === "--yes") {
|
|
788
815
|
args.yes = true;
|
|
816
|
+
} else if (arg === "--minimal") {
|
|
817
|
+
args.minimal = true;
|
|
789
818
|
} else if (arg === "--skip-install") {
|
|
790
819
|
args.skipInstall = true;
|
|
791
820
|
} else if (arg === "--no-git") {
|
|
792
821
|
args.noGit = true;
|
|
822
|
+
} else if (arg === "--template" || arg === "-t") {
|
|
823
|
+
const value = argv[++i];
|
|
824
|
+
if (value && isAdapterType(value)) {
|
|
825
|
+
args.adapter = value;
|
|
826
|
+
} else if (value === "react") {
|
|
827
|
+
args.adapter = "react";
|
|
828
|
+
} else {
|
|
829
|
+
console.error(`Invalid template: ${value}. Choose: react, basic, slack, telegram, discord, web`);
|
|
830
|
+
process.exit(1);
|
|
831
|
+
}
|
|
793
832
|
} else if (arg === "--adapter" || arg === "-a") {
|
|
794
833
|
const value = argv[++i];
|
|
795
834
|
if (value && isAdapterType(value)) {
|
|
796
835
|
args.adapter = value;
|
|
797
836
|
} else {
|
|
798
|
-
console.error(`Invalid adapter: ${value}. Choose: basic, slack, telegram, discord, web`);
|
|
837
|
+
console.error(`Invalid adapter: ${value}. Choose: react, basic, slack, telegram, discord, web`);
|
|
799
838
|
process.exit(1);
|
|
800
839
|
}
|
|
801
840
|
} else if (arg === "--pm" || arg === "--package-manager") {
|
|
@@ -833,16 +872,20 @@ ${pc.bold("Usage:")}
|
|
|
833
872
|
npx create-fluxy-chat [project-name] [options]
|
|
834
873
|
|
|
835
874
|
${pc.bold("Options:")}
|
|
836
|
-
-a, --adapter <type> Adapter: basic, slack, telegram, discord, web
|
|
875
|
+
-a, --adapter <type> Adapter: react, basic, slack, telegram, discord, web
|
|
876
|
+
-t, --template <type> Alias for --adapter (e.g. react)
|
|
837
877
|
--pm <manager> Package manager: npm, pnpm, yarn
|
|
838
878
|
-l, --language <lang> Language: typescript (default) or javascript
|
|
839
879
|
-y, --yes Skip prompts and accept defaults
|
|
880
|
+
--minimal Chat-only widget (ui-kit) \u2014 no platform modules
|
|
840
881
|
--skip-install Skip dependency installation
|
|
841
882
|
--no-git Skip git repository initialization
|
|
842
883
|
-h, --help Show this help
|
|
843
884
|
|
|
844
885
|
${pc.bold("Examples:")}
|
|
845
|
-
${pc.cyan("npx create-fluxy-chat my-
|
|
886
|
+
${pc.cyan("npx create-fluxy-chat my-chat --minimal")}
|
|
887
|
+
${pc.cyan("npx create-fluxy-chat my-chat --template react")}
|
|
888
|
+
${pc.cyan("npx create-fluxy-chat my-bot --adapter basic")}
|
|
846
889
|
${pc.cyan("npx create-fluxy-chat my-bot --adapter slack")}
|
|
847
890
|
${pc.cyan("npx create-fluxy-chat my-bot --adapter telegram --pm pnpm")}
|
|
848
891
|
${pc.cyan("npx create-fluxy-chat my-bot -y --adapter discord")}
|
|
@@ -860,6 +903,7 @@ async function main() {
|
|
|
860
903
|
packageManager: args.pm,
|
|
861
904
|
language: args.language,
|
|
862
905
|
yes: args.yes,
|
|
906
|
+
minimal: args.minimal,
|
|
863
907
|
shouldInstall: args.skipInstall ? false : void 0,
|
|
864
908
|
shouldInitGit: args.noGit ? false : void 0
|
|
865
909
|
});
|
|
@@ -882,19 +926,37 @@ async function main() {
|
|
|
882
926
|
s.start("Creating project files");
|
|
883
927
|
try {
|
|
884
928
|
fs2.mkdirSync(projectDir, { recursive: true });
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
929
|
+
if (config.minimal) {
|
|
930
|
+
const templateRoot = path2.join(templatesDir(), "minimal");
|
|
931
|
+
copyDir(templateRoot, projectDir);
|
|
932
|
+
const pkgPath = path2.join(projectDir, "package.json");
|
|
933
|
+
const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf8"));
|
|
934
|
+
pkg.name = config.name;
|
|
935
|
+
writeJson(projectDir, "package.json", pkg);
|
|
936
|
+
s.stop("Minimal chat widget created.");
|
|
937
|
+
} else if (config.adapter === "react") {
|
|
938
|
+
const templateRoot = path2.join(templatesDir(), "react");
|
|
939
|
+
copyDir(templateRoot, projectDir);
|
|
940
|
+
const pkgPath = path2.join(projectDir, "package.json");
|
|
941
|
+
const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf8"));
|
|
942
|
+
pkg.name = config.name;
|
|
943
|
+
writeJson(projectDir, "package.json", pkg);
|
|
944
|
+
s.stop("React chat app created.");
|
|
945
|
+
} else {
|
|
946
|
+
writeJson(projectDir, "package.json", generatePackageJson(config));
|
|
947
|
+
if (config.language === "typescript") {
|
|
948
|
+
writeJson(projectDir, "tsconfig.json", generateTsConfig());
|
|
949
|
+
}
|
|
950
|
+
writeFile(projectDir, "wrangler.toml", generateWranglerToml(config));
|
|
951
|
+
writeFile(projectDir, ".dev.vars", generateDevVars());
|
|
952
|
+
writeFile(projectDir, ".env.example", generateEnvExample(config));
|
|
953
|
+
writeFile(projectDir, ".gitignore", generateGitignore());
|
|
954
|
+
const ext = config.language === "typescript" ? "ts" : "js";
|
|
955
|
+
writeFile(projectDir, `src/index.${ext}`, generateWorkerIndex(config));
|
|
956
|
+
writeFile(projectDir, `src/bot.${ext}`, generateBotHandler(config));
|
|
957
|
+
writeFile(projectDir, "README.md", generateReadme(config));
|
|
958
|
+
s.stop("Project files created.");
|
|
888
959
|
}
|
|
889
|
-
writeFile(projectDir, "wrangler.toml", generateWranglerToml(config));
|
|
890
|
-
writeFile(projectDir, ".dev.vars", generateDevVars());
|
|
891
|
-
writeFile(projectDir, ".env.example", generateEnvExample(config));
|
|
892
|
-
writeFile(projectDir, ".gitignore", generateGitignore());
|
|
893
|
-
const ext = config.language === "typescript" ? "ts" : "js";
|
|
894
|
-
writeFile(projectDir, `src/index.${ext}`, generateWorkerIndex(config));
|
|
895
|
-
writeFile(projectDir, `src/bot.${ext}`, generateBotHandler(config));
|
|
896
|
-
writeFile(projectDir, "README.md", generateReadme(config));
|
|
897
|
-
s.stop("Project files created.");
|
|
898
960
|
} catch (error) {
|
|
899
961
|
s.stop("Failed to create project files.");
|
|
900
962
|
throw error;
|
|
@@ -928,7 +990,11 @@ async function main() {
|
|
|
928
990
|
}
|
|
929
991
|
}
|
|
930
992
|
note(
|
|
931
|
-
[
|
|
993
|
+
config.adapter === "react" ? [
|
|
994
|
+
`cd ${config.name}`,
|
|
995
|
+
"cp .env.example .env",
|
|
996
|
+
`${config.packageManager === "npm" ? "npm run" : config.packageManager} dev`
|
|
997
|
+
].join("\n") : [
|
|
932
998
|
`cd ${config.name}`,
|
|
933
999
|
"cp .env.example .dev.vars",
|
|
934
1000
|
`${config.packageManager === "npm" ? "npm run" : config.packageManager} dev`
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -5,14 +5,21 @@ Scaffold a new [FluxyChat](https://github.com/AlessandroFare/fluxychat) bot proj
|
|
|
5
5
|
## Quick start
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
|
|
8
|
+
# Minimal chat widget (recommended — 3 lines in App.tsx)
|
|
9
|
+
npx create-fluxy-chat my-chat --minimal
|
|
10
|
+
|
|
11
|
+
# React + useChat (more control)
|
|
12
|
+
npx create-fluxy-chat my-chat --template react
|
|
9
13
|
```
|
|
10
14
|
|
|
11
|
-
|
|
15
|
+
Interactive mode defaults to the **React chat app** template.
|
|
12
16
|
|
|
13
17
|
## Non-interactive usage
|
|
14
18
|
|
|
15
19
|
```bash
|
|
20
|
+
# React + Vite + useChat (recommended)
|
|
21
|
+
npx create-fluxy-chat my-chat --template react -y
|
|
22
|
+
|
|
16
23
|
# Create a Slack bot with pnpm
|
|
17
24
|
npx create-fluxy-chat my-bot --adapter slack --pm pnpm
|
|
18
25
|
|
|
@@ -30,10 +37,12 @@ npx create-fluxy-chat my-bot --adapter basic
|
|
|
30
37
|
|
|
31
38
|
| Flag | Short | Description |
|
|
32
39
|
| --- | --- | --- |
|
|
33
|
-
| `--adapter <type>` | `-a` | Adapter: `basic`, `slack`, `telegram`, `discord`, `web` |
|
|
40
|
+
| `--adapter <type>` | `-a` | Adapter: `react`, `basic`, `slack`, `telegram`, `discord`, `web` |
|
|
41
|
+
| `--template <type>` | `-t` | Alias for `--adapter (e.g. react)` |
|
|
34
42
|
| `--pm <manager>` | | Package manager: `npm`, `pnpm`, `yarn` |
|
|
35
43
|
| `--language <lang>` | `-l` | Language: `typescript` (default) or `javascript` |
|
|
36
44
|
| `--yes` | `-y` | Skip prompts and accept defaults |
|
|
45
|
+
| `--minimal` | | Chat-only widget (`@fluxy-chat/ui-kit`) — no platform modules |
|
|
37
46
|
| `--skip-install` | | Skip dependency installation |
|
|
38
47
|
| `--no-git` | | Skip git repository initialization |
|
|
39
48
|
| `--help` | `-h` | Show help |
|
|
@@ -42,6 +51,7 @@ npx create-fluxy-chat my-bot --adapter basic
|
|
|
42
51
|
|
|
43
52
|
| Adapter | Description | Platform |
|
|
44
53
|
| --- | --- | --- |
|
|
54
|
+
| `react` | Vite + React chat UI with `useChat` | Browser / SPA |
|
|
45
55
|
| `basic` | Generic webhook bot | Cloudflare Workers |
|
|
46
56
|
| `slack` | Slack Events API bot | Slack |
|
|
47
57
|
| `telegram` | Telegram webhook bot | Telegram |
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# FluxyChat minimal starter
|
|
2
|
+
|
|
3
|
+
Three-line chat widget — no platform modules in the generated project.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
cp .env.example .env
|
|
7
|
+
pnpm dev
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Uses `@fluxy-chat/ui-kit` (`FluxyChatWidget`).
|
|
11
|
+
|
|
12
|
+
## Full platform
|
|
13
|
+
|
|
14
|
+
See [docs.fluxychat.com](https://docs.fluxychat.com/docs) for agents, stream, IoT, and self-host.
|
|
@@ -0,0 +1,12 @@
|
|
|
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.0" />
|
|
6
|
+
<title>FluxyChat</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fluxy-chat-minimal",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "tsc -b && vite build",
|
|
9
|
+
"preview": "vite preview"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@fluxy-chat/ui-kit": "^0.1.0",
|
|
13
|
+
"@fluxy-chat/ui": "^0.1.2",
|
|
14
|
+
"@fluxy-chat/react": "^0.1.1",
|
|
15
|
+
"@fluxy-chat/sdk": "^0.6.0",
|
|
16
|
+
"react": "^19.0.0",
|
|
17
|
+
"react-dom": "^19.0.0"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/react": "^19.0.0",
|
|
21
|
+
"@types/react-dom": "^19.0.0",
|
|
22
|
+
"@vitejs/plugin-react": "^4.3.0",
|
|
23
|
+
"typescript": "^5.6.0",
|
|
24
|
+
"vite": "^6.0.0"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { FluxyChatWidget } from "@fluxy-chat/ui-kit";
|
|
2
|
+
|
|
3
|
+
const workerUrl = import.meta.env.VITE_FLUXYCHAT_WORKER_URL;
|
|
4
|
+
const token = import.meta.env.VITE_FLUXYCHAT_MEMBER_JWT;
|
|
5
|
+
const roomId = import.meta.env.VITE_FLUXYCHAT_ROOM_ID || "general";
|
|
6
|
+
|
|
7
|
+
export function App() {
|
|
8
|
+
return (
|
|
9
|
+
<main style={{ maxWidth: 720, margin: "2rem auto", padding: "0 1rem" }}>
|
|
10
|
+
<h1 style={{ fontSize: "1.25rem", marginBottom: "1rem" }}>FluxyChat</h1>
|
|
11
|
+
<FluxyChatWidget
|
|
12
|
+
roomId={roomId}
|
|
13
|
+
workerUrl={workerUrl}
|
|
14
|
+
token={token}
|
|
15
|
+
theme="default"
|
|
16
|
+
height={520}
|
|
17
|
+
/>
|
|
18
|
+
<p style={{ marginTop: "1rem", fontSize: 13, color: "#71717a" }}>
|
|
19
|
+
Need stream, IoT, or agents? See{" "}
|
|
20
|
+
<a href="https://docs.fluxychat.com/docs">full platform docs</a>.
|
|
21
|
+
</p>
|
|
22
|
+
</main>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"lib": ["ES2020", "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
|
+
},
|
|
16
|
+
"include": ["src"]
|
|
17
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# FluxyChat worker URL (hosted or self-hosted)
|
|
2
|
+
VITE_FLUXYCHAT_WORKER_URL=https://your-worker.example.workers.dev
|
|
3
|
+
|
|
4
|
+
# Member JWT from POST /auth/token or onboarding wizard
|
|
5
|
+
VITE_FLUXYCHAT_MEMBER_JWT=eyJ...
|
|
6
|
+
|
|
7
|
+
# Demo room — create in console or onboarding
|
|
8
|
+
VITE_FLUXYCHAT_ROOM_ID=demo
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# FluxyChat React starter
|
|
2
|
+
|
|
3
|
+
Minimal Vite + React app with `useChat` — first message in minutes.
|
|
4
|
+
|
|
5
|
+
## Quick start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
cp .env.example .env
|
|
9
|
+
# Edit .env with your Worker URL + member JWT
|
|
10
|
+
npm install
|
|
11
|
+
npm run dev
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Open http://localhost:5173 and send a message.
|
|
15
|
+
|
|
16
|
+
## Get credentials
|
|
17
|
+
|
|
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
|
|
20
|
+
|
|
21
|
+
## Scripts
|
|
22
|
+
|
|
23
|
+
| Command | Description |
|
|
24
|
+
|---------|-------------|
|
|
25
|
+
| `npm run dev` | Vite dev server |
|
|
26
|
+
| `npm run build` | Production build |
|
|
27
|
+
| `npm run preview` | Preview production build |
|
|
28
|
+
|
|
29
|
+
## Next steps
|
|
30
|
+
|
|
31
|
+
- Add `@fluxy-chat/ui` for polished message bubbles
|
|
32
|
+
- Use `useInbox` for unified feed
|
|
33
|
+
- See [chat-only quickstart](https://docs.fluxychat.com/docs/getting-started/chat-only-quickstart)
|
|
@@ -0,0 +1,12 @@
|
|
|
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.0" />
|
|
6
|
+
<title>FluxyChat</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fluxy-chat-app",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "tsc -b && vite build",
|
|
9
|
+
"preview": "vite preview"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@fluxy-chat/react": "^0.1.1",
|
|
13
|
+
"@fluxy-chat/sdk": "^0.6.0",
|
|
14
|
+
"react": "^19.0.0",
|
|
15
|
+
"react-dom": "^19.0.0",
|
|
16
|
+
"zustand": "^5.0.0"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/react": "^19.0.0",
|
|
20
|
+
"@types/react-dom": "^19.0.0",
|
|
21
|
+
"@vitejs/plugin-react": "^4.3.0",
|
|
22
|
+
"typescript": "^5.6.0",
|
|
23
|
+
"vite": "^6.0.0"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { useMemo, useState } from "react";
|
|
2
|
+
import { FluxyChatClient } from "@fluxy-chat/sdk";
|
|
3
|
+
import { FluxyRealtimeProvider, useChat } from "@fluxy-chat/react";
|
|
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";
|
|
8
|
+
|
|
9
|
+
function ChatPanel({ roomId }: { roomId: string }) {
|
|
10
|
+
const { messages, sendMessage, connectionState } = useChat({
|
|
11
|
+
roomId,
|
|
12
|
+
markReadLatest: true,
|
|
13
|
+
});
|
|
14
|
+
const [draft, setDraft] = useState("");
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<section className="chat-panel">
|
|
18
|
+
<header className="chat-header">
|
|
19
|
+
<strong>{roomId}</strong>
|
|
20
|
+
<span className="status">{connectionState.status}</span>
|
|
21
|
+
</header>
|
|
22
|
+
<ul className="messages">
|
|
23
|
+
{messages.map((m) => (
|
|
24
|
+
<li key={m.id} className="message">
|
|
25
|
+
<span className="author">{m.userId}</span>
|
|
26
|
+
<span>{m.content}</span>
|
|
27
|
+
</li>
|
|
28
|
+
))}
|
|
29
|
+
</ul>
|
|
30
|
+
<form
|
|
31
|
+
className="composer"
|
|
32
|
+
onSubmit={(e) => {
|
|
33
|
+
e.preventDefault();
|
|
34
|
+
const text = draft.trim();
|
|
35
|
+
if (!text) return;
|
|
36
|
+
void sendMessage(text);
|
|
37
|
+
setDraft("");
|
|
38
|
+
}}
|
|
39
|
+
>
|
|
40
|
+
<input
|
|
41
|
+
value={draft}
|
|
42
|
+
onChange={(e) => setDraft(e.target.value)}
|
|
43
|
+
placeholder="Type a message…"
|
|
44
|
+
aria-label="Message"
|
|
45
|
+
/>
|
|
46
|
+
<button type="submit">Send</button>
|
|
47
|
+
</form>
|
|
48
|
+
</section>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
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
|
+
}, []);
|
|
63
|
+
|
|
64
|
+
if (!client) {
|
|
65
|
+
return (
|
|
66
|
+
<main className="shell">
|
|
67
|
+
<h1>FluxyChat — chat-only starter</h1>
|
|
68
|
+
<p>
|
|
69
|
+
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>.
|
|
71
|
+
</p>
|
|
72
|
+
<p>
|
|
73
|
+
Get credentials from{" "}
|
|
74
|
+
<a href="https://fluxychat.com/onboarding" target="_blank" rel="noreferrer">
|
|
75
|
+
onboarding
|
|
76
|
+
</a>{" "}
|
|
77
|
+
or <code>pnpm run first-message</code> in the monorepo.
|
|
78
|
+
</p>
|
|
79
|
+
</main>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<main className="shell">
|
|
85
|
+
<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} />
|
|
92
|
+
</FluxyRealtimeProvider>
|
|
93
|
+
</main>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
3
|
+
line-height: 1.5;
|
|
4
|
+
color: #0f172a;
|
|
5
|
+
background: #f8fafc;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
* {
|
|
9
|
+
box-sizing: border-box;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
body {
|
|
13
|
+
margin: 0;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.shell {
|
|
17
|
+
max-width: 720px;
|
|
18
|
+
margin: 0 auto;
|
|
19
|
+
padding: 1.5rem;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.chat-panel {
|
|
23
|
+
display: flex;
|
|
24
|
+
flex-direction: column;
|
|
25
|
+
height: min(70vh, 560px);
|
|
26
|
+
border: 1px solid #e2e8f0;
|
|
27
|
+
border-radius: 12px;
|
|
28
|
+
background: #fff;
|
|
29
|
+
overflow: hidden;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.chat-header {
|
|
33
|
+
display: flex;
|
|
34
|
+
justify-content: space-between;
|
|
35
|
+
padding: 0.75rem 1rem;
|
|
36
|
+
border-bottom: 1px solid #e2e8f0;
|
|
37
|
+
background: #2563eb;
|
|
38
|
+
color: #fff;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.status {
|
|
42
|
+
font-size: 0.75rem;
|
|
43
|
+
opacity: 0.9;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.messages {
|
|
47
|
+
flex: 1;
|
|
48
|
+
overflow-y: auto;
|
|
49
|
+
list-style: none;
|
|
50
|
+
margin: 0;
|
|
51
|
+
padding: 1rem;
|
|
52
|
+
display: flex;
|
|
53
|
+
flex-direction: column;
|
|
54
|
+
gap: 0.5rem;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.message {
|
|
58
|
+
display: flex;
|
|
59
|
+
flex-direction: column;
|
|
60
|
+
gap: 0.15rem;
|
|
61
|
+
padding: 0.5rem 0.75rem;
|
|
62
|
+
border-radius: 8px;
|
|
63
|
+
background: #f1f5f9;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.author {
|
|
67
|
+
font-size: 0.7rem;
|
|
68
|
+
color: #64748b;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.composer {
|
|
72
|
+
display: flex;
|
|
73
|
+
gap: 0.5rem;
|
|
74
|
+
padding: 0.75rem;
|
|
75
|
+
border-top: 1px solid #e2e8f0;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.composer input {
|
|
79
|
+
flex: 1;
|
|
80
|
+
border: 1px solid #cbd5e1;
|
|
81
|
+
border-radius: 8px;
|
|
82
|
+
padding: 0.5rem 0.75rem;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.composer button {
|
|
86
|
+
border: none;
|
|
87
|
+
border-radius: 8px;
|
|
88
|
+
background: #2563eb;
|
|
89
|
+
color: #fff;
|
|
90
|
+
padding: 0.5rem 1rem;
|
|
91
|
+
cursor: pointer;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.room-picker {
|
|
95
|
+
display: flex;
|
|
96
|
+
flex-direction: column;
|
|
97
|
+
gap: 0.25rem;
|
|
98
|
+
margin-bottom: 1rem;
|
|
99
|
+
font-size: 0.875rem;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.room-picker input {
|
|
103
|
+
padding: 0.5rem 0.75rem;
|
|
104
|
+
border: 1px solid #cbd5e1;
|
|
105
|
+
border-radius: 8px;
|
|
106
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
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
|
+
}
|
|
8
|
+
|
|
9
|
+
interface ImportMeta {
|
|
10
|
+
readonly env: ImportMetaEnv;
|
|
11
|
+
}
|
|
@@ -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
|
+
}
|