import React, { useEffect, useMemo, useRef, useState } from "react";
import {
Search,
Hash,
Plus,
Send,
Sun,
Moon,
Users,
MessageSquare,
Bot,
Sparkles,
X,
Settings,
ShieldCheck,
Compass,
Reply,
ChevronRight,
} from "lucide-react";
/**
* AICORD — single-user, Discord-style AI sandbox
* ------------------------------------------------
* - Everything (servers, channels, users, messages) is procedurally generated.
* - No backend, no auth, no real people. Just you and bots.
* - Features:
* • Servers + channels, DM drawer
* • Server discovery (join/leave/open)
* • Profiles (click avatars/members)
* • User settings (name, status, bio, theme)
* • Search (servers/channels/users/messages, Cmd/Ctrl-K)
* • Reactions + “reply” button (cosmetic)
*/
// ----------------------------- Utilities ----------------------------------
const clamp = (n, a, b) => Math.max(a, Math.min(b, n));
// Lightweight seeded RNG (xorshift32)
function makeRNG(seed) {
let x = seed >>> 0 || 123456789;
return function rand() {
x ^= x << 13;
x ^= x >>> 17;
x ^= x << 5;
return (x >>> 0) / 4294967296;
};
}
function pick(rng, arr) {
return arr[Math.floor(rng() * arr.length)];
}
function chance(rng, p = 0.5) {
return rng() < p;
}
// Silly name & text generators
const FIRST = [
"Nova", "Rex", "Mira", "Orion", "Ada", "Zeph", "Quill", "Echo", "Zara",
"Kai", "Lumi", "Rune", "Iris", "Sol", "Nyx", "Vega", "Juno", "Aria",
"Atlas", "Rhea", "Bex", "Pax", "Noa", "Rune", "Sage",
];
const LAST = [
"Skylark", "Kepler", "Hawthorne", "Cobalt", "Faraday", "Quasar", "Sterling",
"Starling", "Wilde", "Ash", "Kestrel", "Onyx", "Hearth", "Vale", "North",
"Blake", "Rook", "Dune", "Voss", "Solis", "Cade", "Wren",
];
const SERVER_TOPICS = [
"Neural Brew Co.",
"Dream-Synth Lab",
"Pixel Parlor",
"Prompt Garden",
"Logic Loom",
"Circuit Circus",
"Vector Valley",
"Glyph Guild",
"Story Forge",
"Audio Arcade",
"Retro Robotics",
"Quantum Café",
"Byte Bazaar",
"Orbital Orchard",
"Shader Sanctuary",
];
const CHANNELS = [
"general",
"prompts",
"showcase",
"bot-lab",
"memes",
"music",
"off-topic",
"ask-anything",
"help",
"work-in-progress",
];
const EMOTES = ["✨", "🤖", "🧠", "⚙️", "🎛️", "🎨", "🛰️", "🧩", "🦾", "🧪", "💡", "🧷", "📎"];
const TOPICS = [
"text-generation",
"image diffusion",
"RLHF",
"agents",
"tool-use",
"embeddings",
"vector DBs",
"audio LLMs",
"synthetic data",
"prompt engineering",
"safety",
];
const OPINIONS = [
"hot take: few-shot beats fine-tune here",
"today I learned: temperature≠creativity",
"long prompts are just configs with lore",
"remember: evals before vibes",
"I ship everything behind a feature flag",
"prefer cosine to dot for retrieval",
"stop putting secrets in prompts",
"agents aren’t interns, they’re workflows",
"small model + good data > huge model + junk",
];
function sentence(rng) {
const who = `${pick(rng, FIRST)} ${pick(rng, LAST)}`;
const emo = pick(rng, EMOTES);
const t = pick(rng, TOPICS);
const op = pick(rng, OPINIONS);
const filler = [
`spun up a mini-agent for ${t}`,
`benching variants; ${op}`,
`shipping a demo ${emo}`,
`trying to reproduce a paper on ${t}`,
`broke prod (again) ${emo}`,
`writing a guide on ${t}`,
`pairing with a bot on codegen ${emo}`,
];
return `${who}: ${pick(rng, filler)}`;
}
function loremMsg(rng) {
const n = 1 + Math.floor(rng() * 3);
return Array.from({ length: n }, () => sentence(rng)).join("\n");
}
function avatar(seedStr) {
// Simple deterministic gradient avatar based on a hash
let h = 0;
for (let i = 0; i < seedStr.length; i++)
h = Math.imul(31, h) + seedStr.charCodeAt(i) | 0;
const hue = Math.abs(h) % 360;
return `linear-gradient(135deg,hsl(${hue} 70% 55%),hsl(${(hue + 60) % 360} 70% 45%))`;
}
// ----------------------------- World Model ---------------------------------
function makeUser(rng, id) {
const name = `${pick(rng, FIRST)} ${pick(rng, LAST)}`;
const handle = `@${name.toLowerCase().replace(/[^a-z]/g, "")}${Math.floor(
rng() * 999
)}`;
const bio = `Into ${pick(rng, TOPICS)}, ${pick(
rng,
TOPICS
)}, and ${pick(rng, TOPICS)}. ${pick(rng, OPINIONS)}.`;
return { id: `u${id}`, name, handle, bio };
}
function makeServer(rng, id) {
const title = pick(rng, SERVER_TOPICS);
const channels = CHANNELS.map((c, i) => ({
id: `c${id}-${i}`,
name: c,
topic: pick(rng, TOPICS),
}));
const members = Array.from(
{ length: 12 + Math.floor(rng() * 30) },
(_, i) => makeUser(rng, `${id}-${i}`)
);
return { id: `s${id}`, title, channels, members };
}
function generateWorld(seed = 42) {
const rng = makeRNG(seed);
const servers = Array.from({ length: 10 }, (_, i) => makeServer(rng, i + 1));
const users = Array.from({ length: 40 }, (_, i) => makeUser(rng, i + 100));
const dms = users.slice(0, 18).map((u, i) => ({
id: `d${i}`,
peer: u,
messages: Array.from(
{ length: 8 + Math.floor(rng() * 8) },
() => ({
id: crypto.randomUUID(),
from: u,
text: loremMsg(rng),
ts: Date.now() - Math.floor(rng() * 1e8),
})
),
}));
return { seed, servers, users, dms };
}
// ----------------------------- Search --------------------------------------
function fuseLikeScore(q, text) {
if (!q) return 0;
const nq = q.trim().toLowerCase();
const nt = text.toLowerCase();
if (nt.includes(nq)) return 100 + nq.length;
// token overlap
const qTokens = new Set(nq.split(/\s+/));
const tTokens = new Set(nt.split(/\s+/));
let overlap = 0;
qTokens.forEach((t) => {
if (t && tTokens.has(t)) overlap++;
});
return overlap * 10;
}
function searchWorld(world, q) {
if (!q) return { servers: [], channels: [], users: [], messages: [] };
const servers = world.servers
.map((s) => ({
item: s,
score: Math.max(
fuseLikeScore(q, s.title),
s.channels.reduce(
(m, ch) => Math.max(m, fuseLikeScore(q, ch.name + " " + ch.topic)),
0
)
),
}))
.filter(({ score }) => score > 0)
.sort((a, b) => b.score - a.score)
.slice(0, 7);
const channels = world.servers
.flatMap((s) =>
s.channels.map((ch) => ({
item: { ...ch, serverId: s.id, serverTitle: s.title },
score: fuseLikeScore(q, `${ch.name} ${ch.topic} ${s.title}`),
}))
)
.filter((r) => r.score > 0)
.sort((a, b) => b.score - a.score)
.slice(0, 10);
const users = [...world.users, ...world.servers.flatMap((s) => s.members)]
.map((u) => ({
item: u,
score: Math.max(
fuseLikeScore(q, u.name),
fuseLikeScore(q, u.handle),
fuseLikeScore(q, u.bio)
),
}))
.filter((r) => r.score > 0)
.sort((a, b) => b.score - a.score)
.slice(0, 10);
const rng = makeRNG(world.seed + 999);
const messages = Array.from({ length: 60 }, () => ({
id: crypto.randomUUID(),
from: pick(rng, world.users),
text: loremMsg(rng),
location: chance(rng, 0.6)
? {
type: "channel",
serverTitle: pick(rng, world.servers).title,
channel: pick(rng, CHANNELS),
}
: { type: "dm", peer: pick(rng, world.users).name },
ts: Date.now() - Math.floor(rng() * 1e9),
}))
.map((m) => ({
item: m,
score: fuseLikeScore(
q,
`${m.from.name} ${m.text} ${
m.location.type === "channel"
? m.location.serverTitle + " #" + m.location.channel
: m.location.peer
}`
),
}))
.filter((r) => r.score > 40)
.sort((a, b) => b.score - a.score)
.slice(0, 12);
return { servers, channels, users, messages };
}
// ----------------------------- UI Bits -------------------------------------
function Badge({ children }) {
return (
{children}
);
}
function Modal({ open, onClose, children, className = "" }) {
if (!open) return null;
return (
e.stopPropagation()}
>
{children}
);
}
function RailButton({ label, active, onClick }) {
return (
);
}
function MemberPill({ user, onOpenProfile }) {
return (
);
}
function ChannelRow({ channel, active, onClick }) {
return (
);
}
function MessageBubble({ m, me = false, onReact, onThread, onOpenProfile }) {
return (
);
}
function ProfileCard({ user }) {
if (!user) return null;
return (
{user.name}
{user.handle}
{user.bio}
AI native
Beta
Add Friend
Message
);
}
function SettingsPanel({
meName,
setMeName,
dark,
setDark,
bio,
setBio,
status,
setStatus,
}) {
return (
User Settings
Appearance
Dark mode
setDark((d) => !d)}
className="rounded-lg border border-neutral-700 px-2 py-1 text-xs"
>
{dark ? "On" : "Off"}
);
}
function Discovery({ servers, joined, onJoinToggle }) {
return (
{servers.map((s) => {
const isJoined = joined.has(s.id);
return (
{s.title}
{s.channels.length} channels · {s.members.length} members
{s.channels.slice(0, 4).map((ch) => (
#{ch.name}
))}
onJoinToggle(s.id, false)}
className={`rounded-lg border px-2 py-1 text-xs ${
isJoined
? "border-neutral-700 hover:border-rose-500"
: "border-indigo-700 text-indigo-300 hover:border-indigo-500"
}`}
>
{isJoined ? "Leave" : "Join"}
onJoinToggle(s.id, true)}
className="rounded-lg border border-neutral-700 px-2 py-1 text-xs hover:border-indigo-500"
>
Open
);
})}
);
}
function Section({ title, items, render }) {
return (
{title}
{items.length === 0 && (
No matches
)}
{items.map((r, i) => (
{render(r)}
))}
);
}
function Composer({ onSend, meName, setMeName }) {
const [val, setVal] = useState("");
const ref = useRef(null);
return (
);
}
function DMThread({ t, onSend, onOpenProfile }) {
const [draft, setDraft] = useState("");
return (
{
e.preventDefault();
onOpenProfile?.(t.peer);
}}
className="h-7 w-7 rounded-full"
style={{ background: avatar(t.peer.handle) }}
/>
{t.peer.name}
{t.peer.handle}
{t.messages.slice(-8).map((m) => (
))}
setDraft(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") {
onSend(t.id, draft);
setDraft("");
}
}}
placeholder={`Message ${t.peer.name}`}
className="flex-1 rounded-lg border border-neutral-300 bg-white px-2 py-1 text-sm outline-none dark:border-neutral-700 dark:bg-neutral-900"
/>
{
onSend(t.id, draft);
setDraft("");
}}
className="rounded-lg border border-neutral-300 bg-white px-2 py-1 text-xs shadow hover:bg-neutral-50 dark:border-neutral-700 dark:bg-neutral-900 dark:hover:bg-neutral-800"
>
);
}
// ----------------------------- Main App ------------------------------------
export default function AICord() {
const PREF_KEY = "aicord.session";
const [session] = useState(() => {
if (typeof window === "undefined") return null;
try {
return JSON.parse(localStorage.getItem(PREF_KEY) || "null");
} catch {
return null;
}
});
const [seed, setSeed] = useState(session?.seed ?? 42);
const world = useMemo(() => generateWorld(seed), [seed]);
const [meName, setMeName] = useState(session?.meName || "You");
const [status, setStatus] = useState(session?.status || "Online");
const [bio, setBio] = useState(session?.bio || "human among bots");
const me = useMemo(
() => ({ id: "me", name: meName, handle: "@you", bio }),
[meName, bio]
);
const [joined, setJoined] = useState(
() =>
new Set(
session?.joinedIds || world.servers.slice(0, 4).map((s) => s.id)
)
);
const [activeServerId, setActiveServerId] = useState(
session?.activeServerId ?? world.servers[0].id
);
const activeServer =
world.servers.find((s) => s.id === activeServerId) || world.servers[0];
const [activeChannelId, setActiveChannelId] = useState(
session?.activeChannelId ?? activeServer.channels[0].id
);
const activeChannel =
activeServer.channels.find((c) => c.id === activeChannelId) ||
activeServer.channels[0];
const [dark, setDark] = useState(session?.dark ?? true);
useEffect(() => {
document.documentElement.classList.toggle("dark", dark);
}, [dark]);
// Persist session
useEffect(() => {
if (typeof window === "undefined") return;
const s = {
seed,
activeServerId,
activeChannelId,
meName,
dark,
status,
bio,
joinedIds: [...joined],
};
localStorage.setItem(PREF_KEY, JSON.stringify(s));
}, [
seed,
activeServerId,
activeChannelId,
meName,
dark,
status,
bio,
joined,
]);
// Chat state
const rng = useMemo(
() => makeRNG(seed * 1337 + activeChannelId.length),
[seed, activeChannelId]
);
const [msgs, setMsgs] = useState(() =>
Array.from({ length: 15 }, () => ({
id: crypto.randomUUID(),
from: pick(rng, activeServer.members),
text: loremMsg(rng),
ts: Date.now() - Math.floor(Math.random() * 1e8),
}))
);
useEffect(() => {
setMsgs(
Array.from({ length: 15 }, () => ({
id: crypto.randomUUID(),
from: pick(rng, activeServer.members),
text: loremMsg(rng),
ts: Date.now() - Math.floor(Math.random() * 1e8),
}))
);
}, [activeServerId, activeChannelId]); // eslint-disable-line react-hooks/exhaustive-deps
const listRef = useRef(null);
useEffect(() => {
listRef.current?.scrollTo({ top: listRef.current.scrollHeight });
}, [msgs]);
function send(text) {
if (!text.trim()) return;
const now = Date.now();
const myMsg = { id: crypto.randomUUID(), from: me, text, ts: now };
setMsgs((m) => [...m, myMsg]);
// AI replies (1–3 messages)
const replies = 1 + Math.floor(Math.random() * 3);
const delay = 300 + Math.floor(Math.random() * 500);
for (let i = 0; i < replies; i++) {
setTimeout(() => {
const bot = pick(rng, activeServer.members);
const reply = {
id: crypto.randomUUID(),
from: bot,
text: loremMsg(rng),
ts: Date.now(),
};
setMsgs((m) => [...m, reply]);
}, delay * (i + 1));
}
}
function reactToMessage() {
// purely cosmetic hook; you could track reactions here if you want
}
function openThread() {
// placeholder for thread UI
}
// DMs
const [dmOpen, setDmOpen] = useState(false);
const [dmThreads, setDmThreads] = useState(() => world.dms);
function sendDM(threadId, text) {
if (!text.trim()) return;
setDmThreads((ts) =>
ts.map((t) =>
t.id === threadId
? {
...t,
messages: [
...t.messages,
{ id: crypto.randomUUID(), from: me, text, ts: Date.now() },
{
id: crypto.randomUUID(),
from: t.peer,
text: loremMsg(makeRNG(seed + threadId.length)),
ts: Date.now() + 200,
},
],
}
: t
)
);
}
// Discovery / Settings / Profiles
function joinToggle(id, open = false) {
setJoined((prev) => {
const n = new Set(prev);
if (n.has(id)) n.delete(id);
else n.add(id);
if (open) setActiveServerId(id);
return n;
});
}
const [showSearch, setShowSearch] = useState(false);
const [showDiscovery, setShowDiscovery] = useState(false);
const [openSettings, setOpenSettings] = useState(false);
const [profileUser, setProfileUser] = useState(null);
// Global search (Ctrl/Cmd-K)
const [q, setQ] = useState("");
const results = useMemo(() => searchWorld({ ...world }, q), [world, q]);
useEffect(() => {
function onKey(e) {
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "k") {
e.preventDefault();
setShowSearch((v) => !v);
}
}
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, []);
// Theme init
useEffect(() => {
document.documentElement.classList.add("dark");
}, []);
return (
{/* Top bar */}
AIcord
all AI, just you
{status}
setShowSearch(true)}
className="rounded-xl border border-neutral-300 bg-white px-2 py-1 text-sm shadow hover:bg-neutral-50 dark:border-neutral-700 dark:bg-neutral-900 dark:hover:bg-neutral-800"
>
setShowDiscovery(true)}
className="rounded-xl border border-neutral-300 bg-white px-2 py-1 text-sm shadow hover:bg-neutral-50 dark:border-neutral-700 dark:bg-neutral-900 dark:hover:bg-neutral-800"
>
setOpenSettings(true)}
className="rounded-xl border border-neutral-300 bg-white px-2 py-1 text-sm shadow hover:bg-neutral-50 dark:border-neutral-700 dark:bg-neutral-900 dark:hover:bg-neutral-800"
>
setDark((d) => !d)}
className="rounded-xl border border-neutral-300 bg-white px-2 py-1 text-sm shadow hover:bg-neutral-50 dark:border-neutral-700 dark:bg-neutral-900 dark:hover:bg-neutral-800"
>
{dark ? : }
setSeed(Math.floor(Math.random() * 1e9))}
className="inline-flex items-center gap-1 rounded-xl border border-indigo-300 bg-indigo-50 px-3 py-1 text-xs font-medium text-indigo-700 shadow-sm hover:bg-indigo-100 dark:border-indigo-800 dark:bg-indigo-900/40 dark:text-indigo-300 dark:hover:bg-indigo-900/60"
>
Regenerate world
{/* Left rail: joined servers */}
{world.servers
.filter((s) => joined.has(s.id))
.map((s) => (
{
setActiveServerId(s.id);
setActiveChannelId(s.channels[0].id);
}}
/>
))}
setShowDiscovery(true)}
className="m-2 flex h-12 w-12 items-center justify-center rounded-2xl bg-neutral-200/70 transition hover:scale-105 dark:bg-neutral-800"
>
{/* Channel list */}
{activeServer.channels.map((ch) => (
setActiveChannelId(ch.id)}
/>
))}
Topic:{" "}
#{activeChannel.name}
{" "}
· {activeChannel.topic}
{/* Chat */}
{activeChannel.name}
{activeServer.title}
setDmOpen((o) => !o)}
className="rounded-lg px-2 py-1 text-sm hover:bg-neutral-100 dark:hover:bg-neutral-800"
>
DMs
{msgs.map((m) => (
))}
setMsgs((m) => [
...m,
...Array.from({ length: 10 }, () => ({
id: crypto.randomUUID(),
from: pick(rng, activeServer.members),
text: loremMsg(rng),
ts:
Date.now() -
Math.floor(Math.random() * 1e8),
})),
])
}
className="rounded-xl border border-neutral-300 bg-white px-3 py-1 text-xs shadow hover:bg-neutral-50 dark:border-neutral-700 dark:bg-neutral-900 dark:hover:bg-neutral-800"
>
Load more
{/* Members / right rail */}
{activeServer.members.slice(0, 30).map((m) => (
))}
{/* DMs Drawer */}
{dmOpen && (
Direct Messages
setDmOpen(false)}
className="rounded-lg p-1 hover:bg-neutral-100 dark:hover:bg-neutral-800"
>
{dmThreads.map((t) => (
))}
)}
{/* Profile Modal */}
setProfileUser(null)}
>
{/* Settings Modal */}
setOpenSettings(false)}
>
{/* Discovery Modal */}
setShowDiscovery(false)}
>
{/* Search Modal */}
{showSearch && (
setQ(e.target.value)}
placeholder="Search servers, channels, users, messages…"
className="w-full bg-transparent py-2 text-sm outline-none placeholder:text-neutral-500"
/>
setShowSearch(false)}
className="rounded-lg p-1 hover:bg-neutral-800"
>
(
{r.item.title}
{r.item.channels.length} channels
)}
/>
(
#{r.item.name}
{r.item.serverTitle}
)}
/>
(
setProfileUser(r.item)}
className="flex w-full items-center gap-2 rounded-xl border border-neutral-800/70 p-3 text-left hover:border-indigo-700/60"
>
{r.item.name}
{r.item.handle}
)}
/>
(
{r.item.text}
{r.item.location.type === "channel"
? `${r.item.location.serverTitle} • #${r.item.location.channel}`
: `DM • ${r.item.location.peer}`}
)}
/>
)}
);
}