1import { ap as fetchEventSource, aq as DiffMatchPatch } from "./GKSP12iv.js"; 2import { j as ABORTED, b as getUserId, k as safeErrorCause, l as uuidv7b64, m as fileToBase64 } from "./B8ejy0IY.js"; 3import { a as atom, f as withAssign, g as reatomBoolean, h as reatomNumber, e as action, r as reatomAsync } from "./BmDlrdls.js"; 4const DEFAULT_EXPERTISE_LEVEL = "highschool"; 5const DEFAULT_SOLUTION_TYPE = "stepby"; 6const urlSafeSlug = (s) => { 7 if (s) { 8 let slug; 9 if (typeof s === "string") { 10 slug = `${s}`; 11 } else { 12 const sol = s; 13 slug = sol.urlSlug || sol.guidanceTitle || sol.problem || sol.shortProblem || ""; 14 } 15 if (slug) { 16 slug = slug.toLowerCase().replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, ""); 17 return encodeURIComponent(slug); 18 } 19 } 20 return null; 21}; 22const DIFF_PREFIX = "@@ "; 23var MathSolutionEventType; 24(function(MathSolutionEventType2) { 25 MathSolutionEventType2["Partial"] = "p"; 26 MathSolutionEventType2["Final"] = "f"; 27})(MathSolutionEventType || (MathSolutionEventType = {})); 28const chatAtom = atom(null, "chat").pipe(withAssign((chatAtom2, name) => { 29 const id = atom(null, name + ".id"); 30 const loading = reatomBoolean(false, name + ".loading"); 31 const error = atom(null, name + ".error"); 32 const visibleSteps = reatomNumber(0, name + ".visibleSteps"); 33 const abortController = atom(null, name + ".abortController"); 34 return { 35 id, 36 loading, 37 error, 38 visibleSteps, 39 abortController, 40 // Action to reset the chat state and start anew. 41 reset: action((ctx, isLoading, newAc) => { 42 chatAtom2(ctx, null); 43 error(ctx, null); 44 visibleSteps(ctx, 0); 45 loading(ctx, !!isLoading); 46 if (isLoading && typeof isLoading === "string") id(ctx, isLoading); 47 const prevAc = ctx.get(abortController); 48 abortController(ctx, newAc || null); 49 if (prevAc) ctx.schedule(() => prevAc.abort(ABORTED)).catch(console.error); 50 }, name + ".reset"), 51 // Action to reveal the next solution step 52 revealNextStep: action((ctx) => { 53 const steps = ctx.get(chatAtom2)?.solution?.steps; 54 if (steps && ctx.get(visibleSteps) < steps.length) { 55 visibleSteps.increment(ctx); 56 return true; 57 } 58 }, "revealNextStep"), 59 hashString: atom(null, name + ".hashString") 60 }; 61})); 62const chatUrlSlug = (chat) => { 63 return chat ? urlSafeSlug(chat.solution || chat.title || chat.content) : null; 64}; 65async function throwResponseError(response) { 66 let suppressedError; 67 let responseError = null; 68 let responseText = null; 69 try { 70 responseText = (await response.text())?.trim(); 71 if (responseText) responseError = JSON.parse(responseText); 72 } catch (e2) { 73 suppressedError = e2; 74 } 75 const e = new Error(responseError && responseError.error ? responseError.error : responseError || responseText || "Failed to load"); 76 safeErrorCause(e, suppressedError); 77 console.error("Failed to load", `${response.status} ${response.statusText}`, e, { 78 response 79 }); 80 throw e; 81} 82const fetchChatAsync = reatomAsync(async (ctx, chatId, userId) => { 83 if (!chatId) { 84 console.error("fetchChatAsync: chatId is required"); 85 return; 86 } 87 if (ctx.get(chatAtom)?.id === chatId || ctx.get(chatAtom.id) === chatId) return; 88 const ac = new AbortController(); 89 chatAtom.reset(ctx, chatId, ac); 90 try { 91 const chat = await fetchChatRequest(chatId, userId || getUserId(ctx), ac.signal); 92 chatAtom(ctx, chat); 93 return chat; 94 } catch (error) { 95 chatAtom.error(ctx, error); 96 throw error; 97 } finally { 98 chatAtom.loading.setFalse(ctx); 99 } 100}, "fetchChatAsync"); 101const fetchChatRequest = async (chatId, userId = getUserId(), signal) => { 102 const response = await fetch(`${API_HOST}/problems/${chatId}?userId=${userId}`, { 103 method: "GET", 104 headers: { 105 "Content-Type": "application/json" 106 }, 107 signal 108 }); 109 return response.ok ? response.json() : await throwResponseError(response); 110}; 111const API_HOST = window.location.origin + "/api"; 112const dmp = new DiffMatchPatch(); 113const submitProblem = reatomAsync(async (ctx, { content, image, lvl, type }) => { 114 if (!content && !image) { 115 console.error("submitProblem: content or image is required"); 116 return; 117 } 118 const ac = new AbortController(); 119 chatAtom.reset(ctx, true, ac); 120 try { 121 const tmpId = uuidv7b64(); 122 content = content?.trim(); 123 const base64Image = !image ? image : typeof image === "string" ? image : await fileToBase64(image); 124 const tmpChatData = { 125 id: tmpId, 126 title: content, 127 solutionType: type || DEFAULT_SOLUTION_TYPE, 128 expertiseLvl: lvl || DEFAULT_EXPERTISE_LEVEL, 129 content: content || "", 130 imageUrl: base64Image || null, 131 unfinished: true 132 }; 133 chatAtom(ctx, structuredClone(tmpChatData)); 134 chatAtom.id(ctx, tmpId); 135 const requestBody = { 136 id: tmpId, 137 userId: getUserId() 138 }; 139 if (content) requestBody.content = content; 140 if (base64Image) requestBody.imageUrl = base64Image; 141 if (lvl && lvl !== DEFAULT_EXPERTISE_LEVEL) requestBody.lvl = lvl; 142 if (type && type !== DEFAULT_SOLUTION_TYPE) requestBody.type = type; 143 let solutionJson = "{}"; 144 await fetchEventSource(`${API_HOST}/problems`, { 145 method: "POST", 146 headers: { 147 "Content-Type": "application/json" 148 }, 149 body: JSON.stringify(requestBody), 150 // Disable retries by setting retry options 151 openWhenHidden: false, 152 onopen: async (response) => { 153 if (response.ok && response.headers.get("content-type")?.includes("text/event-stream")) { 154 return; 155 } 156 try { 157 await throwResponseError(response); 158 } catch (error) { 159 chatAtom.loading.setFalse(ctx); 160 chatAtom.error(ctx, error); 161 throw error; 162 } 163 }, 164 onmessage: (event) => { 165 const me = event; 166 if (!me.event) return; 167 if (me.event === MathSolutionEventType.Final) { 168 if (me.data) { 169 const realId = me.data; 170 if (tmpChatData.id !== realId) { 171 tmpChatData.id = realId; 172 } 173 } 174 tmpChatData.unfinished = false;
175 chatAtom(ctx, structuredClone(tmpChatData)); 176 chatAtom.loading.setFalse(ctx); 177 } else if (me.data) { 178 const diff = DIFF_PREFIX + JSON.parse(me.data); 179 const patch = dmp.patch_fromText(diff); 180 const updatedJson = dmp.patch_apply(patch, solutionJson)[0]; 181 if (updatedJson === solutionJson) { 182 console.warn("Failed to apply patch:", { 183 patch, 184 solutionJson, 185 updatedJson 186 }); 187 return; 188 } 189 solutionJson = updatedJson; 190 const solution = JSON.parse(solutionJson); 191 const realId = solution.id; 192 if (realId) { 193 tmpChatData.id = realId; 194 chatAtom.id(ctx, realId); 195 } 196 delete solution.id; 197 tmpChatData.solution = solution; 198 chatAtom(ctx, structuredClone(tmpChatData)); 199 chatAtom.loading.setFalse(ctx); 200 } 201 }, 202 onerror: (e) => { 203 const err = e instanceof Error ? e : new Error("Failed to submit problem " + e); 204 safeErrorCause(err, e); 205 chatAtom.error(ctx, err); 206 chatAtom.loading.setFalse(ctx); 207 throw err; 208 } 209 }); 210 } catch (error) { 211 chatAtom.error(ctx, error); 212 throw error; 213 } finally { 214 chatAtom.loading.setFalse(ctx); 215 } 216}, "submitProblemAsync"); 217export { 218 API_HOST as A, 219 chatUrlSlug as a, 220 chatAtom as c, 221 fetchChatAsync as f, 222 submitProblem as s, 223 throwResponseError as t 224};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.