// Gyeol JavaScript SDK — typed decisions over Korean text. Works in Node 18+ and browsers. // import { Gyeol } from "https://gyeol.pltt.dev/sdk/gyeol.js"; // const g = new Gyeol({ apiKey: "gk_..." }); // const r = await g.decide("배송이 3일째 멈춰 있어요.", { // team: Gyeol.choice("이 문의를 처리할 팀은?", { "배송": "배송 지연·추적", "환불": "취소·환불" }), // cancel: Gyeol.noul("고객이 취소 의사를 밝혔는가?"), // urgency: Gyeol.score("긴급도를 평가하라.", ["기한 없음", "며칠 내", "명확한 기한", "오늘 안에"]), // }); // r.team.choice; r.cancel.noul; r.urgency.score; r.urgency.probabilities; export class GyeolError extends Error { constructor(status, detail){ super(`${status}: ${detail}`); this.status = status; this.detail = detail; } } export class Gyeol { constructor({ apiKey = null, baseUrl = "https://gyeol.pltt.dev", timeoutMs = 30000, fetch: f } = {}) { this.apiKey = apiKey; this.baseUrl = baseUrl.replace(/\/$/, ""); this.timeoutMs = timeoutMs; this.fetch = f || globalThis.fetch.bind(globalThis); } static choice(instructions, criteria) { const crit = Array.isArray(criteria) ? Object.fromEntries(criteria.map(c => [c, null])) : { ...criteria }; return { type: "choice", instructions, criteria: crit }; } static noul(instructions, opts = {}) { const q = { type: "noul", instructions }; if (opts.true || opts.false) q.criteria = { true: opts.true || "", false: opts.false || "" }; return q; } static score(instructions, levels) { return { type: "score", instructions, criteria: [...levels] }; } async decide(state, questions, { model = "gyeol-latest" } = {}) { const path = this.apiKey ? "/v1/systemone" : "/v1/demo"; const headers = { "Content-Type": "application/json" }; if (this.apiKey) headers["Authorization"] = `Bearer ${this.apiKey}`; const ctrl = new AbortController(); const t = setTimeout(() => ctrl.abort(), this.timeoutMs); let r; try { r = await this.fetch(this.baseUrl + path, { method: "POST", headers, body: JSON.stringify({ model, state, questions }), signal: ctrl.signal }); } finally { clearTimeout(t); } const d = await r.json().catch(() => ({})); if (!r.ok) throw new GyeolError(r.status, d.detail || r.statusText); const out = {}; for (const [name, a] of Object.entries(d.answers)) { out[name] = { ...a, value: a.type === "choice" ? a.choice : a.type === "noul" ? a.noul : a.score, top: Object.entries(a.probabilities).sort((x, y) => y[1] - x[1])[0][0] }; } Object.defineProperty(out, "_meta", { value: { ...d.timing, model: d.model, usage: d.usage }, enumerable: false }); return out; } async me() { const r = await this.fetch(this.baseUrl + "/v1/me", { headers: { Authorization: `Bearer ${this.apiKey}` } }); const d = await r.json(); if (!r.ok) throw new GyeolError(r.status, d.detail); return d; } } export default Gyeol;