[{"data":1,"prerenderedAt":7939},["ShallowReactive",2],{"post-building-a-chat-interface-to-search-github":3},{"id":4,"title":5,"body":6,"cover":7922,"date":7923,"description":7924,"draft":7925,"extension":7926,"hashnodeId":7927,"meta":7928,"navigation":279,"path":7929,"seo":7930,"slug":7931,"stem":7931,"tags":7932,"__hash__":7938},"posts\u002Fbuilding-a-chat-interface-to-search-github.md","Rethinking GitHub Search: How I built a Chat Interface to search GitHub",{"type":7,"value":8,"toc":7888},"minimark",[9,21,24,29,41,48,57,61,64,83,86,93,101,104,108,128,133,136,186,190,215,221,236,301,308,312,323,336,383,389,438,444,501,517,520,523,527,530,534,537,557,560,788,815,822,1108,1111,1131,1134,1613,1617,1628,2159,2173,2177,2180,2339,2342,2346,2349,2361,3066,3087,3126,3145,3148,3152,3158,3168,3179,3185,3189,3192,4052,4059,4113,4117,4127,4621,4631,4685,4688,4719,4723,4730,5524,5527,5574,5577,5581,5588,5594,5598,5609,5620,5789,5805,5816,6016,6022,6077,6081,6091,6094,6334,6345,6352,6356,6359,6363,6372,6379,6475,6485,7151,7154,7529,7533,7536,7758,7762,7765,7771,7774,7777,7781,7784,7788,7792,7800,7803,7813,7817,7835,7838,7852,7855,7859,7862,7865,7868,7871,7874,7884],[10,11,12,13,20],"p",{},"Here I am again, exploring the world of chat interfaces. If you've been following my journey (I mean my ",[14,15,19],"a",{"href":16,"rel":17},"https:\u002F\u002Frajeev.dev\u002Fcreate-cloudflare-workers-ai-llm-playground-using-nuxthub-and-nuxtui",[18],"nofollow","last post","), you might think, 'Rajeev, you're becoming obsessed with chatting!' And you wouldn't be entirely wrong. While I'm not always the most talkative person in a room, give me the right topic - like simplifying how we interact with technology - and my excitement becomes difficult to contain.",[10,22,23],{},"This time, my enthusiasm has led me to tackle a challenge many developers face often: searching GitHub efficiently. Let me introduce Chat GitHub, a tool where the complexity of code repositories meets the simplicity of conversation.",[25,26,28],"h2",{"id":27},"why-chat-github","Why Chat GitHub?",[10,30,31,32,36,37,40],{},"So, why a chat interface for GitHub search? Well, GitHub search, as powerful as it is with all its filters, can’t beat the simplicity of natural language queries. Let’s be real—it won’t answer simple questions like, ",[33,34,35],"em",{},"“When did I make my first commit?”"," (What filters would you even use to find this information with the current GitHub Search?). GitHub certainly knows (reminds me of ",[33,38,39],{},"“I Know What You Did Last Summer”","), but it just won’t answer you directly.",[10,42,43,44,47],{},"Natural language makes the experience frictionless. Instead of crafting complex queries, you can just ",[33,45,46],{},"ask"," GitHub, like you would a colleague.",[10,49,50,51,56],{},"When ",[14,52,55],{"href":53,"rel":54},"https:\u002F\u002Fhashnode.com\u002F@atinuxt",[18],"Sébastien Chopin"," mentioned the idea to me, I was onboard immediately (also because I was itching to build something…).",[25,58,60],{"id":59},"how-it-works","How it Works?",[10,62,63],{},"At its core, Chat GitHub leverages the power of OpenAI's language models to interpret your natural language queries and translate them into GitHub's search syntax. Here's a simplified breakdown of the process:",[65,66,67,71,74,77,80],"ol",{},[68,69,70],"li",{},"You enter a query in plain English",[68,72,73],{},"The AI model interprets your request",[68,75,76],{},"It then selects the appropriate search endpoint, and generates the necessary GitHub API query parameters",[68,78,79],{},"We send a request to GitHub's API with these details",[68,81,82],{},"The results are fetched and presented to you in a clean, easy-to-digest chat interface",[10,84,85],{},"Here's a sneak peek of what the chat interface looks like:",[10,87,88],{},[89,90],"img",{"alt":91,"src":92},"chat github's chatting interface","\u002Fimages\u002Fposts\u002Fbuilding-a-chat-interface-to-search-github\u002F2b8c0fe0-0564-4f0f-8609-19d249549829-662eee868c.png",[10,94,95,96],{},"You can try it out live here: ",[14,97,100],{"href":98,"rel":99},"https:\u002F\u002Fchat-github.nuxt.dev\u002F",[18],"https:\u002F\u002Fchat-github.nuxt.dev",[10,102,103],{},"We’ll explore the key aspects of this process in the sections to follow. Ready? Let’s get started.",[25,105,107],{"id":106},"project-setup","Project Setup",[10,109,110,111,116,117,122,123,127],{},"This project follows a similar setup to my last one ",[14,112,115],{"href":113,"rel":114},"https:\u002F\u002Fhub-chat.nuxt.dev",[18],"Hub Chat"," (",[14,118,121],{"href":119,"rel":120},"https:\u002F\u002Fgithub.com\u002Fra-jeev\u002Fhub-chat",[18],"GitHub link","), and I’ve reused several components with some slight modifications. I won't bore you by repeating the same details, but if you’re new here, feel free to follow along with both posts (",[14,124,126],{"href":16,"rel":125},[18],"previous post",") for a more complete picture.",[129,130,132],"h3",{"id":131},"tech-stack","Tech Stack",[10,134,135],{},"Here’s a quick breakdown of the tech stack:",[137,138,139,146,152,162,168,174,180],"ul",{},[68,140,141,145],{},[142,143,144],"strong",{},"Nuxt 3",": For the overall framework and routing.",[68,147,148,151],{},[142,149,150],{},"OpenAI APIs",": To handle the natural language processing.",[68,153,154,157,158,161],{},[142,155,156],{},"GitHub API",": To fetch the data you’re looking for—remember? Only ",[33,159,160],{},"it"," knows what you did last summer.",[68,163,164,167],{},[142,165,166],{},"NuxtUI",": To make sure the UI is smooth and responsive.",[68,169,170,173],{},[142,171,172],{},"Nuxt-Auth-Utils",": For user authentication and handling GitHub login (Only authenticated users can start a chat).",[68,175,176,179],{},[142,177,178],{},"Nuxt MDC:"," For parsing and displaying the markdown responses",[68,181,182,185],{},[142,183,184],{},"NuxtHub",": For deployment, database, and caching (all powered by Cloudflare).",[129,187,189],{"id":188},"prerequisites","Prerequisites",[137,191,192,203,209],{},[68,193,194,197,198,202],{},[142,195,196],{},"GitHub account:"," You'll need this to generate a ",[199,200,201],"code",{},"GITHUB_TOKEN"," for API queries, and to create an OAuth App for authentication.",[68,204,205,208],{},[142,206,207],{},"OpenAI account:"," For creating an OpenAI API key, so the app can process your queries.",[68,210,211,214],{},[142,212,213],{},"Optional",": If you're looking to deploy the project yourself, you'll need Cloudflare and a NuxtHub account.",[129,216,218],{"id":217},"setting-up-the-project",[142,219,220],{},"Setting up the project",[10,222,223,224,227,228,231,232,235],{},"Follow the setup process from my previous article. Just remember to add the ",[199,225,226],{},"nuxt-auth-utils",", ",[199,229,230],{},"@octokit\u002Frest"," and ",[199,233,234],{},"OpenAI"," dependencies.",[237,238,243],"pre",{"className":239,"code":240,"language":241,"meta":242,"style":242},"language-bash shiki shiki-themes github-light github-dark","# Add the nuxt-auth-utils module\nnpx nuxi module add auth-utils\n\n# Add the Octokit Rest & OpenAI libraries\npnpm add @octokit\u002Frest openai\n","bash","",[199,244,245,254,274,281,287],{"__ignoreMap":242},[246,247,250],"span",{"class":248,"line":249},"line",1,[246,251,253],{"class":252},"sJ8bj","# Add the nuxt-auth-utils module\n",[246,255,257,261,265,268,271],{"class":248,"line":256},2,[246,258,260],{"class":259},"sScJk","npx",[246,262,264],{"class":263},"sZZnC"," nuxi",[246,266,267],{"class":263}," module",[246,269,270],{"class":263}," add",[246,272,273],{"class":263}," auth-utils\n",[246,275,277],{"class":248,"line":276},3,[246,278,280],{"emptyLinePlaceholder":279},true,"\n",[246,282,284],{"class":248,"line":283},4,[246,285,286],{"class":252},"# Add the Octokit Rest & OpenAI libraries\n",[246,288,290,293,295,298],{"class":248,"line":289},5,[246,291,292],{"class":259},"pnpm",[246,294,270],{"class":263},[246,296,297],{"class":263}," @octokit\u002Frest",[246,299,300],{"class":263}," openai\n",[10,302,303,304,307],{},"Once the dependencies are set up, you should be able to run the project with ",[199,305,306],{},"pnpm dev"," and see the default app UI at localhost:3000.",[129,309,311],{"id":310},"configs-and-environment-variables","Configs and Environment Variables",[10,313,314,315,318,319,322],{},"At this point, you can enable the hub database and cache in the ",[199,316,317],{},"nuxt.config.ts"," file for later use, as well as create the necessary API tokens and keys to place in the ",[199,320,321],{},".env"," file.",[10,324,325,326,231,329,332,333,335],{},"Enabling ",[199,327,328],{},"database",[199,330,331],{},"cache"," in ",[199,334,317],{},":",[237,337,341],{"className":338,"code":339,"language":340,"meta":242,"style":242},"language-typescript shiki shiki-themes github-light github-dark","hub: {\n  cache: true,\n  database: true,\n},\n","typescript",[199,342,343,352,367,378],{"__ignoreMap":242},[246,344,345,348],{"class":248,"line":249},[246,346,347],{"class":259},"hub",[246,349,351],{"class":350},"sVt8B",": {\n",[246,353,354,357,360,364],{"class":248,"line":256},[246,355,356],{"class":259},"  cache",[246,358,359],{"class":350},": ",[246,361,363],{"class":362},"sj4cs","true",[246,365,366],{"class":350},",\n",[246,368,369,372,374,376],{"class":248,"line":276},[246,370,371],{"class":259},"  database",[246,373,359],{"class":350},[246,375,363],{"class":362},[246,377,366],{"class":350},[246,379,380],{"class":248,"line":283},[246,381,382],{"class":350},"},\n",[10,384,385,386,388],{},"Next, generate the following tokens and keys, and store them in your ",[199,387,321],{}," file (located in the root of your project):",[137,390,391,403,415],{},[68,392,393,396,397,402],{},[142,394,395],{},"GitHub Token",": Create a ",[14,398,401],{"href":399,"rel":400},"https:\u002F\u002Fgithub.com\u002Fsettings\u002Fpersonal-access-tokens\u002Fnew",[18],"GitHub token"," (no special scope required) for making API calls.",[68,404,405,408,409,414],{},[142,406,407],{},"OpenAI API Key",": Generate a key from your ",[14,410,413],{"href":411,"rel":412},"https:\u002F\u002Fplatform.openai.com\u002Fapi-keys",[18],"OpenAI dashboard",".",[68,416,417,396,420,425,426,231,429,432,433],{},[142,418,419],{},"GitHub OAuth App",[14,421,424],{"href":422,"rel":423},"https:\u002F\u002Fgithub.com\u002Fsettings\u002Fapplications\u002Fnew",[18],"GitHub OAuth app"," and get its ",[199,427,428],{},"CLIENT_ID",[199,430,431],{},"CLIENT_SECRET",". This will be used to authenticate users (only authenticated users can start a chat). For more information on how to create a GitHub OAuth app, refer to the ",[14,434,437],{"href":435,"rel":436},"https:\u002F\u002Fdocs.github.com\u002Fen\u002Fdevelopers\u002Fapps\u002Fbuilding-oauth-apps\u002Fcreating-an-oauth-app",[18],"GitHub documentation.",[10,439,440,441,443],{},"Your ",[199,442,321],{}," should contain the following entries:",[237,445,447],{"className":239,"code":446,"language":241,"meta":242,"style":242},"NUXT_SESSION_PASSWORD=at_least_32_chars_string\nNUXT_OAUTH_GITHUB_CLIENT_ID=github_oauth_client_id\nNUXT_OAUTH_GITHUB_CLIENT_SECRET=github_oauth_client_secret\nNUXT_GITHUB_TOKEN=your_personal_access_token\nOPENAI_API_KEY=your_openai_api_key\n",[199,448,449,461,471,481,491],{"__ignoreMap":242},[246,450,451,454,458],{"class":248,"line":249},[246,452,453],{"class":350},"NUXT_SESSION_PASSWORD",[246,455,457],{"class":456},"szBVR","=",[246,459,460],{"class":263},"at_least_32_chars_string\n",[246,462,463,466,468],{"class":248,"line":256},[246,464,465],{"class":350},"NUXT_OAUTH_GITHUB_CLIENT_ID",[246,467,457],{"class":456},[246,469,470],{"class":263},"github_oauth_client_id\n",[246,472,473,476,478],{"class":248,"line":276},[246,474,475],{"class":350},"NUXT_OAUTH_GITHUB_CLIENT_SECRET",[246,477,457],{"class":456},[246,479,480],{"class":263},"github_oauth_client_secret\n",[246,482,483,486,488],{"class":248,"line":283},[246,484,485],{"class":350},"NUXT_GITHUB_TOKEN",[246,487,457],{"class":456},[246,489,490],{"class":263},"your_personal_access_token\n",[246,492,493,496,498],{"class":248,"line":289},[246,494,495],{"class":350},"OPENAI_API_KEY",[246,497,457],{"class":456},[246,499,500],{"class":263},"your_openai_api_key\n",[10,502,503,506,507,506,509,506,512,506,514],{},[33,504,505],{},"Note:"," ",[199,508,453],{},[33,510,511],{},"will automatically be created by",[199,513,226],{},[33,515,516],{},"in development if you haven’t set it manually.",[10,518,519],{},"And that’s it for the setup—phew!",[10,521,522],{},"In the next section, we’ll explore the core of the chat process: making sense of the user query, converting it to a GitHub API call amd generating a final response.",[25,524,526],{"id":525},"from-user-query-to-github-api-call","From User Query to GitHub API Call",[10,528,529],{},"Now, let’s break down how Chat GitHub processes your query, identifies the necessary actions, and makes the appropriate GitHub API call. This involves three main steps: interpreting the user query, calling the necessary tools (the GitHub API), and generating the final response.",[129,531,533],{"id":532},"_1-interpreting-the-user-query","1. Interpreting the User Query",[10,535,536],{},"The first challenge is understanding what the user is asking for. To do this, the system relies on OpenAI’s language models to parse natural language inputs. It takes into account several factors:",[137,538,539,545,551],{},[68,540,541,544],{},[142,542,543],{},"Intent Recognition",": What is the user trying to achieve? Are they searching for commits, issues, repositories, or user profiles?",[68,546,547,550],{},[142,548,549],{},"Parameter Extraction",": Once the intent is clear, the model extracts necessary parameters like repo name, user, dates, and other filters. These details are critical for constructing a meaningful API call.",[68,552,553,556],{},[142,554,555],{},"Tool Selection",": Based on the query, the AI determines if a GitHub API tool needs to be invoked (for example, searching commits or issues).",[10,558,559],{},"To guide the AI, I created a detailed system prompt to provide context. Here’s a portion of that prompt:",[237,561,563],{"className":338,"code":562,"language":340,"meta":242,"style":242},"const systemPropmt = `You are a concise assistant who helps \\\nusers find information on GitHub. Use the supplied tools to \\\nfind information when asked.\n\nAvailable endpoints and key parameters:\n\n\u002F\u002F ...\n\n2. issues (Also searches PRs):\n  - Sort options: comments, reactions, reactions-+1, ...\n  - Query qualifiers: type, is, state, author, assignee, ...\n  - use \"type\" or \"is\" qualifier to search issues or PRs (type:issue\u002Ftype:pr)\n\n\u002F\u002F ...\n\nWhen using searchGithub function:\n1. Choose the appropriate search endpoint.\n2. Formulate a concise query (q) as per the user's request.\n3. Add any relevant sort or order parameters if needed.\n4. Always use appropriate per_page value to limit the number of results.\n\n\u002F\u002F ...\n\nExamples: \n\u002F\u002F ...\n\n2. Find the total number of repositories of a user\narguments: \n{\n  \"endpoint\": \"repositories\",\n  \"q\": \"user:\u003Cuser_login>\",\n  \"per_page\": 1\n}\n\nSummarize final response concisely using markdown when appropriate \\\n(for all links add {target=\"_blank\"} at the end). Do not include \\\nimages, commit SHA or hashes etc. in your summary.`\n",[199,564,565,582,589,594,598,603,608,614,619,625,631,637,643,648,653,658,664,670,676,682,688,693,698,703,709,714,719,725,731,737,743,749,755,761,766,774,782],{"__ignoreMap":242},[246,566,567,570,573,576,579],{"class":248,"line":249},[246,568,569],{"class":456},"const",[246,571,572],{"class":362}," systemPropmt",[246,574,575],{"class":456}," =",[246,577,578],{"class":263}," `You are a concise assistant who helps ",[246,580,581],{"class":362},"\\\n",[246,583,584,587],{"class":248,"line":256},[246,585,586],{"class":263},"users find information on GitHub. Use the supplied tools to ",[246,588,581],{"class":362},[246,590,591],{"class":248,"line":276},[246,592,593],{"class":263},"find information when asked.\n",[246,595,596],{"class":248,"line":283},[246,597,280],{"emptyLinePlaceholder":279},[246,599,600],{"class":248,"line":289},[246,601,602],{"class":263},"Available endpoints and key parameters:\n",[246,604,606],{"class":248,"line":605},6,[246,607,280],{"emptyLinePlaceholder":279},[246,609,611],{"class":248,"line":610},7,[246,612,613],{"class":263},"\u002F\u002F ...\n",[246,615,617],{"class":248,"line":616},8,[246,618,280],{"emptyLinePlaceholder":279},[246,620,622],{"class":248,"line":621},9,[246,623,624],{"class":263},"2. issues (Also searches PRs):\n",[246,626,628],{"class":248,"line":627},10,[246,629,630],{"class":263},"  - Sort options: comments, reactions, reactions-+1, ...\n",[246,632,634],{"class":248,"line":633},11,[246,635,636],{"class":263},"  - Query qualifiers: type, is, state, author, assignee, ...\n",[246,638,640],{"class":248,"line":639},12,[246,641,642],{"class":263},"  - use \"type\" or \"is\" qualifier to search issues or PRs (type:issue\u002Ftype:pr)\n",[246,644,646],{"class":248,"line":645},13,[246,647,280],{"emptyLinePlaceholder":279},[246,649,651],{"class":248,"line":650},14,[246,652,613],{"class":263},[246,654,656],{"class":248,"line":655},15,[246,657,280],{"emptyLinePlaceholder":279},[246,659,661],{"class":248,"line":660},16,[246,662,663],{"class":263},"When using searchGithub function:\n",[246,665,667],{"class":248,"line":666},17,[246,668,669],{"class":263},"1. Choose the appropriate search endpoint.\n",[246,671,673],{"class":248,"line":672},18,[246,674,675],{"class":263},"2. Formulate a concise query (q) as per the user's request.\n",[246,677,679],{"class":248,"line":678},19,[246,680,681],{"class":263},"3. Add any relevant sort or order parameters if needed.\n",[246,683,685],{"class":248,"line":684},20,[246,686,687],{"class":263},"4. Always use appropriate per_page value to limit the number of results.\n",[246,689,691],{"class":248,"line":690},21,[246,692,280],{"emptyLinePlaceholder":279},[246,694,696],{"class":248,"line":695},22,[246,697,613],{"class":263},[246,699,701],{"class":248,"line":700},23,[246,702,280],{"emptyLinePlaceholder":279},[246,704,706],{"class":248,"line":705},24,[246,707,708],{"class":263},"Examples: \n",[246,710,712],{"class":248,"line":711},25,[246,713,613],{"class":263},[246,715,717],{"class":248,"line":716},26,[246,718,280],{"emptyLinePlaceholder":279},[246,720,722],{"class":248,"line":721},27,[246,723,724],{"class":263},"2. Find the total number of repositories of a user\n",[246,726,728],{"class":248,"line":727},28,[246,729,730],{"class":263},"arguments: \n",[246,732,734],{"class":248,"line":733},29,[246,735,736],{"class":263},"{\n",[246,738,740],{"class":248,"line":739},30,[246,741,742],{"class":263},"  \"endpoint\": \"repositories\",\n",[246,744,746],{"class":248,"line":745},31,[246,747,748],{"class":263},"  \"q\": \"user:\u003Cuser_login>\",\n",[246,750,752],{"class":248,"line":751},32,[246,753,754],{"class":263},"  \"per_page\": 1\n",[246,756,758],{"class":248,"line":757},33,[246,759,760],{"class":263},"}\n",[246,762,764],{"class":248,"line":763},34,[246,765,280],{"emptyLinePlaceholder":279},[246,767,769,772],{"class":248,"line":768},35,[246,770,771],{"class":263},"Summarize final response concisely using markdown when appropriate ",[246,773,581],{"class":362},[246,775,777,780],{"class":248,"line":776},36,[246,778,779],{"class":263},"(for all links add {target=\"_blank\"} at the end). Do not include ",[246,781,581],{"class":362},[246,783,785],{"class":248,"line":784},37,[246,786,787],{"class":263},"images, commit SHA or hashes etc. in your summary.`\n",[10,789,790,506,793,796,506,799,802,506,804,506,807,506,810,813],{},[33,791,792],{},"Note: I restricted the AI to only the most important GitHub search endpoints",[199,794,795],{},"\u002Fsearch\u002Fcommits",[33,797,798],{},",",[199,800,801],{},"\u002Fsearch\u002Fissues",[33,803,798],{},[199,805,806],{},"\u002Fsearch\u002Frepositories",[33,808,809],{},"and",[199,811,812],{},"\u002Fsearch\u002Fusers",[33,814,414],{},[10,816,817,818,821],{},"In addition to the system prompt, we create tools definitions that lists the types of tools, their names, and their specific parameters (in this case I only create one function tool, ",[199,819,820],{},"searchGithub",").",[237,823,825],{"className":338,"code":824,"language":340,"meta":242,"style":242},"const tools: OpenAI.ChatCompletionTool[] = [\n  {\n    type: 'function',\n    function: {\n      name: 'searchGithub',\n      description:\n        'Searches GitHub for information using the GitHub API. Call this if you need to find information on GitHub.',\n      parameters: {\n        type: 'object',\n        properties: {\n          endpoint: {\n            type: 'string',\n            description: `The specific search endpoint to use. One of ['commits', 'issues', 'repositories', 'users']`,\n          },\n          q: {\n            type: 'string',\n            description: 'the search query using applicable qualifiers',\n          },\n          sort: {\n            type: 'string',\n            description: 'The sort field (optional, depends on the endpoint)',\n          },\n          order: {\n            type: 'string',\n            description: 'The sort order (optional, asc or desc)',\n          },\n          per_page: {\n            type: 'string',\n            description:\n              'Number of results to fetch per page (max 25)',\n          },\n        },\n        required: ['endpoint', 'q', 'per_page'],\n        additionalProperties: false,\n      },\n    },\n  },\n];\n",[199,826,827,852,857,867,872,882,887,894,899,909,914,919,929,939,944,949,957,966,970,975,983,992,996,1001,1009,1018,1022,1027,1035,1040,1047,1051,1056,1077,1087,1092,1097,1102],{"__ignoreMap":242},[246,828,829,831,834,836,839,841,844,847,849],{"class":248,"line":249},[246,830,569],{"class":456},[246,832,833],{"class":362}," tools",[246,835,335],{"class":456},[246,837,838],{"class":259}," OpenAI",[246,840,414],{"class":350},[246,842,843],{"class":259},"ChatCompletionTool",[246,845,846],{"class":350},"[] ",[246,848,457],{"class":456},[246,850,851],{"class":350}," [\n",[246,853,854],{"class":248,"line":256},[246,855,856],{"class":350},"  {\n",[246,858,859,862,865],{"class":248,"line":276},[246,860,861],{"class":350},"    type: ",[246,863,864],{"class":263},"'function'",[246,866,366],{"class":350},[246,868,869],{"class":248,"line":283},[246,870,871],{"class":350},"    function: {\n",[246,873,874,877,880],{"class":248,"line":289},[246,875,876],{"class":350},"      name: ",[246,878,879],{"class":263},"'searchGithub'",[246,881,366],{"class":350},[246,883,884],{"class":248,"line":605},[246,885,886],{"class":350},"      description:\n",[246,888,889,892],{"class":248,"line":610},[246,890,891],{"class":263},"        'Searches GitHub for information using the GitHub API. Call this if you need to find information on GitHub.'",[246,893,366],{"class":350},[246,895,896],{"class":248,"line":616},[246,897,898],{"class":350},"      parameters: {\n",[246,900,901,904,907],{"class":248,"line":621},[246,902,903],{"class":350},"        type: ",[246,905,906],{"class":263},"'object'",[246,908,366],{"class":350},[246,910,911],{"class":248,"line":627},[246,912,913],{"class":350},"        properties: {\n",[246,915,916],{"class":248,"line":633},[246,917,918],{"class":350},"          endpoint: {\n",[246,920,921,924,927],{"class":248,"line":639},[246,922,923],{"class":350},"            type: ",[246,925,926],{"class":263},"'string'",[246,928,366],{"class":350},[246,930,931,934,937],{"class":248,"line":645},[246,932,933],{"class":350},"            description: ",[246,935,936],{"class":263},"`The specific search endpoint to use. One of ['commits', 'issues', 'repositories', 'users']`",[246,938,366],{"class":350},[246,940,941],{"class":248,"line":650},[246,942,943],{"class":350},"          },\n",[246,945,946],{"class":248,"line":655},[246,947,948],{"class":350},"          q: {\n",[246,950,951,953,955],{"class":248,"line":660},[246,952,923],{"class":350},[246,954,926],{"class":263},[246,956,366],{"class":350},[246,958,959,961,964],{"class":248,"line":666},[246,960,933],{"class":350},[246,962,963],{"class":263},"'the search query using applicable qualifiers'",[246,965,366],{"class":350},[246,967,968],{"class":248,"line":672},[246,969,943],{"class":350},[246,971,972],{"class":248,"line":678},[246,973,974],{"class":350},"          sort: {\n",[246,976,977,979,981],{"class":248,"line":684},[246,978,923],{"class":350},[246,980,926],{"class":263},[246,982,366],{"class":350},[246,984,985,987,990],{"class":248,"line":690},[246,986,933],{"class":350},[246,988,989],{"class":263},"'The sort field (optional, depends on the endpoint)'",[246,991,366],{"class":350},[246,993,994],{"class":248,"line":695},[246,995,943],{"class":350},[246,997,998],{"class":248,"line":700},[246,999,1000],{"class":350},"          order: {\n",[246,1002,1003,1005,1007],{"class":248,"line":705},[246,1004,923],{"class":350},[246,1006,926],{"class":263},[246,1008,366],{"class":350},[246,1010,1011,1013,1016],{"class":248,"line":711},[246,1012,933],{"class":350},[246,1014,1015],{"class":263},"'The sort order (optional, asc or desc)'",[246,1017,366],{"class":350},[246,1019,1020],{"class":248,"line":716},[246,1021,943],{"class":350},[246,1023,1024],{"class":248,"line":721},[246,1025,1026],{"class":350},"          per_page: {\n",[246,1028,1029,1031,1033],{"class":248,"line":727},[246,1030,923],{"class":350},[246,1032,926],{"class":263},[246,1034,366],{"class":350},[246,1036,1037],{"class":248,"line":733},[246,1038,1039],{"class":350},"            description:\n",[246,1041,1042,1045],{"class":248,"line":739},[246,1043,1044],{"class":263},"              'Number of results to fetch per page (max 25)'",[246,1046,366],{"class":350},[246,1048,1049],{"class":248,"line":745},[246,1050,943],{"class":350},[246,1052,1053],{"class":248,"line":751},[246,1054,1055],{"class":350},"        },\n",[246,1057,1058,1061,1064,1066,1069,1071,1074],{"class":248,"line":757},[246,1059,1060],{"class":350},"        required: [",[246,1062,1063],{"class":263},"'endpoint'",[246,1065,227],{"class":350},[246,1067,1068],{"class":263},"'q'",[246,1070,227],{"class":350},[246,1072,1073],{"class":263},"'per_page'",[246,1075,1076],{"class":350},"],\n",[246,1078,1079,1082,1085],{"class":248,"line":763},[246,1080,1081],{"class":350},"        additionalProperties: ",[246,1083,1084],{"class":362},"false",[246,1086,366],{"class":350},[246,1088,1089],{"class":248,"line":768},[246,1090,1091],{"class":350},"      },\n",[246,1093,1094],{"class":248,"line":776},[246,1095,1096],{"class":350},"    },\n",[246,1098,1099],{"class":248,"line":784},[246,1100,1101],{"class":350},"  },\n",[246,1103,1105],{"class":248,"line":1104},38,[246,1106,1107],{"class":350},"];\n",[10,1109,1110],{},"A couple of key design decisions we made:",[137,1112,1113,1119,1125],{},[68,1114,1115,1118],{},[142,1116,1117],{},"Complimentary System Prompt & Tool Definition",": The system prompt gives context, while the tool definition ensures the API queries are correctly structured.",[68,1120,1121,1124],{},[142,1122,1123],{},"Result Limit",": While GitHub allows more items per response, we restrict results to 25 to keep things manageable.",[68,1126,1127,1130],{},[142,1128,1129],{},"No Pagination",": We decided to skip pagination, keeping things simple and avoiding unnecessary complexity.",[10,1132,1133],{},"Now, the AI is ready to handle the user query and transform it into a structured format that the system can use. Here is the relevant code:",[237,1135,1137],{"className":338,"code":1136,"language":340,"meta":242,"style":242},"let _openai: OpenAI;\nfunction useOpenAI() {\n  if (!_openai) {\n    _openai = new OpenAI({\n      apiKey: process.env.OPENAI_API_KEY,\n    });\n  }\n\n  return _openai;\n}\n\nexport const handleMessageWithOpenAI = async (\n  event: H3Event,\n  messages: OpenAI.Chat.ChatCompletionMessageParam[]\n) => {\n  const openai = useOpenAI();\n  const response = await openai.chat.completions.create({\n    model: MODEL, \u002F\u002F used gpt-4o\n    messages, \u002F\u002F contains system prompt and the complete chat history\n    tools, \u002F\u002F defined above\n  });\n    \n  const responseMessage = response.choices[0].message;\n  const toolCalls = responseMessage.tool_calls;\n    \n  if (toolCalls) {\n    messages.push(responseMessage);\n    \n    for (const toolCall of toolCalls) {\n      const functionName = toolCall.function.name;\n      if (functionName === 'searchGithub') {\n        const functionArgs = JSON.parse(toolCall.function.arguments);\n        const functionResponse = await searchGithub(\n          functionArgs.endpoint,\n          {\n            q: functionArgs.q,\n            sort: functionArgs.sort,\n            order: functionArgs.order,\n            per_page: functionArgs.per_page,\n          }\n        );\n    \n        \u002F\u002F ..\n      }\n    }\n    \n    \u002F\u002F ...\n  }\n    \n  return responseMessage.content;\n}\n",[199,1138,1139,1154,1165,1178,1193,1202,1207,1212,1216,1224,1228,1232,1251,1264,1286,1297,1312,1332,1345,1353,1361,1366,1371,1389,1401,1405,1412,1423,1427,1445,1458,1475,1496,1513,1518,1523,1528,1533,1538,1544,1550,1556,1561,1567,1573,1579,1584,1590,1595,1600,1608],{"__ignoreMap":242},[246,1140,1141,1144,1147,1149,1151],{"class":248,"line":249},[246,1142,1143],{"class":456},"let",[246,1145,1146],{"class":350}," _openai",[246,1148,335],{"class":456},[246,1150,838],{"class":259},[246,1152,1153],{"class":350},";\n",[246,1155,1156,1159,1162],{"class":248,"line":256},[246,1157,1158],{"class":456},"function",[246,1160,1161],{"class":259}," useOpenAI",[246,1163,1164],{"class":350},"() {\n",[246,1166,1167,1170,1172,1175],{"class":248,"line":276},[246,1168,1169],{"class":456},"  if",[246,1171,116],{"class":350},[246,1173,1174],{"class":456},"!",[246,1176,1177],{"class":350},"_openai) {\n",[246,1179,1180,1183,1185,1188,1190],{"class":248,"line":283},[246,1181,1182],{"class":350},"    _openai ",[246,1184,457],{"class":456},[246,1186,1187],{"class":456}," new",[246,1189,838],{"class":259},[246,1191,1192],{"class":350},"({\n",[246,1194,1195,1198,1200],{"class":248,"line":289},[246,1196,1197],{"class":350},"      apiKey: process.env.",[246,1199,495],{"class":362},[246,1201,366],{"class":350},[246,1203,1204],{"class":248,"line":605},[246,1205,1206],{"class":350},"    });\n",[246,1208,1209],{"class":248,"line":610},[246,1210,1211],{"class":350},"  }\n",[246,1213,1214],{"class":248,"line":616},[246,1215,280],{"emptyLinePlaceholder":279},[246,1217,1218,1221],{"class":248,"line":621},[246,1219,1220],{"class":456},"  return",[246,1222,1223],{"class":350}," _openai;\n",[246,1225,1226],{"class":248,"line":627},[246,1227,760],{"class":350},[246,1229,1230],{"class":248,"line":633},[246,1231,280],{"emptyLinePlaceholder":279},[246,1233,1234,1237,1240,1243,1245,1248],{"class":248,"line":639},[246,1235,1236],{"class":456},"export",[246,1238,1239],{"class":456}," const",[246,1241,1242],{"class":259}," handleMessageWithOpenAI",[246,1244,575],{"class":456},[246,1246,1247],{"class":456}," async",[246,1249,1250],{"class":350}," (\n",[246,1252,1253,1257,1259,1262],{"class":248,"line":645},[246,1254,1256],{"class":1255},"s4XuR","  event",[246,1258,335],{"class":456},[246,1260,1261],{"class":259}," H3Event",[246,1263,366],{"class":350},[246,1265,1266,1269,1271,1273,1275,1278,1280,1283],{"class":248,"line":650},[246,1267,1268],{"class":1255},"  messages",[246,1270,335],{"class":456},[246,1272,838],{"class":259},[246,1274,414],{"class":350},[246,1276,1277],{"class":259},"Chat",[246,1279,414],{"class":350},[246,1281,1282],{"class":259},"ChatCompletionMessageParam",[246,1284,1285],{"class":350},"[]\n",[246,1287,1288,1291,1294],{"class":248,"line":655},[246,1289,1290],{"class":350},") ",[246,1292,1293],{"class":456},"=>",[246,1295,1296],{"class":350}," {\n",[246,1298,1299,1302,1305,1307,1309],{"class":248,"line":660},[246,1300,1301],{"class":456},"  const",[246,1303,1304],{"class":362}," openai",[246,1306,575],{"class":456},[246,1308,1161],{"class":259},[246,1310,1311],{"class":350},"();\n",[246,1313,1314,1316,1319,1321,1324,1327,1330],{"class":248,"line":666},[246,1315,1301],{"class":456},[246,1317,1318],{"class":362}," response",[246,1320,575],{"class":456},[246,1322,1323],{"class":456}," await",[246,1325,1326],{"class":350}," openai.chat.completions.",[246,1328,1329],{"class":259},"create",[246,1331,1192],{"class":350},[246,1333,1334,1337,1340,1342],{"class":248,"line":672},[246,1335,1336],{"class":350},"    model: ",[246,1338,1339],{"class":362},"MODEL",[246,1341,227],{"class":350},[246,1343,1344],{"class":252},"\u002F\u002F used gpt-4o\n",[246,1346,1347,1350],{"class":248,"line":678},[246,1348,1349],{"class":350},"    messages, ",[246,1351,1352],{"class":252},"\u002F\u002F contains system prompt and the complete chat history\n",[246,1354,1355,1358],{"class":248,"line":684},[246,1356,1357],{"class":350},"    tools, ",[246,1359,1360],{"class":252},"\u002F\u002F defined above\n",[246,1362,1363],{"class":248,"line":690},[246,1364,1365],{"class":350},"  });\n",[246,1367,1368],{"class":248,"line":695},[246,1369,1370],{"class":350},"    \n",[246,1372,1373,1375,1378,1380,1383,1386],{"class":248,"line":700},[246,1374,1301],{"class":456},[246,1376,1377],{"class":362}," responseMessage",[246,1379,575],{"class":456},[246,1381,1382],{"class":350}," response.choices[",[246,1384,1385],{"class":362},"0",[246,1387,1388],{"class":350},"].message;\n",[246,1390,1391,1393,1396,1398],{"class":248,"line":705},[246,1392,1301],{"class":456},[246,1394,1395],{"class":362}," toolCalls",[246,1397,575],{"class":456},[246,1399,1400],{"class":350}," responseMessage.tool_calls;\n",[246,1402,1403],{"class":248,"line":711},[246,1404,1370],{"class":350},[246,1406,1407,1409],{"class":248,"line":716},[246,1408,1169],{"class":456},[246,1410,1411],{"class":350}," (toolCalls) {\n",[246,1413,1414,1417,1420],{"class":248,"line":721},[246,1415,1416],{"class":350},"    messages.",[246,1418,1419],{"class":259},"push",[246,1421,1422],{"class":350},"(responseMessage);\n",[246,1424,1425],{"class":248,"line":727},[246,1426,1370],{"class":350},[246,1428,1429,1432,1434,1436,1439,1442],{"class":248,"line":733},[246,1430,1431],{"class":456},"    for",[246,1433,116],{"class":350},[246,1435,569],{"class":456},[246,1437,1438],{"class":362}," toolCall",[246,1440,1441],{"class":456}," of",[246,1443,1444],{"class":350}," toolCalls) {\n",[246,1446,1447,1450,1453,1455],{"class":248,"line":739},[246,1448,1449],{"class":456},"      const",[246,1451,1452],{"class":362}," functionName",[246,1454,575],{"class":456},[246,1456,1457],{"class":350}," toolCall.function.name;\n",[246,1459,1460,1463,1466,1469,1472],{"class":248,"line":745},[246,1461,1462],{"class":456},"      if",[246,1464,1465],{"class":350}," (functionName ",[246,1467,1468],{"class":456},"===",[246,1470,1471],{"class":263}," 'searchGithub'",[246,1473,1474],{"class":350},") {\n",[246,1476,1477,1480,1483,1485,1488,1490,1493],{"class":248,"line":751},[246,1478,1479],{"class":456},"        const",[246,1481,1482],{"class":362}," functionArgs",[246,1484,575],{"class":456},[246,1486,1487],{"class":362}," JSON",[246,1489,414],{"class":350},[246,1491,1492],{"class":259},"parse",[246,1494,1495],{"class":350},"(toolCall.function.arguments);\n",[246,1497,1498,1500,1503,1505,1507,1510],{"class":248,"line":757},[246,1499,1479],{"class":456},[246,1501,1502],{"class":362}," functionResponse",[246,1504,575],{"class":456},[246,1506,1323],{"class":456},[246,1508,1509],{"class":259}," searchGithub",[246,1511,1512],{"class":350},"(\n",[246,1514,1515],{"class":248,"line":763},[246,1516,1517],{"class":350},"          functionArgs.endpoint,\n",[246,1519,1520],{"class":248,"line":768},[246,1521,1522],{"class":350},"          {\n",[246,1524,1525],{"class":248,"line":776},[246,1526,1527],{"class":350},"            q: functionArgs.q,\n",[246,1529,1530],{"class":248,"line":784},[246,1531,1532],{"class":350},"            sort: functionArgs.sort,\n",[246,1534,1535],{"class":248,"line":1104},[246,1536,1537],{"class":350},"            order: functionArgs.order,\n",[246,1539,1541],{"class":248,"line":1540},39,[246,1542,1543],{"class":350},"            per_page: functionArgs.per_page,\n",[246,1545,1547],{"class":248,"line":1546},40,[246,1548,1549],{"class":350},"          }\n",[246,1551,1553],{"class":248,"line":1552},41,[246,1554,1555],{"class":350},"        );\n",[246,1557,1559],{"class":248,"line":1558},42,[246,1560,1370],{"class":350},[246,1562,1564],{"class":248,"line":1563},43,[246,1565,1566],{"class":252},"        \u002F\u002F ..\n",[246,1568,1570],{"class":248,"line":1569},44,[246,1571,1572],{"class":350},"      }\n",[246,1574,1576],{"class":248,"line":1575},45,[246,1577,1578],{"class":350},"    }\n",[246,1580,1582],{"class":248,"line":1581},46,[246,1583,1370],{"class":350},[246,1585,1587],{"class":248,"line":1586},47,[246,1588,1589],{"class":252},"    \u002F\u002F ...\n",[246,1591,1593],{"class":248,"line":1592},48,[246,1594,1211],{"class":350},[246,1596,1598],{"class":248,"line":1597},49,[246,1599,1370],{"class":350},[246,1601,1603,1605],{"class":248,"line":1602},50,[246,1604,1220],{"class":456},[246,1606,1607],{"class":350}," responseMessage.content;\n",[246,1609,1611],{"class":248,"line":1610},51,[246,1612,760],{"class":350},[129,1614,1616],{"id":1615},"_2-using-tool-calls-for-github-search","2. Using Tool Calls for GitHub Search",[10,1618,1619,1620,1623,1624,1627],{},"Once the AI determines the need for a GitHub API call, it generates a ",[199,1621,1622],{},"tool_call"," with the necessary parameters. Here’s how we handle this with the ",[199,1625,1626],{},"searchGitHub"," function:",[237,1629,1631],{"className":338,"code":1630,"language":340,"meta":242,"style":242},"export type SearchParams = {\n  endpoint: string;\n  q: string;\n  order?: string;\n  sort?: string;\n  per_page: string;\n};\n\nconst allowedEndpoints = {\n  commits: 'GET \u002Fsearch\u002Fcommits',\n  issues: 'GET \u002Fsearch\u002Fissues',\n  repositories: 'GET \u002Fsearch\u002Frepositories',\n  users: 'GET \u002Fsearch\u002Fusers',\n} as const;\n\ntype EndpointType = keyof typeof allowedEndpoints;\n\nlet _octokit: Octokit;\n\nfunction useOctokit() {\n  if (!_octokit) {\n    _octokit = new Octokit({\n      auth: process.env.NUXT_GITHUB_TOKEN,\n    });\n  }\n\n  return _octokit;\n}\n\nexport const searchGithub = async (\n  endpoint: string,\n  params: Omit\u003CSearchParams, 'endpoint'>\n) => {\n  if (!endpoint || !allowedEndpoints[endpoint as EndpointType]) {\n    throw createError({\n      statusCode: 404,\n      message: 'Endpoint not supported',\n    });\n  }\n\n  const octokit = useOctokit();\n  const endpointToUse = allowedEndpoints[endpoint as EndpointType];\n\n  try {\n    const response = await octokit.request(endpointToUse as string, params);\n\n    return response.data;\n  } catch (error) {\n    console.error(error);\n    throw createError({\n      statusCode: 500,\n      message: 'Error searching GitHub',\n    });\n  }\n};\n",[199,1632,1633,1647,1659,1670,1682,1693,1704,1709,1713,1724,1734,1744,1754,1764,1776,1780,1799,1803,1817,1821,1830,1841,1854,1863,1867,1871,1875,1882,1886,1890,1904,1914,1937,1945,1972,1982,1992,2002,2006,2010,2014,2027,2045,2049,2056,2083,2087,2095,2106,2117,2125,2134,2144,2149,2154],{"__ignoreMap":242},[246,1634,1635,1637,1640,1643,1645],{"class":248,"line":249},[246,1636,1236],{"class":456},[246,1638,1639],{"class":456}," type",[246,1641,1642],{"class":259}," SearchParams",[246,1644,575],{"class":456},[246,1646,1296],{"class":350},[246,1648,1649,1652,1654,1657],{"class":248,"line":256},[246,1650,1651],{"class":1255},"  endpoint",[246,1653,335],{"class":456},[246,1655,1656],{"class":362}," string",[246,1658,1153],{"class":350},[246,1660,1661,1664,1666,1668],{"class":248,"line":276},[246,1662,1663],{"class":1255},"  q",[246,1665,335],{"class":456},[246,1667,1656],{"class":362},[246,1669,1153],{"class":350},[246,1671,1672,1675,1678,1680],{"class":248,"line":283},[246,1673,1674],{"class":1255},"  order",[246,1676,1677],{"class":456},"?:",[246,1679,1656],{"class":362},[246,1681,1153],{"class":350},[246,1683,1684,1687,1689,1691],{"class":248,"line":289},[246,1685,1686],{"class":1255},"  sort",[246,1688,1677],{"class":456},[246,1690,1656],{"class":362},[246,1692,1153],{"class":350},[246,1694,1695,1698,1700,1702],{"class":248,"line":605},[246,1696,1697],{"class":1255},"  per_page",[246,1699,335],{"class":456},[246,1701,1656],{"class":362},[246,1703,1153],{"class":350},[246,1705,1706],{"class":248,"line":610},[246,1707,1708],{"class":350},"};\n",[246,1710,1711],{"class":248,"line":616},[246,1712,280],{"emptyLinePlaceholder":279},[246,1714,1715,1717,1720,1722],{"class":248,"line":621},[246,1716,569],{"class":456},[246,1718,1719],{"class":362}," allowedEndpoints",[246,1721,575],{"class":456},[246,1723,1296],{"class":350},[246,1725,1726,1729,1732],{"class":248,"line":627},[246,1727,1728],{"class":350},"  commits: ",[246,1730,1731],{"class":263},"'GET \u002Fsearch\u002Fcommits'",[246,1733,366],{"class":350},[246,1735,1736,1739,1742],{"class":248,"line":633},[246,1737,1738],{"class":350},"  issues: ",[246,1740,1741],{"class":263},"'GET \u002Fsearch\u002Fissues'",[246,1743,366],{"class":350},[246,1745,1746,1749,1752],{"class":248,"line":639},[246,1747,1748],{"class":350},"  repositories: ",[246,1750,1751],{"class":263},"'GET \u002Fsearch\u002Frepositories'",[246,1753,366],{"class":350},[246,1755,1756,1759,1762],{"class":248,"line":645},[246,1757,1758],{"class":350},"  users: ",[246,1760,1761],{"class":263},"'GET \u002Fsearch\u002Fusers'",[246,1763,366],{"class":350},[246,1765,1766,1769,1772,1774],{"class":248,"line":650},[246,1767,1768],{"class":350},"} ",[246,1770,1771],{"class":456},"as",[246,1773,1239],{"class":456},[246,1775,1153],{"class":350},[246,1777,1778],{"class":248,"line":655},[246,1779,280],{"emptyLinePlaceholder":279},[246,1781,1782,1785,1788,1790,1793,1796],{"class":248,"line":660},[246,1783,1784],{"class":456},"type",[246,1786,1787],{"class":259}," EndpointType",[246,1789,575],{"class":456},[246,1791,1792],{"class":456}," keyof",[246,1794,1795],{"class":456}," typeof",[246,1797,1798],{"class":350}," allowedEndpoints;\n",[246,1800,1801],{"class":248,"line":666},[246,1802,280],{"emptyLinePlaceholder":279},[246,1804,1805,1807,1810,1812,1815],{"class":248,"line":672},[246,1806,1143],{"class":456},[246,1808,1809],{"class":350}," _octokit",[246,1811,335],{"class":456},[246,1813,1814],{"class":259}," Octokit",[246,1816,1153],{"class":350},[246,1818,1819],{"class":248,"line":678},[246,1820,280],{"emptyLinePlaceholder":279},[246,1822,1823,1825,1828],{"class":248,"line":684},[246,1824,1158],{"class":456},[246,1826,1827],{"class":259}," useOctokit",[246,1829,1164],{"class":350},[246,1831,1832,1834,1836,1838],{"class":248,"line":690},[246,1833,1169],{"class":456},[246,1835,116],{"class":350},[246,1837,1174],{"class":456},[246,1839,1840],{"class":350},"_octokit) {\n",[246,1842,1843,1846,1848,1850,1852],{"class":248,"line":695},[246,1844,1845],{"class":350},"    _octokit ",[246,1847,457],{"class":456},[246,1849,1187],{"class":456},[246,1851,1814],{"class":259},[246,1853,1192],{"class":350},[246,1855,1856,1859,1861],{"class":248,"line":700},[246,1857,1858],{"class":350},"      auth: process.env.",[246,1860,485],{"class":362},[246,1862,366],{"class":350},[246,1864,1865],{"class":248,"line":705},[246,1866,1206],{"class":350},[246,1868,1869],{"class":248,"line":711},[246,1870,1211],{"class":350},[246,1872,1873],{"class":248,"line":716},[246,1874,280],{"emptyLinePlaceholder":279},[246,1876,1877,1879],{"class":248,"line":721},[246,1878,1220],{"class":456},[246,1880,1881],{"class":350}," _octokit;\n",[246,1883,1884],{"class":248,"line":727},[246,1885,760],{"class":350},[246,1887,1888],{"class":248,"line":733},[246,1889,280],{"emptyLinePlaceholder":279},[246,1891,1892,1894,1896,1898,1900,1902],{"class":248,"line":739},[246,1893,1236],{"class":456},[246,1895,1239],{"class":456},[246,1897,1509],{"class":259},[246,1899,575],{"class":456},[246,1901,1247],{"class":456},[246,1903,1250],{"class":350},[246,1905,1906,1908,1910,1912],{"class":248,"line":745},[246,1907,1651],{"class":1255},[246,1909,335],{"class":456},[246,1911,1656],{"class":362},[246,1913,366],{"class":350},[246,1915,1916,1919,1921,1924,1927,1930,1932,1934],{"class":248,"line":751},[246,1917,1918],{"class":1255},"  params",[246,1920,335],{"class":456},[246,1922,1923],{"class":259}," Omit",[246,1925,1926],{"class":350},"\u003C",[246,1928,1929],{"class":259},"SearchParams",[246,1931,227],{"class":350},[246,1933,1063],{"class":263},[246,1935,1936],{"class":350},">\n",[246,1938,1939,1941,1943],{"class":248,"line":757},[246,1940,1290],{"class":350},[246,1942,1293],{"class":456},[246,1944,1296],{"class":350},[246,1946,1947,1949,1951,1953,1956,1959,1962,1965,1967,1969],{"class":248,"line":763},[246,1948,1169],{"class":456},[246,1950,116],{"class":350},[246,1952,1174],{"class":456},[246,1954,1955],{"class":350},"endpoint ",[246,1957,1958],{"class":456},"||",[246,1960,1961],{"class":456}," !",[246,1963,1964],{"class":350},"allowedEndpoints[endpoint ",[246,1966,1771],{"class":456},[246,1968,1787],{"class":259},[246,1970,1971],{"class":350},"]) {\n",[246,1973,1974,1977,1980],{"class":248,"line":768},[246,1975,1976],{"class":456},"    throw",[246,1978,1979],{"class":259}," createError",[246,1981,1192],{"class":350},[246,1983,1984,1987,1990],{"class":248,"line":776},[246,1985,1986],{"class":350},"      statusCode: ",[246,1988,1989],{"class":362},"404",[246,1991,366],{"class":350},[246,1993,1994,1997,2000],{"class":248,"line":784},[246,1995,1996],{"class":350},"      message: ",[246,1998,1999],{"class":263},"'Endpoint not supported'",[246,2001,366],{"class":350},[246,2003,2004],{"class":248,"line":1104},[246,2005,1206],{"class":350},[246,2007,2008],{"class":248,"line":1540},[246,2009,1211],{"class":350},[246,2011,2012],{"class":248,"line":1546},[246,2013,280],{"emptyLinePlaceholder":279},[246,2015,2016,2018,2021,2023,2025],{"class":248,"line":1552},[246,2017,1301],{"class":456},[246,2019,2020],{"class":362}," octokit",[246,2022,575],{"class":456},[246,2024,1827],{"class":259},[246,2026,1311],{"class":350},[246,2028,2029,2031,2034,2036,2039,2041,2043],{"class":248,"line":1558},[246,2030,1301],{"class":456},[246,2032,2033],{"class":362}," endpointToUse",[246,2035,575],{"class":456},[246,2037,2038],{"class":350}," allowedEndpoints[endpoint ",[246,2040,1771],{"class":456},[246,2042,1787],{"class":259},[246,2044,1107],{"class":350},[246,2046,2047],{"class":248,"line":1563},[246,2048,280],{"emptyLinePlaceholder":279},[246,2050,2051,2054],{"class":248,"line":1569},[246,2052,2053],{"class":456},"  try",[246,2055,1296],{"class":350},[246,2057,2058,2061,2063,2065,2067,2070,2073,2076,2078,2080],{"class":248,"line":1575},[246,2059,2060],{"class":456},"    const",[246,2062,1318],{"class":362},[246,2064,575],{"class":456},[246,2066,1323],{"class":456},[246,2068,2069],{"class":350}," octokit.",[246,2071,2072],{"class":259},"request",[246,2074,2075],{"class":350},"(endpointToUse ",[246,2077,1771],{"class":456},[246,2079,1656],{"class":362},[246,2081,2082],{"class":350},", params);\n",[246,2084,2085],{"class":248,"line":1581},[246,2086,280],{"emptyLinePlaceholder":279},[246,2088,2089,2092],{"class":248,"line":1586},[246,2090,2091],{"class":456},"    return",[246,2093,2094],{"class":350}," response.data;\n",[246,2096,2097,2100,2103],{"class":248,"line":1592},[246,2098,2099],{"class":350},"  } ",[246,2101,2102],{"class":456},"catch",[246,2104,2105],{"class":350}," (error) {\n",[246,2107,2108,2111,2114],{"class":248,"line":1597},[246,2109,2110],{"class":350},"    console.",[246,2112,2113],{"class":259},"error",[246,2115,2116],{"class":350},"(error);\n",[246,2118,2119,2121,2123],{"class":248,"line":1602},[246,2120,1976],{"class":456},[246,2122,1979],{"class":259},[246,2124,1192],{"class":350},[246,2126,2127,2129,2132],{"class":248,"line":1610},[246,2128,1986],{"class":350},[246,2130,2131],{"class":362},"500",[246,2133,366],{"class":350},[246,2135,2137,2139,2142],{"class":248,"line":2136},52,[246,2138,1996],{"class":350},[246,2140,2141],{"class":263},"'Error searching GitHub'",[246,2143,366],{"class":350},[246,2145,2147],{"class":248,"line":2146},53,[246,2148,1206],{"class":350},[246,2150,2152],{"class":248,"line":2151},54,[246,2153,1211],{"class":350},[246,2155,2157],{"class":248,"line":2156},55,[246,2158,1708],{"class":350},[10,2160,2161,2162,2164,2165,2167,2168,414],{},"The function is a straightforward GitHub API search using the ",[199,2163,230],{}," library. The endpoint and parameters are passed from the ",[199,2166,1622],{},", and we make the request to GitHub’s API, fetching the appropriate data. You can learn more about the GitHub search APIs from the official ",[14,2169,2172],{"href":2170,"rel":2171},"https:\u002F\u002Fdocs.github.com\u002Fen\u002Frest\u002Fsearch\u002Fsearch?apiVersion=2022-11-28",[18],"GitHub Docs",[129,2174,2176],{"id":2175},"_3-generating-the-final-response","3. Generating the Final Response",[10,2178,2179],{},"Once the GitHub API returns its results, the final step is to package them into a user-friendly response. The AI processes the raw data—whether commits, issues, repos, or users—and generates readable summaries. Here’s the relevant code snippet:",[237,2181,2183],{"className":338,"code":2182,"language":340,"meta":242,"style":242},"if (tool_calls) {\n  \u002F\u002F ...\n\n  for (const toolCall of toolCalls) {\n    \u002F\u002F ...\n\n    messages.push({\n      tool_call_id: toolCall.id,\n      role: 'tool',\n      content: JSON.stringify(functionResponse),\n    });\n  }\n\n  const finalResponse = await openai.chat.completions.create({\n    model: MODEL,\n    messages: messages,\n  });\n\n  return finalResponse.choices[0].message.content;\n}\n\n\u002F\u002F ..\n",[199,2184,2185,2193,2198,2202,2217,2221,2225,2233,2238,2248,2264,2268,2272,2276,2293,2301,2306,2310,2314,2326,2330,2334],{"__ignoreMap":242},[246,2186,2187,2190],{"class":248,"line":249},[246,2188,2189],{"class":456},"if",[246,2191,2192],{"class":350}," (tool_calls) {\n",[246,2194,2195],{"class":248,"line":256},[246,2196,2197],{"class":252},"  \u002F\u002F ...\n",[246,2199,2200],{"class":248,"line":276},[246,2201,280],{"emptyLinePlaceholder":279},[246,2203,2204,2207,2209,2211,2213,2215],{"class":248,"line":283},[246,2205,2206],{"class":456},"  for",[246,2208,116],{"class":350},[246,2210,569],{"class":456},[246,2212,1438],{"class":362},[246,2214,1441],{"class":456},[246,2216,1444],{"class":350},[246,2218,2219],{"class":248,"line":289},[246,2220,1589],{"class":252},[246,2222,2223],{"class":248,"line":605},[246,2224,280],{"emptyLinePlaceholder":279},[246,2226,2227,2229,2231],{"class":248,"line":610},[246,2228,1416],{"class":350},[246,2230,1419],{"class":259},[246,2232,1192],{"class":350},[246,2234,2235],{"class":248,"line":616},[246,2236,2237],{"class":350},"      tool_call_id: toolCall.id,\n",[246,2239,2240,2243,2246],{"class":248,"line":621},[246,2241,2242],{"class":350},"      role: ",[246,2244,2245],{"class":263},"'tool'",[246,2247,366],{"class":350},[246,2249,2250,2253,2256,2258,2261],{"class":248,"line":627},[246,2251,2252],{"class":350},"      content: ",[246,2254,2255],{"class":362},"JSON",[246,2257,414],{"class":350},[246,2259,2260],{"class":259},"stringify",[246,2262,2263],{"class":350},"(functionResponse),\n",[246,2265,2266],{"class":248,"line":633},[246,2267,1206],{"class":350},[246,2269,2270],{"class":248,"line":639},[246,2271,1211],{"class":350},[246,2273,2274],{"class":248,"line":645},[246,2275,280],{"emptyLinePlaceholder":279},[246,2277,2278,2280,2283,2285,2287,2289,2291],{"class":248,"line":650},[246,2279,1301],{"class":456},[246,2281,2282],{"class":362}," finalResponse",[246,2284,575],{"class":456},[246,2286,1323],{"class":456},[246,2288,1326],{"class":350},[246,2290,1329],{"class":259},[246,2292,1192],{"class":350},[246,2294,2295,2297,2299],{"class":248,"line":655},[246,2296,1336],{"class":350},[246,2298,1339],{"class":362},[246,2300,366],{"class":350},[246,2302,2303],{"class":248,"line":660},[246,2304,2305],{"class":350},"    messages: messages,\n",[246,2307,2308],{"class":248,"line":666},[246,2309,1365],{"class":350},[246,2311,2312],{"class":248,"line":672},[246,2313,280],{"emptyLinePlaceholder":279},[246,2315,2316,2318,2321,2323],{"class":248,"line":678},[246,2317,1220],{"class":456},[246,2319,2320],{"class":350}," finalResponse.choices[",[246,2322,1385],{"class":362},[246,2324,2325],{"class":350},"].message.content;\n",[246,2327,2328],{"class":248,"line":684},[246,2329,760],{"class":350},[246,2331,2332],{"class":248,"line":690},[246,2333,280],{"emptyLinePlaceholder":279},[246,2335,2336],{"class":248,"line":695},[246,2337,2338],{"class":252},"\u002F\u002F ..\n",[10,2340,2341],{},"In the code above, you can see how we take the API response and push it to the messages array, preparing it for the AI to format into a concise response that’s easy for the user to understand.",[25,2343,2345],{"id":2344},"managing-github-api-rate-limits","Managing GitHub API Rate Limits",[10,2347,2348],{},"If you’ve used the GitHub API (or any third-party API), you’ll know that most of them come with rate limits. So, how can we minimize GitHub API calls? The answer is simple: we avoid making duplicate calls by caching every GitHub response. If a user requests the same information that another user (or even themselves) asked for earlier, we pull the data from the cache instead of making another API call.",[10,2350,2351,2352,2357,2358,2360],{},"Now, the question is: how do we implement this in Nuxt? It’s actually quite easy, thanks to ",[14,2353,2356],{"href":2354,"rel":2355},"https:\u002F\u002Fnitro.unjs.io\u002Fguide\u002Fcache#cached-functions",[18],"Nitro’s Cached Functions"," (Nitro is an open source framework to build web servers which Nuxt uses internally). Let’s take a look at the revised ",[199,2359,820],{}," function below:",[237,2362,2364],{"className":338,"code":2363,"language":340,"meta":242,"style":242},"export const searchGithub = defineCachedFunction(\n  async (\n    event: H3Event,\n    endpoint: string,\n    params: Omit\u003CSearchParams, 'endpoint'>\n  ) => {\n    if (!endpoint || !allowedEndpoints[endpoint as EndpointType]) {\n      throw createError({\n        statusCode: 404,\n        message: 'Endpoint not supported',\n      });\n    }\n\n    const octokit = useOctokit();\n    const endpointToUse = allowedEndpoints[endpoint as EndpointType];\n\n    try {\n      const response = await octokit.request(endpointToUse as string, params);\n\n      return response.data;\n    } catch (error) {\n      console.error(error);\n      throw createError({\n        statusCode: 500,\n        message: 'Error searching GitHub',\n      });\n    }\n  },\n  {\n    maxAge: 60 * 60, \u002F\u002F 1 hour\n    group: 'github',\n    name: 'search',\n    getKey: (\n      event: H3Event,\n      endpoint: string,\n      params: Omit\u003CSearchParams, 'endpoint'>\n    ) => {\n      const q = params.q.trim().toLowerCase().split(' ');\n\n      const mainQuery = q.filter((term) => !term.includes(':')).join(' ');\n      const qualifiers = q.filter((term) => term.includes(':')).sort();\n\n      const finalQuery = [...(mainQuery ? [mainQuery] : []), ...qualifiers]\n        .join(' ')\n        .replace(\u002F[\\s:]\u002Fg, '_');\n\n      let key = endpoint + '_q_' + finalQuery;\n\n      if (params.per_page) {\n        key += '_per_page_' + params.per_page;\n      }\n\n      if (params.order) {\n        key += '_order_' + params.order;\n      }\n\n      if (params.sort) {\n        key += '_sort_' + params.sort;\n      }\n\n      return key;\n    },\n  }\n);\n",[199,2365,2366,2381,2388,2393,2398,2412,2421,2444,2453,2462,2471,2476,2480,2484,2496,2512,2516,2523,2545,2549,2556,2565,2574,2582,2590,2598,2602,2606,2610,2614,2633,2643,2653,2661,2672,2683,2702,2711,2746,2750,2800,2837,2841,2875,2889,2916,2920,2945,2949,2956,2972,2976,2980,2987,3001,3005,3010,3018,3033,3038,3043,3051,3056,3061],{"__ignoreMap":242},[246,2367,2368,2370,2372,2374,2376,2379],{"class":248,"line":249},[246,2369,1236],{"class":456},[246,2371,1239],{"class":456},[246,2373,1509],{"class":362},[246,2375,575],{"class":456},[246,2377,2378],{"class":259}," defineCachedFunction",[246,2380,1512],{"class":350},[246,2382,2383,2386],{"class":248,"line":256},[246,2384,2385],{"class":259},"  async",[246,2387,1250],{"class":350},[246,2389,2390],{"class":248,"line":276},[246,2391,2392],{"class":350},"    event: H3Event,\n",[246,2394,2395],{"class":248,"line":283},[246,2396,2397],{"class":350},"    endpoint: string,\n",[246,2399,2400,2403,2405,2408,2410],{"class":248,"line":289},[246,2401,2402],{"class":350},"    params: Omit",[246,2404,1926],{"class":456},[246,2406,2407],{"class":350},"SearchParams, ",[246,2409,1063],{"class":263},[246,2411,1936],{"class":456},[246,2413,2414,2417,2419],{"class":248,"line":605},[246,2415,2416],{"class":350},"  ) ",[246,2418,1293],{"class":456},[246,2420,1296],{"class":350},[246,2422,2423,2426,2428,2430,2432,2434,2436,2438,2440,2442],{"class":248,"line":610},[246,2424,2425],{"class":456},"    if",[246,2427,116],{"class":350},[246,2429,1174],{"class":456},[246,2431,1955],{"class":350},[246,2433,1958],{"class":456},[246,2435,1961],{"class":456},[246,2437,1964],{"class":350},[246,2439,1771],{"class":456},[246,2441,1787],{"class":259},[246,2443,1971],{"class":350},[246,2445,2446,2449,2451],{"class":248,"line":616},[246,2447,2448],{"class":456},"      throw",[246,2450,1979],{"class":259},[246,2452,1192],{"class":350},[246,2454,2455,2458,2460],{"class":248,"line":621},[246,2456,2457],{"class":350},"        statusCode: ",[246,2459,1989],{"class":362},[246,2461,366],{"class":350},[246,2463,2464,2467,2469],{"class":248,"line":627},[246,2465,2466],{"class":350},"        message: ",[246,2468,1999],{"class":263},[246,2470,366],{"class":350},[246,2472,2473],{"class":248,"line":633},[246,2474,2475],{"class":350},"      });\n",[246,2477,2478],{"class":248,"line":639},[246,2479,1578],{"class":350},[246,2481,2482],{"class":248,"line":645},[246,2483,280],{"emptyLinePlaceholder":279},[246,2485,2486,2488,2490,2492,2494],{"class":248,"line":650},[246,2487,2060],{"class":456},[246,2489,2020],{"class":362},[246,2491,575],{"class":456},[246,2493,1827],{"class":259},[246,2495,1311],{"class":350},[246,2497,2498,2500,2502,2504,2506,2508,2510],{"class":248,"line":655},[246,2499,2060],{"class":456},[246,2501,2033],{"class":362},[246,2503,575],{"class":456},[246,2505,2038],{"class":350},[246,2507,1771],{"class":456},[246,2509,1787],{"class":259},[246,2511,1107],{"class":350},[246,2513,2514],{"class":248,"line":660},[246,2515,280],{"emptyLinePlaceholder":279},[246,2517,2518,2521],{"class":248,"line":666},[246,2519,2520],{"class":456},"    try",[246,2522,1296],{"class":350},[246,2524,2525,2527,2529,2531,2533,2535,2537,2539,2541,2543],{"class":248,"line":672},[246,2526,1449],{"class":456},[246,2528,1318],{"class":362},[246,2530,575],{"class":456},[246,2532,1323],{"class":456},[246,2534,2069],{"class":350},[246,2536,2072],{"class":259},[246,2538,2075],{"class":350},[246,2540,1771],{"class":456},[246,2542,1656],{"class":362},[246,2544,2082],{"class":350},[246,2546,2547],{"class":248,"line":678},[246,2548,280],{"emptyLinePlaceholder":279},[246,2550,2551,2554],{"class":248,"line":684},[246,2552,2553],{"class":456},"      return",[246,2555,2094],{"class":350},[246,2557,2558,2561,2563],{"class":248,"line":690},[246,2559,2560],{"class":350},"    } ",[246,2562,2102],{"class":456},[246,2564,2105],{"class":350},[246,2566,2567,2570,2572],{"class":248,"line":695},[246,2568,2569],{"class":350},"      console.",[246,2571,2113],{"class":259},[246,2573,2116],{"class":350},[246,2575,2576,2578,2580],{"class":248,"line":700},[246,2577,2448],{"class":456},[246,2579,1979],{"class":259},[246,2581,1192],{"class":350},[246,2583,2584,2586,2588],{"class":248,"line":705},[246,2585,2457],{"class":350},[246,2587,2131],{"class":362},[246,2589,366],{"class":350},[246,2591,2592,2594,2596],{"class":248,"line":711},[246,2593,2466],{"class":350},[246,2595,2141],{"class":263},[246,2597,366],{"class":350},[246,2599,2600],{"class":248,"line":716},[246,2601,2475],{"class":350},[246,2603,2604],{"class":248,"line":721},[246,2605,1578],{"class":350},[246,2607,2608],{"class":248,"line":727},[246,2609,1101],{"class":350},[246,2611,2612],{"class":248,"line":733},[246,2613,856],{"class":350},[246,2615,2616,2619,2622,2625,2628,2630],{"class":248,"line":739},[246,2617,2618],{"class":350},"    maxAge: ",[246,2620,2621],{"class":362},"60",[246,2623,2624],{"class":456}," *",[246,2626,2627],{"class":362}," 60",[246,2629,227],{"class":350},[246,2631,2632],{"class":252},"\u002F\u002F 1 hour\n",[246,2634,2635,2638,2641],{"class":248,"line":745},[246,2636,2637],{"class":350},"    group: ",[246,2639,2640],{"class":263},"'github'",[246,2642,366],{"class":350},[246,2644,2645,2648,2651],{"class":248,"line":751},[246,2646,2647],{"class":350},"    name: ",[246,2649,2650],{"class":263},"'search'",[246,2652,366],{"class":350},[246,2654,2655,2658],{"class":248,"line":757},[246,2656,2657],{"class":259},"    getKey",[246,2659,2660],{"class":350},": (\n",[246,2662,2663,2666,2668,2670],{"class":248,"line":763},[246,2664,2665],{"class":1255},"      event",[246,2667,335],{"class":456},[246,2669,1261],{"class":259},[246,2671,366],{"class":350},[246,2673,2674,2677,2679,2681],{"class":248,"line":768},[246,2675,2676],{"class":1255},"      endpoint",[246,2678,335],{"class":456},[246,2680,1656],{"class":362},[246,2682,366],{"class":350},[246,2684,2685,2688,2690,2692,2694,2696,2698,2700],{"class":248,"line":776},[246,2686,2687],{"class":1255},"      params",[246,2689,335],{"class":456},[246,2691,1923],{"class":259},[246,2693,1926],{"class":350},[246,2695,1929],{"class":259},[246,2697,227],{"class":350},[246,2699,1063],{"class":263},[246,2701,1936],{"class":350},[246,2703,2704,2707,2709],{"class":248,"line":784},[246,2705,2706],{"class":350},"    ) ",[246,2708,1293],{"class":456},[246,2710,1296],{"class":350},[246,2712,2713,2715,2718,2720,2723,2726,2729,2732,2734,2737,2740,2743],{"class":248,"line":1104},[246,2714,1449],{"class":456},[246,2716,2717],{"class":362}," q",[246,2719,575],{"class":456},[246,2721,2722],{"class":350}," params.q.",[246,2724,2725],{"class":259},"trim",[246,2727,2728],{"class":350},"().",[246,2730,2731],{"class":259},"toLowerCase",[246,2733,2728],{"class":350},[246,2735,2736],{"class":259},"split",[246,2738,2739],{"class":350},"(",[246,2741,2742],{"class":263},"' '",[246,2744,2745],{"class":350},");\n",[246,2747,2748],{"class":248,"line":1540},[246,2749,280],{"emptyLinePlaceholder":279},[246,2751,2752,2754,2757,2759,2762,2765,2768,2771,2773,2775,2777,2780,2783,2785,2788,2791,2794,2796,2798],{"class":248,"line":1546},[246,2753,1449],{"class":456},[246,2755,2756],{"class":362}," mainQuery",[246,2758,575],{"class":456},[246,2760,2761],{"class":350}," q.",[246,2763,2764],{"class":259},"filter",[246,2766,2767],{"class":350},"((",[246,2769,2770],{"class":1255},"term",[246,2772,1290],{"class":350},[246,2774,1293],{"class":456},[246,2776,1961],{"class":456},[246,2778,2779],{"class":350},"term.",[246,2781,2782],{"class":259},"includes",[246,2784,2739],{"class":350},[246,2786,2787],{"class":263},"':'",[246,2789,2790],{"class":350},")).",[246,2792,2793],{"class":259},"join",[246,2795,2739],{"class":350},[246,2797,2742],{"class":263},[246,2799,2745],{"class":350},[246,2801,2802,2804,2807,2809,2811,2813,2815,2817,2819,2821,2824,2826,2828,2830,2832,2835],{"class":248,"line":1552},[246,2803,1449],{"class":456},[246,2805,2806],{"class":362}," qualifiers",[246,2808,575],{"class":456},[246,2810,2761],{"class":350},[246,2812,2764],{"class":259},[246,2814,2767],{"class":350},[246,2816,2770],{"class":1255},[246,2818,1290],{"class":350},[246,2820,1293],{"class":456},[246,2822,2823],{"class":350}," term.",[246,2825,2782],{"class":259},[246,2827,2739],{"class":350},[246,2829,2787],{"class":263},[246,2831,2790],{"class":350},[246,2833,2834],{"class":259},"sort",[246,2836,1311],{"class":350},[246,2838,2839],{"class":248,"line":1558},[246,2840,280],{"emptyLinePlaceholder":279},[246,2842,2843,2845,2848,2850,2853,2856,2859,2862,2865,2867,2870,2872],{"class":248,"line":1563},[246,2844,1449],{"class":456},[246,2846,2847],{"class":362}," finalQuery",[246,2849,575],{"class":456},[246,2851,2852],{"class":350}," [",[246,2854,2855],{"class":456},"...",[246,2857,2858],{"class":350},"(mainQuery ",[246,2860,2861],{"class":456},"?",[246,2863,2864],{"class":350}," [mainQuery] ",[246,2866,335],{"class":456},[246,2868,2869],{"class":350}," []), ",[246,2871,2855],{"class":456},[246,2873,2874],{"class":350},"qualifiers]\n",[246,2876,2877,2880,2882,2884,2886],{"class":248,"line":1569},[246,2878,2879],{"class":350},"        .",[246,2881,2793],{"class":259},[246,2883,2739],{"class":350},[246,2885,2742],{"class":263},[246,2887,2888],{"class":350},")\n",[246,2890,2891,2893,2896,2898,2901,2904,2906,2909,2911,2914],{"class":248,"line":1575},[246,2892,2879],{"class":350},[246,2894,2895],{"class":259},"replace",[246,2897,2739],{"class":350},[246,2899,2900],{"class":263},"\u002F",[246,2902,2903],{"class":362},"[\\s:]",[246,2905,2900],{"class":263},[246,2907,2908],{"class":456},"g",[246,2910,227],{"class":350},[246,2912,2913],{"class":263},"'_'",[246,2915,2745],{"class":350},[246,2917,2918],{"class":248,"line":1581},[246,2919,280],{"emptyLinePlaceholder":279},[246,2921,2922,2925,2928,2930,2933,2936,2939,2942],{"class":248,"line":1586},[246,2923,2924],{"class":456},"      let",[246,2926,2927],{"class":350}," key ",[246,2929,457],{"class":456},[246,2931,2932],{"class":350}," endpoint ",[246,2934,2935],{"class":456},"+",[246,2937,2938],{"class":263}," '_q_'",[246,2940,2941],{"class":456}," +",[246,2943,2944],{"class":350}," finalQuery;\n",[246,2946,2947],{"class":248,"line":1592},[246,2948,280],{"emptyLinePlaceholder":279},[246,2950,2951,2953],{"class":248,"line":1597},[246,2952,1462],{"class":456},[246,2954,2955],{"class":350}," (params.per_page) {\n",[246,2957,2958,2961,2964,2967,2969],{"class":248,"line":1602},[246,2959,2960],{"class":350},"        key ",[246,2962,2963],{"class":456},"+=",[246,2965,2966],{"class":263}," '_per_page_'",[246,2968,2941],{"class":456},[246,2970,2971],{"class":350}," params.per_page;\n",[246,2973,2974],{"class":248,"line":1610},[246,2975,1572],{"class":350},[246,2977,2978],{"class":248,"line":2136},[246,2979,280],{"emptyLinePlaceholder":279},[246,2981,2982,2984],{"class":248,"line":2146},[246,2983,1462],{"class":456},[246,2985,2986],{"class":350}," (params.order) {\n",[246,2988,2989,2991,2993,2996,2998],{"class":248,"line":2151},[246,2990,2960],{"class":350},[246,2992,2963],{"class":456},[246,2994,2995],{"class":263}," '_order_'",[246,2997,2941],{"class":456},[246,2999,3000],{"class":350}," params.order;\n",[246,3002,3003],{"class":248,"line":2156},[246,3004,1572],{"class":350},[246,3006,3008],{"class":248,"line":3007},56,[246,3009,280],{"emptyLinePlaceholder":279},[246,3011,3013,3015],{"class":248,"line":3012},57,[246,3014,1462],{"class":456},[246,3016,3017],{"class":350}," (params.sort) {\n",[246,3019,3021,3023,3025,3028,3030],{"class":248,"line":3020},58,[246,3022,2960],{"class":350},[246,3024,2963],{"class":456},[246,3026,3027],{"class":263}," '_sort_'",[246,3029,2941],{"class":456},[246,3031,3032],{"class":350}," params.sort;\n",[246,3034,3036],{"class":248,"line":3035},59,[246,3037,1572],{"class":350},[246,3039,3041],{"class":248,"line":3040},60,[246,3042,280],{"emptyLinePlaceholder":279},[246,3044,3046,3048],{"class":248,"line":3045},61,[246,3047,2553],{"class":456},[246,3049,3050],{"class":350}," key;\n",[246,3052,3054],{"class":248,"line":3053},62,[246,3055,1096],{"class":350},[246,3057,3059],{"class":248,"line":3058},63,[246,3060,1211],{"class":350},[246,3062,3064],{"class":248,"line":3063},64,[246,3065,2745],{"class":350},[10,3067,3068,3069,3072,3073,3076,3077,3080,3081,3086],{},"We’ve modified our earlier function to use ",[199,3070,3071],{},"cachedFunction",", and added ",[199,3074,3075],{},"H3Event"," (from the ",[199,3078,3079],{},"\u002Fchat"," API endpoint call) as the first parameter—this is needed because the app is deployed on the edge with Cloudflare (more details ",[14,3082,3085],{"href":3083,"rel":3084},"https:\u002F\u002Fnitro.unjs.io\u002Fguide\u002Fcache#edge-workers",[18],"here","). The most important part here is how we create a unique cache key. Here’s a breakdown:",[65,3088,3089,3110,3120],{},[68,3090,3091,3094,3095,3098,3099,3102,3103,3105,3106,3109],{},[142,3092,3093],{},"Query Qualifiers",": First, we break down the ",[199,3096,3097],{},"q"," parameter into its qualifier pairs (e.g., for finding the first PR of a GitHub user like ",[199,3100,3101],{},"ra-jeev","—yes, that’s me—the ",[199,3104,3097],{}," value would be ",[199,3107,3108],{},"author:ra-jeev type:pr","). Sometimes, it may contain the direct query string too, and the code accounts for this.",[68,3111,3112,3115,3116,3119],{},[142,3113,3114],{},"Sorting Qualifiers",": Next, we sort the qualifiers alphabetically. This is because the AI could create the same query as ",[199,3117,3118],{},"type:pr author:ra-jeev",". We could handle this in the system prompt, but why over-complicate things for the AI?",[68,3121,3122,3125],{},[142,3123,3124],{},"Creating the Cache Key",": Finally, we combine all the qualifiers and other parameters into a single string, separated by underscores.",[10,3127,3128,3129,3132,3133,3135,3136,3138,3139,3142,3143,414],{},"We set the cache duration to 1 hour, as seen in the ",[199,3130,3131],{},"maxAge"," setting, which means all ",[199,3134,1626],{}," responses are stored for that time. To use cache in ",[199,3137,184],{}," production we’d already enabled ",[199,3140,3141],{},"cache: true"," in our ",[199,3144,317],{},[10,3146,3147],{},"Now that we’ve tackled rate limiting, it’s time to shift our focus to response streaming. In the next section, we’ll explore how to implement streaming for a more seamless and efficient user experience.",[25,3149,3151],{"id":3150},"adding-ai-response-streaming","Adding AI Response Streaming",[10,3153,3154,3155,3157],{},"Enabling AI response streaming is usually straightforward: you pass a parameter when making the API call, and the AI returns the response as a stream. In our ",[142,3156,115],{}," project, for example, we handled the stream chunks directly client-side, ensuring that responses trickled in smoothly for the user.",[10,3159,3160,3161,3164,3165,3167],{},"But in the case of ",[142,3162,3163],{},"Chat GitHub",", there’s an additional complexity—",[142,3166,1622],{}," responses. So, we have two approaches to manage:",[65,3169,3170,3173],{},[68,3171,3172],{},"Enable streaming only for the final response generation, but this limits us when there’s no tool_call, wasting an opportunity to stream from the start.",[68,3174,3175,3176,3178],{},"Enable stream response for both the OpenAI calls, but this requires us to manage a stream more carefully on the server side (as we need to handle the ",[199,3177,1622],{},", if any, there itself).",[10,3180,3181,3182],{},"I took the seemingly complex path and went ahead with the second approach—",[33,3183,3184],{},"”Two roads diverged in a wood, and I—I took the one less traveled by, And that has made all the difference.”",[129,3186,3188],{"id":3187},"enable-streaming-in-openai-calls","Enable Streaming in OpenAI Calls",[10,3190,3191],{},"Here’s how the OpenAI API call was modified to enable streaming:",[237,3193,3195],{"className":338,"code":3194,"language":340,"meta":242,"style":242},"export const handleMessageWithOpenAI = async function* (\n  event: H3Event,\n  messages: OpenAI.ChatCompletionMessageParam[],\n) {\n  const openai = useOpenAI();\n  const responseStream = await openai.chat.completions.create({\n    model: MODEL,\n    messages,\n    tools,\n    stream: true, \u002F\u002F enable streaming\n  });\n\n  const currentToolCalls: OpenAI.ChatCompletionMessageToolCall[] = [];\n\n  for await (const chunk of responseStream) {\n    const choice = chunk.choices[0];\n\n    \u002F\u002F if it is normal text chunk just yield it\n    if (choice.delta.content) {\n      yield choice.delta.content;\n    }\n\n    \u002F\u002F if the delta contains tool_calls, then collect all chunks\n    if (choice.delta.tool_calls) {\n      for (const toolCall of choice.delta.tool_calls) {\n        if (toolCall.index !== undefined) {\n          if (!currentToolCalls[toolCall.index]) {\n            currentToolCalls[toolCall.index] = {\n              id: toolCall.id || '',\n              type: 'function' as const,\n              function: {\n                name: toolCall.function?.name || '',\n                arguments: '',\n              },\n            };\n          }\n\n          if (toolCall.function?.arguments) {\n            currentToolCalls[toolCall.index].function.arguments +=\n              toolCall.function.arguments;\n          }\n        }\n      }\n    }\n\n    \u002F\u002F once it has returned all chunks with tool_calls, we will\n    \u002F\u002F get the final finish_reason as 'tool_calls'. We can call\n    \u002F\u002F the mentioned tool now\n    if (choice.finish_reason === 'tool_calls') {\n      messages.push({\n        role: 'assistant',\n        tool_calls: currentToolCalls,\n      });\n\n      for (const toolCall of currentToolCalls) {\n        if (toolCall.function.name === 'searchGithub') {\n          try {\n            const functionArgs = JSON.parse(toolCall.function.arguments);\n            const toolResult = await searchGithub(\n              event,\n              functionArgs.endpoint,\n              {\n                q: functionArgs.q,\n                sort: functionArgs.sort,\n                order: functionArgs.order,\n                per_page: functionArgs.per_page,\n              }\n            );\n\n            messages.push({\n              role: 'tool',\n              tool_call_id: toolCall.id,\n              content: JSON.stringify(toolResult),\n            });\n          } catch (error) {\n            console.error('Error parsing tool call arguments:', error);\n            throw error;\n          }\n        }\n      }\n\n      try {\n        const finalResponse = await openai.chat.completions.create({\n          model: MODEL,\n          messages,\n          stream: true,\n        });\n\n        for await (const chunk of finalResponse) {\n          if (chunk.choices[0].delta.content) {\n            yield chunk.choices[0].delta.content;\n          }\n        }\n      } catch (error) {\n        console.error(\n          'Error generating final response or saving user query :',\n          error\n        );\n\n        throw error;\n      }\n    }\n  }\n};\n",[199,3196,3197,3214,3224,3239,3243,3255,3272,3280,3285,3290,3302,3306,3310,3333,3337,3355,3371,3375,3380,3387,3395,3399,3403,3408,3415,3431,3447,3459,3468,3480,3494,3499,3510,3520,3525,3530,3534,3538,3545,3553,3558,3562,3567,3571,3575,3579,3584,3589,3594,3608,3617,3627,3632,3636,3640,3655,3668,3675,3692,3707,3712,3717,3722,3727,3732,3738,3744,3750,3756,3761,3771,3781,3787,3802,3808,3818,3834,3843,3848,3853,3858,3863,3871,3888,3898,3904,3914,3920,3925,3944,3957,3970,3975,3980,3990,4000,4008,4014,4019,4024,4032,4037,4042,4047],{"__ignoreMap":242},[246,3198,3199,3201,3203,3205,3207,3209,3212],{"class":248,"line":249},[246,3200,1236],{"class":456},[246,3202,1239],{"class":456},[246,3204,1242],{"class":259},[246,3206,575],{"class":456},[246,3208,1247],{"class":456},[246,3210,3211],{"class":456}," function*",[246,3213,1250],{"class":350},[246,3215,3216,3218,3220,3222],{"class":248,"line":256},[246,3217,1256],{"class":1255},[246,3219,335],{"class":456},[246,3221,1261],{"class":259},[246,3223,366],{"class":350},[246,3225,3226,3228,3230,3232,3234,3236],{"class":248,"line":276},[246,3227,1268],{"class":1255},[246,3229,335],{"class":456},[246,3231,838],{"class":259},[246,3233,414],{"class":350},[246,3235,1282],{"class":259},[246,3237,3238],{"class":350},"[],\n",[246,3240,3241],{"class":248,"line":283},[246,3242,1474],{"class":350},[246,3244,3245,3247,3249,3251,3253],{"class":248,"line":289},[246,3246,1301],{"class":456},[246,3248,1304],{"class":362},[246,3250,575],{"class":456},[246,3252,1161],{"class":259},[246,3254,1311],{"class":350},[246,3256,3257,3259,3262,3264,3266,3268,3270],{"class":248,"line":605},[246,3258,1301],{"class":456},[246,3260,3261],{"class":362}," responseStream",[246,3263,575],{"class":456},[246,3265,1323],{"class":456},[246,3267,1326],{"class":350},[246,3269,1329],{"class":259},[246,3271,1192],{"class":350},[246,3273,3274,3276,3278],{"class":248,"line":610},[246,3275,1336],{"class":350},[246,3277,1339],{"class":362},[246,3279,366],{"class":350},[246,3281,3282],{"class":248,"line":616},[246,3283,3284],{"class":350},"    messages,\n",[246,3286,3287],{"class":248,"line":621},[246,3288,3289],{"class":350},"    tools,\n",[246,3291,3292,3295,3297,3299],{"class":248,"line":627},[246,3293,3294],{"class":350},"    stream: ",[246,3296,363],{"class":362},[246,3298,227],{"class":350},[246,3300,3301],{"class":252},"\u002F\u002F enable streaming\n",[246,3303,3304],{"class":248,"line":633},[246,3305,1365],{"class":350},[246,3307,3308],{"class":248,"line":639},[246,3309,280],{"emptyLinePlaceholder":279},[246,3311,3312,3314,3317,3319,3321,3323,3326,3328,3330],{"class":248,"line":645},[246,3313,1301],{"class":456},[246,3315,3316],{"class":362}," currentToolCalls",[246,3318,335],{"class":456},[246,3320,838],{"class":259},[246,3322,414],{"class":350},[246,3324,3325],{"class":259},"ChatCompletionMessageToolCall",[246,3327,846],{"class":350},[246,3329,457],{"class":456},[246,3331,3332],{"class":350}," [];\n",[246,3334,3335],{"class":248,"line":650},[246,3336,280],{"emptyLinePlaceholder":279},[246,3338,3339,3341,3343,3345,3347,3350,3352],{"class":248,"line":655},[246,3340,2206],{"class":456},[246,3342,1323],{"class":456},[246,3344,116],{"class":350},[246,3346,569],{"class":456},[246,3348,3349],{"class":362}," chunk",[246,3351,1441],{"class":456},[246,3353,3354],{"class":350}," responseStream) {\n",[246,3356,3357,3359,3362,3364,3367,3369],{"class":248,"line":660},[246,3358,2060],{"class":456},[246,3360,3361],{"class":362}," choice",[246,3363,575],{"class":456},[246,3365,3366],{"class":350}," chunk.choices[",[246,3368,1385],{"class":362},[246,3370,1107],{"class":350},[246,3372,3373],{"class":248,"line":666},[246,3374,280],{"emptyLinePlaceholder":279},[246,3376,3377],{"class":248,"line":672},[246,3378,3379],{"class":252},"    \u002F\u002F if it is normal text chunk just yield it\n",[246,3381,3382,3384],{"class":248,"line":678},[246,3383,2425],{"class":456},[246,3385,3386],{"class":350}," (choice.delta.content) {\n",[246,3388,3389,3392],{"class":248,"line":684},[246,3390,3391],{"class":456},"      yield",[246,3393,3394],{"class":350}," choice.delta.content;\n",[246,3396,3397],{"class":248,"line":690},[246,3398,1578],{"class":350},[246,3400,3401],{"class":248,"line":695},[246,3402,280],{"emptyLinePlaceholder":279},[246,3404,3405],{"class":248,"line":700},[246,3406,3407],{"class":252},"    \u002F\u002F if the delta contains tool_calls, then collect all chunks\n",[246,3409,3410,3412],{"class":248,"line":705},[246,3411,2425],{"class":456},[246,3413,3414],{"class":350}," (choice.delta.tool_calls) {\n",[246,3416,3417,3420,3422,3424,3426,3428],{"class":248,"line":711},[246,3418,3419],{"class":456},"      for",[246,3421,116],{"class":350},[246,3423,569],{"class":456},[246,3425,1438],{"class":362},[246,3427,1441],{"class":456},[246,3429,3430],{"class":350}," choice.delta.tool_calls) {\n",[246,3432,3433,3436,3439,3442,3445],{"class":248,"line":716},[246,3434,3435],{"class":456},"        if",[246,3437,3438],{"class":350}," (toolCall.index ",[246,3440,3441],{"class":456},"!==",[246,3443,3444],{"class":362}," undefined",[246,3446,1474],{"class":350},[246,3448,3449,3452,3454,3456],{"class":248,"line":721},[246,3450,3451],{"class":456},"          if",[246,3453,116],{"class":350},[246,3455,1174],{"class":456},[246,3457,3458],{"class":350},"currentToolCalls[toolCall.index]) {\n",[246,3460,3461,3464,3466],{"class":248,"line":727},[246,3462,3463],{"class":350},"            currentToolCalls[toolCall.index] ",[246,3465,457],{"class":456},[246,3467,1296],{"class":350},[246,3469,3470,3473,3475,3478],{"class":248,"line":733},[246,3471,3472],{"class":350},"              id: toolCall.id ",[246,3474,1958],{"class":456},[246,3476,3477],{"class":263}," ''",[246,3479,366],{"class":350},[246,3481,3482,3485,3487,3490,3492],{"class":248,"line":739},[246,3483,3484],{"class":350},"              type: ",[246,3486,864],{"class":263},[246,3488,3489],{"class":456}," as",[246,3491,1239],{"class":456},[246,3493,366],{"class":350},[246,3495,3496],{"class":248,"line":745},[246,3497,3498],{"class":350},"              function: {\n",[246,3500,3501,3504,3506,3508],{"class":248,"line":751},[246,3502,3503],{"class":350},"                name: toolCall.function?.name ",[246,3505,1958],{"class":456},[246,3507,3477],{"class":263},[246,3509,366],{"class":350},[246,3511,3512,3515,3518],{"class":248,"line":757},[246,3513,3514],{"class":350},"                arguments: ",[246,3516,3517],{"class":263},"''",[246,3519,366],{"class":350},[246,3521,3522],{"class":248,"line":763},[246,3523,3524],{"class":350},"              },\n",[246,3526,3527],{"class":248,"line":768},[246,3528,3529],{"class":350},"            };\n",[246,3531,3532],{"class":248,"line":776},[246,3533,1549],{"class":350},[246,3535,3536],{"class":248,"line":784},[246,3537,280],{"emptyLinePlaceholder":279},[246,3539,3540,3542],{"class":248,"line":1104},[246,3541,3451],{"class":456},[246,3543,3544],{"class":350}," (toolCall.function?.arguments) {\n",[246,3546,3547,3550],{"class":248,"line":1540},[246,3548,3549],{"class":350},"            currentToolCalls[toolCall.index].function.arguments ",[246,3551,3552],{"class":456},"+=\n",[246,3554,3555],{"class":248,"line":1546},[246,3556,3557],{"class":350},"              toolCall.function.arguments;\n",[246,3559,3560],{"class":248,"line":1552},[246,3561,1549],{"class":350},[246,3563,3564],{"class":248,"line":1558},[246,3565,3566],{"class":350},"        }\n",[246,3568,3569],{"class":248,"line":1563},[246,3570,1572],{"class":350},[246,3572,3573],{"class":248,"line":1569},[246,3574,1578],{"class":350},[246,3576,3577],{"class":248,"line":1575},[246,3578,280],{"emptyLinePlaceholder":279},[246,3580,3581],{"class":248,"line":1581},[246,3582,3583],{"class":252},"    \u002F\u002F once it has returned all chunks with tool_calls, we will\n",[246,3585,3586],{"class":248,"line":1586},[246,3587,3588],{"class":252},"    \u002F\u002F get the final finish_reason as 'tool_calls'. We can call\n",[246,3590,3591],{"class":248,"line":1592},[246,3592,3593],{"class":252},"    \u002F\u002F the mentioned tool now\n",[246,3595,3596,3598,3601,3603,3606],{"class":248,"line":1597},[246,3597,2425],{"class":456},[246,3599,3600],{"class":350}," (choice.finish_reason ",[246,3602,1468],{"class":456},[246,3604,3605],{"class":263}," 'tool_calls'",[246,3607,1474],{"class":350},[246,3609,3610,3613,3615],{"class":248,"line":1602},[246,3611,3612],{"class":350},"      messages.",[246,3614,1419],{"class":259},[246,3616,1192],{"class":350},[246,3618,3619,3622,3625],{"class":248,"line":1610},[246,3620,3621],{"class":350},"        role: ",[246,3623,3624],{"class":263},"'assistant'",[246,3626,366],{"class":350},[246,3628,3629],{"class":248,"line":2136},[246,3630,3631],{"class":350},"        tool_calls: currentToolCalls,\n",[246,3633,3634],{"class":248,"line":2146},[246,3635,2475],{"class":350},[246,3637,3638],{"class":248,"line":2151},[246,3639,280],{"emptyLinePlaceholder":279},[246,3641,3642,3644,3646,3648,3650,3652],{"class":248,"line":2156},[246,3643,3419],{"class":456},[246,3645,116],{"class":350},[246,3647,569],{"class":456},[246,3649,1438],{"class":362},[246,3651,1441],{"class":456},[246,3653,3654],{"class":350}," currentToolCalls) {\n",[246,3656,3657,3659,3662,3664,3666],{"class":248,"line":3007},[246,3658,3435],{"class":456},[246,3660,3661],{"class":350}," (toolCall.function.name ",[246,3663,1468],{"class":456},[246,3665,1471],{"class":263},[246,3667,1474],{"class":350},[246,3669,3670,3673],{"class":248,"line":3012},[246,3671,3672],{"class":456},"          try",[246,3674,1296],{"class":350},[246,3676,3677,3680,3682,3684,3686,3688,3690],{"class":248,"line":3020},[246,3678,3679],{"class":456},"            const",[246,3681,1482],{"class":362},[246,3683,575],{"class":456},[246,3685,1487],{"class":362},[246,3687,414],{"class":350},[246,3689,1492],{"class":259},[246,3691,1495],{"class":350},[246,3693,3694,3696,3699,3701,3703,3705],{"class":248,"line":3035},[246,3695,3679],{"class":456},[246,3697,3698],{"class":362}," toolResult",[246,3700,575],{"class":456},[246,3702,1323],{"class":456},[246,3704,1509],{"class":259},[246,3706,1512],{"class":350},[246,3708,3709],{"class":248,"line":3040},[246,3710,3711],{"class":350},"              event,\n",[246,3713,3714],{"class":248,"line":3045},[246,3715,3716],{"class":350},"              functionArgs.endpoint,\n",[246,3718,3719],{"class":248,"line":3053},[246,3720,3721],{"class":350},"              {\n",[246,3723,3724],{"class":248,"line":3058},[246,3725,3726],{"class":350},"                q: functionArgs.q,\n",[246,3728,3729],{"class":248,"line":3063},[246,3730,3731],{"class":350},"                sort: functionArgs.sort,\n",[246,3733,3735],{"class":248,"line":3734},65,[246,3736,3737],{"class":350},"                order: functionArgs.order,\n",[246,3739,3741],{"class":248,"line":3740},66,[246,3742,3743],{"class":350},"                per_page: functionArgs.per_page,\n",[246,3745,3747],{"class":248,"line":3746},67,[246,3748,3749],{"class":350},"              }\n",[246,3751,3753],{"class":248,"line":3752},68,[246,3754,3755],{"class":350},"            );\n",[246,3757,3759],{"class":248,"line":3758},69,[246,3760,280],{"emptyLinePlaceholder":279},[246,3762,3764,3767,3769],{"class":248,"line":3763},70,[246,3765,3766],{"class":350},"            messages.",[246,3768,1419],{"class":259},[246,3770,1192],{"class":350},[246,3772,3774,3777,3779],{"class":248,"line":3773},71,[246,3775,3776],{"class":350},"              role: ",[246,3778,2245],{"class":263},[246,3780,366],{"class":350},[246,3782,3784],{"class":248,"line":3783},72,[246,3785,3786],{"class":350},"              tool_call_id: toolCall.id,\n",[246,3788,3790,3793,3795,3797,3799],{"class":248,"line":3789},73,[246,3791,3792],{"class":350},"              content: ",[246,3794,2255],{"class":362},[246,3796,414],{"class":350},[246,3798,2260],{"class":259},[246,3800,3801],{"class":350},"(toolResult),\n",[246,3803,3805],{"class":248,"line":3804},74,[246,3806,3807],{"class":350},"            });\n",[246,3809,3811,3814,3816],{"class":248,"line":3810},75,[246,3812,3813],{"class":350},"          } ",[246,3815,2102],{"class":456},[246,3817,2105],{"class":350},[246,3819,3821,3824,3826,3828,3831],{"class":248,"line":3820},76,[246,3822,3823],{"class":350},"            console.",[246,3825,2113],{"class":259},[246,3827,2739],{"class":350},[246,3829,3830],{"class":263},"'Error parsing tool call arguments:'",[246,3832,3833],{"class":350},", error);\n",[246,3835,3837,3840],{"class":248,"line":3836},77,[246,3838,3839],{"class":456},"            throw",[246,3841,3842],{"class":350}," error;\n",[246,3844,3846],{"class":248,"line":3845},78,[246,3847,1549],{"class":350},[246,3849,3851],{"class":248,"line":3850},79,[246,3852,3566],{"class":350},[246,3854,3856],{"class":248,"line":3855},80,[246,3857,1572],{"class":350},[246,3859,3861],{"class":248,"line":3860},81,[246,3862,280],{"emptyLinePlaceholder":279},[246,3864,3866,3869],{"class":248,"line":3865},82,[246,3867,3868],{"class":456},"      try",[246,3870,1296],{"class":350},[246,3872,3874,3876,3878,3880,3882,3884,3886],{"class":248,"line":3873},83,[246,3875,1479],{"class":456},[246,3877,2282],{"class":362},[246,3879,575],{"class":456},[246,3881,1323],{"class":456},[246,3883,1326],{"class":350},[246,3885,1329],{"class":259},[246,3887,1192],{"class":350},[246,3889,3891,3894,3896],{"class":248,"line":3890},84,[246,3892,3893],{"class":350},"          model: ",[246,3895,1339],{"class":362},[246,3897,366],{"class":350},[246,3899,3901],{"class":248,"line":3900},85,[246,3902,3903],{"class":350},"          messages,\n",[246,3905,3907,3910,3912],{"class":248,"line":3906},86,[246,3908,3909],{"class":350},"          stream: ",[246,3911,363],{"class":362},[246,3913,366],{"class":350},[246,3915,3917],{"class":248,"line":3916},87,[246,3918,3919],{"class":350},"        });\n",[246,3921,3923],{"class":248,"line":3922},88,[246,3924,280],{"emptyLinePlaceholder":279},[246,3926,3928,3931,3933,3935,3937,3939,3941],{"class":248,"line":3927},89,[246,3929,3930],{"class":456},"        for",[246,3932,1323],{"class":456},[246,3934,116],{"class":350},[246,3936,569],{"class":456},[246,3938,3349],{"class":362},[246,3940,1441],{"class":456},[246,3942,3943],{"class":350}," finalResponse) {\n",[246,3945,3947,3949,3952,3954],{"class":248,"line":3946},90,[246,3948,3451],{"class":456},[246,3950,3951],{"class":350}," (chunk.choices[",[246,3953,1385],{"class":362},[246,3955,3956],{"class":350},"].delta.content) {\n",[246,3958,3960,3963,3965,3967],{"class":248,"line":3959},91,[246,3961,3962],{"class":456},"            yield",[246,3964,3366],{"class":350},[246,3966,1385],{"class":362},[246,3968,3969],{"class":350},"].delta.content;\n",[246,3971,3973],{"class":248,"line":3972},92,[246,3974,1549],{"class":350},[246,3976,3978],{"class":248,"line":3977},93,[246,3979,3566],{"class":350},[246,3981,3983,3986,3988],{"class":248,"line":3982},94,[246,3984,3985],{"class":350},"      } ",[246,3987,2102],{"class":456},[246,3989,2105],{"class":350},[246,3991,3993,3996,3998],{"class":248,"line":3992},95,[246,3994,3995],{"class":350},"        console.",[246,3997,2113],{"class":259},[246,3999,1512],{"class":350},[246,4001,4003,4006],{"class":248,"line":4002},96,[246,4004,4005],{"class":263},"          'Error generating final response or saving user query :'",[246,4007,366],{"class":350},[246,4009,4011],{"class":248,"line":4010},97,[246,4012,4013],{"class":350},"          error\n",[246,4015,4017],{"class":248,"line":4016},98,[246,4018,1555],{"class":350},[246,4020,4022],{"class":248,"line":4021},99,[246,4023,280],{"emptyLinePlaceholder":279},[246,4025,4027,4030],{"class":248,"line":4026},100,[246,4028,4029],{"class":456},"        throw",[246,4031,3842],{"class":350},[246,4033,4035],{"class":248,"line":4034},101,[246,4036,1572],{"class":350},[246,4038,4040],{"class":248,"line":4039},102,[246,4041,1578],{"class":350},[246,4043,4045],{"class":248,"line":4044},103,[246,4046,1211],{"class":350},[246,4048,4050],{"class":248,"line":4049},104,[246,4051,1708],{"class":350},[10,4053,4054,4055,4058],{},"The revised ",[199,4056,4057],{},"handleMessageWithOpenAI"," function works like this:",[137,4060,4061,4067,4076,4086,4104],{},[68,4062,4063,4066],{},[142,4064,4065],{},"Converted it to an AsyncGenerator",": This allows the function to yield data chunks progressively as they are received.",[68,4068,4069,506,4072,4075],{},[142,4070,4071],{},"Added",[199,4073,4074],{},"stream: true"," to both OpenAI API calls: This tells OpenAI to stream the response back to us.",[68,4077,4078,4081,4082,4085],{},[142,4079,4080],{},"Yielding Response Chunks",": For each chunk of text that we get from the stream, we simply ",[199,4083,4084],{},"yield"," it to the caller.",[68,4087,4088,4091,4092,4094,4095,4098,4099,4101,4102,414],{},[142,4089,4090],{},"Handling tool_calls",": Since streaming is enabled, the ",[199,4093,1622],{}," information also arrives in chunks. We collect these chunks until the OpenAI API signals the completion of this part (",[199,4096,4097],{},"finish_reason === 'tool_calls'","), and then invoke the ",[199,4100,1626],{}," function using the parameters provided by the ",[199,4103,1622],{},[68,4105,4106,4109,4110,4112],{},[142,4107,4108],{},"Final Response",": After the GitHub search is done, we ",[199,4111,4084],{}," the response in chunks in the same way.",[129,4114,4116],{"id":4115},"converting-to-a-readablestream","Converting to a ReadableStream",[10,4118,4119,4120,4122,4123,4126],{},"To complete the process, the chunks from ",[199,4121,4057],{}," are converted into a ",[142,4124,4125],{},"ReadableStream"," format, which is then returned to the client (not shown here). Here’s how that works:",[237,4128,4130],{"className":338,"code":4129,"language":340,"meta":242,"style":242},"export const asyncGeneratorToStream = (\n  asyncGenerator: AsyncGenerator\u003Cstring, void, unknown>\n) => {\n  let cancelled = false;\n  const encoder = new TextEncoder();\n  const stream = new ReadableStream({\n    async start(controller) {\n      try {\n        for await (const value of asyncGenerator) {\n          if (cancelled) {\n            break;\n          }\n\n          controller.enqueue(\n            encoder.encode(`data: ${JSON.stringify({ response: value })}\\n\\n`)\n          );\n        }\n\n        \u002F\u002F Send done to signal end of stream\n        controller.enqueue(encoder.encode(`data: [DONE]\\n\\n`));\n\n        controller.close();\n      } catch (err) {\n        console.log('Error in stream:', err);\n\n        \u002F* eslint-disable @stylistic\u002Foperator-linebreak *\u002F\n        const errorMessage =\n          err instanceof Error\n            ? err.message\n            : 'An error occurred in the stream';\n        \u002F* eslint-enable @stylistic\u002Foperator-linebreak *\u002F\n\n        controller.enqueue(\n          encoder.encode(\n            `event: error\\ndata: ${JSON.stringify({\n              message: errorMessage,\n            })}\\n\\n`\n          )\n        );\n\n        controller.close();\n      }\n    },\n    cancel(reason) {\n      console.log('Client closed connection. Reason:', reason);\n      cancelled = true;\n    },\n  });\n\n  return stream;\n};\n",[199,4131,4132,4145,4172,4180,4195,4211,4227,4242,4248,4266,4273,4280,4284,4288,4298,4337,4342,4346,4350,4355,4379,4383,4392,4401,4416,4420,4425,4435,4446,4454,4464,4469,4473,4481,4490,4509,4519,4531,4536,4540,4544,4552,4556,4560,4572,4586,4598,4602,4606,4610,4617],{"__ignoreMap":242},[246,4133,4134,4136,4138,4141,4143],{"class":248,"line":249},[246,4135,1236],{"class":456},[246,4137,1239],{"class":456},[246,4139,4140],{"class":259}," asyncGeneratorToStream",[246,4142,575],{"class":456},[246,4144,1250],{"class":350},[246,4146,4147,4150,4152,4155,4157,4160,4162,4165,4167,4170],{"class":248,"line":256},[246,4148,4149],{"class":1255},"  asyncGenerator",[246,4151,335],{"class":456},[246,4153,4154],{"class":259}," AsyncGenerator",[246,4156,1926],{"class":350},[246,4158,4159],{"class":362},"string",[246,4161,227],{"class":350},[246,4163,4164],{"class":362},"void",[246,4166,227],{"class":350},[246,4168,4169],{"class":362},"unknown",[246,4171,1936],{"class":350},[246,4173,4174,4176,4178],{"class":248,"line":276},[246,4175,1290],{"class":350},[246,4177,1293],{"class":456},[246,4179,1296],{"class":350},[246,4181,4182,4185,4188,4190,4193],{"class":248,"line":283},[246,4183,4184],{"class":456},"  let",[246,4186,4187],{"class":350}," cancelled ",[246,4189,457],{"class":456},[246,4191,4192],{"class":362}," false",[246,4194,1153],{"class":350},[246,4196,4197,4199,4202,4204,4206,4209],{"class":248,"line":289},[246,4198,1301],{"class":456},[246,4200,4201],{"class":362}," encoder",[246,4203,575],{"class":456},[246,4205,1187],{"class":456},[246,4207,4208],{"class":259}," TextEncoder",[246,4210,1311],{"class":350},[246,4212,4213,4215,4218,4220,4222,4225],{"class":248,"line":605},[246,4214,1301],{"class":456},[246,4216,4217],{"class":362}," stream",[246,4219,575],{"class":456},[246,4221,1187],{"class":456},[246,4223,4224],{"class":259}," ReadableStream",[246,4226,1192],{"class":350},[246,4228,4229,4232,4235,4237,4240],{"class":248,"line":610},[246,4230,4231],{"class":456},"    async",[246,4233,4234],{"class":259}," start",[246,4236,2739],{"class":350},[246,4238,4239],{"class":1255},"controller",[246,4241,1474],{"class":350},[246,4243,4244,4246],{"class":248,"line":616},[246,4245,3868],{"class":456},[246,4247,1296],{"class":350},[246,4249,4250,4252,4254,4256,4258,4261,4263],{"class":248,"line":621},[246,4251,3930],{"class":456},[246,4253,1323],{"class":456},[246,4255,116],{"class":350},[246,4257,569],{"class":456},[246,4259,4260],{"class":362}," value",[246,4262,1441],{"class":456},[246,4264,4265],{"class":350}," asyncGenerator) {\n",[246,4267,4268,4270],{"class":248,"line":627},[246,4269,3451],{"class":456},[246,4271,4272],{"class":350}," (cancelled) {\n",[246,4274,4275,4278],{"class":248,"line":633},[246,4276,4277],{"class":456},"            break",[246,4279,1153],{"class":350},[246,4281,4282],{"class":248,"line":639},[246,4283,1549],{"class":350},[246,4285,4286],{"class":248,"line":645},[246,4287,280],{"emptyLinePlaceholder":279},[246,4289,4290,4293,4296],{"class":248,"line":650},[246,4291,4292],{"class":350},"          controller.",[246,4294,4295],{"class":259},"enqueue",[246,4297,1512],{"class":350},[246,4299,4300,4303,4306,4308,4311,4313,4315,4317,4320,4323,4326,4329,4332,4335],{"class":248,"line":655},[246,4301,4302],{"class":350},"            encoder.",[246,4304,4305],{"class":259},"encode",[246,4307,2739],{"class":350},[246,4309,4310],{"class":263},"`data: ${",[246,4312,2255],{"class":362},[246,4314,414],{"class":263},[246,4316,2260],{"class":259},[246,4318,4319],{"class":263},"({ response: ",[246,4321,4322],{"class":350},"value",[246,4324,4325],{"class":263}," })",[246,4327,4328],{"class":263},"}",[246,4330,4331],{"class":362},"\\n\\n",[246,4333,4334],{"class":263},"`",[246,4336,2888],{"class":350},[246,4338,4339],{"class":248,"line":660},[246,4340,4341],{"class":350},"          );\n",[246,4343,4344],{"class":248,"line":666},[246,4345,3566],{"class":350},[246,4347,4348],{"class":248,"line":672},[246,4349,280],{"emptyLinePlaceholder":279},[246,4351,4352],{"class":248,"line":678},[246,4353,4354],{"class":252},"        \u002F\u002F Send done to signal end of stream\n",[246,4356,4357,4360,4362,4365,4367,4369,4372,4374,4376],{"class":248,"line":684},[246,4358,4359],{"class":350},"        controller.",[246,4361,4295],{"class":259},[246,4363,4364],{"class":350},"(encoder.",[246,4366,4305],{"class":259},[246,4368,2739],{"class":350},[246,4370,4371],{"class":263},"`data: [DONE]",[246,4373,4331],{"class":362},[246,4375,4334],{"class":263},[246,4377,4378],{"class":350},"));\n",[246,4380,4381],{"class":248,"line":690},[246,4382,280],{"emptyLinePlaceholder":279},[246,4384,4385,4387,4390],{"class":248,"line":695},[246,4386,4359],{"class":350},[246,4388,4389],{"class":259},"close",[246,4391,1311],{"class":350},[246,4393,4394,4396,4398],{"class":248,"line":700},[246,4395,3985],{"class":350},[246,4397,2102],{"class":456},[246,4399,4400],{"class":350}," (err) {\n",[246,4402,4403,4405,4408,4410,4413],{"class":248,"line":705},[246,4404,3995],{"class":350},[246,4406,4407],{"class":259},"log",[246,4409,2739],{"class":350},[246,4411,4412],{"class":263},"'Error in stream:'",[246,4414,4415],{"class":350},", err);\n",[246,4417,4418],{"class":248,"line":711},[246,4419,280],{"emptyLinePlaceholder":279},[246,4421,4422],{"class":248,"line":716},[246,4423,4424],{"class":252},"        \u002F* eslint-disable @stylistic\u002Foperator-linebreak *\u002F\n",[246,4426,4427,4429,4432],{"class":248,"line":721},[246,4428,1479],{"class":456},[246,4430,4431],{"class":362}," errorMessage",[246,4433,4434],{"class":456}," =\n",[246,4436,4437,4440,4443],{"class":248,"line":727},[246,4438,4439],{"class":350},"          err ",[246,4441,4442],{"class":456},"instanceof",[246,4444,4445],{"class":259}," Error\n",[246,4447,4448,4451],{"class":248,"line":733},[246,4449,4450],{"class":456},"            ?",[246,4452,4453],{"class":350}," err.message\n",[246,4455,4456,4459,4462],{"class":248,"line":739},[246,4457,4458],{"class":456},"            :",[246,4460,4461],{"class":263}," 'An error occurred in the stream'",[246,4463,1153],{"class":350},[246,4465,4466],{"class":248,"line":745},[246,4467,4468],{"class":252},"        \u002F* eslint-enable @stylistic\u002Foperator-linebreak *\u002F\n",[246,4470,4471],{"class":248,"line":751},[246,4472,280],{"emptyLinePlaceholder":279},[246,4474,4475,4477,4479],{"class":248,"line":757},[246,4476,4359],{"class":350},[246,4478,4295],{"class":259},[246,4480,1512],{"class":350},[246,4482,4483,4486,4488],{"class":248,"line":763},[246,4484,4485],{"class":350},"          encoder.",[246,4487,4305],{"class":259},[246,4489,1512],{"class":350},[246,4491,4492,4495,4498,4501,4503,4505,4507],{"class":248,"line":768},[246,4493,4494],{"class":263},"            `event: error",[246,4496,4497],{"class":362},"\\n",[246,4499,4500],{"class":263},"data: ${",[246,4502,2255],{"class":362},[246,4504,414],{"class":263},[246,4506,2260],{"class":259},[246,4508,1192],{"class":263},[246,4510,4511,4514,4517],{"class":248,"line":776},[246,4512,4513],{"class":263},"              message: ",[246,4515,4516],{"class":350},"errorMessage",[246,4518,366],{"class":263},[246,4520,4521,4524,4526,4528],{"class":248,"line":784},[246,4522,4523],{"class":263},"            })",[246,4525,4328],{"class":263},[246,4527,4331],{"class":362},[246,4529,4530],{"class":263},"`\n",[246,4532,4533],{"class":248,"line":1104},[246,4534,4535],{"class":350},"          )\n",[246,4537,4538],{"class":248,"line":1540},[246,4539,1555],{"class":350},[246,4541,4542],{"class":248,"line":1546},[246,4543,280],{"emptyLinePlaceholder":279},[246,4545,4546,4548,4550],{"class":248,"line":1552},[246,4547,4359],{"class":350},[246,4549,4389],{"class":259},[246,4551,1311],{"class":350},[246,4553,4554],{"class":248,"line":1558},[246,4555,1572],{"class":350},[246,4557,4558],{"class":248,"line":1563},[246,4559,1096],{"class":350},[246,4561,4562,4565,4567,4570],{"class":248,"line":1569},[246,4563,4564],{"class":259},"    cancel",[246,4566,2739],{"class":350},[246,4568,4569],{"class":1255},"reason",[246,4571,1474],{"class":350},[246,4573,4574,4576,4578,4580,4583],{"class":248,"line":1575},[246,4575,2569],{"class":350},[246,4577,4407],{"class":259},[246,4579,2739],{"class":350},[246,4581,4582],{"class":263},"'Client closed connection. Reason:'",[246,4584,4585],{"class":350},", reason);\n",[246,4587,4588,4591,4593,4596],{"class":248,"line":1581},[246,4589,4590],{"class":350},"      cancelled ",[246,4592,457],{"class":456},[246,4594,4595],{"class":362}," true",[246,4597,1153],{"class":350},[246,4599,4600],{"class":248,"line":1586},[246,4601,1096],{"class":350},[246,4603,4604],{"class":248,"line":1592},[246,4605,1365],{"class":350},[246,4607,4608],{"class":248,"line":1597},[246,4609,280],{"emptyLinePlaceholder":279},[246,4611,4612,4614],{"class":248,"line":1602},[246,4613,1220],{"class":456},[246,4615,4616],{"class":350}," stream;\n",[246,4618,4619],{"class":248,"line":1610},[246,4620,1708],{"class":350},[10,4622,4623,4624,4627,4628,4630],{},"This code transforms the ",[199,4625,4626],{},"AsyncGenerator"," we created earlier into a ",[142,4629,4125],{},". Here’s a breakdown of what’s happening:",[137,4632,4633,4641,4651,4665,4675],{},[68,4634,4635,4637,4638,4640],{},[142,4636,4626],{},": We pass the AsyncGenerator from ",[199,4639,4057],{}," as an argument to this function.",[68,4642,4643,4646,4647,4650],{},[142,4644,4645],{},"Creating a ReadableStream",": Inside the ",[199,4648,4649],{},"start"," method of the ReadableStream, we wait for chunks from the AsyncGenerator.",[68,4652,4653,4656,4657,4661,4662,414],{},[142,4654,4655],{},"Formatting Chunks",": For each text chunk received, we format it according to the Server-Sent Events (SSE) convention (You can read more about SSE in my ",[14,4658,126],{"href":4659,"rel":4660},"https:\u002F\u002Frajeev.dev\u002Fcreate-cloudflare-workers-ai-llm-playground-using-nuxthub-and-nuxtui#heading-consuming-server-sent-events",[18],"). Each chunk is wrapped in this format: ",[199,4663,4664],{},"data: ${JSON.stringify({ response: value })}\\n\\n",[68,4666,4667,4670,4671,4674],{},[142,4668,4669],{},"Encoding the Stream",": Using ",[199,4672,4673],{},"TextEncoder",", we encode the chunks before sending them to the client. This encoding step is crucial, especially in production environments, as it ensures that the client correctly receives the stream in real-time.",[68,4676,4677,4680,4681,4684],{},[142,4678,4679],{},"Error Handling",": If an error occurs (as we throw errors from the AsyncGenerator), we send an ",[199,4682,4683],{},"event: error"," message to the client, signaling that something went wrong, and then close the stream to terminate the connection cleanly.",[10,4686,4687],{},"And then this stream is returned to the client from the API endpoint",[237,4689,4691],{"className":338,"code":4690,"language":340,"meta":242,"style":242},"\u002F\u002F inside \u002Fapi\u002Fchat event handler\nreturn asyncGeneratorToStream(\n  handleMessageWithOpenAI(event, llmMessages)\n);\n",[199,4692,4693,4698,4707,4715],{"__ignoreMap":242},[246,4694,4695],{"class":248,"line":249},[246,4696,4697],{"class":252},"\u002F\u002F inside \u002Fapi\u002Fchat event handler\n",[246,4699,4700,4703,4705],{"class":248,"line":256},[246,4701,4702],{"class":456},"return",[246,4704,4140],{"class":259},[246,4706,1512],{"class":350},[246,4708,4709,4712],{"class":248,"line":276},[246,4710,4711],{"class":259},"  handleMessageWithOpenAI",[246,4713,4714],{"class":350},"(event, llmMessages)\n",[246,4716,4717],{"class":248,"line":283},[246,4718,2745],{"class":350},[129,4720,4722],{"id":4721},"handling-the-stream-on-the-client-side","Handling the Stream on the Client-Side",[10,4724,4725,4726,4729],{},"To handle the streamed response, we’ll use a refactored version of the ",[199,4727,4728],{},"useChat"," composable, initially created for the Hub Chat project. This time, we’ll extend it to handle error events. Here's the updated code:",[237,4731,4733],{"className":338,"code":4732,"language":340,"meta":242,"style":242},"export function useChat(apiBase: string, body: Record\u003Cstring, unknown>) {\n  async function* chat(): AsyncGenerator\u003Cstring, void, unknown> {\n    try {\n      const response = await $fetch(apiBase, {\n        method: 'POST',\n        body,\n        responseType: 'stream',\n      });\n\n      let buffer = '';\n      const reader = (response as ReadableStream)\n        .pipeThrough(new TextDecoderStream())\n        .getReader();\n\n      while (true) {\n        const { value, done } = await reader.read();\n\n        if (done) {\n          if (buffer.trim()) {\n            console.warn('Stream ended with unparsed data:', buffer);\n          }\n\n          return;\n        }\n\n        buffer += value;\n        const messages = buffer.split('\\n\\n');\n        buffer = messages.pop() || '';\n\n        for (const message of messages) {\n          const lines = message.split('\\n');\n          let event = '';\n          let data = '';\n\n          for (const line of lines) {\n            if (line.startsWith('event:')) {\n              event = line.slice('event:'.length).trim();\n            } else if (line.startsWith('data:')) {\n              data = line.slice('data:'.length).trim();\n            }\n          }\n\n          if (event === 'error') {\n            const parsedError = JSON.parse(data);\n            console.error('Stream error:', parsedError);\n\n            throw new Error(\n              parsedError.message ?? 'Failed to generate response'\n            );\n          } else if (data) {\n            if (data === '[DONE]') return;\n\n            try {\n              const jsonData = JSON.parse(data);\n              if (jsonData.response) {\n                yield jsonData.response;\n              }\n            } catch (parseError) {\n              console.warn('Error parsing JSON:', parseError);\n            }\n          }\n        }\n      }\n    } catch (error) {\n      console.error('Error sending message:', error);\n      throw error;\n    }\n  }\n\n  return chat;\n}\n",[199,4734,4735,4775,4806,4812,4828,4838,4843,4853,4857,4861,4874,4892,4910,4919,4923,4934,4963,4967,4974,4986,5001,5005,5009,5016,5020,5024,5034,5059,5080,5084,5100,5125,5139,5152,5156,5173,5192,5220,5242,5267,5272,5276,5280,5294,5312,5326,5330,5341,5352,5356,5367,5385,5389,5396,5414,5422,5430,5434,5443,5458,5462,5466,5470,5474,5482,5495,5501,5505,5509,5513,5520],{"__ignoreMap":242},[246,4736,4737,4739,4742,4745,4747,4750,4752,4754,4756,4759,4761,4764,4766,4768,4770,4772],{"class":248,"line":249},[246,4738,1236],{"class":456},[246,4740,4741],{"class":456}," function",[246,4743,4744],{"class":259}," useChat",[246,4746,2739],{"class":350},[246,4748,4749],{"class":1255},"apiBase",[246,4751,335],{"class":456},[246,4753,1656],{"class":362},[246,4755,227],{"class":350},[246,4757,4758],{"class":1255},"body",[246,4760,335],{"class":456},[246,4762,4763],{"class":259}," Record",[246,4765,1926],{"class":350},[246,4767,4159],{"class":362},[246,4769,227],{"class":350},[246,4771,4169],{"class":362},[246,4773,4774],{"class":350},">) {\n",[246,4776,4777,4779,4781,4784,4787,4789,4791,4793,4795,4797,4799,4801,4803],{"class":248,"line":256},[246,4778,2385],{"class":456},[246,4780,3211],{"class":456},[246,4782,4783],{"class":259}," chat",[246,4785,4786],{"class":350},"()",[246,4788,335],{"class":456},[246,4790,4154],{"class":259},[246,4792,1926],{"class":350},[246,4794,4159],{"class":362},[246,4796,227],{"class":350},[246,4798,4164],{"class":362},[246,4800,227],{"class":350},[246,4802,4169],{"class":362},[246,4804,4805],{"class":350},"> {\n",[246,4807,4808,4810],{"class":248,"line":276},[246,4809,2520],{"class":456},[246,4811,1296],{"class":350},[246,4813,4814,4816,4818,4820,4822,4825],{"class":248,"line":283},[246,4815,1449],{"class":456},[246,4817,1318],{"class":362},[246,4819,575],{"class":456},[246,4821,1323],{"class":456},[246,4823,4824],{"class":259}," $fetch",[246,4826,4827],{"class":350},"(apiBase, {\n",[246,4829,4830,4833,4836],{"class":248,"line":289},[246,4831,4832],{"class":350},"        method: ",[246,4834,4835],{"class":263},"'POST'",[246,4837,366],{"class":350},[246,4839,4840],{"class":248,"line":605},[246,4841,4842],{"class":350},"        body,\n",[246,4844,4845,4848,4851],{"class":248,"line":610},[246,4846,4847],{"class":350},"        responseType: ",[246,4849,4850],{"class":263},"'stream'",[246,4852,366],{"class":350},[246,4854,4855],{"class":248,"line":616},[246,4856,2475],{"class":350},[246,4858,4859],{"class":248,"line":621},[246,4860,280],{"emptyLinePlaceholder":279},[246,4862,4863,4865,4868,4870,4872],{"class":248,"line":627},[246,4864,2924],{"class":456},[246,4866,4867],{"class":350}," buffer ",[246,4869,457],{"class":456},[246,4871,3477],{"class":263},[246,4873,1153],{"class":350},[246,4875,4876,4878,4881,4883,4886,4888,4890],{"class":248,"line":633},[246,4877,1449],{"class":456},[246,4879,4880],{"class":362}," reader",[246,4882,575],{"class":456},[246,4884,4885],{"class":350}," (response ",[246,4887,1771],{"class":456},[246,4889,4224],{"class":259},[246,4891,2888],{"class":350},[246,4893,4894,4896,4899,4901,4904,4907],{"class":248,"line":639},[246,4895,2879],{"class":350},[246,4897,4898],{"class":259},"pipeThrough",[246,4900,2739],{"class":350},[246,4902,4903],{"class":456},"new",[246,4905,4906],{"class":259}," TextDecoderStream",[246,4908,4909],{"class":350},"())\n",[246,4911,4912,4914,4917],{"class":248,"line":645},[246,4913,2879],{"class":350},[246,4915,4916],{"class":259},"getReader",[246,4918,1311],{"class":350},[246,4920,4921],{"class":248,"line":650},[246,4922,280],{"emptyLinePlaceholder":279},[246,4924,4925,4928,4930,4932],{"class":248,"line":655},[246,4926,4927],{"class":456},"      while",[246,4929,116],{"class":350},[246,4931,363],{"class":362},[246,4933,1474],{"class":350},[246,4935,4936,4938,4941,4943,4945,4948,4951,4953,4955,4958,4961],{"class":248,"line":660},[246,4937,1479],{"class":456},[246,4939,4940],{"class":350}," { ",[246,4942,4322],{"class":362},[246,4944,227],{"class":350},[246,4946,4947],{"class":362},"done",[246,4949,4950],{"class":350}," } ",[246,4952,457],{"class":456},[246,4954,1323],{"class":456},[246,4956,4957],{"class":350}," reader.",[246,4959,4960],{"class":259},"read",[246,4962,1311],{"class":350},[246,4964,4965],{"class":248,"line":666},[246,4966,280],{"emptyLinePlaceholder":279},[246,4968,4969,4971],{"class":248,"line":672},[246,4970,3435],{"class":456},[246,4972,4973],{"class":350}," (done) {\n",[246,4975,4976,4978,4981,4983],{"class":248,"line":678},[246,4977,3451],{"class":456},[246,4979,4980],{"class":350}," (buffer.",[246,4982,2725],{"class":259},[246,4984,4985],{"class":350},"()) {\n",[246,4987,4988,4990,4993,4995,4998],{"class":248,"line":684},[246,4989,3823],{"class":350},[246,4991,4992],{"class":259},"warn",[246,4994,2739],{"class":350},[246,4996,4997],{"class":263},"'Stream ended with unparsed data:'",[246,4999,5000],{"class":350},", buffer);\n",[246,5002,5003],{"class":248,"line":690},[246,5004,1549],{"class":350},[246,5006,5007],{"class":248,"line":695},[246,5008,280],{"emptyLinePlaceholder":279},[246,5010,5011,5014],{"class":248,"line":700},[246,5012,5013],{"class":456},"          return",[246,5015,1153],{"class":350},[246,5017,5018],{"class":248,"line":705},[246,5019,3566],{"class":350},[246,5021,5022],{"class":248,"line":711},[246,5023,280],{"emptyLinePlaceholder":279},[246,5025,5026,5029,5031],{"class":248,"line":716},[246,5027,5028],{"class":350},"        buffer ",[246,5030,2963],{"class":456},[246,5032,5033],{"class":350}," value;\n",[246,5035,5036,5038,5041,5043,5046,5048,5050,5053,5055,5057],{"class":248,"line":721},[246,5037,1479],{"class":456},[246,5039,5040],{"class":362}," messages",[246,5042,575],{"class":456},[246,5044,5045],{"class":350}," buffer.",[246,5047,2736],{"class":259},[246,5049,2739],{"class":350},[246,5051,5052],{"class":263},"'",[246,5054,4331],{"class":362},[246,5056,5052],{"class":263},[246,5058,2745],{"class":350},[246,5060,5061,5063,5065,5068,5071,5074,5076,5078],{"class":248,"line":727},[246,5062,5028],{"class":350},[246,5064,457],{"class":456},[246,5066,5067],{"class":350}," messages.",[246,5069,5070],{"class":259},"pop",[246,5072,5073],{"class":350},"() ",[246,5075,1958],{"class":456},[246,5077,3477],{"class":263},[246,5079,1153],{"class":350},[246,5081,5082],{"class":248,"line":733},[246,5083,280],{"emptyLinePlaceholder":279},[246,5085,5086,5088,5090,5092,5095,5097],{"class":248,"line":739},[246,5087,3930],{"class":456},[246,5089,116],{"class":350},[246,5091,569],{"class":456},[246,5093,5094],{"class":362}," message",[246,5096,1441],{"class":456},[246,5098,5099],{"class":350}," messages) {\n",[246,5101,5102,5105,5108,5110,5113,5115,5117,5119,5121,5123],{"class":248,"line":745},[246,5103,5104],{"class":456},"          const",[246,5106,5107],{"class":362}," lines",[246,5109,575],{"class":456},[246,5111,5112],{"class":350}," message.",[246,5114,2736],{"class":259},[246,5116,2739],{"class":350},[246,5118,5052],{"class":263},[246,5120,4497],{"class":362},[246,5122,5052],{"class":263},[246,5124,2745],{"class":350},[246,5126,5127,5130,5133,5135,5137],{"class":248,"line":751},[246,5128,5129],{"class":456},"          let",[246,5131,5132],{"class":350}," event ",[246,5134,457],{"class":456},[246,5136,3477],{"class":263},[246,5138,1153],{"class":350},[246,5140,5141,5143,5146,5148,5150],{"class":248,"line":757},[246,5142,5129],{"class":456},[246,5144,5145],{"class":350}," data ",[246,5147,457],{"class":456},[246,5149,3477],{"class":263},[246,5151,1153],{"class":350},[246,5153,5154],{"class":248,"line":763},[246,5155,280],{"emptyLinePlaceholder":279},[246,5157,5158,5161,5163,5165,5168,5170],{"class":248,"line":768},[246,5159,5160],{"class":456},"          for",[246,5162,116],{"class":350},[246,5164,569],{"class":456},[246,5166,5167],{"class":362}," line",[246,5169,1441],{"class":456},[246,5171,5172],{"class":350}," lines) {\n",[246,5174,5175,5178,5181,5184,5186,5189],{"class":248,"line":776},[246,5176,5177],{"class":456},"            if",[246,5179,5180],{"class":350}," (line.",[246,5182,5183],{"class":259},"startsWith",[246,5185,2739],{"class":350},[246,5187,5188],{"class":263},"'event:'",[246,5190,5191],{"class":350},")) {\n",[246,5193,5194,5197,5199,5202,5205,5207,5209,5211,5214,5216,5218],{"class":248,"line":784},[246,5195,5196],{"class":350},"              event ",[246,5198,457],{"class":456},[246,5200,5201],{"class":350}," line.",[246,5203,5204],{"class":259},"slice",[246,5206,2739],{"class":350},[246,5208,5188],{"class":263},[246,5210,414],{"class":350},[246,5212,5213],{"class":362},"length",[246,5215,821],{"class":350},[246,5217,2725],{"class":259},[246,5219,1311],{"class":350},[246,5221,5222,5225,5228,5231,5233,5235,5237,5240],{"class":248,"line":1104},[246,5223,5224],{"class":350},"            } ",[246,5226,5227],{"class":456},"else",[246,5229,5230],{"class":456}," if",[246,5232,5180],{"class":350},[246,5234,5183],{"class":259},[246,5236,2739],{"class":350},[246,5238,5239],{"class":263},"'data:'",[246,5241,5191],{"class":350},[246,5243,5244,5247,5249,5251,5253,5255,5257,5259,5261,5263,5265],{"class":248,"line":1540},[246,5245,5246],{"class":350},"              data ",[246,5248,457],{"class":456},[246,5250,5201],{"class":350},[246,5252,5204],{"class":259},[246,5254,2739],{"class":350},[246,5256,5239],{"class":263},[246,5258,414],{"class":350},[246,5260,5213],{"class":362},[246,5262,821],{"class":350},[246,5264,2725],{"class":259},[246,5266,1311],{"class":350},[246,5268,5269],{"class":248,"line":1546},[246,5270,5271],{"class":350},"            }\n",[246,5273,5274],{"class":248,"line":1552},[246,5275,1549],{"class":350},[246,5277,5278],{"class":248,"line":1558},[246,5279,280],{"emptyLinePlaceholder":279},[246,5281,5282,5284,5287,5289,5292],{"class":248,"line":1563},[246,5283,3451],{"class":456},[246,5285,5286],{"class":350}," (event ",[246,5288,1468],{"class":456},[246,5290,5291],{"class":263}," 'error'",[246,5293,1474],{"class":350},[246,5295,5296,5298,5301,5303,5305,5307,5309],{"class":248,"line":1569},[246,5297,3679],{"class":456},[246,5299,5300],{"class":362}," parsedError",[246,5302,575],{"class":456},[246,5304,1487],{"class":362},[246,5306,414],{"class":350},[246,5308,1492],{"class":259},[246,5310,5311],{"class":350},"(data);\n",[246,5313,5314,5316,5318,5320,5323],{"class":248,"line":1575},[246,5315,3823],{"class":350},[246,5317,2113],{"class":259},[246,5319,2739],{"class":350},[246,5321,5322],{"class":263},"'Stream error:'",[246,5324,5325],{"class":350},", parsedError);\n",[246,5327,5328],{"class":248,"line":1581},[246,5329,280],{"emptyLinePlaceholder":279},[246,5331,5332,5334,5336,5339],{"class":248,"line":1586},[246,5333,3839],{"class":456},[246,5335,1187],{"class":456},[246,5337,5338],{"class":259}," Error",[246,5340,1512],{"class":350},[246,5342,5343,5346,5349],{"class":248,"line":1592},[246,5344,5345],{"class":350},"              parsedError.message ",[246,5347,5348],{"class":456},"??",[246,5350,5351],{"class":263}," 'Failed to generate response'\n",[246,5353,5354],{"class":248,"line":1597},[246,5355,3755],{"class":350},[246,5357,5358,5360,5362,5364],{"class":248,"line":1602},[246,5359,3813],{"class":350},[246,5361,5227],{"class":456},[246,5363,5230],{"class":456},[246,5365,5366],{"class":350}," (data) {\n",[246,5368,5369,5371,5374,5376,5379,5381,5383],{"class":248,"line":1610},[246,5370,5177],{"class":456},[246,5372,5373],{"class":350}," (data ",[246,5375,1468],{"class":456},[246,5377,5378],{"class":263}," '[DONE]'",[246,5380,1290],{"class":350},[246,5382,4702],{"class":456},[246,5384,1153],{"class":350},[246,5386,5387],{"class":248,"line":2136},[246,5388,280],{"emptyLinePlaceholder":279},[246,5390,5391,5394],{"class":248,"line":2146},[246,5392,5393],{"class":456},"            try",[246,5395,1296],{"class":350},[246,5397,5398,5401,5404,5406,5408,5410,5412],{"class":248,"line":2151},[246,5399,5400],{"class":456},"              const",[246,5402,5403],{"class":362}," jsonData",[246,5405,575],{"class":456},[246,5407,1487],{"class":362},[246,5409,414],{"class":350},[246,5411,1492],{"class":259},[246,5413,5311],{"class":350},[246,5415,5416,5419],{"class":248,"line":2156},[246,5417,5418],{"class":456},"              if",[246,5420,5421],{"class":350}," (jsonData.response) {\n",[246,5423,5424,5427],{"class":248,"line":3007},[246,5425,5426],{"class":456},"                yield",[246,5428,5429],{"class":350}," jsonData.response;\n",[246,5431,5432],{"class":248,"line":3012},[246,5433,3749],{"class":350},[246,5435,5436,5438,5440],{"class":248,"line":3020},[246,5437,5224],{"class":350},[246,5439,2102],{"class":456},[246,5441,5442],{"class":350}," (parseError) {\n",[246,5444,5445,5448,5450,5452,5455],{"class":248,"line":3035},[246,5446,5447],{"class":350},"              console.",[246,5449,4992],{"class":259},[246,5451,2739],{"class":350},[246,5453,5454],{"class":263},"'Error parsing JSON:'",[246,5456,5457],{"class":350},", parseError);\n",[246,5459,5460],{"class":248,"line":3040},[246,5461,5271],{"class":350},[246,5463,5464],{"class":248,"line":3045},[246,5465,1549],{"class":350},[246,5467,5468],{"class":248,"line":3053},[246,5469,3566],{"class":350},[246,5471,5472],{"class":248,"line":3058},[246,5473,1572],{"class":350},[246,5475,5476,5478,5480],{"class":248,"line":3063},[246,5477,2560],{"class":350},[246,5479,2102],{"class":456},[246,5481,2105],{"class":350},[246,5483,5484,5486,5488,5490,5493],{"class":248,"line":3734},[246,5485,2569],{"class":350},[246,5487,2113],{"class":259},[246,5489,2739],{"class":350},[246,5491,5492],{"class":263},"'Error sending message:'",[246,5494,3833],{"class":350},[246,5496,5497,5499],{"class":248,"line":3740},[246,5498,2448],{"class":456},[246,5500,3842],{"class":350},[246,5502,5503],{"class":248,"line":3746},[246,5504,1578],{"class":350},[246,5506,5507],{"class":248,"line":3752},[246,5508,1211],{"class":350},[246,5510,5511],{"class":248,"line":3758},[246,5512,280],{"emptyLinePlaceholder":279},[246,5514,5515,5517],{"class":248,"line":3763},[246,5516,1220],{"class":456},[246,5518,5519],{"class":350}," chat;\n",[246,5521,5522],{"class":248,"line":3773},[246,5523,760],{"class":350},[10,5525,5526],{},"Here’s a breakdown of what the code does:",[137,5528,5529,5544,5558,5568],{},[68,5530,5531,5532,5535,5536,5539,5540,5543],{},"We use Nuxt’s ",[199,5533,5534],{},"$fetch"," to make a ",[199,5537,5538],{},"POST"," request to the API endpoint, passing the ",[199,5541,5542],{},"responseType: 'stream'",". This tells the client to expect a streaming response.",[68,5545,5546,5547,5549,5550,5553,5554,5557],{},"Once we receive the ",[199,5548,4125],{},", we create a ",[199,5551,5552],{},"streamReader"," for it. This allows us to process the chunks one at a time as they arrive. We also pass the chunks through a ",[199,5555,5556],{},"TextDecoder"," to convert the raw bytes into readable text.",[68,5559,5560,5561,231,5564,5567],{},"The stream is in Server-Sent Events (SSE) format, so we parse and handle the ",[199,5562,5563],{},"event",[199,5565,5566],{},"data"," parts appropriately. Each data chunk is parsed and returned to the client component for rendering.",[68,5569,5570,5571,5573],{},"The code also listens for and handles any ",[199,5572,2113],{}," events that may occur, ensuring a smoother user experience by gracefully handling stream interruptions or API errors.",[10,5575,5576],{},"And this concludes the road less traveled that we took earlier. In the next section, we’ll cover how we can authenticate our users.",[25,5578,5580],{"id":5579},"user-authentication-with-github-oauth","User Authentication with GitHub OAuth",[10,5582,5583,5584,5587],{},"Now that our backend is ready to handle client requests, how do we restrict access to authenticated users? And how do we provide context to the AI, like answering a query such as, ",[33,5585,5586],{},"“When did I make my first ever commit?”"," To control who can access the backend, we use authentication. And to give the AI context about the user, we rely on GitHub OAuth for authentication.",[10,5589,5590,5591,5593],{},"We’re leveraging ",[199,5592,226],{},", which simplifies integrating GitHub OAuth into our Nuxt app. This allows us to authenticate users with their GitHub accounts and manage sessions effortlessly.",[129,5595,5597],{"id":5596},"server-side-implementation","Server-Side Implementation",[10,5599,5600,5601,231,5603,5605,5606,5608],{},"On the server side, we need to create a route that handles the GitHub access token when the user logs in. Make sure to create a GitHub OAuth app and add the ",[199,5602,428],{},[199,5604,431],{}," to the ",[199,5607,321],{}," as mentioned in the setup section.",[10,5610,5611,5612,5615,5616,5619],{},"To implement this, create a ",[199,5613,5614],{},"github.get.ts"," file in the ",[199,5617,5618],{},"route\u002Fauth"," folder of the server directory with the following content:",[237,5621,5623],{"className":338,"code":5622,"language":340,"meta":242,"style":242},"export default oauthGitHubEventHandler({\n  async onSuccess(event, { user }) {\n    await setUserSession(event, {\n      user: {\n        id: user.id,\n        login: user.login,\n        name: user.name,\n        avatarUrl: user.avatar_url,\n        htmlUrl: user.html_url,\n        publicRepos: user.public_repos,\n      },\n    });\n\n    return sendRedirect(event, '\u002Fchat');\n  },\n  \u002F\u002F Optional, will return a json error and 401 status code by default\n  onError(event, error) {\n    console.error('GitHub OAuth error:', error);\n    return sendRedirect(event, '\u002F');\n  },\n});\n",[199,5624,5625,5637,5657,5668,5673,5678,5683,5688,5693,5698,5703,5707,5711,5715,5730,5734,5739,5754,5767,5780,5784],{"__ignoreMap":242},[246,5626,5627,5629,5632,5635],{"class":248,"line":249},[246,5628,1236],{"class":456},[246,5630,5631],{"class":456}," default",[246,5633,5634],{"class":259}," oauthGitHubEventHandler",[246,5636,1192],{"class":350},[246,5638,5639,5641,5644,5646,5648,5651,5654],{"class":248,"line":256},[246,5640,2385],{"class":456},[246,5642,5643],{"class":259}," onSuccess",[246,5645,2739],{"class":350},[246,5647,5563],{"class":1255},[246,5649,5650],{"class":350},", { ",[246,5652,5653],{"class":1255},"user",[246,5655,5656],{"class":350}," }) {\n",[246,5658,5659,5662,5665],{"class":248,"line":276},[246,5660,5661],{"class":456},"    await",[246,5663,5664],{"class":259}," setUserSession",[246,5666,5667],{"class":350},"(event, {\n",[246,5669,5670],{"class":248,"line":283},[246,5671,5672],{"class":350},"      user: {\n",[246,5674,5675],{"class":248,"line":289},[246,5676,5677],{"class":350},"        id: user.id,\n",[246,5679,5680],{"class":248,"line":605},[246,5681,5682],{"class":350},"        login: user.login,\n",[246,5684,5685],{"class":248,"line":610},[246,5686,5687],{"class":350},"        name: user.name,\n",[246,5689,5690],{"class":248,"line":616},[246,5691,5692],{"class":350},"        avatarUrl: user.avatar_url,\n",[246,5694,5695],{"class":248,"line":621},[246,5696,5697],{"class":350},"        htmlUrl: user.html_url,\n",[246,5699,5700],{"class":248,"line":627},[246,5701,5702],{"class":350},"        publicRepos: user.public_repos,\n",[246,5704,5705],{"class":248,"line":633},[246,5706,1091],{"class":350},[246,5708,5709],{"class":248,"line":639},[246,5710,1206],{"class":350},[246,5712,5713],{"class":248,"line":645},[246,5714,280],{"emptyLinePlaceholder":279},[246,5716,5717,5719,5722,5725,5728],{"class":248,"line":650},[246,5718,2091],{"class":456},[246,5720,5721],{"class":259}," sendRedirect",[246,5723,5724],{"class":350},"(event, ",[246,5726,5727],{"class":263},"'\u002Fchat'",[246,5729,2745],{"class":350},[246,5731,5732],{"class":248,"line":655},[246,5733,1101],{"class":350},[246,5735,5736],{"class":248,"line":660},[246,5737,5738],{"class":252},"  \u002F\u002F Optional, will return a json error and 401 status code by default\n",[246,5740,5741,5744,5746,5748,5750,5752],{"class":248,"line":666},[246,5742,5743],{"class":259},"  onError",[246,5745,2739],{"class":350},[246,5747,5563],{"class":1255},[246,5749,227],{"class":350},[246,5751,2113],{"class":1255},[246,5753,1474],{"class":350},[246,5755,5756,5758,5760,5762,5765],{"class":248,"line":672},[246,5757,2110],{"class":350},[246,5759,2113],{"class":259},[246,5761,2739],{"class":350},[246,5763,5764],{"class":263},"'GitHub OAuth error:'",[246,5766,3833],{"class":350},[246,5768,5769,5771,5773,5775,5778],{"class":248,"line":678},[246,5770,2091],{"class":456},[246,5772,5721],{"class":259},[246,5774,5724],{"class":350},[246,5776,5777],{"class":263},"'\u002F'",[246,5779,2745],{"class":350},[246,5781,5782],{"class":248,"line":684},[246,5783,1101],{"class":350},[246,5785,5786],{"class":248,"line":690},[246,5787,5788],{"class":350},"});\n",[10,5790,5791,5792,5795,5796,5800,5801,5804],{},"The event handler name is important and should be ",[199,5793,5794],{},"oauthGitHubEventHandler"," (more details can be found ",[14,5797,3085],{"href":5798,"rel":5799},"https:\u002F\u002Fgithub.com\u002Fatinux\u002Fnuxt-auth-utils?tab=readme-ov-file#oauth-event-handlers",[18],"). On successful login, we call the ",[199,5802,5803],{},"setUserSession"," utility function to store the user details in an HTTP cookie and redirect them to the chat page.",[10,5806,5807,5808,5811,5812,5815],{},"For our API routes, we can then call the ",[199,5809,5810],{},"requireUserSession"," utility to ensure only authenticated users can make requests. Below is the full ",[199,5813,5814],{},"\u002Fapi\u002Fchat"," endpoint handler:",[237,5817,5819],{"className":338,"code":5818,"language":340,"meta":242,"style":242},"export default defineEventHandler(async (event) => {\n  const userSession = await requireUserSession(event);\n\n  const { messages } = await readBody(event);\n  if (!messages) {\n    throw createError({\n      statusCode: 400,\n      message: 'User messages are required',\n    });\n  }\n\n  const llmMessages = [\n    {\n      role: 'system',\n      content: getSystemPrompt(userSession.user.login),\n    },\n    ...messages,\n  ];\n\n  return asyncGeneratorToStream(\n    handleMessageWithOpenAI(event, llmMessages, userSession.user.login)\n  );\n});\n",[199,5820,5821,5845,5862,5866,5886,5897,5905,5914,5923,5927,5931,5935,5946,5951,5960,5970,5974,5982,5987,5991,5999,6007,6012],{"__ignoreMap":242},[246,5822,5823,5825,5827,5830,5832,5835,5837,5839,5841,5843],{"class":248,"line":249},[246,5824,1236],{"class":456},[246,5826,5631],{"class":456},[246,5828,5829],{"class":259}," defineEventHandler",[246,5831,2739],{"class":350},[246,5833,5834],{"class":456},"async",[246,5836,116],{"class":350},[246,5838,5563],{"class":1255},[246,5840,1290],{"class":350},[246,5842,1293],{"class":456},[246,5844,1296],{"class":350},[246,5846,5847,5849,5852,5854,5856,5859],{"class":248,"line":256},[246,5848,1301],{"class":456},[246,5850,5851],{"class":362}," userSession",[246,5853,575],{"class":456},[246,5855,1323],{"class":456},[246,5857,5858],{"class":259}," requireUserSession",[246,5860,5861],{"class":350},"(event);\n",[246,5863,5864],{"class":248,"line":276},[246,5865,280],{"emptyLinePlaceholder":279},[246,5867,5868,5870,5872,5875,5877,5879,5881,5884],{"class":248,"line":283},[246,5869,1301],{"class":456},[246,5871,4940],{"class":350},[246,5873,5874],{"class":362},"messages",[246,5876,4950],{"class":350},[246,5878,457],{"class":456},[246,5880,1323],{"class":456},[246,5882,5883],{"class":259}," readBody",[246,5885,5861],{"class":350},[246,5887,5888,5890,5892,5894],{"class":248,"line":289},[246,5889,1169],{"class":456},[246,5891,116],{"class":350},[246,5893,1174],{"class":456},[246,5895,5896],{"class":350},"messages) {\n",[246,5898,5899,5901,5903],{"class":248,"line":605},[246,5900,1976],{"class":456},[246,5902,1979],{"class":259},[246,5904,1192],{"class":350},[246,5906,5907,5909,5912],{"class":248,"line":610},[246,5908,1986],{"class":350},[246,5910,5911],{"class":362},"400",[246,5913,366],{"class":350},[246,5915,5916,5918,5921],{"class":248,"line":616},[246,5917,1996],{"class":350},[246,5919,5920],{"class":263},"'User messages are required'",[246,5922,366],{"class":350},[246,5924,5925],{"class":248,"line":621},[246,5926,1206],{"class":350},[246,5928,5929],{"class":248,"line":627},[246,5930,1211],{"class":350},[246,5932,5933],{"class":248,"line":633},[246,5934,280],{"emptyLinePlaceholder":279},[246,5936,5937,5939,5942,5944],{"class":248,"line":639},[246,5938,1301],{"class":456},[246,5940,5941],{"class":362}," llmMessages",[246,5943,575],{"class":456},[246,5945,851],{"class":350},[246,5947,5948],{"class":248,"line":645},[246,5949,5950],{"class":350},"    {\n",[246,5952,5953,5955,5958],{"class":248,"line":650},[246,5954,2242],{"class":350},[246,5956,5957],{"class":263},"'system'",[246,5959,366],{"class":350},[246,5961,5962,5964,5967],{"class":248,"line":655},[246,5963,2252],{"class":350},[246,5965,5966],{"class":259},"getSystemPrompt",[246,5968,5969],{"class":350},"(userSession.user.login),\n",[246,5971,5972],{"class":248,"line":660},[246,5973,1096],{"class":350},[246,5975,5976,5979],{"class":248,"line":666},[246,5977,5978],{"class":456},"    ...",[246,5980,5981],{"class":350},"messages,\n",[246,5983,5984],{"class":248,"line":672},[246,5985,5986],{"class":350},"  ];\n",[246,5988,5989],{"class":248,"line":678},[246,5990,280],{"emptyLinePlaceholder":279},[246,5992,5993,5995,5997],{"class":248,"line":684},[246,5994,1220],{"class":456},[246,5996,4140],{"class":259},[246,5998,1512],{"class":350},[246,6000,6001,6004],{"class":248,"line":690},[246,6002,6003],{"class":259},"    handleMessageWithOpenAI",[246,6005,6006],{"class":350},"(event, llmMessages, userSession.user.login)\n",[246,6008,6009],{"class":248,"line":695},[246,6010,6011],{"class":350},"  );\n",[246,6013,6014],{"class":248,"line":700},[246,6015,5788],{"class":350},[10,6017,6018,6019,6021],{},"As you can see, we retrieve the currently logged-in GitHub user’s details and pass the login info into the system prompt. This gives OpenAI the context it needs to answer queries like, “When did I make my first commit?” The ",[199,6020,5966],{}," utility helps with that:",[237,6023,6025],{"className":338,"code":6024,"language":340,"meta":242,"style":242},"export const getSystemPrompt = (loggedInUserName: string) =>\n  systemPrompt +\n  `\\n\\nNote: The currently logged in github user is \"${loggedInUserName}\".`;\n",[199,6026,6027,6052,6060],{"__ignoreMap":242},[246,6028,6029,6031,6033,6036,6038,6040,6043,6045,6047,6049],{"class":248,"line":249},[246,6030,1236],{"class":456},[246,6032,1239],{"class":456},[246,6034,6035],{"class":259}," getSystemPrompt",[246,6037,575],{"class":456},[246,6039,116],{"class":350},[246,6041,6042],{"class":1255},"loggedInUserName",[246,6044,335],{"class":456},[246,6046,1656],{"class":362},[246,6048,1290],{"class":350},[246,6050,6051],{"class":456},"=>\n",[246,6053,6054,6057],{"class":248,"line":256},[246,6055,6056],{"class":350},"  systemPrompt ",[246,6058,6059],{"class":456},"+\n",[246,6061,6062,6065,6067,6070,6072,6075],{"class":248,"line":276},[246,6063,6064],{"class":263},"  `",[246,6066,4331],{"class":362},[246,6068,6069],{"class":263},"Note: The currently logged in github user is \"${",[246,6071,6042],{"class":350},[246,6073,6074],{"class":263},"}\".`",[246,6076,1153],{"class":350},[129,6078,6080],{"id":6079},"client-side-handling","Client-Side Handling",[10,6082,6083,6084,6087,6088,6090],{},"On the client side, we use the built-in ",[199,6085,6086],{},"AuthState"," component from ",[199,6089,226],{}," to manage authentication flows, like logging in and checking if a user is signed in.",[10,6092,6093],{},"Here’s how to handle sign-in:",[237,6095,6097],{"className":338,"code":6096,"language":340,"meta":242,"style":242},"\u003CAuthState v-slot=\"{ loggedIn }\">\n  \u003CUButton\n    v-if=\"loggedIn\"\n    size=\"lg\"\n    trailing-icon=\"i-heroicons-arrow-right-16-solid\"\n    to=\"\u002Fchat\"\n  >\n    Go to Chat\n  \u003C\u002FUButton>\n\n  \u003Cdiv v-else class=\"flex flex-col items-center justify-center gap-y-2\">\n    \u003CUButton\n      size=\"lg\"\n      icon=\"i-simple-icons-github\"\n      to=\"\u002Fauth\u002Fgithub\"\n      external\n    >\n      Sign in with GitHub\n    \u003C\u002FUButton>\n    \u003Cp class=\"text-sm text-gray-600 dark:text-gray-300 text-center\">\n      Start Chatting Now!\n    \u003C\u002Fp>\n  \u003C\u002Fdiv>\n\u003C\u002FAuthState>\n",[199,6098,6099,6119,6127,6138,6148,6163,6173,6178,6183,6193,6197,6217,6224,6233,6243,6253,6258,6263,6277,6286,6300,6308,6316,6325],{"__ignoreMap":242},[246,6100,6101,6103,6106,6109,6112,6114,6117],{"class":248,"line":249},[246,6102,1926],{"class":456},[246,6104,6105],{"class":350},"AuthState v",[246,6107,6108],{"class":456},"-",[246,6110,6111],{"class":350},"slot",[246,6113,457],{"class":456},[246,6115,6116],{"class":263},"\"{ loggedIn }\"",[246,6118,1936],{"class":456},[246,6120,6121,6124],{"class":248,"line":256},[246,6122,6123],{"class":456},"  \u003C",[246,6125,6126],{"class":350},"UButton\n",[246,6128,6129,6132,6135],{"class":248,"line":276},[246,6130,6131],{"class":350},"    v",[246,6133,6134],{"class":456},"-if=",[246,6136,6137],{"class":263},"\"loggedIn\"\n",[246,6139,6140,6143,6145],{"class":248,"line":283},[246,6141,6142],{"class":350},"    size",[246,6144,457],{"class":456},[246,6146,6147],{"class":263},"\"lg\"\n",[246,6149,6150,6153,6155,6158,6160],{"class":248,"line":289},[246,6151,6152],{"class":350},"    trailing",[246,6154,6108],{"class":456},[246,6156,6157],{"class":350},"icon",[246,6159,457],{"class":456},[246,6161,6162],{"class":263},"\"i-heroicons-arrow-right-16-solid\"\n",[246,6164,6165,6168,6170],{"class":248,"line":605},[246,6166,6167],{"class":350},"    to",[246,6169,457],{"class":456},[246,6171,6172],{"class":263},"\"\u002Fchat\"\n",[246,6174,6175],{"class":248,"line":610},[246,6176,6177],{"class":456},"  >\n",[246,6179,6180],{"class":248,"line":616},[246,6181,6182],{"class":350},"    Go to Chat\n",[246,6184,6185,6188,6191],{"class":248,"line":621},[246,6186,6187],{"class":456},"  \u003C\u002F",[246,6189,6190],{"class":350},"UButton",[246,6192,1936],{"class":456},[246,6194,6195],{"class":248,"line":627},[246,6196,280],{"emptyLinePlaceholder":279},[246,6198,6199,6201,6204,6207,6210,6212,6215],{"class":248,"line":633},[246,6200,6123],{"class":456},[246,6202,6203],{"class":350},"div v",[246,6205,6206],{"class":456},"-else",[246,6208,6209],{"class":350}," class",[246,6211,457],{"class":456},[246,6213,6214],{"class":263},"\"flex flex-col items-center justify-center gap-y-2\"",[246,6216,1936],{"class":456},[246,6218,6219,6222],{"class":248,"line":639},[246,6220,6221],{"class":456},"    \u003C",[246,6223,6126],{"class":350},[246,6225,6226,6229,6231],{"class":248,"line":645},[246,6227,6228],{"class":350},"      size",[246,6230,457],{"class":456},[246,6232,6147],{"class":263},[246,6234,6235,6238,6240],{"class":248,"line":650},[246,6236,6237],{"class":350},"      icon",[246,6239,457],{"class":456},[246,6241,6242],{"class":263},"\"i-simple-icons-github\"\n",[246,6244,6245,6248,6250],{"class":248,"line":655},[246,6246,6247],{"class":350},"      to",[246,6249,457],{"class":456},[246,6251,6252],{"class":263},"\"\u002Fauth\u002Fgithub\"\n",[246,6254,6255],{"class":248,"line":660},[246,6256,6257],{"class":350},"      external\n",[246,6259,6260],{"class":248,"line":666},[246,6261,6262],{"class":456},"    >\n",[246,6264,6265,6268,6271,6274],{"class":248,"line":672},[246,6266,6267],{"class":350},"      Sign ",[246,6269,6270],{"class":456},"in",[246,6272,6273],{"class":456}," with",[246,6275,6276],{"class":350}," GitHub\n",[246,6278,6279,6282,6284],{"class":248,"line":678},[246,6280,6281],{"class":456},"    \u003C\u002F",[246,6283,6190],{"class":350},[246,6285,1936],{"class":456},[246,6287,6288,6290,6293,6295,6298],{"class":248,"line":684},[246,6289,6221],{"class":456},[246,6291,6292],{"class":350},"p class",[246,6294,457],{"class":456},[246,6296,6297],{"class":263},"\"text-sm text-gray-600 dark:text-gray-300 text-center\"",[246,6299,1936],{"class":456},[246,6301,6302,6305],{"class":248,"line":690},[246,6303,6304],{"class":350},"      Start Chatting Now",[246,6306,6307],{"class":456},"!\n",[246,6309,6310,6312,6314],{"class":248,"line":695},[246,6311,6281],{"class":456},[246,6313,10],{"class":350},[246,6315,1936],{"class":456},[246,6317,6318,6320,6323],{"class":248,"line":700},[246,6319,6187],{"class":456},[246,6321,6322],{"class":350},"div",[246,6324,1936],{"class":456},[246,6326,6327,6330,6332],{"class":248,"line":705},[246,6328,6329],{"class":456},"\u003C\u002F",[246,6331,6086],{"class":350},[246,6333,1936],{"class":456},[10,6335,6336,6337,6340,6341,6344],{},"Notice the ",[199,6338,6339],{},"external"," attribute on the Sign-In button. This attribute is essential—it tells the framework to treat the GitHub authentication as an external process. Without it, the framework will try to redirect you to the ",[199,6342,6343],{},"\u002Fauth\u002Fgithub"," route on the client side, causing errors (It did get me for sure).",[10,6346,6347,6348,6351],{},"This completes the server-side and client-side setup for user authentication. You can go through the shared GitHub repo to see how we also restrict navigation on the client side by using an ",[199,6349,6350],{},"auth"," middleware, ensuring that only authenticated users can access the chat page.",[25,6353,6355],{"id":6354},"bonus-enhancing-engagement","Bonus: Enhancing Engagement",[10,6357,6358],{},"As the project neared completion, I kept thinking about how to make it more engaging. I wanted to show what users were querying about and who they were querying for. However, I didn’t want to save every type of query—especially those like “When did I make my first commit?”—as they were both pointless and numerous. Instead, I decided to save only the queries made about other users, repositories, and other entities, excluding self-referential queries.",[129,6360,6362],{"id":6361},"database-setup","Database Setup",[10,6364,6365,6366,3142,6369,6371],{},"To achieve this, we needed a database, which is why we enabled ",[199,6367,6368],{},"database: true",[199,6370,317],{}," file. This setting binds Cloudflare’s D1 database in production and uses its platform proxy during development.",[10,6373,6374,6375,6378],{},"First, we create the necessary database tables using the ",[199,6376,6377],{},"hubDatabase"," server composable:",[237,6380,6382],{"className":338,"code":6381,"language":340,"meta":242,"style":242},"await hubDatabase().exec(\n  `CREATE TABLE IF NOT EXISTS queries (\n    id INTEGER PRIMARY KEY AUTOINCREMENT,\n    text TEXT NOT NULL,\n    response TEXT NOT NULL,\n    github_request TEXT NOT NULL,\n    github_response TEXT NOT NULL,\n    queried_at DATETIME DEFAULT CURRENT_TIMESTAMP\n  );\n`.replace(\u002F\\n\u002Fg, '')\n);\n\n\u002F\u002F ... other tables and indexes\n",[199,6383,6384,6399,6404,6409,6414,6419,6424,6429,6434,6438,6462,6466,6470],{"__ignoreMap":242},[246,6385,6386,6389,6392,6394,6397],{"class":248,"line":249},[246,6387,6388],{"class":456},"await",[246,6390,6391],{"class":259}," hubDatabase",[246,6393,2728],{"class":350},[246,6395,6396],{"class":259},"exec",[246,6398,1512],{"class":350},[246,6400,6401],{"class":248,"line":256},[246,6402,6403],{"class":263},"  `CREATE TABLE IF NOT EXISTS queries (\n",[246,6405,6406],{"class":248,"line":276},[246,6407,6408],{"class":263},"    id INTEGER PRIMARY KEY AUTOINCREMENT,\n",[246,6410,6411],{"class":248,"line":283},[246,6412,6413],{"class":263},"    text TEXT NOT NULL,\n",[246,6415,6416],{"class":248,"line":289},[246,6417,6418],{"class":263},"    response TEXT NOT NULL,\n",[246,6420,6421],{"class":248,"line":605},[246,6422,6423],{"class":263},"    github_request TEXT NOT NULL,\n",[246,6425,6426],{"class":248,"line":610},[246,6427,6428],{"class":263},"    github_response TEXT NOT NULL,\n",[246,6430,6431],{"class":248,"line":616},[246,6432,6433],{"class":263},"    queried_at DATETIME DEFAULT CURRENT_TIMESTAMP\n",[246,6435,6436],{"class":248,"line":621},[246,6437,6011],{"class":263},[246,6439,6440,6442,6444,6446,6448,6450,6452,6454,6456,6458,6460],{"class":248,"line":627},[246,6441,4334],{"class":263},[246,6443,414],{"class":350},[246,6445,2895],{"class":259},[246,6447,2739],{"class":350},[246,6449,2900],{"class":263},[246,6451,4497],{"class":362},[246,6453,2900],{"class":263},[246,6455,2908],{"class":456},[246,6457,227],{"class":350},[246,6459,3517],{"class":263},[246,6461,2888],{"class":350},[246,6463,6464],{"class":248,"line":633},[246,6465,2745],{"class":350},[246,6467,6468],{"class":248,"line":639},[246,6469,280],{"emptyLinePlaceholder":279},[246,6471,6472],{"class":248,"line":645},[246,6473,6474],{"class":252},"\u002F\u002F ... other tables and indexes\n",[10,6476,6477,6478,6481,6482,6484],{},"Next, we utilize utility functions to save the queries. The ",[199,6479,6480],{},"saveUserQuery"," function is invoked only if a tool call was made from the ",[199,6483,4057],{}," function we discussed earlier.",[237,6486,6488],{"className":338,"code":6487,"language":340,"meta":242,"style":242},"export type ToolCallDetails = {\n  \u002F\u002F eslint-disable-next-line @typescript-eslint\u002Fno-explicit-any\n  response: any;\n  request: SearchParams;\n};\n\nexport type UserQuery = {\n  userMessage: string;\n  toolCalls: ToolCallDetails[];\n  assistantReply: string;\n};\n\nconst getAvatarUrl = (toolCall: ToolCallDetails) => {\n  let avatarUrl;\n  const responseItem = toolCall.response.items[0];\n\n  if (responseItem) {\n    if (responseItem.author) {\n      avatarUrl = responseItem.author.avatar_url;\n    } else if (responseItem.user) {\n      avatarUrl = responseItem.user.avatar_url;\n    } else if (responseItem.owner) {\n      avatarUrl = responseItem.owner.avatar_url;\n    } else if (responseItem.avatar_url) {\n      avatarUrl = responseItem.avatar_url;\n    }\n  }\n\n  return avatarUrl;\n};\n\nconst shouldSaveUserQuery = (\n  toolCall: ToolCallDetails,\n  loggedInUser: string\n) => {\n  const responseItem = toolCall.response.items[0];\n  if (\n    responseItem &&\n    ((responseItem.author && responseItem.author.login === loggedInUser) ||\n      (responseItem.user && responseItem.user.login === loggedInUser) ||\n      (responseItem.owner && responseItem.owner.login === loggedInUser) ||\n      responseItem.login === loggedInUser)\n  ) {\n    return false;\n  }\n\n  return true;\n};\n\nexport const saveUserQuery = async (\n  loggedInUser: string,\n  userQuery: UserQuery\n) => {\n  const toolCall = userQuery.toolCalls[0];\n  const matchedUser = toolCall.request.q.match(\u002F(?:author:|user:)(\\S+)\u002F);\n  if (matchedUser) {\n    const queriedUser = matchedUser[1].toLowerCase();\n    if (queriedUser !== loggedInUser) {\n      const avatarUrl = getAvatarUrl(toolCall);\n\n      await storeQuery(\n        userQuery.userMessage,\n        userQuery.assistantReply,\n        toolCall,\n        { login: queriedUser, avatarUrl }\n      );\n    }\n  } else if (shouldSaveUserQuery(toolCall, loggedInUser)) {\n    await storeQuery(userQuery.userMessage, userQuery.assistantReply, toolCall);\n  }\n};\n",[199,6489,6490,6503,6508,6520,6531,6535,6539,6552,6563,6575,6586,6590,6594,6618,6625,6641,6645,6652,6659,6669,6680,6689,6700,6709,6720,6729,6733,6737,6741,6747,6751,6755,6766,6777,6787,6795,6809,6815,6823,6842,6858,6874,6884,6889,6897,6901,6905,6913,6917,6921,6936,6946,6956,6964,6979,7020,7027,7049,7061,7075,7079,7089,7094,7099,7104,7109,7114,7118,7134,7143,7147],{"__ignoreMap":242},[246,6491,6492,6494,6496,6499,6501],{"class":248,"line":249},[246,6493,1236],{"class":456},[246,6495,1639],{"class":456},[246,6497,6498],{"class":259}," ToolCallDetails",[246,6500,575],{"class":456},[246,6502,1296],{"class":350},[246,6504,6505],{"class":248,"line":256},[246,6506,6507],{"class":252},"  \u002F\u002F eslint-disable-next-line @typescript-eslint\u002Fno-explicit-any\n",[246,6509,6510,6513,6515,6518],{"class":248,"line":276},[246,6511,6512],{"class":1255},"  response",[246,6514,335],{"class":456},[246,6516,6517],{"class":362}," any",[246,6519,1153],{"class":350},[246,6521,6522,6525,6527,6529],{"class":248,"line":283},[246,6523,6524],{"class":1255},"  request",[246,6526,335],{"class":456},[246,6528,1642],{"class":259},[246,6530,1153],{"class":350},[246,6532,6533],{"class":248,"line":289},[246,6534,1708],{"class":350},[246,6536,6537],{"class":248,"line":605},[246,6538,280],{"emptyLinePlaceholder":279},[246,6540,6541,6543,6545,6548,6550],{"class":248,"line":610},[246,6542,1236],{"class":456},[246,6544,1639],{"class":456},[246,6546,6547],{"class":259}," UserQuery",[246,6549,575],{"class":456},[246,6551,1296],{"class":350},[246,6553,6554,6557,6559,6561],{"class":248,"line":616},[246,6555,6556],{"class":1255},"  userMessage",[246,6558,335],{"class":456},[246,6560,1656],{"class":362},[246,6562,1153],{"class":350},[246,6564,6565,6568,6570,6572],{"class":248,"line":621},[246,6566,6567],{"class":1255},"  toolCalls",[246,6569,335],{"class":456},[246,6571,6498],{"class":259},[246,6573,6574],{"class":350},"[];\n",[246,6576,6577,6580,6582,6584],{"class":248,"line":627},[246,6578,6579],{"class":1255},"  assistantReply",[246,6581,335],{"class":456},[246,6583,1656],{"class":362},[246,6585,1153],{"class":350},[246,6587,6588],{"class":248,"line":633},[246,6589,1708],{"class":350},[246,6591,6592],{"class":248,"line":639},[246,6593,280],{"emptyLinePlaceholder":279},[246,6595,6596,6598,6601,6603,6605,6608,6610,6612,6614,6616],{"class":248,"line":645},[246,6597,569],{"class":456},[246,6599,6600],{"class":259}," getAvatarUrl",[246,6602,575],{"class":456},[246,6604,116],{"class":350},[246,6606,6607],{"class":1255},"toolCall",[246,6609,335],{"class":456},[246,6611,6498],{"class":259},[246,6613,1290],{"class":350},[246,6615,1293],{"class":456},[246,6617,1296],{"class":350},[246,6619,6620,6622],{"class":248,"line":650},[246,6621,4184],{"class":456},[246,6623,6624],{"class":350}," avatarUrl;\n",[246,6626,6627,6629,6632,6634,6637,6639],{"class":248,"line":655},[246,6628,1301],{"class":456},[246,6630,6631],{"class":362}," responseItem",[246,6633,575],{"class":456},[246,6635,6636],{"class":350}," toolCall.response.items[",[246,6638,1385],{"class":362},[246,6640,1107],{"class":350},[246,6642,6643],{"class":248,"line":660},[246,6644,280],{"emptyLinePlaceholder":279},[246,6646,6647,6649],{"class":248,"line":666},[246,6648,1169],{"class":456},[246,6650,6651],{"class":350}," (responseItem) {\n",[246,6653,6654,6656],{"class":248,"line":672},[246,6655,2425],{"class":456},[246,6657,6658],{"class":350}," (responseItem.author) {\n",[246,6660,6661,6664,6666],{"class":248,"line":678},[246,6662,6663],{"class":350},"      avatarUrl ",[246,6665,457],{"class":456},[246,6667,6668],{"class":350}," responseItem.author.avatar_url;\n",[246,6670,6671,6673,6675,6677],{"class":248,"line":684},[246,6672,2560],{"class":350},[246,6674,5227],{"class":456},[246,6676,5230],{"class":456},[246,6678,6679],{"class":350}," (responseItem.user) {\n",[246,6681,6682,6684,6686],{"class":248,"line":690},[246,6683,6663],{"class":350},[246,6685,457],{"class":456},[246,6687,6688],{"class":350}," responseItem.user.avatar_url;\n",[246,6690,6691,6693,6695,6697],{"class":248,"line":695},[246,6692,2560],{"class":350},[246,6694,5227],{"class":456},[246,6696,5230],{"class":456},[246,6698,6699],{"class":350}," (responseItem.owner) {\n",[246,6701,6702,6704,6706],{"class":248,"line":700},[246,6703,6663],{"class":350},[246,6705,457],{"class":456},[246,6707,6708],{"class":350}," responseItem.owner.avatar_url;\n",[246,6710,6711,6713,6715,6717],{"class":248,"line":705},[246,6712,2560],{"class":350},[246,6714,5227],{"class":456},[246,6716,5230],{"class":456},[246,6718,6719],{"class":350}," (responseItem.avatar_url) {\n",[246,6721,6722,6724,6726],{"class":248,"line":711},[246,6723,6663],{"class":350},[246,6725,457],{"class":456},[246,6727,6728],{"class":350}," responseItem.avatar_url;\n",[246,6730,6731],{"class":248,"line":716},[246,6732,1578],{"class":350},[246,6734,6735],{"class":248,"line":721},[246,6736,1211],{"class":350},[246,6738,6739],{"class":248,"line":727},[246,6740,280],{"emptyLinePlaceholder":279},[246,6742,6743,6745],{"class":248,"line":733},[246,6744,1220],{"class":456},[246,6746,6624],{"class":350},[246,6748,6749],{"class":248,"line":739},[246,6750,1708],{"class":350},[246,6752,6753],{"class":248,"line":745},[246,6754,280],{"emptyLinePlaceholder":279},[246,6756,6757,6759,6762,6764],{"class":248,"line":751},[246,6758,569],{"class":456},[246,6760,6761],{"class":259}," shouldSaveUserQuery",[246,6763,575],{"class":456},[246,6765,1250],{"class":350},[246,6767,6768,6771,6773,6775],{"class":248,"line":757},[246,6769,6770],{"class":1255},"  toolCall",[246,6772,335],{"class":456},[246,6774,6498],{"class":259},[246,6776,366],{"class":350},[246,6778,6779,6782,6784],{"class":248,"line":763},[246,6780,6781],{"class":1255},"  loggedInUser",[246,6783,335],{"class":456},[246,6785,6786],{"class":362}," string\n",[246,6788,6789,6791,6793],{"class":248,"line":768},[246,6790,1290],{"class":350},[246,6792,1293],{"class":456},[246,6794,1296],{"class":350},[246,6796,6797,6799,6801,6803,6805,6807],{"class":248,"line":776},[246,6798,1301],{"class":456},[246,6800,6631],{"class":362},[246,6802,575],{"class":456},[246,6804,6636],{"class":350},[246,6806,1385],{"class":362},[246,6808,1107],{"class":350},[246,6810,6811,6813],{"class":248,"line":784},[246,6812,1169],{"class":456},[246,6814,1250],{"class":350},[246,6816,6817,6820],{"class":248,"line":1104},[246,6818,6819],{"class":350},"    responseItem ",[246,6821,6822],{"class":456},"&&\n",[246,6824,6825,6828,6831,6834,6836,6839],{"class":248,"line":1540},[246,6826,6827],{"class":350},"    ((responseItem.author ",[246,6829,6830],{"class":456},"&&",[246,6832,6833],{"class":350}," responseItem.author.login ",[246,6835,1468],{"class":456},[246,6837,6838],{"class":350}," loggedInUser) ",[246,6840,6841],{"class":456},"||\n",[246,6843,6844,6847,6849,6852,6854,6856],{"class":248,"line":1546},[246,6845,6846],{"class":350},"      (responseItem.user ",[246,6848,6830],{"class":456},[246,6850,6851],{"class":350}," responseItem.user.login ",[246,6853,1468],{"class":456},[246,6855,6838],{"class":350},[246,6857,6841],{"class":456},[246,6859,6860,6863,6865,6868,6870,6872],{"class":248,"line":1552},[246,6861,6862],{"class":350},"      (responseItem.owner ",[246,6864,6830],{"class":456},[246,6866,6867],{"class":350}," responseItem.owner.login ",[246,6869,1468],{"class":456},[246,6871,6838],{"class":350},[246,6873,6841],{"class":456},[246,6875,6876,6879,6881],{"class":248,"line":1558},[246,6877,6878],{"class":350},"      responseItem.login ",[246,6880,1468],{"class":456},[246,6882,6883],{"class":350}," loggedInUser)\n",[246,6885,6886],{"class":248,"line":1563},[246,6887,6888],{"class":350},"  ) {\n",[246,6890,6891,6893,6895],{"class":248,"line":1569},[246,6892,2091],{"class":456},[246,6894,4192],{"class":362},[246,6896,1153],{"class":350},[246,6898,6899],{"class":248,"line":1575},[246,6900,1211],{"class":350},[246,6902,6903],{"class":248,"line":1581},[246,6904,280],{"emptyLinePlaceholder":279},[246,6906,6907,6909,6911],{"class":248,"line":1586},[246,6908,1220],{"class":456},[246,6910,4595],{"class":362},[246,6912,1153],{"class":350},[246,6914,6915],{"class":248,"line":1592},[246,6916,1708],{"class":350},[246,6918,6919],{"class":248,"line":1597},[246,6920,280],{"emptyLinePlaceholder":279},[246,6922,6923,6925,6927,6930,6932,6934],{"class":248,"line":1602},[246,6924,1236],{"class":456},[246,6926,1239],{"class":456},[246,6928,6929],{"class":259}," saveUserQuery",[246,6931,575],{"class":456},[246,6933,1247],{"class":456},[246,6935,1250],{"class":350},[246,6937,6938,6940,6942,6944],{"class":248,"line":1610},[246,6939,6781],{"class":1255},[246,6941,335],{"class":456},[246,6943,1656],{"class":362},[246,6945,366],{"class":350},[246,6947,6948,6951,6953],{"class":248,"line":2136},[246,6949,6950],{"class":1255},"  userQuery",[246,6952,335],{"class":456},[246,6954,6955],{"class":259}," UserQuery\n",[246,6957,6958,6960,6962],{"class":248,"line":2146},[246,6959,1290],{"class":350},[246,6961,1293],{"class":456},[246,6963,1296],{"class":350},[246,6965,6966,6968,6970,6972,6975,6977],{"class":248,"line":2151},[246,6967,1301],{"class":456},[246,6969,1438],{"class":362},[246,6971,575],{"class":456},[246,6973,6974],{"class":350}," userQuery.toolCalls[",[246,6976,1385],{"class":362},[246,6978,1107],{"class":350},[246,6980,6981,6983,6986,6988,6991,6994,6996,6998,7002,7005,7008,7011,7013,7016,7018],{"class":248,"line":2156},[246,6982,1301],{"class":456},[246,6984,6985],{"class":362}," matchedUser",[246,6987,575],{"class":456},[246,6989,6990],{"class":350}," toolCall.request.q.",[246,6992,6993],{"class":259},"match",[246,6995,2739],{"class":350},[246,6997,2900],{"class":263},[246,6999,7001],{"class":7000},"sA_wV","(?:author:",[246,7003,7004],{"class":456},"|",[246,7006,7007],{"class":7000},"user:)(",[246,7009,7010],{"class":362},"\\S",[246,7012,2935],{"class":456},[246,7014,7015],{"class":7000},")",[246,7017,2900],{"class":263},[246,7019,2745],{"class":350},[246,7021,7022,7024],{"class":248,"line":3007},[246,7023,1169],{"class":456},[246,7025,7026],{"class":350}," (matchedUser) {\n",[246,7028,7029,7031,7034,7036,7039,7042,7045,7047],{"class":248,"line":3012},[246,7030,2060],{"class":456},[246,7032,7033],{"class":362}," queriedUser",[246,7035,575],{"class":456},[246,7037,7038],{"class":350}," matchedUser[",[246,7040,7041],{"class":362},"1",[246,7043,7044],{"class":350},"].",[246,7046,2731],{"class":259},[246,7048,1311],{"class":350},[246,7050,7051,7053,7056,7058],{"class":248,"line":3020},[246,7052,2425],{"class":456},[246,7054,7055],{"class":350}," (queriedUser ",[246,7057,3441],{"class":456},[246,7059,7060],{"class":350}," loggedInUser) {\n",[246,7062,7063,7065,7068,7070,7072],{"class":248,"line":3035},[246,7064,1449],{"class":456},[246,7066,7067],{"class":362}," avatarUrl",[246,7069,575],{"class":456},[246,7071,6600],{"class":259},[246,7073,7074],{"class":350},"(toolCall);\n",[246,7076,7077],{"class":248,"line":3040},[246,7078,280],{"emptyLinePlaceholder":279},[246,7080,7081,7084,7087],{"class":248,"line":3045},[246,7082,7083],{"class":456},"      await",[246,7085,7086],{"class":259}," storeQuery",[246,7088,1512],{"class":350},[246,7090,7091],{"class":248,"line":3053},[246,7092,7093],{"class":350},"        userQuery.userMessage,\n",[246,7095,7096],{"class":248,"line":3058},[246,7097,7098],{"class":350},"        userQuery.assistantReply,\n",[246,7100,7101],{"class":248,"line":3063},[246,7102,7103],{"class":350},"        toolCall,\n",[246,7105,7106],{"class":248,"line":3734},[246,7107,7108],{"class":350},"        { login: queriedUser, avatarUrl }\n",[246,7110,7111],{"class":248,"line":3740},[246,7112,7113],{"class":350},"      );\n",[246,7115,7116],{"class":248,"line":3746},[246,7117,1578],{"class":350},[246,7119,7120,7122,7124,7126,7128,7131],{"class":248,"line":3752},[246,7121,2099],{"class":350},[246,7123,5227],{"class":456},[246,7125,5230],{"class":456},[246,7127,116],{"class":350},[246,7129,7130],{"class":259},"shouldSaveUserQuery",[246,7132,7133],{"class":350},"(toolCall, loggedInUser)) {\n",[246,7135,7136,7138,7140],{"class":248,"line":3758},[246,7137,5661],{"class":456},[246,7139,7086],{"class":259},[246,7141,7142],{"class":350},"(userQuery.userMessage, userQuery.assistantReply, toolCall);\n",[246,7144,7145],{"class":248,"line":3763},[246,7146,1211],{"class":350},[246,7148,7149],{"class":248,"line":3773},[246,7150,1708],{"class":350},[10,7152,7153],{},"It checks if the query involves the currently logged-in user. If it does, we simply return without logging anything; otherwise, we store the query details in the database using:",[237,7155,7157],{"className":338,"code":7156,"language":340,"meta":242,"style":242},"const storeQuery = async (\n  queryText: string,\n  assistantReply: string,\n  toolCall: ToolCallDetails,\n  queriedUser?: { login: string; avatarUrl?: string }\n) => {\n  try {\n    const db = hubDatabase();\n\n    const queryStmt = db\n      .prepare(\n        'INSERT INTO queries (text, response, github_request, github_response) VALUES (?1, ?2, ?3, ?4)'\n      )\n      .bind(\n        queryText,\n        assistantReply,\n        JSON.stringify(toolCall.request),\n        JSON.stringify(toolCall.response)\n      );\n    if (queriedUser) {\n      const [batchRes1, batchRes2] = await db.batch([\n        queryStmt,\n        db\n          .prepare(\n            `INSERT INTO trending_users (username, search_count, last_searched, avatar_url)\n              VALUES (?1, 1, CURRENT_TIMESTAMP, ?2)\n              ON CONFLICT(username)\n              DO UPDATE SET search_count = search_count + 1, last_searched = CURRENT_TIMESTAMP, avatar_url = COALESCE(?2, avatar_url)`\n          )\n          .bind(queriedUser.login, queriedUser.avatarUrl),\n      ]);\n\n      console.log('storeQuery: ', batchRes1, batchRes2);\n    } else {\n      const res = await queryStmt.run();\n\n      console.log('storeQuery: ', res);\n    }\n  } catch (error) {\n    console.error('Failed to store query: ', error);\n  }\n};\n",[199,7158,7159,7171,7182,7192,7202,7231,7239,7245,7258,7262,7274,7284,7289,7294,7303,7308,7313,7325,7336,7340,7347,7377,7382,7387,7396,7401,7406,7411,7416,7420,7429,7434,7438,7452,7460,7479,7483,7496,7500,7508,7521,7525],{"__ignoreMap":242},[246,7160,7161,7163,7165,7167,7169],{"class":248,"line":249},[246,7162,569],{"class":456},[246,7164,7086],{"class":259},[246,7166,575],{"class":456},[246,7168,1247],{"class":456},[246,7170,1250],{"class":350},[246,7172,7173,7176,7178,7180],{"class":248,"line":256},[246,7174,7175],{"class":1255},"  queryText",[246,7177,335],{"class":456},[246,7179,1656],{"class":362},[246,7181,366],{"class":350},[246,7183,7184,7186,7188,7190],{"class":248,"line":276},[246,7185,6579],{"class":1255},[246,7187,335],{"class":456},[246,7189,1656],{"class":362},[246,7191,366],{"class":350},[246,7193,7194,7196,7198,7200],{"class":248,"line":283},[246,7195,6770],{"class":1255},[246,7197,335],{"class":456},[246,7199,6498],{"class":259},[246,7201,366],{"class":350},[246,7203,7204,7207,7209,7211,7214,7216,7218,7221,7224,7226,7228],{"class":248,"line":289},[246,7205,7206],{"class":1255},"  queriedUser",[246,7208,1677],{"class":456},[246,7210,4940],{"class":350},[246,7212,7213],{"class":1255},"login",[246,7215,335],{"class":456},[246,7217,1656],{"class":362},[246,7219,7220],{"class":350},"; ",[246,7222,7223],{"class":1255},"avatarUrl",[246,7225,1677],{"class":456},[246,7227,1656],{"class":362},[246,7229,7230],{"class":350}," }\n",[246,7232,7233,7235,7237],{"class":248,"line":605},[246,7234,1290],{"class":350},[246,7236,1293],{"class":456},[246,7238,1296],{"class":350},[246,7240,7241,7243],{"class":248,"line":610},[246,7242,2053],{"class":456},[246,7244,1296],{"class":350},[246,7246,7247,7249,7252,7254,7256],{"class":248,"line":616},[246,7248,2060],{"class":456},[246,7250,7251],{"class":362}," db",[246,7253,575],{"class":456},[246,7255,6391],{"class":259},[246,7257,1311],{"class":350},[246,7259,7260],{"class":248,"line":621},[246,7261,280],{"emptyLinePlaceholder":279},[246,7263,7264,7266,7269,7271],{"class":248,"line":627},[246,7265,2060],{"class":456},[246,7267,7268],{"class":362}," queryStmt",[246,7270,575],{"class":456},[246,7272,7273],{"class":350}," db\n",[246,7275,7276,7279,7282],{"class":248,"line":633},[246,7277,7278],{"class":350},"      .",[246,7280,7281],{"class":259},"prepare",[246,7283,1512],{"class":350},[246,7285,7286],{"class":248,"line":639},[246,7287,7288],{"class":263},"        'INSERT INTO queries (text, response, github_request, github_response) VALUES (?1, ?2, ?3, ?4)'\n",[246,7290,7291],{"class":248,"line":645},[246,7292,7293],{"class":350},"      )\n",[246,7295,7296,7298,7301],{"class":248,"line":650},[246,7297,7278],{"class":350},[246,7299,7300],{"class":259},"bind",[246,7302,1512],{"class":350},[246,7304,7305],{"class":248,"line":655},[246,7306,7307],{"class":350},"        queryText,\n",[246,7309,7310],{"class":248,"line":660},[246,7311,7312],{"class":350},"        assistantReply,\n",[246,7314,7315,7318,7320,7322],{"class":248,"line":666},[246,7316,7317],{"class":362},"        JSON",[246,7319,414],{"class":350},[246,7321,2260],{"class":259},[246,7323,7324],{"class":350},"(toolCall.request),\n",[246,7326,7327,7329,7331,7333],{"class":248,"line":672},[246,7328,7317],{"class":362},[246,7330,414],{"class":350},[246,7332,2260],{"class":259},[246,7334,7335],{"class":350},"(toolCall.response)\n",[246,7337,7338],{"class":248,"line":678},[246,7339,7113],{"class":350},[246,7341,7342,7344],{"class":248,"line":684},[246,7343,2425],{"class":456},[246,7345,7346],{"class":350}," (queriedUser) {\n",[246,7348,7349,7351,7353,7356,7358,7361,7364,7366,7368,7371,7374],{"class":248,"line":690},[246,7350,1449],{"class":456},[246,7352,2852],{"class":350},[246,7354,7355],{"class":362},"batchRes1",[246,7357,227],{"class":350},[246,7359,7360],{"class":362},"batchRes2",[246,7362,7363],{"class":350},"] ",[246,7365,457],{"class":456},[246,7367,1323],{"class":456},[246,7369,7370],{"class":350}," db.",[246,7372,7373],{"class":259},"batch",[246,7375,7376],{"class":350},"([\n",[246,7378,7379],{"class":248,"line":695},[246,7380,7381],{"class":350},"        queryStmt,\n",[246,7383,7384],{"class":248,"line":700},[246,7385,7386],{"class":350},"        db\n",[246,7388,7389,7392,7394],{"class":248,"line":705},[246,7390,7391],{"class":350},"          .",[246,7393,7281],{"class":259},[246,7395,1512],{"class":350},[246,7397,7398],{"class":248,"line":711},[246,7399,7400],{"class":263},"            `INSERT INTO trending_users (username, search_count, last_searched, avatar_url)\n",[246,7402,7403],{"class":248,"line":716},[246,7404,7405],{"class":263},"              VALUES (?1, 1, CURRENT_TIMESTAMP, ?2)\n",[246,7407,7408],{"class":248,"line":721},[246,7409,7410],{"class":263},"              ON CONFLICT(username)\n",[246,7412,7413],{"class":248,"line":727},[246,7414,7415],{"class":263},"              DO UPDATE SET search_count = search_count + 1, last_searched = CURRENT_TIMESTAMP, avatar_url = COALESCE(?2, avatar_url)`\n",[246,7417,7418],{"class":248,"line":733},[246,7419,4535],{"class":350},[246,7421,7422,7424,7426],{"class":248,"line":739},[246,7423,7391],{"class":350},[246,7425,7300],{"class":259},[246,7427,7428],{"class":350},"(queriedUser.login, queriedUser.avatarUrl),\n",[246,7430,7431],{"class":248,"line":745},[246,7432,7433],{"class":350},"      ]);\n",[246,7435,7436],{"class":248,"line":751},[246,7437,280],{"emptyLinePlaceholder":279},[246,7439,7440,7442,7444,7446,7449],{"class":248,"line":757},[246,7441,2569],{"class":350},[246,7443,4407],{"class":259},[246,7445,2739],{"class":350},[246,7447,7448],{"class":263},"'storeQuery: '",[246,7450,7451],{"class":350},", batchRes1, batchRes2);\n",[246,7453,7454,7456,7458],{"class":248,"line":763},[246,7455,2560],{"class":350},[246,7457,5227],{"class":456},[246,7459,1296],{"class":350},[246,7461,7462,7464,7467,7469,7471,7474,7477],{"class":248,"line":768},[246,7463,1449],{"class":456},[246,7465,7466],{"class":362}," res",[246,7468,575],{"class":456},[246,7470,1323],{"class":456},[246,7472,7473],{"class":350}," queryStmt.",[246,7475,7476],{"class":259},"run",[246,7478,1311],{"class":350},[246,7480,7481],{"class":248,"line":776},[246,7482,280],{"emptyLinePlaceholder":279},[246,7484,7485,7487,7489,7491,7493],{"class":248,"line":784},[246,7486,2569],{"class":350},[246,7488,4407],{"class":259},[246,7490,2739],{"class":350},[246,7492,7448],{"class":263},[246,7494,7495],{"class":350},", res);\n",[246,7497,7498],{"class":248,"line":1104},[246,7499,1578],{"class":350},[246,7501,7502,7504,7506],{"class":248,"line":1540},[246,7503,2099],{"class":350},[246,7505,2102],{"class":456},[246,7507,2105],{"class":350},[246,7509,7510,7512,7514,7516,7519],{"class":248,"line":1546},[246,7511,2110],{"class":350},[246,7513,2113],{"class":259},[246,7515,2739],{"class":350},[246,7517,7518],{"class":263},"'Failed to store query: '",[246,7520,3833],{"class":350},[246,7522,7523],{"class":248,"line":1552},[246,7524,1211],{"class":350},[246,7526,7527],{"class":248,"line":1558},[246,7528,1708],{"class":350},[129,7530,7532],{"id":7531},"api-endpoints","API Endpoints",[10,7534,7535],{},"We then create API endpoints to retrieve the trending users and recent queries, as shown below (note the use of endpoint caching):",[237,7537,7539],{"className":338,"code":7538,"language":340,"meta":242,"style":242},"export const getRecentQueries = async () => {\n  const db = hubDatabase();\n  console.log('getRecentQueries');\n  const result = await db\n    .prepare(\n      'SELECT id, text, response, queried_at FROM queries ORDER BY queried_at DESC LIMIT ?'\n    )\n    .bind(10)\n    .all\u003CRecentQuery>();\n\n  console.log('getRecentQueries: ', result);\n  return result.results;\n};\n\nexport default defineCachedEventHandler(\n  async () => {\n    const results = await getRecentQueries();\n\n    return results;\n  },\n  {\n    maxAge: 10 * 60, \u002F\u002F 10 minutes\n  }\n);\n",[199,7540,7541,7561,7573,7587,7600,7609,7614,7619,7632,7647,7651,7665,7672,7676,7680,7691,7701,7716,7720,7727,7731,7735,7750,7754],{"__ignoreMap":242},[246,7542,7543,7545,7547,7550,7552,7554,7557,7559],{"class":248,"line":249},[246,7544,1236],{"class":456},[246,7546,1239],{"class":456},[246,7548,7549],{"class":259}," getRecentQueries",[246,7551,575],{"class":456},[246,7553,1247],{"class":456},[246,7555,7556],{"class":350}," () ",[246,7558,1293],{"class":456},[246,7560,1296],{"class":350},[246,7562,7563,7565,7567,7569,7571],{"class":248,"line":256},[246,7564,1301],{"class":456},[246,7566,7251],{"class":362},[246,7568,575],{"class":456},[246,7570,6391],{"class":259},[246,7572,1311],{"class":350},[246,7574,7575,7578,7580,7582,7585],{"class":248,"line":276},[246,7576,7577],{"class":350},"  console.",[246,7579,4407],{"class":259},[246,7581,2739],{"class":350},[246,7583,7584],{"class":263},"'getRecentQueries'",[246,7586,2745],{"class":350},[246,7588,7589,7591,7594,7596,7598],{"class":248,"line":283},[246,7590,1301],{"class":456},[246,7592,7593],{"class":362}," result",[246,7595,575],{"class":456},[246,7597,1323],{"class":456},[246,7599,7273],{"class":350},[246,7601,7602,7605,7607],{"class":248,"line":289},[246,7603,7604],{"class":350},"    .",[246,7606,7281],{"class":259},[246,7608,1512],{"class":350},[246,7610,7611],{"class":248,"line":605},[246,7612,7613],{"class":263},"      'SELECT id, text, response, queried_at FROM queries ORDER BY queried_at DESC LIMIT ?'\n",[246,7615,7616],{"class":248,"line":610},[246,7617,7618],{"class":350},"    )\n",[246,7620,7621,7623,7625,7627,7630],{"class":248,"line":616},[246,7622,7604],{"class":350},[246,7624,7300],{"class":259},[246,7626,2739],{"class":350},[246,7628,7629],{"class":362},"10",[246,7631,2888],{"class":350},[246,7633,7634,7636,7639,7641,7644],{"class":248,"line":621},[246,7635,7604],{"class":350},[246,7637,7638],{"class":259},"all",[246,7640,1926],{"class":350},[246,7642,7643],{"class":259},"RecentQuery",[246,7645,7646],{"class":350},">();\n",[246,7648,7649],{"class":248,"line":627},[246,7650,280],{"emptyLinePlaceholder":279},[246,7652,7653,7655,7657,7659,7662],{"class":248,"line":633},[246,7654,7577],{"class":350},[246,7656,4407],{"class":259},[246,7658,2739],{"class":350},[246,7660,7661],{"class":263},"'getRecentQueries: '",[246,7663,7664],{"class":350},", result);\n",[246,7666,7667,7669],{"class":248,"line":639},[246,7668,1220],{"class":456},[246,7670,7671],{"class":350}," result.results;\n",[246,7673,7674],{"class":248,"line":645},[246,7675,1708],{"class":350},[246,7677,7678],{"class":248,"line":650},[246,7679,280],{"emptyLinePlaceholder":279},[246,7681,7682,7684,7686,7689],{"class":248,"line":655},[246,7683,1236],{"class":456},[246,7685,5631],{"class":456},[246,7687,7688],{"class":259}," defineCachedEventHandler",[246,7690,1512],{"class":350},[246,7692,7693,7695,7697,7699],{"class":248,"line":660},[246,7694,2385],{"class":456},[246,7696,7556],{"class":350},[246,7698,1293],{"class":456},[246,7700,1296],{"class":350},[246,7702,7703,7705,7708,7710,7712,7714],{"class":248,"line":666},[246,7704,2060],{"class":456},[246,7706,7707],{"class":362}," results",[246,7709,575],{"class":456},[246,7711,1323],{"class":456},[246,7713,7549],{"class":259},[246,7715,1311],{"class":350},[246,7717,7718],{"class":248,"line":672},[246,7719,280],{"emptyLinePlaceholder":279},[246,7721,7722,7724],{"class":248,"line":678},[246,7723,2091],{"class":456},[246,7725,7726],{"class":350}," results;\n",[246,7728,7729],{"class":248,"line":684},[246,7730,1101],{"class":350},[246,7732,7733],{"class":248,"line":690},[246,7734,856],{"class":350},[246,7736,7737,7739,7741,7743,7745,7747],{"class":248,"line":695},[246,7738,2618],{"class":350},[246,7740,7629],{"class":362},[246,7742,2624],{"class":456},[246,7744,2627],{"class":362},[246,7746,227],{"class":350},[246,7748,7749],{"class":252},"\u002F\u002F 10 minutes\n",[246,7751,7752],{"class":248,"line":700},[246,7753,1211],{"class":350},[246,7755,7756],{"class":248,"line":705},[246,7757,2745],{"class":350},[129,7759,7761],{"id":7760},"frontend-integration","Frontend Integration",[10,7763,7764],{},"This setup allows us to display the data in the frontend, providing users with insights into trending queries and recently searched users, as illustrated in the screenshot below.",[10,7766,7767],{},[89,7768],{"alt":7769,"src":7770},"Chat GitHub home page showing recent queries","\u002Fimages\u002Fposts\u002Fbuilding-a-chat-interface-to-search-github\u002F2944e305-f533-4eec-8e7f-02a33daf79a2-fd33705e89.png",[10,7772,7773],{},"You can go through the shared GitHub repo to see the whole implementation in detail.",[10,7775,7776],{},"And with that, now you can also know what you did last summer—phew!",[25,7778,7780],{"id":7779},"source-code","Source Code",[10,7782,7783],{},"The project’s code is open source and can be checked here",[7785,7786],"media-embed",{"url":7787},"https:\u002F\u002Fgithub.com\u002Fra-jeev\u002Fchat-github",[25,7789,7791],{"id":7790},"deployment","Deployment",[10,7793,7794,7795,414],{},"You can deploy the app using your NuxtHub admin panel or manually through the NuxtHub CLI. For more details on deploying an app through NuxtHub, refer to the ",[14,7796,7799],{"href":7797,"rel":7798},"https:\u002F\u002Fhub.nuxt.com\u002Fdocs\u002Fgetting-started\u002Fdeploy",[18],"official documentation",[10,7801,7802],{},"The best part is that this project is now listed as a template on the NuxtHub Templates page. So, if you already have a NuxtHub account, you can deploy this project in one click using the button below (Just remember to add the necessary environment variables in the panel).",[10,7804,7805],{},[14,7806,7809],{"href":7807,"rel":7808},"https:\u002F\u002Fhub.nuxt.com\u002Fnew?template=chat-github",[18],[89,7810],{"alt":7811,"src":7812},"One click NuxtHub deploy button","https:\u002F\u002Fhub.nuxt.com\u002Fbutton.svg",[25,7814,7816],{"id":7815},"further-enhancements","Further Enhancements",[10,7818,7819,7820,231,7823,7826,7827,7830,7831,7834],{},"Currently, we rely on the AI's ability to generate GitHub API queries from natural language input. While we’ve provided it with details about various qualifiers, it can still struggle with more complex or intricate queries. I also experimented with tool-calling models from ",[199,7821,7822],{},"Cloudflare’s Workers AI",[199,7824,7825],{},"Groq API",", and found that ",[199,7828,7829],{},"gpt-4o"," performed better for these tasks. ",[199,7832,7833],{},"Claude 3.5 Sonnet"," should definitely offer even better results, but it tends to be more expensive.",[10,7836,7837],{},"Here are a few alternative approaches we could explore to improve query accuracy and results:",[137,7839,7840,7846],{},[68,7841,7842,7845],{},[142,7843,7844],{},"Fine-tune\u002Ftrain a tool-calling model"," specifically on the GitHub Search API documentation.",[68,7847,7848,7851],{},[142,7849,7850],{},"Create embeddings"," from the GitHub Search documentation and store them in a vector database. Cloudflare offers a free tier for its vector database now, which we could leverage. When a user query is made, we could retrieve relevant information from the embeddings and include it in the system prompt.",[10,7853,7854],{},"If you have any other ideas for improving this project—or any feedback—please feel free to share them in the comments!",[25,7856,7858],{"id":7857},"conclusion","Conclusion",[10,7860,7861],{},"And there you have it—a GitHub search tool, powered by OpenAI, wrapped in a chat interface, and ready for action. We’ve gone through setting up the project, streaming responses, authenticating users, and even added a sprinkle of engagement to make things interesting.",[10,7863,7864],{},"While the project has plenty of room for improvements, it’s a solid foundation to build upon. And who knows—maybe with a few tweaks, it’ll be predicting your next GitHub repo before you even think of it :-).",[10,7866,7867],{},"May all your commits be bug-free!",[10,7869,7870],{},"Until next time!",[7872,7873],"hr",{},[7875,7876,7877],"blockquote",{},[10,7878,7879],{},[33,7880,7881],{},[142,7882,7883],{},"Keep adding the bits and soon you'll have a lot of bytes to share with the world.",[7885,7886,7887],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sA_wV, html code.shiki .sA_wV{--shiki-default:#032F62;--shiki-dark:#DBEDFF}",{"title":242,"searchDepth":256,"depth":256,"links":7889},[7890,7891,7892,7898,7903,7904,7909,7913,7918,7919,7920,7921],{"id":27,"depth":256,"text":28},{"id":59,"depth":256,"text":60},{"id":106,"depth":256,"text":107,"children":7893},[7894,7895,7896,7897],{"id":131,"depth":276,"text":132},{"id":188,"depth":276,"text":189},{"id":217,"depth":276,"text":220},{"id":310,"depth":276,"text":311},{"id":525,"depth":256,"text":526,"children":7899},[7900,7901,7902],{"id":532,"depth":276,"text":533},{"id":1615,"depth":276,"text":1616},{"id":2175,"depth":276,"text":2176},{"id":2344,"depth":256,"text":2345},{"id":3150,"depth":256,"text":3151,"children":7905},[7906,7907,7908],{"id":3187,"depth":276,"text":3188},{"id":4115,"depth":276,"text":4116},{"id":4721,"depth":276,"text":4722},{"id":5579,"depth":256,"text":5580,"children":7910},[7911,7912],{"id":5596,"depth":276,"text":5597},{"id":6079,"depth":276,"text":6080},{"id":6354,"depth":256,"text":6355,"children":7914},[7915,7916,7917],{"id":6361,"depth":276,"text":6362},{"id":7531,"depth":276,"text":7532},{"id":7760,"depth":276,"text":7761},{"id":7779,"depth":256,"text":7780},{"id":7790,"depth":256,"text":7791},{"id":7815,"depth":256,"text":7816},{"id":7857,"depth":256,"text":7858},"\u002Fimages\u002Fposts\u002Fbuilding-a-chat-interface-to-search-github\u002F5f866b99-3c0b-4c3d-859b-fa98566ea7fd-ab6fef7095.png","2024-10-04T11:53:21.011Z","Explore how to create a chat interface for GitHub searches, utilizing AI to simplify natural language queries and boost search efficiency",false,"md","cm1uo2esz002o09jk7n6y1xhz",{},"\u002Fbuilding-a-chat-interface-to-search-github",{"title":5,"description":7924},"building-a-chat-interface-to-search-github",[7933,7934,7935,7936,7937],"cloudflare","github","nuxt","openai","nuxthub","-2egEZImq_Vrq8QQFK4c6eHS7RL5yNrfdTeGk_vyJdo",1780400660363]