back to index

AI Pictionary - Answer.AI dev chat #5


Whisper Transcript | Transcript Only Page

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:34.960 | >> Yeah, very inexpensive. >> And so on.
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:16.480 | >> Nice. >> And so, we can look
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:45.440 | previews. >> I really like those. Yeah.
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:45.280 | >> Nice. >> So I guess we can look at join.
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:06.080 | Also, just, like, I really like that
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:06.640 | And -- >> And, like, if you wanted to,
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:39.600 | I/O read, but -- yeah. >> Okay.
00:27:43.680 | >> Might take five minutes. >> Yeah.
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:36.480 | >> Nice. >> I'm saving the images and, like,
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:13.280 | >> Yeah. >> Yeah. Okay. I think
00:34:17.040 | that's pretty much it. >> Thanks, John.
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.
00:34:56.480 | >> Cool. >> If you are. See you.