# Project export: Narrative.ai

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: TreeHacks 2024
- Tagline: Collective story-craft.
- Devpost: https://devpost.com/software/narrative-ai
- GitHub: https://github.com/RK22000/Narative.IO
- Team: 2 GitHub contributor(s) — Ben Fu (12 commits), Rahul (10 commits)

## Devpost submission (written by the team)

### Inspiration

Struck by the affective power of 2000s-era visual novels (ノベルゲーム), forged at the now-global intersection between the abject alienation at the heart of the individual subject and the novel totality of closely-knit social relations that constitute the subject as such—hence, moe—we set out to build a new take—but with a taste of the familiar—on the formal unity of the visual novel genre. Eliezer Yudkowsky remarks somewhere that ours is, so to speak, the world-historic era of AI-alignment. For him, one divides into two: a road diverges into an either/or, between our self-destruction and descent into the dystopia of Roko and Ginsberg's terrible Moloch or the messianic reclamation of ourselves. We cannot disavow the creative destruction of the unspeakable void that reproduces us but instead must—together—work through the malaise of alienation as such. This collective VN project comes as a kind of a preliminary answer to this animating problematic of our time.

### What it does

Narrative.ai is a collaborative story-crafting web-game powered by Convex, Together.ai, and React. Players contribute to the narrativic process by penning their own ideas for how the ongoing narrative may proceed, and when prompted, Narrative.ai processes the players' collaborative outline and generates the next chapter in the narrative, from where players are re-immersed into the collective, practical activity of storytelling. In this way, Narrative.ai mirrors the ancient storytelling traditions of the Homeric epics and their process of coming-into-being, re-casting and re-articulating them in a novel light.

### How we built it

Narrative.ai is powered by Convex, Together.ai, and React.

### Challenges we ran into

We worked with new and unfamiliar languages, APIs, and frameworks—all an opportunity to learn and gain practical experience from tinkering, as well as mentorship from sponsors and organizers. One of our teammates fell ill and dropped out! We shrank from three to a duo, and were forced towards a regime of relative austerity on our productive capacity. We worked through three separate ideas, until settling on the visual novel form not too long from the deadline! Sleep deprivation.

### Accomplishments we're proud of

We are proud of having ironed out a working project, built from previously unfamiliar components, that is delightful to play with. We understand that old adage, that there is no royal road to science...

### What we learned

We learned the basics of Convex and how to use it for backend development. We learned how to use Together.ai's LLM models and how to integrate it into a full-stack web development project.

### What's next

We hope to extend the visual storytelling aspect of Narrative.ai to collaboratively generated images, as well as introducing new ludic features such as a system of competition—such as points-keeping, based on uniqueness of prompt.

## README (from the GitHub repository)

# Narrative.ai

![Narrativeai](narrativeai.png)
[Devpost](https://devpost.com/software/narrative-ai)

## Inspiration

Struck by the affective power of 2000s-era visual novels (ノベルゲーム), forged at the now-global intersection between the abject alienation at the heart of the individual subject and the novel totality of closely-knit social relations that constitute the subject as such—hence, moe—we set out to build a new take—but with a taste of the familiar—on the formal unity of the visual novel genre. Eliezer Yudkowsky remarks somewhere that ours is, so to speak, the world-historic era of AI-alignment. For him, one divides into two: a road diverges into an either/or, between our self-destruction and descent into the dystopia of Roko and Ginsberg's terrible Moloch or the messianic reclamation of ourselves. We cannot disavow the creative destruction of the unspeakable void that reproduces us but instead must—together—work through the malaise of alienation as such. This collective VN project comes as a kind of a preliminary answer to this animating problematic of our time.

## What it does

Narrative.ai is a collaborative story-crafting web-game powered by Convex, Together.ai, and React. Players contribute to the narrativic process by penning their own ideas for how the ongoing narrative may proceed, and when prompted, Narrative.ai processes the players' collaborative outline and generates the next chapter in the narrative, from where players are re-immersed into the collective, practical activity of storytelling. In this way, Narrative.ai mirrors the ancient storytelling traditions of the Homeric epics and their process of coming-into-being, re-casting and re-articulating them in a novel light.

## How we built it

Narrative.ai is powered by Convex, Together.ai, and React.

## Challenges we ran into

- We worked with new and unfamiliar languages, APIs, and frameworks—all an opportunity to learn and gain practical experience from tinkering, as well as mentorship from sponsors and organizers.
One of our teammates fell ill and dropped out! We shrank from three to a duo, and were forced towards a regime of relative austerity on our productive capacity. We worked through three separate ideas, until settling on the visual novel form not too long from the deadline!
- Sleep deprivation.

## Accomplishments that we're proud of

We are proud of having ironed out a working project, built from previously unfamiliar components, that is delightful to play with. We understand that old adage, that there is no royal road to science...

## What we learned

We learned the basics of Convex and how to use it for backend development.
We learned how to use Together.ai's LLM models and how to integrate it into a full-stack web development project.


## What's next for Narrative.ai

We hope to add more new features to the visual storytelling aspect of Narrative.ai, as well as introducing new ludic features such as a system of competition—such as points-keeping, based on uniqueness of prompt.


### Credits
- convex-hacks-pack repo for starting off our hack


## Detected evidence (automated analysis)

Indexed codebase: 27 recognized source files, 44 KB.
- CSS (language) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- OpenAI (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (35 of 35)

```
_gitignore
.eslintrc.cjs
.gitignore
components.json
convex/_generated/api.d.ts
convex/_generated/api.js
convex/_generated/dataModel.d.ts
convex/_generated/server.d.ts
convex/_generated/server.js
convex/_schema.ts
convex/campingFunctions.ts
convex/LLMcalls.ts
convex/myFunctions.ts
convex/README.md
convex/tsconfig.json
index.html
package.json
postcss.config.js
README.md
src/App.tsx
src/Camp.tsx
src/components/typography/code.tsx
src/components/typography/link.tsx
src/components/ui/button.tsx
src/components/ui/checkbox.tsx
src/components/ui/input.tsx
src/components/ui/label.tsx
src/index.css
src/lib/utils.tsx
src/main.tsx
src/vite-env.d.ts
tailwind.config.js
tsconfig.json
tsconfig.node.json
vite.config.ts
```

### Dependencies

- package.json: @hookform/resolvers@^3.3.4, @nextui-org/react@^2.2.9, @radix-ui/react-checkbox@^1.0.4, @radix-ui/react-icons@^1.3.0, @radix-ui/react-label@^2.0.2, @radix-ui/react-slot@^1.0.2, @types/node@^20.11.19, @types/react@^18.2.55, @types/react-dom@^18.2.19, @typescript-eslint/eslint-plugin@^7.0.1, @typescript-eslint/parser@^7.0.1, @vitejs/plugin-react@^4.2.1, autoprefixer@^10.4.17, class-variance-authority@^0.7.0, clsx@^2.1.0, cmdk@^0.2.1, convex@^1.9.0, date-fns@^3.3.1, dotenv@^16.4.4, eslint@^8.56.0, eslint-plugin-react-hooks@^4.6.0, eslint-plugin-react-refresh@^0.4.5, framer-motion@^11.0.5, npm-run-all@^4.1.5, postcss@^8.4.35, react@^18.2.0, react-day-picker@^8.10.0, react-dom@^18.2.0, react-hook-form@^7.50.1, tailwind-merge@^2.2.1, tailwindcss@^3.4.1, tailwindcss-animate@^1.0.7, together-ai-sdk@^0.0.7, typescript@^5.3.3, vite@^5.1.3, zod@^3.22.4

### Recent commits (newest first)

- Update README.md
- Cleared all strict errors
- Updated environment variables and stuff
- Saving to push to vercel
- Cleaning merge conflict
- Saving to deploy to vercel
- Added UI and Backend stuff
- Update imports in api.d.ts
- Update README.md
- Update README.md
- Update README.md
- Update README.md
- Update README.md
- Update README.md
- Update README.md
- Update README.md
- Add files via upload
- Update README.md
- Saving before using a typesript api for together ai
- Stoping exploring nextUI

## Key source files (fetched from GitHub, selected and truncated for size)

### package.json

```
{
  "name": "convex-hack-pack",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "npm-run-all dev:init --parallel dev:frontend dev:backend",
    "dev:frontend": "vite --open",
    "dev:backend": "convex dev",
    "dev:init": "convex dev --until-success && convex dashboard",
    "build": "tsc && vite build",
    "lint": "eslint . --ext ts,tsx ",
    "preview": "vite preview"
  },
  "dependencies": {
    "@hookform/resolvers": "^3.3.4",
    "@nextui-org/react": "^2.2.9",
    "@radix-ui/react-checkbox": "^1.0.4",
    "@radix-ui/react-icons": "^1.3.0",
    "@radix-ui/react-label": "^2.0.2",
    "@radix-ui/react-slot": "^1.0.2",
    "class-variance-authority": "^0.7.0",
    "clsx": "^2.1.0",
    "cmdk": "^0.2.1",
    "convex": "^1.9.0",
    "date-fns": "^3.3.1",
    "dotenv": "^16.4.4",
    "framer-motion": "^11.0.5",
    "react": "^18.2.0",
    "react-day-picker": "^8.10.0",
    "react-dom": "^18.2.0",
    "react-hook-form": "^7.50.1",
    "tailwind-merge": "^2.2.1",
    "tailwindcss-animate": "^1.0.7",
    "together-ai-sdk": "^0.0.7",
    "zod": "^3.22.4"
  },
  "devDependencies": {
    "@types/node": "^20.11.19",
    "@types/react": "^18.2.55",
    "@types/react-dom": "^18.2.19",
    "@typescript-eslint/eslint-plugin": "^7.0.1",
    "@typescript-eslint/parser": "^7.0.1",
    "@vitejs/plugin-react": "^4.2.1",
    "autoprefixer": "^10.4.17",
    "eslint": "^8.56.0",
    "eslint-plugin-react-hooks": "^4.6.0",
    "eslint-plugin-react-refresh": "^0.4.5",
    "npm-run-all": "^4.1.5",
    "postcss": "^8.4.35",
    "tailwindcss": "^3.4.1",
    "typescript": "^5.3.3",
    "vite": "^5.1.3"
  }
}

```

### src/main.tsx

```typescript
import { ConvexProvider, ConvexReactClient } from "convex/react";
import React from "react";
import ReactDOM from "react-dom/client";
// import App from "./App";
import Camp from "./Camp";
import "./index.css";

const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL as string);

ReactDOM.createRoot(document.getElementById("root")!).render(
  <div>
    <React.StrictMode>
      <ConvexProvider client={convex}>
        {/* <App /> */}
        <Camp />
      </ConvexProvider>
    </React.StrictMode>,
  </div>
);

```

### src/App.tsx

```typescript
import { useAction, useMutation, useQuery } from "convex/react";
import { api } from "../convex/_generated/api";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Checkbox } from "@/components/ui/checkbox";
import { Label } from "@/components/ui/label";

function App() {
  const [newIdea, setNewIdea] = useState("");
  const [includeRandom, setIncludeRandom] = useState(true);

  const ideas = useQuery(api.myFunctions.listIdeas);
  const saveIdea = useMutation(api.myFunctions.saveIdea);
  const generateIdea = useAction(api.myFunctions.fetchRandomIdea);

  // const deleteAllIdeas = useAction(api.myFunctions.clearIdeasTable);

  return (
    <>
      <main className="container max-w-2xl flex flex-col gap-8">
        <h1 className="text-3xl font-extrabold mt-8 text-center">
          HELLO GUYS
        </h1>

        <h2 className="text-center">Let's brainstorm apps to build!</h2>

        <form className="flex gap-2">
          <Input
            type="text"
            value={newIdea}
            onChange={(event) => setNewIdea(event.target.value)}
            placeholder="Type your app idea here"
          />
          <Button
            type="submit"
            disabled={!newIdea}
            title={
              newIdea
                ? "Save your idea to the database"
                : "You must enter an idea first"
            }
            onClick={async (e) => {
              e.preventDefault();
              await saveIdea({ idea: newIdea.trim(), random: false });
              setNewIdea("");
            }}
            className="min-w-fit"
          >
            Save idea
          </Button>
        </form>

        <div className="flex justify-between items-center">
          <Button
            onClick={async () => {
              await generateIdea();
            }}
            title="Save a randomly generated app idea to the database"
          >
            Generate a random app idea
          </Button>

          <div
            className="flex gap-2"
            title="Uh oh, this checkbox doesn't work! Until we fix it ;)"
          >
            <Checkbox
              id="show-random"
              checked={includeRandom}
              onCheckedChange={() => setIncludeRandom(!includeRandom)}
            />
            <Label htmlFor="show-random">Include random ideas</Label>
          </div>
        </div>

        <ul>
          {ideas?.map((document, i) => (
            <li key={i}>
              {document.random ? "🤖 " : "💡 "}
              {document.idea}
            </li>
          ))}
        </ul>
      </main>
      <footer className="text-center text-xs mb-5 mt-10 w-full">
        <p>
          Built with <a href="https://convex.dev">Convex</a>,{" "}
          <a href="https://www.typescriptlang.org">TypeScript</a>,{" "}
          <a href="https://react.dev">React</a>, and{" "}
          <a href="https://vitejs.dev">Vite</a>
        </p>
        <p>
          Random app ideas thanks to{" "}
          <a target="_blank" href="https://appideagenerator.com/">
            appideagenerator.com
          </a>
        </p>
      </footer>
    </>
  );
}

export default App;

```

### convex/_generated/server.js

```javascript
/* eslint-disable */
/**
 * Generated utilities for implementing server-side Convex query and mutation functions.
 *
 * THIS CODE IS AUTOMATICALLY GENERATED.
 *
 * Generated by convex@1.9.0.
 * To regenerate, run `npx convex dev`.
 * @module
 */

import {
  actionGeneric,
  httpActionGeneric,
  queryGeneric,
  mutationGeneric,
  internalActionGeneric,
  internalMutationGeneric,
  internalQueryGeneric,
} from "convex/server";

/**
 * Define a query in this Convex app's public API.
 *
 * This function will be allowed to read your Convex database and will be accessible from the client.
 *
 * @param func - The query function. It receives a {@link QueryCtx} as its first argument.
 * @returns The wrapped query. Include this as an `export` to name it and make it accessible.
 */
export const query = queryGeneric;

/**
 * Define a query that is only accessible from other Convex functions (but not from the client).
 *
 * This function will be allowed to read from your Convex database. It will not be accessible from the client.
 *
 * @param func - The query function. It receives a {@link QueryCtx} as its first argument.
 * @returns The wrapped query. Include this as an `export` to name it and make it accessible.
 */
export const internalQuery = internalQueryGeneric;

/**
 * Define a mutation in this Convex app's public API.
 *
 * This function will be allowed to modify your Convex database and will be accessible from the client.
 *
 * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
 * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
 */
export const mutation = mutationGeneric;

/**
 * Define a mutation that is only accessible from other Convex functions (but not from the client).
 *
 * This function will be allowed to modify your Convex database. It will not be accessible from the client.
 *
 * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
 * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
 */
export const internalMutation = internalMutationGeneric;

/**
 * Define an action in this Convex app's public API.
 *
 * An action is a function which can execute any JavaScript code, including non-deterministic
 * code and code with side-effects, like calling third-party services.
 * They can be run in Convex's JavaScript environment or in Node.js using the "use node" directive.
 * They can interact with the database indirectly by calling queries and mutations using the {@link ActionCtx}.
 *
 * @param func - The action. It receives an {@link ActionCtx} as its first argument.
 * @returns The wrapped action. Include this as an `export` to name it and make it accessible.
 */
export const action = actionGeneric;

/**
 * Define an action that is only accessible from other Convex functions (but not from the client).
 *
 * @param func - The function. It receives an {@link ActionCtx} as its first argument.
 * @returns The wrapped function. Include this as an `export` to name it and make it accessible.
 */
export const internalAction = internalActionGeneric;

/**
 * Define a Convex HTTP action.
 *
 * @param func - The function. It receives an {@link ActionCtx} as its first argument, and a `Request` object
 * as its second.
 * @returns The wrapped endpoint function. Route a URL path to this function in `convex/http.js`.
 */
export const httpAction = httpActionGeneric;

```

### postcss.config.js

```javascript
export default {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
};

```

### vite.config.ts

```typescript
import path from "path";
import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
  },
});

```

### index.html

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Convex Hack Pack</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

```

### tailwind.config.js

```javascript
const {nextui} = require("@nextui-org/react")

/** @type {import('tailwindcss').Config} */
module.exports = {
  darkMode: ["class"],
  content: [
    "./index.html", 
    "./src/**/*.{js,ts,jsx,tsx}",
    "./node_modules/@nextui-org/theme/dist/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    container: {
      center: true,
      padding: "2rem",
      screens: {
        "2xl": "1400px",
      },
    },
    extend: {
      colors: {
        border: "hsl(var(--border))",
        input: "hsl(var(--input))",
        ring: "hsl(var(--ring))",
        background: "hsl(var(--background))",
        foreground: "hsl(var(--foreground))",
        primary: {
          DEFAULT: "hsl(var(--primary))",
          foreground: "hsl(var(--primary-foreground))",
        },
        secondary: {
          DEFAULT: "hsl(var(--secondary))",
          foreground: "hsl(var(--secondary-foreground))",
        },
        destructive: {
          DEFAULT: "hsl(var(--destructive))",
          foreground: "hsl(var(--destructive-foreground))",
        },
        muted: {
          DEFAULT: "hsl(var(--muted))",
          foreground: "hsl(var(--muted-foreground))",
        },
        accent: {
          DEFAULT: "hsl(var(--accent))",
          foreground: "hsl(var(--accent-foreground))",
        },
        popover: {
          DEFAULT: "hsl(var(--popover))",
          foreground: "hsl(var(--popover-foreground))",
        },
        card: {
          DEFAULT: "hsl(var(--card))",
          foreground: "hsl(var(--card-foreground))",
        },
      },
      borderRadius: {
        lg: "var(--radius)",
        md: "calc(var(--radius) - 2px)",
        sm: "calc(var(--radius) - 4px)",
      },
      keyframes: {
        "accordion-down": {
          from: { height: 0 },
          to: { height: "var(--radix-accordion-content-height)" },
        },
        "accordion-up": {
          from: { height: "var(--radix-accordion-content-height)" },
          to: { height: 0 },
        },
      },
      animation: {
        "accordion-down": "accordion-down 0.2s ease-out",
        "accordion-up": "accordion-up 0.2s ease-out",
      },
    },
  },
  darkmode: "class",
  plugins: [require("tailwindcss-animate")],
};

```

### src/vite-env.d.ts

```typescript
/// <reference types="vite/client" />

```

### convex/_schema.ts

```typescript
// OPTIONAL: Rename this file to `schema.ts` to declare the shape
// of the data in your database.
// See https://docs.convex.dev/database/schemas.

import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";

export default defineSchema(
  {
    documents: defineTable({
      fieldOne: v.string(),
      fieldTwo: v.object({
        subFieldOne: v.array(v.number()),
      }),
    }),
    // This definition matches the example query and mutation code:
    numbers: defineTable({
      value: v.number(),
    }),
    ideas: defineTable({
      random: v.boolean(),
      idea: v.string(),
    })
    
  },
  // If you ever get an error about schema mismatch
  // between your data and your schema, and you cannot
  // change the schema to match the current data in your database,
  // you can:
  //  1. Use the dashboard to delete tables or individual documents
  //     that are causing the error.
  //  2. Change this option to `false` and make changes to the data
  //     freely, ignoring the schema. Don't forget to change back to `true`!
  { schemaValidation: true },
);

```

[16 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]