|
83 | 83 | "name": "stdout",
|
84 | 84 | "output_type": "stream",
|
85 | 85 | "text": [
|
86 |
| - "<!doctype html>\n", |
| 86 | + "<!doctype html></!doctype>\n", |
87 | 87 | "\n",
|
88 | 88 | "<html>\n",
|
89 | 89 | " <head>\n",
|
|
117 | 117 | {
|
118 | 118 | "data": {
|
119 | 119 | "text/html": [
|
120 |
| - "<!doctype html>\n", |
| 120 | + "<!doctype html></!doctype>\n", |
121 | 121 | "\n",
|
122 | 122 | "<html>\n",
|
123 | 123 | " <head>\n",
|
|
191 | 191 | {
|
192 | 192 | "data": {
|
193 | 193 | "text/plain": [
|
194 |
| - "'<!doctype html>\\n\\n<html>\\n <head>\\n <title>FastHTML page</title>\\n <script src=\"https://unpkg.com/htmx.org@1.9.12\" crossorigin=\"anonymous\" integrity=\"sha384-ujb1lZYygJmzgSwoxRggbCHcjc0rB2XoQrxeTUQyRjrOnlCoYta87iKBWq3EsdM2\"></script>\\n </head>\\n <body>\\n <div>\\n <h1>Hello, World</h1>\\n <p>Some text</p>\\n <p>Some more text</p>\\n </div>\\n </body>\\n</html>\\n'" |
| 194 | + "'<!doctype html> </!doctype>\\n\\n<html>\\n <head>\\n <title>FastHTML page</title>\\n < meta charset=\"utf-8\"></meta>\\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, viewport-fit=cover\"></meta>\\n <script src=\"https://unpkg.com/htmx.org@ next/dist/htmx.min.js\"></script>\\n <script src=\" https://cdn.jsdelivr.net/gh/answerdotai/[email protected]/surreal.js\"></script>\\n <script src=\" https://cdn.jsdelivr.net/gh/gnat/css-scope-inline@main/script.js\"></script>\\n </head>\\n <body>\\n<div>\\n <h1>Hello, World</h1>\\n <p>Some text</p>\\n <p>Some more text</p>\\n</div>\\n </body>\\n</html>\\n'" |
195 | 195 | ]
|
196 | 196 | },
|
197 | 197 | "execution_count": null,
|
|
224 | 224 | "name": "stdout",
|
225 | 225 | "output_type": "stream",
|
226 | 226 | "text": [
|
227 |
| - "<!doctype html>\n", |
| 227 | + "<!doctype html></!doctype>\n", |
228 | 228 | "\n",
|
229 | 229 | "<html>\n",
|
230 | 230 | " <head>\n",
|
231 | 231 | " <title>Page Demo</title>\n",
|
232 |
| - " <script src=\"https://unpkg.com/ [email protected]\" crossorigin=\"anonymous\" integrity=\"sha384-ujb1lZYygJmzgSwoxRggbCHcjc0rB2XoQrxeTUQyRjrOnlCoYta87iKBWq3EsdM2\"></script>\n", |
| 232 | + " <meta charset=\"utf-8\"></meta>\n", |
| 233 | + " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, viewport-fit=cover\"></meta>\n", |
| 234 | + " <script src=\"https://unpkg.com/htmx.org@next/dist/htmx.min.js\"></script>\n", |
| 235 | + " <script src=\"https://cdn.jsdelivr.net/gh/answerdotai/ [email protected]/surreal.js\"></script>\n", |
| 236 | + " <script src=\"https://cdn.jsdelivr.net/gh/gnat/css-scope-inline@main/script.js\"></script>\n", |
233 | 237 | " </head>\n",
|
234 | 238 | " <body>\n",
|
235 | 239 | "<div>\n",
|
|
415 | 419 | "cell_type": "markdown",
|
416 | 420 | "metadata": {},
|
417 | 421 | "source": [
|
418 |
| - "You can check out the pico [examples](https://picocss.com/examples) page to see how different elements will look. If everything is working, the page should now render nice text with our custom font, and it should respect the user's light/dark mode preferences too:\n", |
419 |
| - "\n", |
420 |
| - "TODO screenshot" |
| 422 | + "You can check out the pico [examples](https://picocss.com/examples) page to see how different elements will look. If everything is working, the page should now render nice text with our custom font, and it should respect the user's light/dark mode preferences too." |
421 | 423 | ]
|
422 | 424 | },
|
423 | 425 | {
|
|
581 | 583 | "```python\n",
|
582 | 584 | "# Main page\n",
|
583 | 585 | "@app.get(\"/\")\n",
|
584 |
| - "async def get():\n", |
| 586 | + "def get():\n", |
585 | 587 | " inp = Input(id=\"new-prompt\", name=\"prompt\", placeholder=\"Enter a prompt\")\n",
|
586 | 588 | " add = Form(Group(inp, Button(\"Generate\")), hx_post=\"/\", target_id='gen-list', hx_swap=\"afterbegin\")\n",
|
587 | 589 | " gen_list = Div(id='gen-list')\n",
|
|
600 | 602 | " hx_trigger='every 1s', hx_swap='outerHTML')\n",
|
601 | 603 | " \n",
|
602 | 604 | "@app.post(\"/generations/{id}\")\n",
|
603 |
| - "async def get(id:int): return generation_preview(id)\n", |
| 605 | + "def get(id:int): return generation_preview(id)\n", |
604 | 606 | "\n",
|
605 | 607 | "@app.post(\"/\")\n",
|
606 |
| - "async def post(prompt:str):\n", |
| 608 | + "def post(prompt:str):\n", |
607 | 609 | " id = len(generations)\n",
|
608 | 610 | " generate_and_save(prompt, id)\n",
|
609 | 611 | " generations.append(prompt)\n",
|
|
741 | 743 | "\n",
|
742 | 744 | "# Main page\n",
|
743 | 745 | "@app.get(\"/\")\n",
|
744 |
| - "async def get():\n", |
| 746 | + "def get():\n", |
745 | 747 | " inp = Input(id=\"new-prompt\", name=\"prompt\", placeholder=\"Enter a prompt\")\n",
|
746 | 748 | " add = Form(Group(inp, Button(\"Generate\")), hx_post=\"/\", target_id='gen-list', hx_swap=\"afterbegin\")\n",
|
747 | 749 | " gen_containers = [generation_preview(g) for g in gens(limit=10)] # Start with last 10\n",
|
|
767 | 769 | "cell_type": "markdown",
|
768 | 770 | "metadata": {},
|
769 | 771 | "source": [
|
770 |
| - "You can see the final result in [main.py](https://github.com/AnswerDotAI/fasthtml-example/blob/main/image_app_simple/main.py) in the `image_app_simple` example directory, along with info on deploying it (tl;dr don't!). We've also deployed a version that only shows *your* generations (tied to browser session) and has a credit system to save our bank accounts. You can access that [here](https://image-gen-public-credit-pool.replit.app/). A tutorial on payment processing with Stripe may be added eventually. For now, how do we keep track of different users?" |
| 772 | + "You can see the final result in [main.py](https://github.com/AnswerDotAI/fasthtml-example/blob/main/image_app_simple/main.py) in the `image_app_simple` example directory, along with info on deploying it (tl;dr don't!). We've also deployed a version that only shows *your* generations (tied to browser session) and has a credit system to save our bank accounts. You can access that [here](https://image-gen-public-credit-pool.replit.app/). Now for the next question: how do we keep track of different users?" |
771 | 773 | ]
|
772 | 774 | },
|
773 | 775 | {
|
|
780 | 782 | "\n",
|
781 | 783 | "```python\n",
|
782 | 784 | "@app.get(\"/\")\n",
|
783 |
| - "async def get(session):\n", |
| 785 | + "def get(session):\n", |
784 | 786 | " if 'session_id' not in session: session['session_id'] = str(uuid.uuid4())\n",
|
785 | 787 | " return H1(f\"Session ID: {session['session_id']}\")\n",
|
786 | 788 | "```\n",
|
|
790 | 792 | "In the image app example, we can add a `session_id` column to our database, and modify our homepage like so:\n",
|
791 | 793 | "```python\n",
|
792 | 794 | "@app.get(\"/\")\n",
|
793 |
| - "async def get(session):\n", |
| 795 | + "def get(session):\n", |
794 | 796 | " if 'session_id' not in session: session['session_id'] = str(uuid.uuid4())\n",
|
795 | 797 | " inp = Input(id=\"new-prompt\", name=\"prompt\", placeholder=\"Enter a prompt\")\n",
|
796 | 798 | " add = Form(Group(inp, Button(\"Generate\")), hx_post=\"/\", target_id='gen-list', hx_swap=\"afterbegin\")\n",
|
|
975 | 977 | ],
|
976 | 978 | "source": [
|
977 | 979 | "@app.get(\"/files/{path}\")\n",
|
978 |
| - "async def txt(path: Path): return path.with_suffix('.txt')\n", |
| 980 | + "def txt(path: Path): return path.with_suffix('.txt')\n", |
979 | 981 | "\n",
|
980 | 982 | "print(cli.get('/files/foo').text)"
|
981 | 983 | ]
|
|
1129 | 1131 | " a:int;b:str\n",
|
1130 | 1132 | "\n",
|
1131 | 1133 | "@app.route(\"/bodie/{nm}\")\n",
|
1132 |
| - "async def post(nm:str, data:Bodie):\n", |
| 1134 | + "def post(nm:str, data:Bodie):\n", |
1133 | 1135 | " res = asdict(data)\n",
|
1134 | 1136 | " res['nm'] = nm\n",
|
1135 | 1137 | " return res\n",
|
|
1154 | 1156 | {
|
1155 | 1157 | "data": {
|
1156 | 1158 | "text/plain": [
|
1157 |
| - "'Set to 2024-06-14 18:48:07.942677'" |
| 1159 | + "'Set to 2024-07-17 08:58:08.810594'" |
1158 | 1160 | ]
|
1159 | 1161 | },
|
1160 | 1162 | "execution_count": null,
|
|
1166 | 1168 | "from datetime import datetime\n",
|
1167 | 1169 | "\n",
|
1168 | 1170 | "@app.get(\"/setcookie\")\n",
|
1169 |
| - "async def setc(req):\n", |
| 1171 | + "def setc(req):\n", |
1170 | 1172 | " now = datetime.now()\n",
|
1171 | 1173 | " res = Response(f'Set to {now}')\n",
|
1172 | 1174 | " res.set_cookie('now', str(now))\n",
|
|
1183 | 1185 | {
|
1184 | 1186 | "data": {
|
1185 | 1187 | "text/plain": [
|
1186 |
| - "'Cookie was set at time 18:48:07.942677'" |
| 1188 | + "'Cookie was set at time 08:58:08.810594'" |
1187 | 1189 | ]
|
1188 | 1190 | },
|
1189 | 1191 | "execution_count": null,
|
|
1193 | 1195 | ],
|
1194 | 1196 | "source": [
|
1195 | 1197 | "@app.get(\"/getcookie\")\n",
|
1196 |
| - "async def getc(now:date): return f'Cookie was set at time {now.time()}'\n", |
| 1198 | + "def getc(now:date): return f'Cookie was set at time {now.time()}'\n", |
1197 | 1199 | "\n",
|
1198 | 1200 | "cli.get('/getcookie').text"
|
1199 | 1201 | ]
|
|
1281 | 1283 | "\n",
|
1282 | 1284 | "```python\n",
|
1283 | 1285 | "@app.get(\"/redirect\")\n",
|
1284 |
| - "async def redirect():\n", |
| 1286 | + "def redirect():\n", |
1285 | 1287 | " return RedirectResponse(url=\"/\")\n",
|
1286 | 1288 | "```\n",
|
1287 | 1289 | "\n",
|
|
1299 | 1301 | "```python\n",
|
1300 | 1302 | "# For images, CSS, etc.\n",
|
1301 | 1303 | "@app.get(\"/{fname:path}.{ext:static}\")\n",
|
1302 |
| - "async def static(fname: str, ext: str):\n", |
| 1304 | + "def static(fname: str, ext: str):\n", |
1303 | 1305 | " return FileResponse(f'{fname}.{ext}')\n",
|
1304 | 1306 | "```\n",
|
1305 | 1307 | "\n",
|
|
1364 | 1366 | "cell_type": "markdown",
|
1365 | 1367 | "metadata": {},
|
1366 | 1368 | "source": [
|
1367 |
| - "## Full Example #3 - Chatbot Example with DaisyUI Components (WIP)\n", |
| 1369 | + "## Full Example #3 - Chatbot Example with DaisyUI Components\n", |
1368 | 1370 | "\n",
|
1369 | 1371 | "Let's go back to the topic of adding components or styling beyond the simple PicoCSS examples so far. How might we adopt a component or framework? In this example, let's build a chatbot UI leveraging the [DaisyUI chat bubble](https://daisyui.com/components/chat/). The final result will look like this:"
|
1370 | 1372 | ]
|
|
1502 | 1504 | "cell_type": "markdown",
|
1503 | 1505 | "metadata": {},
|
1504 | 1506 | "source": [
|
1505 |
| - "The actual chat functionality of the app is based on our *cosette* library, a version of [claudette](https://claudette.answer.ai/) for OpenAI models. As with the image example, we face a potential hiccup in that getting a response from an LLM is slow. We need a way to have the user message added to the UI immadiately, and then have the response added once it's available. We could do something similar to the image generation example above, or dive into WebSockets or Server-Sent Events. The example TODO link includes both (websockets TODO)." |
| 1507 | + "The actual chat functionality of the app is based on our *cosette* library, a version of [claudette](https://claudette.answer.ai/) for OpenAI models. As with the image example, we face a potential hiccup in that getting a response from an LLM is slow. We need a way to have the user message added to the UI immadiately, and then have the response added once it's available. We could do something similar to the image generation example above, or use websockets. Check out the [full example](https://github.com/AnswerDotAI/fasthtml-example/tree/main/02_chatbot) for implementations of both, along with further details." |
1506 | 1508 | ]
|
1507 | 1509 | },
|
1508 | 1510 | {
|
|
0 commit comments