Public
HTTP (deprecated)
Val Town is a social website to write and deploy JavaScript.
Build APIs and schedule functions from your browser.
Readme

It's Late. Get a drink. Have a chat. Welcome to Nighthawks.

Nighthawks is an experiment in building interesting NPC characters with AI. The system combines large language models, simulates conversations using character sheets, and attempts to build memories based on the conversation.

Improvise, have fun. Approach them like you would at a late night diner.

Inspired by the Nighthawks painting. Built on a stripped down agent-based memory system.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/** @jsx jsx */
import { Hono } from "npm:hono@3";
import { jsx } from "npm:hono@3/jsx";
import { Nighthawks } from "https://esm.town/v/yawnxyz/nighthawks";
import { ModelProvider, modelProvider } from "https://esm.town/v/yawnxyz/ai";
import { KV } from "https://esm.town/v/yawnxyz/blobManager";
const app = new Hono();
const nighthawks = new Nighthawks();
const kv = new KV();
async function generateText(prompt, charId) {
if (charId) {
await nighthawks.loadCharacter(charId);
if (nighthawks.characters.length > 0) {
const response = await nighthawks.characters[0].gen({ prompt });
await nighthawks.saveCharacter(nighthawks.characters[0]);
// return response.text;
return {text: response.text, char: nighthawks.characters[0]};
}
}
return "No character available.";
}
async function createCharacter(obj) {
console.log('Creating character with:', obj)
await nighthawks.createCharacter(obj);
const char = nighthawks.characters[nighthawks.characters.length - 1];
await nighthawks.saveCharacter(char);
return char;
}
async function saveCharacter(char) {
console.log('Saving character:', char)
await nighthawks.saveCharacter(char);
return char;
}
async function getStoredCharacter(charId) {
await nighthawks.loadCharacter(charId);
if (nighthawks.characters.length > 0) {
return nighthawks.characters[0];
}
return null;
}
// Server-side rendering
app.get("/", async (c) => {
const charId = c.req.query("char");
const test = c.req.query("test");
const img = c.req.query("img") || "https://upload.wikimedia.org/wikipedia/commons/thumb/a/a8/Nighthawks_by_Edward_Hopper_1942.jpg/1920px-Nighthawks_by_Edward_Hopper_1942.jpg" ;
let character = null;
if (charId) {
character = await getStoredCharacter(charId);
}
let create = false
if (typeof c.req.query("create") !== 'undefined') {
create = true
}
const html = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nighthawks ${charId ? `| ${charId}` : ''}</title>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
<style>
.w-fill-available {
width: -webkit-fill-available;
}
.text-light-green {
color: #dbecde;
}
.message:nth-of-type(odd) {
color: #dbecde;
<!-- patron color / light green -->
}
.message:nth-of-type(even) {
color: #66dc66;
<!-- user color / bright green -->
}
</style>
</head>
<body class="bg-black min-h-screen flex">
<div class="flex flex-col justify-between w-1/2 max-h-screen">
<div class="flex-grow">
<!-- Additional content can go here if needed -->
</div>
<div class="flex justify-end">
<img class="max-w-full h-auto | this overrides: fixed bottom-0 w-1/2" alt="Nighthawks by Hopper" src="${img}" />
</div>
</div>
<div class="chat-container | flex flex-col w-1/2 bg-teal-900/50 p-2" x-data="formData(${charId ? `'${charId}'` : 'null'})">
<div class="Details | text-white text-xs pb-2" x-show="charId">
<details>
<summary>Character Details: <span x-text="charId"></summary>
yawnxyz-nighthawkschat.web.val.run
May 31, 2024