[{"data":1,"prerenderedAt":954},["ShallowReactive",2],{"blog-building-amit-ai-part-1-the-frontend":3},{"post":4,"minutes":511,"surround":949},{"id":5,"title":6,"body":7,"color":929,"cover":930,"coverAlt":930,"crossPost":930,"date":931,"description":932,"draft":933,"extension":934,"meta":935,"navigation":366,"path":936,"seo":937,"stem":938,"tags":939,"tldr":943,"updated":930,"__hash__":948},"blogs\u002Fblogs\u002Fbuilding-amit-ai-part-1-the-frontend.md","Building Amit.AI, Part 1: The Frontend",{"type":8,"value":9,"toc":916},"minimark",[10,25,28,33,36,39,52,55,65,70,78,81,108,111,114,118,132,143,146,164,167,174,178,185,201,204,228,252,255,258,262,269,276,657,664,676,687,691,694,697,718,725,729,736,750,753,811,814,821,851,862,866,900,904,907,912],[11,12,13,14,19,20,24],"p",{},"Go to the ",[15,16,18],"a",{"href":17},"\u002Fchat","chat page"," and you are talking to me, in terminal form. This is Part 1 of how I built it: the browser side. ",[15,21,23],{"href":22},"","Part 2 - coming soon"," covers the server.",[11,26,27],{},"I'll skip the secrets and the exact knobs. This is the fun-parts tour, not a blueprint.",[29,30,32],"h2",{"id":31},"_1-the-best-llm-call-is-the-one-you-dont-make","1. The best LLM call is the one you don't make",[11,34,35],{},"My first version of Amit.AI was a static FAQ matcher. No AI, just \"if the question looks like this, show that answer\". It was fast and free, and also confidently wrong in creative ways.",[11,37,38],{},"Then I added a real LLM backend, and the obvious move was to send everything to it. I didn't, for three reasons:",[40,41,42,46,49],"ul",{},[43,44,45],"li",{},"Free-tier LLM quotas are small, and visitors keep asking \"what do you do?\" and \"are you available?\"",[43,47,48],{},"The backend sleeps when nobody is around. A FAQ answer doesn't need it awake.",[43,50,51],{},"Instant answers feel better than streamed ones.",[11,53,54],{},"So the chat is two tiers. Tier 0 lives in the browser and answers what it is sure about. Everything else gets handed to the backend.",[56,57,62],"pre",{"className":58,"code":60,"language":61,"meta":22},[59],"language-text","                                     → confident? answer it\nmessage → guardrails → FAQ matcher -|\n                                     → not sure?  defer to the LLM\n","text",[63,64,60],"code",{"__ignoreMap":22},[66,67,69],"h3",{"id":68},"basically-a-tiny-free-intent-router","Basically a tiny, free intent router",[11,71,72,73,77],{},"Looking back, the Tier 0 matcher is a poor man's ",[74,75,76],"strong",{},"intent router",": a cheap first stage that decides what to do with a message before an expensive LLM gets involved.",[11,79,80],{},"There are real, proper versions of this idea:",[40,82,83,92],{},[43,84,85,91],{},[15,86,90],{"href":87,"rel":88},"https:\u002F\u002Ftypesafe.ai\u002F",[89],"nofollow","Jev"," from TypeSafe AI is pitched as a model for decisions inside software rather than chat. You ask it a structured question, and it returns a typed decision with a probability, so your code can act when confidence is high and escalate when it's low.",[43,93,94,99,100,103,104,107],{},[15,95,98],{"href":96,"rel":97},"https:\u002F\u002Fgithub.com\u002FNandhaKishorM\u002Flaya",[89],"Laya"," is an open-source (Apache-2.0) \"System 1\" decision engine that doesn't generate text at all. It answers ",[63,101,102],{},"choice",", ",[63,105,106],{},"score"," and yes\u002Fno questions about a piece of text, and ships presets for routing and prompt guardrails.",[11,109,110],{},"Mine is kind of in the same shape with none of the ML. No model, no training, no GPU, just a token-overlap score, a few thresholds and a JSON file. It runs in the visitor's browser for free. The \"confidence\" is whether a message clears the gates below, and low confidence means \"send it to the big model\".",[11,112,113],{},"It's much dumber than either of those, and it's also ₹0 and instant. For a portfolio chatbot with a few dozen FAQ topics, that trade is fine.",[29,115,117],{"id":116},"_2-a-matcher-that-knows-when-to-shut-up","2. A matcher that knows when to shut up",[11,119,120,121,103,124,127,128,131],{},"The matcher returns one of three things: ",[63,122,123],{},"guard",[63,125,126],{},"faq"," or ",[63,129,130],{},"defer",". There is deliberately no fourth \"I'm not sure\" answer. A defer is silent. The visitor just sees the typing indicator a little longer.",[11,133,134,135,138,139,142],{},"The first version used one similarity threshold, and it failed in a way I found hilarious. Ask \"How good are you at ",[74,136,137],{},"Java","?\" and it happily returned my ",[74,140,141],{},"React"," answer, because the other words overlapped.",[11,144,145],{},"The fix was to stop trusting a single score. An FAQ answer only goes out when it clears every gate:",[40,147,148,155,158,161],{},[43,149,150,151,154],{},"An ",[74,152,153],{},"exact phrasing"," match always wins.",[43,156,157],{},"Otherwise, most of the visitor's content words must be covered by one FAQ phrasing, so \"Java\" can't hide behind \"good\" and \"you\".",[43,159,160],{},"At least two meaningful words must overlap.",[43,162,163],{},"The winner must beat the runner-up by a clear margin. Two close candidates mean the matcher is guessing, so it defers.",[11,165,166],{},"Words like \"that\", \"more\" and \"those\" force a defer on any turn after the first, because the matcher has no memory. \"Tell me more\" means something different every time.",[11,168,169,170,173],{},"My rule while tuning: ",[74,171,172],{},"wrong-but-confident is worse than slow-but-right."," Every change I made pushed towards deferring.",[66,175,177],{"id":176},"the-stopword-rabbit-hole","The stopword rabbit hole",[11,179,180,181,184],{},"A ",[74,182,183],{},"stopword"," is a filler word the matcher ignores when scoring, like \"what\", \"are\", \"the\" or \"you\". Only the remaining content words count towards the overlap and coverage gates above.",[11,186,187,188,192,193,196,197,200],{},"Here's the problem. My FAQ is written in the second person, because the terminal ",[189,190,191],"em",{},"is"," me: \"What are your skills?\" But visitors don't know that. They look at a site about a person called Amit and ask \"what are ",[74,194,195],{},"his"," skills?\" or \"what is ",[74,198,199],{},"Amit's"," stack?\".",[11,202,203],{},"Walk through the first one:",[40,205,206,213,225],{},[43,207,208,209,212],{},"The FAQ phrasing \"what are your skills\" reduces to one content word: ",[63,210,211],{},"skills",". (\"what\", \"are\" and \"your\" are all stopwords.)",[43,214,215,216,218,219,221,222,224],{},"The visitor's \"what are his skills\" reduces to ",[63,217,195],{}," and ",[63,220,211],{},", because ",[63,223,195],{}," wasn't a stopword.",[43,226,227],{},"Only one of the visitor's two content words is covered, so coverage is 0.5, below the 0.7 gate. The matcher defers, and a question it should have answered instantly goes to the LLM.",[11,229,230,231,103,233,103,236,103,239,103,242,218,245,248,249,251],{},"The fix was to add ",[63,232,195],{},[63,234,235],{},"him",[63,237,238],{},"her",[63,240,241],{},"their",[63,243,244],{},"amit",[63,246,247],{},"mondal"," to the stopword list. They carry no meaning on a site about exactly one person, so they should be invisible to scoring. Now both phrasings reduce to ",[63,250,211],{},", and it's an instant answer.",[11,253,254],{},"There's a flip side. Pronouns like \"it\", \"that\" and \"those\" normally mean \"I'm pointing at something said earlier\", which is why they force a defer. But \"he\" and \"his\" are different here. On this site they can only mean me, so I deliberately left them out of that list. A question like \"what are his skills?\" is standalone, even though it contains a pronoun.",[11,256,257],{},"Small things like this are the difference between a matcher that works in a demo and one that works for strangers.",[29,259,261],{"id":260},"_3-streaming-without-eventsource","3. Streaming without EventSource",[11,263,264,265,268],{},"The backend streams its answer as Server-Sent Events, so tokens appear as they are generated. Browsers have a built-in ",[63,266,267],{},"EventSource"," for this, but it can only make GET requests, and I need to POST the conversation.",[11,270,271,272,275],{},"So I used ",[63,273,274],{},"fetch"," and read the body stream myself. The core is about a dozen lines:",[56,277,282],{"className":278,"code":279,"filename":280,"language":281,"meta":22,"style":22},"language-ts shiki shiki-themes tokyo-night","const reader = res.body.getReader();\nconst decoder = new TextDecoder();\nlet buffer = '';\n\nwhile (true) {\n  const { value, done } = await reader.read();\n  if (done) break;\n  buffer += decoder.decode(value, { stream: true }).replace(\u002F\\r\\n\u002Fg, '\\n');\n\n  let sep: number;\n  while ((sep = buffer.indexOf('\\n\\n')) !== -1) {\n    const frame = parseFrame(buffer.slice(0, sep));\n    buffer = buffer.slice(sep + 2);\n    \u002F\u002F token → append to the bubble, done → resolve, error → throw\n  }\n}\n","useChatStream.ts","ts",[63,283,284,325,345,361,368,387,425,444,509,514,531,572,610,638,645,651],{"__ignoreMap":22},[285,286,289,293,297,301,305,308,312,314,318,322],"span",{"class":287,"line":288},"line",1,[285,290,292],{"class":291},"sN7LL","const",[285,294,296],{"class":295},"sd1Qi"," reader",[285,298,300],{"class":299},"sAklC"," =",[285,302,304],{"class":303},"sE3pS"," res",[285,306,307],{"class":299},".",[285,309,311],{"class":310},"su0L4","body",[285,313,307],{"class":299},[285,315,317],{"class":316},"s3R4Z","getReader",[285,319,321],{"class":320},"sgJMe","()",[285,323,324],{"class":299},";\n",[285,326,328,330,333,335,338,341,343],{"class":287,"line":327},2,[285,329,292],{"class":291},[285,331,332],{"class":295}," decoder",[285,334,300],{"class":299},[285,336,337],{"class":299}," new",[285,339,340],{"class":316}," TextDecoder",[285,342,321],{"class":320},[285,344,324],{"class":299},[285,346,348,351,354,356,359],{"class":287,"line":347},3,[285,349,350],{"class":291},"let",[285,352,353],{"class":295}," buffer",[285,355,300],{"class":299},[285,357,358],{"class":299}," ''",[285,360,324],{"class":299},[285,362,364],{"class":287,"line":363},4,[285,365,367],{"emptyLinePlaceholder":366},true,"\n",[285,369,371,374,377,381,384],{"class":287,"line":370},5,[285,372,373],{"class":295},"while",[285,375,376],{"class":320}," (",[285,378,380],{"class":379},"sOJ5S","true",[285,382,383],{"class":320},")",[285,385,386],{"class":320}," {\n",[285,388,390,393,396,399,402,405,408,410,414,416,418,421,423],{"class":287,"line":389},6,[285,391,392],{"class":291},"  const",[285,394,395],{"class":299}," {",[285,397,398],{"class":295}," value",[285,400,401],{"class":299},",",[285,403,404],{"class":295}," done",[285,406,407],{"class":299}," }",[285,409,300],{"class":299},[285,411,413],{"class":412},"sEsAJ"," await",[285,415,296],{"class":303},[285,417,307],{"class":299},[285,419,420],{"class":316},"read",[285,422,321],{"class":320},[285,424,324],{"class":299},[285,426,428,431,433,436,439,442],{"class":287,"line":427},7,[285,429,430],{"class":295},"  if",[285,432,376],{"class":320},[285,434,435],{"class":303},"done",[285,437,438],{"class":320},") ",[285,440,441],{"class":295},"break",[285,443,324],{"class":299},[285,445,447,450,453,455,457,460,463,466,468,471,475,478,481,484,486,489,491,494,497,500,502,505,507],{"class":287,"line":446},8,[285,448,449],{"class":303},"  buffer",[285,451,452],{"class":299}," +=",[285,454,332],{"class":303},[285,456,307],{"class":299},[285,458,459],{"class":316},"decode",[285,461,462],{"class":320},"(",[285,464,465],{"class":303},"value",[285,467,401],{"class":299},[285,469,470],{"class":320}," { ",[285,472,474],{"class":473},"syYvs","stream",[285,476,477],{"class":299},":",[285,479,480],{"class":379}," true",[285,482,483],{"class":320}," })",[285,485,307],{"class":299},[285,487,488],{"class":316},"replace",[285,490,462],{"class":320},[285,492,493],{"class":299},"\u002F",[285,495,496],{"class":295},"\\r\\n",[285,498,499],{"class":299},"\u002Fg",[285,501,401],{"class":299},[285,503,504],{"class":299}," '\\n'",[285,506,383],{"class":320},[285,508,324],{"class":299},[285,510,512],{"class":287,"line":511},9,[285,513,367],{"emptyLinePlaceholder":366},[285,515,517,520,523,525,529],{"class":287,"line":516},10,[285,518,519],{"class":291},"  let",[285,521,522],{"class":295}," sep",[285,524,477],{"class":299},[285,526,528],{"class":527},"sySf4"," number",[285,530,324],{"class":299},[285,532,534,537,540,543,545,547,549,552,554,557,560,563,566,569],{"class":287,"line":533},11,[285,535,536],{"class":295},"  while",[285,538,539],{"class":320}," ((",[285,541,542],{"class":303},"sep",[285,544,300],{"class":299},[285,546,353],{"class":303},[285,548,307],{"class":299},[285,550,551],{"class":316},"indexOf",[285,553,462],{"class":320},[285,555,556],{"class":299},"'\\n\\n'",[285,558,559],{"class":320},")) ",[285,561,562],{"class":295},"!==",[285,564,565],{"class":299}," -",[285,567,568],{"class":379},"1",[285,570,571],{"class":320},") {\n",[285,573,575,578,581,583,586,588,591,593,596,598,601,603,605,608],{"class":287,"line":574},12,[285,576,577],{"class":291},"    const",[285,579,580],{"class":295}," frame",[285,582,300],{"class":299},[285,584,585],{"class":316}," parseFrame",[285,587,462],{"class":320},[285,589,590],{"class":303},"buffer",[285,592,307],{"class":299},[285,594,595],{"class":316},"slice",[285,597,462],{"class":320},[285,599,600],{"class":379},"0",[285,602,401],{"class":299},[285,604,522],{"class":303},[285,606,607],{"class":320},"))",[285,609,324],{"class":299},[285,611,613,616,618,620,622,624,626,628,631,634,636],{"class":287,"line":612},13,[285,614,615],{"class":303},"    buffer",[285,617,300],{"class":299},[285,619,353],{"class":303},[285,621,307],{"class":299},[285,623,595],{"class":316},[285,625,462],{"class":320},[285,627,542],{"class":303},[285,629,630],{"class":299}," +",[285,632,633],{"class":379}," 2",[285,635,383],{"class":320},[285,637,324],{"class":299},[285,639,641],{"class":287,"line":640},14,[285,642,644],{"class":643},"sbD-w","    \u002F\u002F token → append to the bubble, done → resolve, error → throw\n",[285,646,648],{"class":287,"line":647},15,[285,649,650],{"class":320},"  }\n",[285,652,654],{"class":287,"line":653},16,[285,655,656],{"class":320},"}\n",[11,658,659,660,663],{},"SSE frames are separated by a blank line, and a network chunk can end in the middle of one. So you buffer, split on ",[63,661,662],{},"\\n\\n",", and keep the leftover for the next read. That one detail is where most hand-rolled parsers break.",[11,665,666,667,103,670,218,672,675],{},"There are three event types: ",[63,668,669],{},"token",[63,671,435],{},[63,673,674],{},"error",". The UI pushes an empty AI bubble on the first token and appends to it. The typing indicator stays up until that first token arrives, so the wait never looks like a freeze.",[11,677,678,679,682,683,686],{},"One safety habit: the message text is rendered with ",[63,680,681],{},"{{ }}",", never ",[63,684,685],{},"v-html",". Model output is untrusted input, and I'm not letting a chatbot inject HTML into my own page.",[29,688,690],{"id":689},"_4-the-sleeping-backend","4. The sleeping backend",[11,692,693],{},"My backend runs on a free tier that goes to sleep when idle. The first request after a nap can take a long time.",[11,695,696],{},"I handled it in layers:",[698,699,700,706,712],"ol",{},[43,701,702,705],{},[74,703,704],{},"Tier 0 covers the common stuff."," Most visitors never notice the backend is asleep.",[43,707,708,711],{},[74,709,710],{},"A warm-up ping."," The moment the chat page loads, the browser quietly pings the backend's health endpoint. By the time you have typed a question, it's often awake.",[43,713,714,717],{},[74,715,716],{},"A generous first-token timeout",", then an overall timeout. After that, a friendly offline message instead of a spinner of doom.",[11,719,720,721,724],{},"The boot animation (those fake ",[63,722,723],{},"> initializing..."," lines) exists partly because it looks cool, and partly because it buys the ping a few seconds. I'm not going to pretend I planned that first.",[29,726,728],{"id":727},"_5-guardrails-on-both-sides","5. Guardrails, on both sides",[11,730,731,732,735],{},"A guardrail here is a cheap pattern check that runs ",[189,733,734],{},"before"," any answer is produced. If a message looks like something the chat shouldn't engage with, it gets a canned reply, and neither the FAQ nor the LLM ever sees it. There are two kinds:",[40,737,738,744],{},[43,739,740,743],{},[74,741,742],{},"Jailbreak-style messages",", like \"ignore all previous instructions\" or \"pretend you are...\". The reply politely says this terminal is scoped to my professional profile.",[43,745,746,749],{},[74,747,748],{},"Requests for private details",", like a phone number, home address or date of birth. The reply says that isn't shared here and points to the Contact page.",[11,751,752],{},"The same idea exists twice, for different reasons:",[754,755,756,772],"table",{},[757,758,759],"thead",{},[760,761,762,766,769],"tr",{},[763,764,765],"th",{},"Where",[763,767,768],{},"Job",[763,770,771],{},"Why it's there",[773,774,775,791],"tbody",{},[760,776,777,781,784],{},[778,779,780],"td",{},"Browser",[778,782,783],{},"Speed",[778,785,786,787,790],{},"A blocked message gets its reply instantly, ",[788,789],"br",{},"with no network request and no waiting for a sleeping backend.",[760,792,793,796,799],{},[778,794,795],{},"Server",[778,797,798],{},"Enforcement",[778,800,801,802,804,805,807,808,810],{},"Anyone can skip my UI and call the API directly. ",[788,803],{},"Code running in a browser is a convenience, never a ",[788,806],{},"security boundary, so the server re-checks everything and has ",[788,809],{},"the final say.",[11,812,813],{},"So the frontend copy makes the common case fast and nice, and the backend copy is the one that actually protects anything. If they ever disagree, the server wins.",[11,815,816,817,820],{},"The hard part wasn't writing patterns, it was ",[189,818,819],{},"not blocking innocent questions",". My first versions were too blunt:",[40,822,823,833,842],{},[43,824,825,826,829,830,832],{},"A bare ",[63,827,828],{},"number"," blocked \"what's the ",[74,831,828],{}," of projects you've built?\"",[43,834,825,835,838,839,841],{},[63,836,837],{},"address"," blocked \"how did you ",[74,840,837],{}," performance issues?\"",[43,843,825,844,847,848,850],{},[63,845,846],{},"dob"," matched inside \"A",[74,849,846],{},"e\".",[11,852,853,854,857,858,861],{},"The fix is two rules: always use word boundaries (",[63,855,856],{},"\\b","), and match the ",[189,859,860],{},"phrase",", not the word. \"phone number\" and \"home address\" are the requests I care about, \"number\" and \"address\" alone are not. A guardrail that annoys real recruiters is worse than no guardrail, so when in doubt I let the message through to the server and the prompt, which are better at judging context.",[29,863,865],{"id":864},"_6-small-touches-that-earn-their-keep","6. Small touches that earn their keep",[40,867,868,882,888,894],{},[43,869,870,873,874,877,878,881],{},[74,871,872],{},"A frontend answer cache."," If you ask the same question twice in a tab, the second answer comes from ",[63,875,876],{},"sessionStorage"," and never leaves the browser. Storage can throw or be blocked, so an in-memory ",[63,879,880],{},"Map"," is the source of truth and storage is a bonus.",[43,883,884,887],{},[74,885,886],{},"History trimming."," Only the last few turns go along with each deferred message. If you ask the same question twice, only the latest pair is kept, so the model isn't fed its own repeats.",[43,889,890,893],{},[74,891,892],{},"A fake thinking pause"," on locally answered messages. Replying in 5ms feels wrong, and a short, slightly random delay feels more human. It is a lie, and I'm at peace with it.",[43,895,896,899],{},[74,897,898],{},"A character counter ring"," on the input that fills as you approach the message limit, so the backend never has to say no.",[29,901,903],{"id":902},"whats-next","What's next",[11,905,906],{},"That's the browser half. The interesting questions are on the other side: how do you ground an LLM on a bio without a vector database, what happens when the free model hits its limit, and how do you stop people making me say silly things?",[11,908,909,910,307],{},"That's ",[15,911,23],{"href":22},[913,914,915],"style",{},"html pre.shiki code .sN7LL, html code.shiki .sN7LL{--shiki-default:#9D7CD8;--shiki-default-font-style:italic}html pre.shiki code .sd1Qi, html code.shiki .sd1Qi{--shiki-default:#BB9AF7}html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .sE3pS, html code.shiki .sE3pS{--shiki-default:#C0CAF5}html pre.shiki code .su0L4, html code.shiki .su0L4{--shiki-default:#7DCFFF}html pre.shiki code .s3R4Z, html code.shiki .s3R4Z{--shiki-default:#7AA2F7}html pre.shiki code .sgJMe, html code.shiki .sgJMe{--shiki-default:#9ABDF5}html pre.shiki code .sOJ5S, html code.shiki .sOJ5S{--shiki-default:#FF9E64}html pre.shiki code .sEsAJ, html code.shiki .sEsAJ{--shiki-default:#BB9AF7;--shiki-default-font-style:italic}html pre.shiki code .syYvs, html code.shiki .syYvs{--shiki-default:#73DACA}html pre.shiki code .sySf4, html code.shiki .sySf4{--shiki-default:#0DB9D7}html pre.shiki code .sbD-w, html code.shiki .sbD-w{--shiki-default:#51597D;--shiki-default-font-style:italic}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);}",{"title":22,"searchDepth":327,"depth":327,"links":917},[918,921,924,925,926,927,928],{"id":31,"depth":327,"text":32,"children":919},[920],{"id":68,"depth":347,"text":69},{"id":116,"depth":327,"text":117,"children":922},[923],{"id":176,"depth":347,"text":177},{"id":260,"depth":327,"text":261},{"id":689,"depth":327,"text":690},{"id":727,"depth":327,"text":728},{"id":864,"depth":327,"text":865},{"id":902,"depth":327,"text":903},"#ff00cc",null,"2026-10-17","How the Amit.AI chat on this site answers instantly, streams over SSE with plain fetch, and survives a sleeping free-tier backend. Nuxt, Vue, TypeScript.",false,"md",{},"\u002Fblogs\u002Fbuilding-amit-ai-part-1-the-frontend",{"title":6,"description":932},"blogs\u002Fbuilding-amit-ai-part-1-the-frontend",[940,941,942],"Nuxt","AI Engineering","Frontend",[944,945,946,947],"Most questions never reach an LLM. A tiny client-side matcher answers them instantly, and when it is unsure it stays quiet and defers.","Wrong-but-confident is worse than slow-but-right, so every threshold is tuned to defer.","SSE over POST needs fetch and a hand-rolled parser, because EventSource cannot POST.","A sleeping free-tier backend is handled with a warm-up ping during the boot animation, which is also why the boot animation exists.","Spel7181Al3z9vNVhUEFzJ_1Ce644lil3o5sFpYaqXM",{"newer":930,"older":950},{"title":951,"path":952,"stem":953,"draft":933,"children":-1},"Why I Am Writing Here","\u002Fblogs\u002Fwhy-i-am-writing-here","blogs\u002Fwhy-i-am-writing-here",1792207884902]