back to indexAI Pictionary - Answer.AI dev chat #5
00:00:00.000 |
>> All right. Hello, everybody. This is Jono here doing another dev chat. This one more 00:00:10.040 |
a look at a work in progress rather than a demonstration of something finished. I'm going 00:00:14.480 |
to be showing Jeremy through the latest iteration of the to-do app. Sorry. Not the to-do app. 00:00:22.080 |
We've heard quite a lot of those. The AIA Fictionary app. >> I was thinking yesterday 00:00:29.960 |
over the weekend when I was sharing my most recent doll utility tool. You and Nate make 00:00:36.440 |
all these really cool, fun things, and I make all these really tedious, dull things. I feel 00:00:42.440 |
jealous. >> The mess behind the scenes. Okay. So, hopefully you can see my screen. AIA Fictionary. 00:00:55.440 |
>> I genuinely think this is going to be huge. I think this is going to be the next viral 00:01:01.500 |
game everybody on the Internet plays. >> Yeah. I feel like it's kind of funny having 00:01:08.600 |
that as a goal. We have to pay for every inference, but I guess it will be fun to be shared around. 00:01:16.560 |
Okay. So, this is the app. And actually, this is the live version here. So, Jeremy, I'll 00:01:22.640 |
send you this in the Zoom chat, and you can play with that if you like. And we can go 00:01:27.320 |
through the basic functionality, and then I'll show the code behind it. And so, how 00:01:32.600 |
this works, when you click play a game, you've got some object that you're trying to draw. 00:01:36.720 |
Here, ladybug. A little timer to add a bit of pressure. >> That's really hard. Ladybug. 00:01:41.480 |
>> Especially with a mouse. >> Oh, my gosh. You don't have a tablet. Okay. People with 00:01:47.640 |
Microsoft services and iPads are going to do better here. And how come it hasn't guessed 00:01:52.160 |
anything yet? >> That's a bug. It should definitely be guessing things. I'm going to switch to 00:01:59.760 |
the local version. I did take it down briefly, because there were some slightly less savory 00:02:05.280 |
images popping through, just very briefly, I think. >> Of course. >> It doesn't take 00:02:11.120 |
long on the internet to be... >> Oh, I love the way you've got two different models showing 00:02:15.800 |
their guesses. >> Yeah. So, okay. So, there we go. As we go in here, hooray, one of them 00:02:21.720 |
must have got it. >> That's a hard one. I don't know if it's a lemon or an orange. >> Yeah, 00:02:26.360 |
lemon is a tricky one to go. So, okay. Turnip, onion. Yeah. So, you've got GPD 4.0, Claude 00:02:32.000 |
Haiku, and Gemini Flash 1.5, all submitting their guesses. >> Haiku. Well played. >> Haiku, 00:02:39.360 |
in this case, was the winner. Yeah. So, you can go and see the past games. >> Oh, nice. 00:02:47.160 |
>> Ongoing games, you'd see them showing up there as well. Yeah. It's kind of fun to see. 00:02:52.360 |
Some of the words are definitely more challenging than others. >> My daughter's going to love 00:02:56.880 |
this. >> Yeah. Yeah. >> Trident, that's too easy. >> Trident, well, yeah. I think the 00:03:02.320 |
actual word was hook. Yeah. Oh, no, fork. >> Oh, I see. >> So, it's easy enough to get 00:03:07.880 |
it wrong as well. Okay. And then this is the... >> Oh, leaderboard. Nice. >> Oh, this is my 00:03:13.440 |
local leaderboard. If you look at the one for the published app, I think it's mostly, 00:03:18.160 |
yeah. I've been beaten up by a lot of players. >> Whoa. Look at that, sub four seconds. >> Yeah. 00:03:24.880 |
Yeah. See, I mean, line, it's kind of an easy... >> Oh, come on. That's ridiculous. >> We will 00:03:32.960 |
have to have a leaderboard per word. >> Yeah. >> Yeah. So, anyway, this is the app as it 00:03:38.160 |
is. >> It's really great. It's like, it's got, you know, the spectate, the leaderboard. These 00:03:46.040 |
little touches are nice. And then the kinds of things which, well, I'd be interested to hear 00:03:50.640 |
what you thought. But I think, like, yeah, the kinds of small additions that with most things, 00:03:55.280 |
they're not actually small additions. But hopefully with fast HTML, it wasn't too hard to add. 00:03:59.280 |
>> Mm. Sorry, I'm getting distracted trying to draw a zoo. I just want to draw a zoo. 00:04:06.400 |
>> Yeah. And does it eventually time out? I see time left. Yes, it does. >> Yeah, yeah. So, 00:04:11.920 |
I've got 30 seconds left. >> Is it, like, illegal to actually write zoo on it? >> No. I saw some 00:04:18.640 |
people on the public leaderboard using that trick. Oh, okay. Definitely some glitches that I've 00:04:25.680 |
introduced in my tinkering around for the demo. >> Anyhow. >> Yeah. The zoo one showing up there. 00:04:35.200 |
Okay. So, yeah. In the last half hour, I've introduced some bugs. But we can look at the 00:04:40.720 |
-- the deployed one I'll try and have working by the time anyone sees this. And we can just go 00:04:47.760 |
through the code for those different pieces of functionality bit by bit. And I'll, like, 00:04:51.520 |
kind of gradually reveal the complexity. Okay. So, this is the code. It's mostly in main.py. 00:04:58.960 |
We'll look at later. There's a little bit of styling and a little bit of JavaScript for the 00:05:03.120 |
canvas itself. >> I see. And you've picked a word list. >> Yeah. >> Did that come from somewhere? 00:05:08.160 |
>> I have a dump of words. I looked online at several different places for the, like, 00:05:13.680 |
common dictionary words. There's also a lot of them are -- I kind of trimmed down many lists 00:05:23.200 |
because you can have things that are easy enough to judge close enough for a human. But I kind of 00:05:29.440 |
just have this, like, string matching, basically. And so, I removed anything too crazy conceptual 00:05:36.400 |
or with too many variants. >> Yeah. And presumably, the word list is not part of the prompt. So, 00:05:42.320 |
the model doesn't know that thing. And also, it's important for people to remember that 00:05:47.040 |
none of these online services are learning from the interactions. So, they're never going to 00:05:53.680 |
learn this word list. >> Right. Exactly. I mean, I'm kind of thinking this might be a fun 00:05:59.520 |
eval for later. In which case, having, like, a known word list might make it 00:06:04.880 |
interesting, like, easier or cheaty. And then also, we definitely need to do some, like, 00:06:11.440 |
something I haven't added yet. Keep track per session of what words you've seen so you don't 00:06:16.080 |
see the same one again if the word list is too small. And if you've seen all of them, 00:06:20.080 |
you should probably, like, be told to stop playing the game and go find something else to do with 00:06:23.840 |
your time. But, yeah. So, that's our list of words. Let's see. We've got different 00:06:31.680 |
clients, API clients for the different models. The database, we're going to keep track of games, 00:06:38.240 |
keep track of the images that are submitted, and keep track of the guesses that the models make. 00:06:42.400 |
So, setting those up with the Fastlight database there. >> Do you have any idea, like, about 00:06:47.600 |
how expensive it is for us to run one game or how many tokens it takes? 00:06:56.240 |
>> I could work backwards. Okay. So, I left it running for about 10 hours, 00:07:02.720 |
constantly guessing two games simultaneously, which is what I've set as the max. 00:07:07.760 |
And that used about 5 million tokens, which was, I think, $1.25 on Haiku, 00:07:16.800 |
maybe even less than $1 for Gemini Flash. And that's, I'd have to do the math, like, yeah, 00:07:24.160 |
10 hours, 30 seconds a game, and actually two games every 30 seconds. So, it's not free, 00:07:30.880 |
but it's also, like, not crazy expensive if there's a limit on the total number of games. 00:07:37.600 |
But that would be a worry if you wanted, you know, like, if you wanted this to be, 00:07:42.720 |
like, Word War or something, where there's hundreds of thousands of people playing 00:07:46.880 |
simultaneously in the morning with a cup of coffee. >> Well, I liked your trick 00:07:50.480 |
you did with your earlier captioning game, where we just have a balance, where if there's no 00:07:55.360 |
money in the balance, people can just donate $5 to play themselves, and everybody else can play too. 00:08:01.840 |
>> Yeah. Yeah, we can see how that goes. At the moment, to keep the spend in check, 00:08:08.480 |
and to keep it tempered, there is a queue. So, if there's more than two people playing, 00:08:12.640 |
then other people will be put into the queue rather than starting a game immediately. 00:08:18.560 |
at the logic for that. That was one of the, like, yeah, like, oh, it's somewhat non-trivial over the 00:08:23.760 |
base case where everyone always sees the canvas. Yeah, let's see. Session. There's a unique session 00:08:31.520 |
ID that lets us do things per player and keep track of who's who. >> Great. 00:08:36.320 |
>> We're loading in some JavaScript, some styling. >> So, that session is kind of like a user, 00:08:44.160 |
but it's attached to a browser, basically. There's no way to have the same jono across 00:08:50.880 |
multiple browsers. But because it's using the starlet sessions, that will be stored as long 00:08:56.800 |
as they maintain their cookies. They'll have the same session. >> Yeah. And so, if you wanted to 00:09:03.200 |
have, you know, leaderboards and high scores and avoid abuse, it might be better to have, like, 00:09:08.240 |
a login with Google and create a proper user account. >> Yeah. 00:09:11.040 |
>> But for now, this is nice. You can just load the page and start playing. You don't even have 00:09:16.400 |
to put in a name until after the game. Not at all. Okay. So, we have a game manager class. 00:09:24.800 |
I should say a lot of this code was written by Sonic 3.5. It's not necessarily the style I do, 00:09:31.120 |
but it picked up the fast HTML surprisingly fast. Like, giving it the starting point that I had 00:09:37.680 |
as a file, that was pretty good. >> Did you use our fast HTML 00:09:42.400 |
board project with all the stuff in there? Okay. Because that also helps quite a bit. I just have 00:09:48.480 |
a project with all the big kind of highly commented demo app loaded into the context, 00:09:55.120 |
which it's really good at writing fast HTML with that. >> Right. Yeah. By the time I started this, 00:10:00.800 |
I already had an existing app that showed a lot of the functionality. And it was just like, oh, 00:10:04.960 |
that does this. Could you add a thing that does, you know, so it like had the pieces already. 00:10:10.160 |
>> I think the reason it's so good at this is because it's kind of like basically a style 00:10:15.120 |
transfer problem. Like, it literally is HTML, just written a different way. So, it's kind of like, 00:10:20.640 |
all of its embeddings are still going to work, except for that last layer or two, 00:10:24.880 |
where it has to use slightly different tokens. >> Yeah. And a lot of the routing, I think, 00:10:29.440 |
is quite familiar from like fast API and things like that. >> Yes, exactly. And Flask. 00:10:34.720 |
>> All right. So, we have a way to keep track of active games, to start and end games. 00:10:40.000 |
At the end of a game, I make a GIF. Totally unnecessary, but kind of nice for the 00:10:47.920 |
>> Yeah. And so, then the way I've organized this is to have the main, like, roots. So, the home, 00:10:55.120 |
the about page, the leaderboard, and the spectate page. And then all the supporting bits of 00:10:59.120 |
infrastructure for that afterwards. I know you don't like the, like, out of order pieces, but... 00:11:04.960 |
>> Sorry, the out of order pieces? >> Oh, to have the home and then 00:11:09.440 |
something that the home depends on, like, a bunch, far later in the code. 00:11:13.360 |
>> It doesn't, though, does it? Like, it's not that it's, well, yeah, I guess it's going to call 00:11:19.840 |
navbar, is it, for example? >> Yeah, exactly. So, home is going to 00:11:23.600 |
use navbar, but because it's all executable... >> There's no reason for me not to like it, 00:11:27.120 |
Jono. It's just, like, you know, when I used to write C and stuff, you had to. And so, 00:11:32.800 |
now I'm like, that ought to work. It works fine. No weird reference. 00:11:37.760 |
>> Cool. Yeah. So, the main page, got a title, got a navbar. This, again, thank you, Claude. 00:11:45.920 |
Links to the different pages. We have this active area is kind of, like, the main 00:11:53.600 |
place where the actual game happens. So, I'll come back to that. 00:11:57.360 |
About page is just a markdown. >> So, sorry, the navbar, 00:12:02.560 |
you just call manually from multiple places, or how does that work? 00:12:07.840 |
>> Yeah. So, in the about section, I call navbar, and I put in this thing where I can say, 00:12:15.120 |
um, link to, you know, just do, like, a null link if you're on that page already. 00:12:20.720 |
>> Nice. >> It's probably a better way to do that. 00:12:23.840 |
>> It's nice and simple, isn't it? That's great. >> And then the title changes to, like, the home 00:12:29.520 |
button. So, this nice big, like, this is how you go home. This is the default. 00:12:35.520 |
>> Yeah. Great. >> And that's nice. It's just little 00:12:40.880 |
conditionals in the... >> And because you're using href 00:12:44.320 |
rather than hxget, you're actually, the URL is changing. It's cool. 00:12:49.120 |
>> Yeah. Yeah. So, this is actually much more like a web 1.0 app than some of our demos. 00:12:56.480 |
The only, you know, you'll see the htmx bits, but a lot of it's, like, separate pages and then 00:13:01.120 |
shareable pages. So, if you're looking at, like, the summary of a game, you know, that's a special 00:13:09.680 |
page that has, it has, yes, the game and who won it and all that. And it also has a link that'll 00:13:18.640 |
take you to Twitter and say, hey, I scored this in this game. And this is the URL. And it's got 00:13:25.520 |
a little preview image. Yeah. So... >> That's really cool. I love it. And I saw, 00:13:30.800 |
interestingly, on the about page, you were using markdown. Are you using JavaScript to convert that 00:13:36.960 |
or... >> Yes. So, we have this markdown.js. It's defined in the fasthtml.js thing. Yeah, 00:13:45.760 |
this will take us there. >> And this is just a little convenience for you. Because, of course, 00:13:50.960 |
you could perfectly well have just written that as h2 and p and whatever. But it's just easier 00:13:56.640 |
to write it in a markdown string. >> Yeah. Yeah. I just wanted to be 00:14:01.840 |
able to, like, come and write this without having to think about that. It's very much 00:14:04.720 |
an afterthought. This was... I can imagine, like, if you wanted to have a blog or something like 00:14:11.200 |
that where you had more actual content, being able to author it in markdown is nice. In this case, 00:14:15.680 |
it doesn't really have much. This is... >> I mean, these minor things that make, like, 00:14:20.640 |
writing an about page less annoying, for example, it's all, like, reducing the friction and making 00:14:27.440 |
the coding more enjoyable. Yeah, I like it. It matters. >> Yeah. Yeah, exactly. And, you know, 00:14:34.400 |
just knowing that, yeah, I just use the simple elements. I've got some styling that means that 00:14:39.520 |
typography is going to look okay. It's not going to fill the whole page. Yeah, we've got the 00:14:44.960 |
markdown rendering so that I could put in images there if I didn't know how to do the image tag 00:14:50.560 |
in fast HTML. Yeah, just makes life a little easier. Okay, so maybe we should look at, like, 00:14:59.600 |
the actual, like, the core game loop. You know, a lot of the other pieces are quite atomic, 00:15:03.120 |
like the leaderboard, but the game active area... >> Well, just show the leaderboard. So how is 00:15:07.840 |
that... is that being stored in a SQLite database? Is that how that works? >> Right. So this is doing 00:15:13.200 |
a query on the database. Like, oh, get many finished games with an end time, 00:15:18.640 |
order by the time taken. It should probably have made that its own column so that it's not doing 00:15:23.280 |
those calculations. And it's doing this every time someone goes to the page. But I have the feeling 00:15:30.480 |
that, like, you know, operations like this, like taking a database of a few hundred games and 00:15:36.080 |
sorting it for every unique visitor is kind of dwarfed by the, like, waiting for big AI models 00:15:42.320 |
to finish and, like, serving out these GIFs and all of that jazz. You know, like, that's a lot 00:15:45.920 |
more computation. Also, even, like, I shared this a little bit this morning, and there's quite a few 00:15:50.640 |
people playing. The CPU usage on the machine that it's deployed on is, like, it just... the graph 00:15:56.400 |
couldn't even read, like, 0.0. You know, 0.0 something. >> Is this running on a little VPS 00:16:02.640 |
or something? >> It's on railway. So just the... >> Oh, railway. Cool. Okay. >> Yeah. So it's 00:16:08.960 |
really not... it doesn't seem to be... I mean, obviously, if it goes viral, we'll see how it 00:16:14.400 |
handles that. But yeah, just at the moment, tricks like this gets it nice and fast. Query the 00:16:19.760 |
database, make a table. So I'm giving a row in the table for each game. Little title, heading, link. 00:16:28.640 |
Yeah. So I don't think there's any HTMX here. It's just, again, rendering a page based on some 00:16:35.440 |
data. We could do a thing to see if any of the games had the player ID that's the same as the 00:16:41.840 |
person viewing the page to, like, flag your own games or something like that. But you can also 00:16:47.840 |
just look for your nickname or something. >> Or you could also do that thing that you often see in 00:16:53.280 |
games where, like, it'll show you the top 10, and then it'll be, like, dot, dot, dot, and then it'll 00:16:57.600 |
show you the five above and below yours, your best. >> Right, right. So place 112 as me, 00:17:04.800 |
my best game or something like that. Or my most recent game. Yeah. Cool. Okay. So the actual game, 00:17:14.160 |
it comes into this area here. And so all the action is in this active area part, 00:17:22.560 |
and then in the handling of the images. >> I just like -- I'm still thinking -- sorry, 00:17:26.720 |
I can't stop thinking about your -- it's so delightfully HTMX-y, this idea that, like, 00:17:35.600 |
you don't have any fancy JavaScript to check, you know, change the class dynamically if blah, 00:17:40.480 |
blah, blah. Like, it's just -- you know, it's static HTML generated from a function. It's 00:17:48.640 |
really great. >> Yeah. Yeah. And it's -- it's -- you know, 00:17:55.360 |
yeah, I really like it. And it also is, like, okay, I'm sending a little bit of redundancy, 00:18:03.120 |
right? I'm sending that on all three pages people visit. Like, what is that? 812 wasted bytes when 00:18:10.240 |
I'm also loading a bunch of GIFs? >> Exactly. So I'm just thinking -- so already -- I mean, 00:18:15.520 |
that function you just showed us, you could change title -- yeah, you could change title 00:18:22.640 |
to title, right? No, the one -- the top -- the up, up, up, up. >> This one here? >> Yeah, 00:18:30.160 |
that one. Rhymes164. Title could become titled. Then you wouldn't need main. You wouldn't need 00:18:37.040 |
class container. But then you could write your own thing like titled that actually calls titled, 00:18:43.680 |
which also adds the navbar, you know what I mean? So you can, like -- so titled already factors out 00:18:50.320 |
the -- you know, the -- oh, actually, you don't have an h1, which is different. So, yeah. Anyway, 00:18:57.600 |
so that's kind of, like, the fast HTML idiomatic approach, I think, is to, like, 00:19:02.720 |
not have templates, but more you just have functions to refactor out the commonly used UI elements. 00:19:11.520 |
>> Yeah. Yeah. Okay. Right. Let's see. So I've gone to the home page, and there's -- I'm looking 00:19:23.200 |
at that area there. And so if I haven't started any games, I'm a refresh user, this is what I'm 00:19:34.160 |
going to see. I'm going to see a button that says play a game. That's going to post to join, 00:19:39.200 |
and it's going to update this active area that's, like, the sort of main play area. 00:19:49.040 |
Join is going to add them to the queue if there's a queue. Otherwise, it's going to start a game, 00:19:58.320 |
and then it's going to return that same active area. But now, whereas before they were a new 00:20:04.880 |
user, now there's a game on or they're in the queue, and so there's different pieces for them 00:20:10.560 |
to see, depending on that. If they're in the queue, we've got a little countdown saying, oh, 00:20:14.240 |
all the games are full. You're this index in the queue. There's this many people in total. 00:20:18.880 |
This is your estimated wait time. And that's going to just keep polling to check 00:20:23.120 |
how many people are in the queue. >> Yeah, so the H6 trigger every one second. 00:20:28.400 |
And this is, like, this important insight that I've been thinking about a lot, which is, like, 00:20:38.960 |
you only need to build a WebSocket app if the thing coming from the server, you don't know 00:20:45.680 |
when it's coming, and you need to know about it immediately, you know. But the vast majority of 00:20:53.120 |
the time, either you know when it's coming, because it's, like, because you asked for it, 00:20:56.480 |
or it's fine just to check every second or so, in which case you don't need 00:21:01.280 |
the custom WebSockets. This every one second trick is perfect. 00:21:13.360 |
there's nothing clever about any of this code. Do you know what I mean? It feels like the 00:21:23.120 |
kind of thing you could write in, like, a boot camp week two. Okay, you've learned, 00:21:30.080 |
you know, loops and if statements, so here's what happens when you put them together. 00:21:33.920 |
Yeah, it's, like, it feels more like the complexity of 00:21:37.920 |
1990s PHP than, like, 2024 Next.js or something. >> Yeah, and you can do things, 00:21:47.520 |
you know, you can do things all sorts of ways, but it feels like it lends itself to this, like, oh, 00:21:53.200 |
I'm going to return some content if one condition is met, otherwise I'm going to return some other 00:21:57.040 |
content. There's lots of reality, I'll make a template that renders one or the other with only 00:22:02.960 |
the important bits changed. >> Yeah, it's just, it's nothing clever. Just say what you want. 00:22:08.640 |
>> Yeah, and exactly, and it's also, like, this is, maybe I should have said this up front, like, 00:22:13.600 |
this is what you get when you have something where it was, I had a starting point, right, 00:22:19.920 |
it was a very simple app that didn't have any of the flashy bits, and then you just quickly add 00:22:24.240 |
all the pieces you need. >> So you didn't have to design the whole thing up front, you just were, 00:22:28.720 |
like, more like, oh, now that's there, my heart says I want to add this now. >> Right, okay, 00:22:35.360 |
now I should add a queue, all right, so now when I'm rendering that active area, I should check if 00:22:39.120 |
there's a queue or not, and then adjust accordingly, you know, so it's, like, all these bits tacked on. 00:22:44.240 |
So I think, like, it's a difference, you end up with a lot of code, like, this is, like, a 700 00:22:50.320 |
lines of code. It's not, like, the most elegant thing, but it is very easy to understand, because 00:22:55.760 |
you could, especially if you had the history, like, oh, we added this route for the leaderboard, 00:23:00.640 |
right, and that's, like, boom, it's, like, its own thing. And maybe, okay, I needed to add an 00:23:05.600 |
extra column in one of the databases because I wanted to show something about that. You know, 00:23:09.760 |
you do, like, touch other pieces of the system, but it feels like a lot of these are, like, oh, 00:23:13.440 |
we add this piece, then we add this piece, then we add this piece. >> Yeah, and then line 267 there 00:23:17.600 |
is just, like, oh, here's a canvas. Okay. >> Yeah. >> So there must be some JavaScript 00:23:23.440 |
somewhere, I assume. I did see you had a JavaScript earlier. >> Yeah, yeah. So if you're in an active 00:23:29.040 |
game, we are now, like, displaying a canvas, and there's actually two bits of JavaScript going on. 00:23:34.080 |
There's a countdown, and there's a canvas. So we can look at the JavaScript for that. 00:23:38.880 |
There's almost none of it written by me. When we first load this or any element, we find anything 00:23:48.000 |
that matches this selector, and then we add some event listeners to it and color the background 00:23:58.240 |
white. We have some drawing code that you'll find in lots and lots of tutorials if you're looking 00:24:05.680 |
for, like, how to draw on an HTML5 canvas. Again, very easy for someone like Claude to write. 00:24:11.360 |
>> It's kind of nice that you actually -- I like the way you haven't attempted to 00:24:15.600 |
use HTMX or any of this. You're just, like, here, this is our JavaScript bit, 00:24:19.920 |
so I'll just use JavaScript. >> Yeah. Yeah. So the one place that I used to have HTMX -- okay, 00:24:25.680 |
so we're drawing some lines, and once we're done drawing, we're going to send the canvas data to 00:24:29.680 |
the server. And so that's this piece here. It's getting the canvas, it's turning it into a blob 00:24:34.400 |
of data, and then it's posting it to a root, and we can intercept that. So you can also 00:24:42.480 |
use HX trigger to, like, from JavaScript, trigger an HTMX event. But yeah, this is, like, actually, 00:24:50.640 |
I found the easy way of doing it. Just whenever I want from within my JavaScript, 00:24:54.880 |
I'm posting this data. I do have some throttling so that it's not like someone's drawing a million 00:25:00.160 |
dots. It's not sending million images. It's sending one every -- >> Yeah, I mean, I bet we could, 00:25:04.720 |
you know, it might be fun, actually, even to look at refactoring that to make it smaller. But, 00:25:09.680 |
like, yeah, when you're just building something, and if Claude says, "Here's the code," then just 00:25:14.560 |
use the code. >> Right. I pasted my previous one in. I said, "This sends a lot of events. If 00:25:19.840 |
someone's doing lots of dots, could you add throttling?" So it's all this, like, iterative, 00:25:24.400 |
like, "Oh, okay, we change sendCanvas to throttled sendCanvas data, and we add a new function." 00:25:29.520 |
Instead of having this be in here. Yeah, okay, so that's the main, like, Canvas piece. 00:25:36.160 |
Let's see, there was a countdown as well. Yeah, it's just a little bit extra to have the, like -- 00:25:45.920 |
>> Nice. >> Here's the countdown flashing there, 00:25:48.960 |
and the reason it's here is because you can also do all sorts of other things easier in JavaScript 00:25:53.600 |
on the web page if you wanted to have big lines counting down, and flashes, and all that sort of 00:25:59.600 |
thing. So in that JavaScript, we saw that it's sending the images to a different root, and so 00:26:09.600 |
that's maybe then the next piece where a lot of important stuff is happening. We're getting an 00:26:14.960 |
image. So first, okay, does this person have any right to be sending us images? Is it from a person 00:26:21.520 |
who's in an ongoing game? If so, go look up the game, and then -- 00:26:26.400 |
>> Just a reminder that sessions are signed, so somebody can't take that. 00:26:30.480 |
>> Right, yeah. So you can't put a new SID in your session. I mean, not that it would make too much 00:26:37.600 |
of a difference here, but yeah. For something more secure, you could potentially store, like, 00:26:42.240 |
a username or a unique key to look up in your database. Yeah, so we're getting an image. We're 00:26:50.240 |
writing it to a file. But importantly, we're not, like, querying those AI models, right? So this 00:26:56.560 |
runs pretty fast, and it's just returning back some -- actually, I could return back nothing, 00:27:01.600 |
but I'm returning some stuff to the JavaScript so that it knows, okay, the game is still ongoing. 00:27:13.200 |
you know, you could make this more performant by adding the word async before def on line 421, 00:27:20.080 |
and image.file.read. You could use await there with, you know, async I/O, 00:27:26.400 |
you know, stuff like that. So, like, there's -- and def.write, you know, so there's a few 00:27:32.240 |
places you could definitely -- well, you'd have to use the -- yeah, I guess the async I/O version of 00:27:46.320 |
>> Or if you're familiar with async, 20 seconds. >> I am not very familiar with it. 00:27:53.920 |
>> I'm not very familiar with it either. Or at least not in Python. 00:27:58.880 |
>> Well, that's a good segue into the process and the actual guessing. 00:28:07.440 |
So, we're storing these images. We're not sending anything back. We could be 00:28:10.880 |
sending back a piece of HTML to say, like, we received your latest guess or something like that, 00:28:14.880 |
or give some other sort of status update there. Okay. So, what's happening to those images? 00:28:21.440 |
They're getting saved. They're getting stored in the games table. We're keeping track of, 00:28:26.000 |
like, the active games and the game manager thing. So, now the design of this is that 00:28:33.760 |
I'm responding to the request straight away, but then in the background, like, kind of constantly, 00:28:39.040 |
I've got some separate threads running that are, like, looking if there's any new images that have 00:28:43.760 |
come in. If so, they're sending them off to the different AI models. When they get guesses back, 00:28:48.800 |
they're, like, responding with those guesses. So, all of that is happening kind of separate to the 00:28:53.200 |
I'm responding to requests part of the app. It's in a different thread. 00:28:58.800 |
And this is the part that's, like, very much ad hoc messy. I got Sonnet to rewrite it, 00:29:04.480 |
and I don't like how it rewrote it. So, it's a little bit, like, hodgepodge. 00:29:07.280 |
But with that caveat, we can see what it's doing. Each of these functions is what you'd see in the, 00:29:13.520 |
like, vision tutorial of the docs for the different APIs. We have a prompt. 00:29:18.560 |
Here's the picturing prompt. >> I definitely want to rewrite 00:29:22.400 |
with this with Claudette. >> Yeah. Although, it's not, like, 00:29:27.360 |
you need a history, right? You don't have any chat history. So, you'd only be using the Claudette, 00:29:31.360 |
like, utility functions. >> Yeah. It's not a big deal. Just a nice 00:29:36.320 |
little thing, like, all the image handling and everything would be done for you. 00:29:40.480 |
>> Yeah. Yeah. It'd definitely be a lot more convenient. 00:29:43.440 |
>> That's totally fine. >> Yeah. Okay. So, these functions 00:29:49.200 |
just take an image and an optional history of past guesses. They send the image to the model, 00:29:55.120 |
and they get back the response. >> And do they all get the same prompt? 00:29:57.680 |
>> Yes. So, every single one has... >> No system message. 00:30:02.480 |
>> Yeah. No system message. So, this is definitely, like... 00:30:06.000 |
>> Oh, passing in the past guesses. That's a good idea. 00:30:10.080 |
>> Yeah. So, if there are previous guesses for that game... 00:30:13.760 |
>> And is that the previous guesses from all the models, or just from that model? 00:30:18.320 |
>> From all the models. It's kind of like the online picture where you see everyone's things 00:30:24.000 |
flowing past. And it helps avoid duplicates, because otherwise, like, everyone guesses banana, 00:30:28.880 |
and then they see the same image again, and they all guess banana again. It's like, 00:30:31.280 |
"Oh, no, no. You already tried that. It didn't work." 00:30:32.800 |
>> Exactly. >> Yeah. So, that gets some variety. 00:30:39.120 |
the image sequences. I kind of want to, like... Separately to this project, you will have this 00:30:43.440 |
data set, and we can try the different, like... Prompt them with the whole image sequence, 00:30:47.520 |
prompt them with guess history versus not, you know, like, experiment with that as, like, a task. 00:30:52.800 |
At the moment, yeah. As you can see, just a simple prompt, and hope for the best. 00:30:58.080 |
Oh, also, like, all of these should be doing things like, you know, checking for rate limiting. 00:31:06.080 |
I think I do have a little bit in this one. The others really do need, like, error catching. 00:31:11.520 |
I noticed once we released it publicly, Jem and I were suddenly having, you know, 00:31:18.560 |
obviously disapproved of some of the image content, the safety moderation. 00:31:21.920 |
>> Of course. >> You didn't get any text back in 00:31:23.840 |
a response. You instead get some different thing. >> That's another thing you'll get for free with 00:31:26.880 |
stuff like Claudette, kind of an exponential back off type thing. 00:31:29.680 |
>> Right. Yeah. So, exponential back off, catching different errors, maybe, like, you know, 00:31:37.600 |
banning a session if the person's drawings are regularly triggering a safety filter. 00:31:43.520 |
But, you know, some sort of, like, maybe separate prompt that's just a moderation 00:31:48.480 |
prompt. Is this something that we want on the general, you know, spectate feed or not? 00:31:52.960 |
Yeah. So, that's definitely lots of room for improvement there. 00:31:57.600 |
And then what's happening is these different functions are being called in this background 00:32:03.520 |
task. And so, we're keeping track of, like, what games are running. There's, like, we don't want to 00:32:14.240 |
repeatedly, like, guess too fast. We also don't want to wait too long. So, we've got a timeout 00:32:20.800 |
and, like, a minimum time. And then at some point, we're, like, running these tasks with an image. 00:32:25.840 |
>> And presumably a lot of this is LLM written? >> Yeah. Pretty much all of this is. 00:32:32.240 |
>> Yeah, you could. >> Yeah. So, I think this is 00:32:37.200 |
the part that I feel like I'm sure I can rewrite this much neater. 00:32:40.880 |
>> But, I mean, you know, it's fine. Like, if it's a, you know, pretty well-defined blocker 00:32:44.880 |
functionality that an LLM can write, then that's good. 00:32:48.400 |
>> Yeah. Yeah. I think it could do with some thinking about, like, handling the errors better, 00:32:56.320 |
logging, and maybe also, like, the way it's updating the database. At the moment, 00:33:03.040 |
the Fastlight has the SQL set to, like, only allow access from a single thread. 00:33:08.080 |
And so, there's some, like, kludgy hacks around that that I did. Whereas, I think that's, like, 00:33:14.320 |
a flag we can toggle. >> Okay. Also, I wrote this little 00:33:18.000 |
Redis-based kind of Neo clone of Fastlight on the weekend, which I'm using for Vercel, 00:33:25.760 |
which is another option. >> Yeah. And it might be nice to put this, 00:33:31.600 |
you know, like, I think the way people build these apps often is, rather than having a volume, 00:33:36.080 |
and, like, I'm storing the images to disk here. Instead, you'd have, like, blob storage for the 00:33:40.720 |
image files, and you'd have, yeah, like, Redis or some other KB cache for your database that you 00:33:45.840 |
could, you know, have separate dashboards for it elsewhere and, you know, like, edit it and tweak 00:33:52.160 |
it and export it. >> But that's hard to be set for, like, 00:33:56.000 |
the simplicity of, like, SQL in a file system, you know? They're things we're pretty familiar with, 00:34:02.080 |
and they're not specific to the service either. >> Yeah. And it's quite nice, like, you know, 00:34:08.480 |
you just zip up that data folder, and you've got your data export, so. 00:34:18.800 |
>> There's another thread. Thank you. Kicking any players who haven't, like, 00:34:22.480 |
accessed the website in over a minute. >> Okay. 00:34:25.040 |
>> You know, so there's all these little pieces that are, like, nice to have. 00:34:27.360 |
>> And you've got that with a start thread decorator, so it's running in the background, 00:34:30.640 |
which is a nice, like, example of having a background task. Very nice. 00:34:34.880 |
>> Yeah. Yeah. I think that's pretty much it. I will have to go check why the live demo stopped, 00:34:42.560 |
but it's done, I think, like, 200 or 300 games in the last few hours. 00:34:46.560 |
>> And perfect timing. Time for me to go and teach my daughter math, so. 00:34:49.840 |
>> Oh, fantastic. >> Thanks for the walkthrough, Johnno. 00:34:52.000 |
>> Yeah, thanks, Jeremy. Hopefully, it's useful. >> And I'll be around again after math.