Using Chrome AI to Rewrite Monstrous JSON

Using Chrome AI to Rewrite Monstrous JSON

Happy Saturday folks, and while this is a topic I've covered many times here, I was bored and wanting to write some code, so I whipped up a quick demo. One of my favorite uses of AI is to take abstract data and write a human readable form of it. Now to be clear, this is not something you need AI for. Given that you know the shape of your data, you can create your own summary using hard-coded rules about what values to show, how to present them, and so forth. What I like about the Gen AI use-case for this is the amount of randomness and creativity you get in the responses. In the past I've done this with weather forecasts and chart data, but today I thought I'd try something different - monsters.

A lot of Dungeons and Dragons content is available, legally even, online in API and JSON formats. The D&D 5e API includes information on all aspects of the game, from classes to spells to monsters. While the API is handy, I knew I wanted the raw data as is so I headed over to the repo and grabbed the raw monsters json file. This JSON file contains information on every known monster. Here's an example, an you can see it is quite extensive (I won't be offended if you just skim this):

Ok, so given this, I built a little demo of turning this into something a bit more manageable.

The Demo

My demo is pretty straight-forward. I'm going to load in the big JSON file (it's a bit over a meg so not too bad, but I would consider client-side storage in a real app) and then let the user click a button to:

a) select a random monster and display the raw JSON b) use Chrome AI to turn this into a paragraph of text.

At the time I'm writing this, the Prompt API is still behind a flag, so even if you're on Chrome, you'll need to either enable the flag or... just wait. ;) I'm going to include examples at the end, so don't fret.

Alright, so the HTML first:

<h2>D&amp;D Monster to Text</h2>
<p>
	This CodePen demonstrates using Chrome AI to rewrite JSON describing content into a paragraph of text for easier consumption. In this case the JSON describes a D&amp;D monster. (JSON source from <a href="https://github.com/5e-bits/5e-database/blob/main/src/2014/5e-SRD-Monsters.json">5e-bits</a> on Github.)
</p>
<div class="twocol">
	<div>
		<textarea id="input"></textarea>
	<p>
		<button disabled id="runBtn">New Random Monster</button>
	</p>
	</div>
	<div id="result"></div>
</div>

Nothing too fancy here, just a textarea for the JSON and an empty div for the result. Do note the button that kicks off the process.

Now let's look at the JavaScript:

const MONSTERS_JSON = 'https://assets.codepen.io/74045/5e-SRD-Monsters.json';
let monsters;

document.addEventListener('DOMContentLoaded', init, false);

let $input, $runBtn, $result;
let session;

async function init() {

	if(!('LanguageModel' in window)) {
		alert('Sorry, but you can\'t use this demo.');
		return;
	}

	let available = (await window.LanguageModel.availability());

	if (available === 'unavailable') {
		alert('Sorry, but you can\'t use this demo.');
		return;
	}

	let dataReq = await fetch(MONSTERS_JSON);
	monsters = await dataReq.json();
	$result = document.querySelector('#result');
	$runBtn = document.querySelector('#runBtn');
	$input = document.querySelector('#input');


	$runBtn.addEventListener('click', runPrompt, false);
	$runBtn.disabled = false;
}

async function runPrompt() {
	
	// first, get a random monster
	let monster = monsters[getRandomIntInclusive(0,monsters.length-1)];
	
	$input.value = JSON.stringify(monster,null,'\t');
	$result.innerHTML = '<i>Working...</i>';
	
	if(!session) {
		session = await window.LanguageModel.create({
			initialPrompts: [
				{ role: 'system', content: 'Given a JSON description of an Dungeons and Dragon monster, turn the raw JSON into a paragraph of 4-5 sentences that describes the monster at a high level.' },			
			],
			monitor(m) {
				m.addEventListener("downloadprogress", e => {
					console.log(`Downloaded ${e.loaded * 100}%`);
					/*
                    why this? the download event _always_ runs at
                    least once, so this prevents the msg showing up
                    when its already done. I've seen it report 0 and 1
                    in this case, so we skip both
                    */
					if(e.loaded === 0 || e.loaded === 1) return;
					$result.innerHTML = `Downloading, currently at ${Math.floor(e.loaded * 100)}%`;
				});
			}			
		});
	}

	$runBtn.disabled = true;
	
	// thanks to Thomas Steiner!
	// not sure if we need to clone though...
	let thisSession = await session.clone();
	let result = await thisSession.prompt($input.value);
	$result.innerHTML = result + `<p><img src="https://www.dnd5eapi.co${monster.image}"></p>`;
	$runBtn.disabled = false;
}

function getRandomIntInclusive(min, max) { 
	min = Math.ceil(min); 
	max = Math.floor(max); 
	return Math.floor(Math.random() * (max - min + 1) + min);  
}

So outside of variable declarations and stuff, the initial part of the code does the feature detection required and aborts if anything isn't going to work. As a reminder, generally I'm recommending Chrome's AI tools in progressive enhancement cases. This demo is an exception as it won't do anything without the feature. I could absolutely fall back to a static simple description instead.

When the button is clicked, I grab one random item, add the JSON to the textarea, and then begin my AI usage. My session sets up a system instruction that clearly defines what it is doing:

Given a JSON description of an Dungeons and Dragon monster, turn the raw 
JSON into a paragraph of 4-5 sentences that describes the monster at a 
high level.

Which then leaves the prompt to simply taking the JSON and working on it:

let result = await thisSession.prompt($input.value);

I then display the result, and as I noticed the API supported images, I go ahead and render it as well.

Examples

Ok, so given that most of you probably can't run it, here are a few examples. First up, a lovely ancient-blue dragon. Here's the JSON:

And here's how Chrome AI described it:

The Ancient Blue Dragon is a gargantuan lawful evil creature known for its immense power and cunning. Possessing a formidable armor class and a massive hit point total of 481, it is a terrifying foe to encounter. This dragon boasts impressive physical capabilities, including a powerful bite, razor-sharp claws, and a devastating lightning breath attack, complemented by legendary resistance to damage. With a challenge rating of 23, it is a truly epic monster, capable of wreaking havoc and instilling fear in all who cross its path, and its innate abilities allow it to control the battlefield with Frightful Presence.

And how about something on the completely other side of the fence... a pony.

The Pony is a medium-sized beast with an unaligned nature. It possesses a decent strength score of 15 and a speed of 40 feet, making it relatively agile. It has a Challenge Rating of 0.125, suggesting it poses a minor threat. The Pony primarily attacks with its hooves, dealing bludgeoning damage. It has a passive perception of 10, allowing it to notice things around it.

Try it Yourself

If you want to try it yourself, or see all of the code, you can see the embed below:

See the Pen DD Monster to Text by Raymond Camden (@cfjedimaster) on CodePen.

Photo by Omar:. Lopez-Rincon on Unsplash