Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cozy UI #274

Closed
wants to merge 13 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,4 @@ dist

convex-local*
convex_local*
.aider*
296 changes: 156 additions & 140 deletions ARCHITECTURE.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# AI Town 🏠💻💌
# Cozy Cafe 🏠💻💌

[Live Demo](https://www.convex.dev/ai-town)

[Join our community Discord: AI Stack Devs](https://discord.gg/PQUmTBTGmT)

<img width="1454" alt="Screen Shot 2023-08-14 at 10 01 00 AM" src="https://github.com/a16z-infra/ai-town/assets/3489963/a4c91f17-23ed-47ec-8c4e-9f9a8505057d">

AI Town is a virtual town where AI characters live, chat and socialize.
Cozy Cafe is a virtual town where AI characters live, chat and socialize.

This project is a deployable starter kit for easily building and customizing your own version of AI
town. Inspired by the research paper
Expand Down
10 changes: 5 additions & 5 deletions fly/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Hosting AI Town on Fly.io
# Hosting Cozy Cafe on Fly.io

Fly.io makes it easy to deploy containers to the cloud.

Expand Down Expand Up @@ -26,7 +26,7 @@ git clone https://github.com/ai-town/ai-town.git
npx convex deploy
```

3. Deploy the AI Town frontend and point it to the Convex backend.
3. Deploy the Cozy Cafe frontend and point it to the Convex backend.

You can get the convex url from the output of the `npx convex deploy` command. Or you can get it
from the [Convex dashboard](https://dashboard.convex.dev/deployment/settings) listed as
Expand Down Expand Up @@ -94,8 +94,8 @@ If you want to self-host the Convex backend on Fly.io, you can follow these step

This admin key will be used to authorize the CLI and access the dashboard.

4. In the root directory of the AI Town repository (`cd ../..`), create a `.env.local` file with the
following variables:
4. In the root directory of the Cozy Cafe repository (`cd ../..`), create a `.env.local` file with
the following variables:

```sh
CONVEX_SELF_HOSTED_URL="<fly-backend-url>"
Expand All @@ -104,7 +104,7 @@ If you want to self-host the Convex backend on Fly.io, you can follow these step

5. Deploy your Convex functions to the backend using the `convex` CLI from the project root.

To deploy the AI Town functions to the backend and start the game engine:
To deploy the Cozy Cafe functions to the backend and start the game engine:

```sh
npx convex dev --run init --once
Expand Down
7 changes: 2 additions & 5 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,8 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/x-icon" sizes="any" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>AI Town</title>
<meta
name="description"
content="A virtual town where AI characters live, chat and socialize"
/>
<title>Cozy Cafe</title>
<meta name="description" content="A virtual cafe AI characters work and socialize" />
<script defer data-domain="convex.dev" src="https://plausible.io/js/script.js"></script>
</head>
<body>
Expand Down
63 changes: 31 additions & 32 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Game from './components/Game.tsx';

import { ToastContainer } from 'react-toastify';
import a16zImg from '../assets/a16z.png';
import convexImg from '../assets/convex.svg';
// Removed convex image import
import starImg from '../assets/star.svg';
import helpImg from '../assets/help.svg';
// import { UserButton } from '@clerk/clerk-react';
Expand All @@ -15,13 +15,13 @@ import Button from './components/buttons/Button.tsx';
import InteractButton from './components/buttons/InteractButton.tsx';
import FreezeButton from './components/FreezeButton.tsx';
import { MAX_HUMAN_PLAYERS } from '../convex/constants.ts';
import PoweredByConvex from './components/PoweredByConvex.tsx';
// Removed PoweredByConvex import

export default function Home() {
const [helpModalOpen, setHelpModalOpen] = useState(false);
return (
<main className="relative flex min-h-screen flex-col items-center justify-between font-body game-background">
<PoweredByConvex />
<main className="relative flex h-screen flex-col items-center justify-between font-body game-background">
{/* Removed PoweredByConvex component */}

<ReactModal
isOpen={helpModalOpen}
Expand All @@ -33,7 +33,7 @@ export default function Home() {
<div className="font-body">
<h1 className="text-center text-6xl font-bold font-display game-title">Help</h1>
<p>
Welcome to AI town. AI town supports both anonymous <i>spectators</i> and logged in{' '}
Welcome to Cozy Cafe. Cozy Cafe supports both anonymous <i>spectators</i> and logged in{' '}
<i>interactivity</i>.
</p>
<h2 className="text-4xl mt-4">Spectating</h2>
Expand All @@ -57,7 +57,7 @@ export default function Home() {
in the messages panel.
</p>
<p className="mt-4">
AI town only supports {MAX_HUMAN_PLAYERS} humans at a time. If you're idle for five
Cozy Cafe only supports {MAX_HUMAN_PLAYERS} humans at a time. If you're idle for five
minutes, you'll be automatically removed from the simulation.
</p>
</div>
Expand All @@ -72,12 +72,12 @@ export default function Home() {
</Unauthenticated>
</div> */}

<div className="w-full lg:h-screen min-h-screen relative isolate overflow-hidden lg:p-8 shadow-2xl flex flex-col justify-start">
<h1 className="mx-auto text-4xl p-3 sm:text-8xl lg:text-9xl font-bold font-display leading-none tracking-wide game-title w-full text-left sm:text-center sm:w-auto">
AI Town
<div className="absolute inset-0 flex flex-col z-10">
<h1 className="mx-auto text-2xl p-3 sm:text-4xl lg:text-5xl font-bold font-display leading-none tracking-wide game-title w-full text-left sm:text-center sm:w-auto">
Cozy Cafe
</h1>

<div className="max-w-xs md:max-w-xl lg:max-w-none mx-auto my-4 text-center text-base sm:text-xl md:text-2xl text-white leading-tight shadow-solid">
<div className="max-w-xs md:max-w-xl lg:max-w-none mx-auto mb-2 text-center text-base sm:text-xl md:text-2xl text-white leading-tight shadow-solid">
A virtual town where AI characters live, chat and socialize.
{/* <Unauthenticated>
<div className="my-1.5 sm:my-0" />
Expand All @@ -86,28 +86,27 @@ export default function Home() {
</Unauthenticated> */}
</div>

<Game />

<footer className="justify-end bottom-0 left-0 w-full flex items-center mt-4 gap-3 p-6 flex-wrap pointer-events-none">
<div className="flex gap-4 flex-grow pointer-events-none">
<FreezeButton />
<MusicButton />
<Button href="https://github.com/a16z-infra/ai-town" imgUrl={starImg}>
Star
</Button>
<InteractButton />
<Button imgUrl={helpImg} onClick={() => setHelpModalOpen(true)}>
Help
</Button>
</div>
<a href="https://a16z.com">
<img className="w-8 h-8 pointer-events-auto" src={a16zImg} alt="a16z" />
</a>
<a href="https://convex.dev/c/ai-town">
<img className="w-20 h-8 pointer-events-auto" src={convexImg} alt="Convex" />
</a>
</footer>
<ToastContainer position="bottom-right" autoClose={2000} closeOnClick theme="dark" />
<div className="flex-grow relative">
<Game />
<footer className="absolute justify-end bottom-0 left-0 w-full flex items-center gap-3 p-6 flex-wrap pointer-events-none">
<div className="flex gap-4 flex-grow pointer-events-none">
<FreezeButton />
<MusicButton />
<Button href="https://github.com/a16z-infra/cozy-cafe" imgUrl={starImg}>
Star
</Button>
<InteractButton />
<Button imgUrl={helpImg} onClick={() => setHelpModalOpen(true)}>
Help
</Button>
</div>
<a href="https://a16z.com">
<img className="w-8 h-8 pointer-events-auto" src={a16zImg} alt="a16z" />
</a>
{/* Removed Convex logo */}
</footer>
<ToastContainer position="bottom-right" autoClose={2000} closeOnClick theme="dark" />
</div>
</div>
</main>
);
Expand Down
17 changes: 13 additions & 4 deletions src/components/Game.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useRef, useState } from 'react';
import { useRef, useState, useEffect } from 'react';
import PixiGame from './PixiGame.tsx';

import { useElementSize } from 'usehooks-ts';
Expand All @@ -11,6 +11,7 @@ import { useHistoricalTime } from '../hooks/useHistoricalTime.ts';
import { DebugTimeManager } from './DebugTimeManager.tsx';
import { GameId } from '../../convex/aiTown/ids.ts';
import { useServerGame } from '../hooks/serverGame.ts';
import LoadingSpinner from './LoadingSpinner';

export const SHOW_DEBUG_UI = !!import.meta.env.VITE_SHOW_DEBUG_UI;

Expand All @@ -36,13 +37,21 @@ export default function Game() {

const scrollViewRef = useRef<HTMLDivElement>(null);

const [isLoading, setIsLoading] = useState(true);

useEffect(() => {
if (worldId && engineId && game) {
setIsLoading(false);
}
}, [worldId, engineId, game]);

if (!worldId || !engineId || !game) {
return null;
return <LoadingSpinner />;
}
return (
<>
{SHOW_DEBUG_UI && <DebugTimeManager timeManager={timeManager} width={200} height={100} />}
<div className="mx-auto w-full max-w grid grid-rows-[240px_1fr] lg:grid-rows-[1fr] lg:grid-cols-[1fr_auto] lg:grow max-w-[1400px] min-h-[480px] game-frame">
<div className="mx-auto w-full h-full grid grid-rows-[1fr] lg:grid-cols-[1fr_auto] game-frame">
{/* Game area */}
<div className="relative overflow-hidden bg-brown-900" ref={gameWrapperRef}>
<div className="absolute inset-0">
Expand All @@ -67,7 +76,7 @@ https://github.com/michalochman/react-pixi-fiber/issues/145#issuecomment-5315492
</div>
{/* Right column area */}
<div
className="flex flex-col overflow-y-auto shrink-0 px-4 py-6 sm:px-6 lg:w-96 xl:pr-6 border-t-8 sm:border-t-0 sm:border-l-8 border-brown-900 bg-brown-800 text-brown-100"
className="flex flex-col overflow-y-auto shrink-0 px-4 py-6 sm:px-6 lg:w-80 xl:w-96 xl:pr-6 border-t-8 sm:border-t-0 sm:border-l-8 border-brown-900 bg-brown-800 text-brown-100"
ref={scrollViewRef}
>
<PlayerDetails
Expand Down
7 changes: 7 additions & 0 deletions src/components/LoadingSpinner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function LoadingSpinner() {
return (
<div className="absolute inset-0 flex items-center justify-center">
<div className="animate-spin rounded-full h-32 w-32 border-t-4 border-b-4 border-white"></div>
</div>
);
}
48 changes: 1 addition & 47 deletions src/components/PoweredByConvex.tsx
Original file line number Diff line number Diff line change
@@ -1,50 +1,4 @@
import bannerBg from '../../assets/convex-bg.webp';
export default function PoweredByConvex() {
return (
<a
href="https://convex.dev/c/ai-town"
target="_blank"
className="group absolute top-0 left-0 w-64 h-64 md:block z-10 hidden shape-top-left-corner overflow-hidden"
aria-label="Powered by Convex"
>
<img
src={bannerBg}
className="absolute inset-0 scale-[1.2] -translate-x-6 group-hover:scale-[1.6] transition-transform"
alt=""
/>

<div className="absolute inset-0 bg-black opacity-0 group-hover:opacity-10 transition-opacity"></div>

<div className="absolute inset-0 flex p-6">
<div className="flex flex-col gap-1 items-center">
<span className="font-system font-medium uppercase tracking-wider text-stone-600">
Powered by
</span>
<svg
width="126"
height="20"
viewBox="0 0 126 20"
fill="black"
xmlns="http://www.w3.org/2000/svg"
>
<title>Convex</title>
<g clip-path="url(#clip0_5_2)">
<path d="M3.18483 17.4674C1.30005 15.782 0.357666 13.2908 0.357666 10.0003C0.357666 6.70977 1.31835 4.2186 3.24278 2.53321C5.16415 0.847812 7.79308 0.00350952 11.1265 0.00350952C12.5111 0.00350952 13.7341 0.103028 14.7985 0.308486C15.8629 0.510733 16.8815 0.854231 17.8544 1.34219V6.68088C16.3417 5.92646 14.6246 5.54765 12.7033 5.54765C11.0106 5.54765 9.76021 5.88473 8.95506 6.55889C8.14686 7.23304 7.74429 8.37911 7.74429 10.0003C7.74429 11.5669 8.14076 12.7001 8.93676 13.4C9.72971 14.103 10.9862 14.453 12.7063 14.453C14.527 14.453 16.2563 14.0067 17.8971 13.1175V18.7034C16.0763 19.5669 13.8073 19.9971 11.0899 19.9971C7.70159 19.9971 5.06961 19.1528 3.18483 17.4674Z" />
<path d="M19.538 9.99679C19.538 6.73194 20.4224 4.2504 22.1913 2.54896C23.9602 0.847512 26.6257 0 30.1909 0C33.7805 0 36.4644 0.850722 38.2485 2.54896C40.0296 4.24719 40.9201 6.73194 40.9201 9.99679C40.9201 16.6613 37.3427 19.9936 30.1909 19.9936C23.0879 19.9968 19.538 16.6645 19.538 9.99679ZM32.7497 13.3997C33.2743 12.6966 33.5365 11.5634 33.5365 10C33.5365 8.46228 33.2743 7.33547 32.7497 6.61958C32.2251 5.90369 31.3712 5.54735 30.1909 5.54735C29.0381 5.54735 28.2024 5.9069 27.6901 6.61958C27.1777 7.33547 26.9215 8.46228 26.9215 10C26.9215 11.5666 27.1777 12.6998 27.6901 13.3997C28.2024 14.1027 29.035 14.4526 30.1909 14.4526C31.3712 14.4526 32.2221 14.0995 32.7497 13.3997Z" />
<path d="M42.6029 0.404494H49.3704L49.5626 1.86196C50.3067 1.32263 51.2552 0.876404 52.408 0.526485C53.5608 0.176565 54.7533 0 55.9854 0C58.2667 0 59.9319 0.5939 60.9841 1.7817C62.0363 2.9695 62.5608 4.80257 62.5608 7.28732V19.5923H55.3328V8.05458C55.3328 7.19101 55.1467 6.57143 54.7747 6.19262C54.4026 5.8138 53.7804 5.62761 52.9082 5.62761C52.3714 5.62761 51.8194 5.75602 51.2552 6.01284C50.691 6.26966 50.2183 6.60032 49.8309 7.00482V19.5923H42.6029V0.404494Z" />
<path d="M62.5818 0.404617H70.1178L73.5794 11.6566L77.0409 0.404617H84.5769L77.3855 19.5924H69.7702L62.5818 0.404617Z" />
<path d="M86.8523 17.9422C84.6809 16.2279 83.6653 13.252 83.6653 10.0385C83.6653 6.90851 84.4735 4.33066 86.3186 2.54896C88.1637 0.767255 90.9757 0 94.5256 0C97.792 0 100.36 0.796147 102.236 2.38844C104.108 3.98074 105.047 6.15409 105.047 8.9053V12.2665H91.302C91.6436 13.2648 92.0766 13.9872 93.141 14.4334C94.2054 14.8796 95.6907 15.1011 97.5907 15.1011C98.7252 15.1011 99.8841 15.008 101.061 14.8186C101.476 14.7512 102.159 14.6453 102.519 14.565V19.2295C100.723 19.7432 98.3287 20 95.6297 20C91.9973 19.9968 89.0238 19.6565 86.8523 17.9422ZM97.4534 8.13804C97.4534 7.1878 96.4135 5.14286 94.3243 5.14286C92.4396 5.14286 91.1952 7.1557 91.1952 8.13804H97.4534Z" />
<path d="M110.723 9.8364L103.955 0.404617H111.799L125.642 19.5924H117.722L114.645 15.3003L111.567 19.5924H103.684L110.723 9.8364Z" />
<path d="M117.548 0.404617H125.356L119.363 8.8059L115.398 3.42227L117.548 0.404617Z" />
</g>
<defs>
<clipPath id="clip0_5_2">
<rect width="126" height="20" />
</clipPath>
</defs>
</svg>
</div>
</div>
</a>
);
return null;
}
6 changes: 3 additions & 3 deletions src/components/buttons/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ export default function Button(props: {
return (
<a
className={clsx(
'button text-white shadow-solid text-xl pointer-events-auto',
'button text-white shadow-solid text-xl pointer-events-auto inline-flex items-center',
props.className,
)}
href={props.href}
title={props.title}
onClick={props.onClick}
>
<div className="inline-block bg-clay-700">
<span>
<div className="inline-block bg-clay-700 w-full h-full">
<span className="block w-full h-full">
<div className="inline-flex h-full items-center gap-4">
<img className="w-4 h-4 sm:w-[30px] sm:h-[30px]" src={props.imgUrl} />
{props.children}
Expand Down
28 changes: 19 additions & 9 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ body {

.game-background {
background: linear-gradient(rgba(41, 41, 41, 0.8), rgba(41, 41, 41, 0.8)),
url(../assets/background.webp);
url(/assets/background.webp);
background-blend-mode: hard-light;
background-position: center;
background-repeat: no-repeat;
Expand All @@ -64,7 +64,7 @@ body {

.game-frame {
border-width: 36px;
border-image-source: url(../assets/ui/frame.svg);
border-image-source: url(/assets/ui/frame.svg);
border-image-repeat: stretch;
border-image-slice: 25%;
}
Expand Down Expand Up @@ -98,49 +98,59 @@ body {

.bubble {
border-width: 30px;
border-image-source: url(../assets/ui/bubble-left.svg);
border-image-source: url(/assets/ui/bubble-left.svg);
border-image-repeat: stretch;
border-image-slice: 20%;
}

.bubble-mine {
border-image-source: url(../assets/ui/bubble-right.svg);
border-image-source: url(/assets/ui/bubble-right.svg);
}

.box {
border-width: 12px;
border-image-source: url(../assets/ui/box.svg);
border-image-source: url(/assets/ui/box.svg);
border-image-repeat: stretch;
border-image-slice: 12.5%;
}

.desc {
border-width: 56px;
border-image-source: url(../assets/ui/desc.svg);
border-image-source: url(/assets/ui/desc.svg);
border-image-repeat: stretch;
border-image-slice: 28%;
}

.chats {
border-width: 24px;
border-image-source: url(../assets/ui/chats.svg);
border-image-source: url(/assets/ui/chats.svg);
border-image-repeat: stretch;
border-image-slice: 40%;
}

.login-prompt {
border-width: 48px;
border-image-source: url(../assets/ui/jewel_box.svg);
border-image-source: url(/assets/ui/jewel_box.svg);
border-image-repeat: stretch;
border-image-slice: 40%;
}

.button {
border-width: 1em;
border-image-source: url(../assets/ui/button.svg);
border-image-source: url(/assets/ui/button.svg);
border-image-repeat: stretch;
border-image-slice: 25%;
cursor: pointer;
display: inline-flex;
align-items: center;
padding: 0.25em 0.5em;
background-color: transparent;
}

.button > div {
width: 100%;
height: 100%;
background-color: #3a4466;
}

.button span {
Expand Down