HI ▾
Uncensored Chatbot APIएक बिना सेंसर LLM तक सीधा API एंडपॉइंट एक्सेसAPI कुंजी पाएँ

Uncensored Chatbot APIWeb Chat

स्ट्रीमिंग वेब चैट बनाएं (Express प्रॉक्सी + साधारण JS)

इस ट्यूटोरियल में आप लगभग 100 लाइनों में एक छोटा चैट पेज बनाएंगे। एक छोटा Express सर्वर आपकी सीक्रेट कुंजी रखता है और अनुरोधों को Uncensored Chatbot API पर रिले करता है, जबकि एक साधारण JavaScript फ्रंट-एंड स्ट्रीम किए गए जवाब को शब्द-दर-शब्द पढ़ता है। कोई फ्रेमवर्क नहीं, कोई बिल्ड स्टेप नहीं, और आपकी कुंजी कभी ब्राउज़र तक नहीं पहुंचती।

अपडेट किया गया

मुख्य बिंदु

  • ब्राउज़र कोड से कभी API कॉल न करें; एक सर्वर रूट कुंजी को निजी रखता है और इनपुट को वैलिडेट करने की अनुमति देता है।
  • प्रॉक्सी को केवल सर्वर-सेंट इवेंट्स को पास करने की आवश्यकता है; पेज [DONE] तक डेटा लाइन्स को पार्स करता है।
  • स्ट्रीमिंग के दौरान textContent का उपयोग करें और मार्कडाउन रेंडर करने से पहले उसे सैनिटाइज़ करें।
  • सर्वर पर इतिहास की लंबाई और max_tokens को कैप करें ताकि एक उपयोगकर्ता आपका पूरा बैलेंस न खर्च कर सके।

हम क्या बना रहे हैं, और प्रॉक्सी क्यों

एकल पेज की कल्पना करें जिसमें एक टेक्स्ट बॉक्स और एक स्क्रॉलिंग लॉग हो। आप एक लाइन टाइप करते हैं, पेज संवाद को अपने सर्वर पर /api/chat पर पोस्ट करता है, और आपका सर्वर इसे असली कुंजी लगाकर अपस्ट्रीम फॉरवर्ड करता है। जवाब स्ट्रीम होकर वापस आता है, और हर टुकड़ा आते ही दिखाई देता है। हम बॉट को थोड़ी व्यक्तित्व देंगे, एक दीपघर कीपर व्रेन, ताकि डेमो जीवंत लगे।

आपके पास Node 18 या उससे नया संस्करण (बिल्ट-इन fetch के लिए) और एक API कुंजी होनी चाहिए। यदि आपके पास अभी तक एक नहीं है, तो key page से मुफ़्त ट्रायल प्राप्त करें: नए खातों को 7 दिनों के लिए $0.50 का प्रीपेड क्रेडिट मिलता है, पेमेंट विवरण की आवश्यकता नहीं है। बेस URL https://api.uncensoredchatbotapi.com/v1 है और मॉडल ID uncensored है।

आपको प्रॉक्सी की आवश्यकता क्यों है

कुंजी को फ्रंट-एंड कोड में पेस्ट करना और API को सीधे कॉल करना आकर्षक लग सकता है। कृपया ऐसा न करें। ब्राउज़र में भेजा गया कुछ भी डेवलपर टूल्स खोलने वाले किसी भी व्यक्ति द्वारा पढ़ा जा सकता है, और एक लीक हुई कुंजी का मतलब है खाली बैलेंस। एक प्रॉक्सी इस समस्या को ठीक करता है और आपको तीन अतिरिक्त लाभ देता है।

  • गोपनीयता। कुंजी सर्वर पर एक एनवायरनमेंट वेरिएबल में रहती है।
  • नियंत्रण। आप सिस्टम प्रॉम्प्ट, इतिहास की लंबाई और max_tokens तय करते हैं, ताकि उपयोगकर्ता उन्हें ओवरराइड न कर सकें।
  • नियमों के लिए एक स्थान। प्रत्येक उपयोगकर्ता की सीमाएं, आयु बाधाएं और लॉगिंग नीति सभी इस परत में आते हैं। सुरक्षा गाइड इसी मार्ग पर निर्माण करता है।

चरण 1: प्रोजेक्ट सेटअप करें

एक फोल्डर बनाएं, इसे एक ES-मॉड्यूल प्रोजेक्ट के रूप में इनिशियलाइज़ करें और Express इंस्टॉल करें। बाकी सब कुछ बिल्ट-इन है।

mkdir lantern-chat && cd lantern-chat
npm init -y
npm pkg set type=module
npm install express
mkdir public

आपके पास अंत में दो चीज़ें होंगी: रूट पर server.js और एक public फोल्डर जिसमें पेज और उसका स्क्रिप्ट है।

चरण 2: Express प्रॉक्सी लिखें

सर्वर स्थिर फ़ाइलों को सर्व करता है और एक POST मार्ग प्रकट करता है। इसे तीन भागों में पढ़ें। पहला, यह आने वाली इतिहास को साफ़ करता है: केवल user और assistant भूमिकाएं पास होती हैं, प्रत्येक संदेश को 4,000 वर्णों तक सीमित किया जाता है, और केवल अंतिम 30 संवाद रखे जाते हैं। दूसरा, यह आपके स्वयं के सिस्टम संदेश को जोड़ता है, जिसे ब्राउज़र कभी नहीं बदल सकता। तीसरा, यह stream: true के साथ अपस्ट्रीम एंडपॉइंट को कॉल करता है और बाइट्स को सीधे वापस पाइप करता है।

import express from "express";

const app = express();
app.use(express.json({ limit: "256kb" }));
app.use(express.static("public"));

const UPSTREAM = "https://api.uncensoredchatbotapi.com/v1/chat/completions";
const SYSTEM = "You are Wren, a dry-witted night-shift lighthouse keeper. Stay in character.";

app.post("/api/chat", async (req, res) => {
  const history = Array.isArray(req.body.messages) ? req.body.messages : [];
  // Keep only well-formed turns and cap the history we forward.
  const turns = history
    .filter((m) => ["user", "assistant"].includes(m.role) && typeof m.content === "string")
    .slice(-30)
    .map((m) => ({ role: m.role, content: m.content.slice(0, 4000) }));

  if (turns.length === 0) return res.status(400).json({ error: "no messages" });

  const upstream = await fetch(UPSTREAM, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      model: "uncensored",
      messages: [{ role: "system", content: SYSTEM }, ...turns],
      stream: true,
      max_tokens: 500,
      temperature: 0.8,
    }),
  });

  if (!upstream.ok) {
    const info = await upstream.json().catch(() => ({}));
    return res.status(upstream.status).json(info);
  }

  res.setHeader("Content-Type", "text/event-stream");
  res.setHeader("Cache-Control", "no-cache");
  for await (const chunk of upstream.body) res.write(chunk);
  res.end();
});

app.listen(3000, () => console.log("http://localhost:3000"));

दो विकल्पों की व्याख्या करने योग्य हैं। कच्चे बाइट्स को पास करने का मतलब है कि आपको सर्वर पर इवेंट फॉर्मेट को समझने की बिल्कुल आवश्यकता नहीं है। और विफलताओं के लिए अपस्ट्रीम स्टेटस लौटाने से पेज को तार्किक रूप से प्रतिक्रिया करने की अनुमति मिलती है; उदाहरण के लिए, 402 का मतलब है कि आपका बैलेंस खाली है और 429 का मतलब है कि कोई बहुत तेज़ी से जा रहा है।

चरण 3: पेज और स्ट्रीम रीडर

अब फ्रंट-एंड। इसे public/index.html के रूप में सहेजें; इसे जानबूझकर बेहद साधारण रखा गया है ताकि आप बाद में इसे रीस्टाइल कर सकें।

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Lantern Chat</title>
  <style>
    body { font: 16px system-ui; max-width: 640px; margin: 2rem auto; padding: 0 1rem; }
    #log { min-height: 320px; border: 1px solid #ccc; padding: 1rem; white-space: pre-wrap; }
    .me { color: #234; font-weight: 600; }
    form { display: flex; gap: .5rem; margin-top: 1rem; }
    input { flex: 1; padding: .6rem; }
  </style>
</head>
<body>
  <h1>Lantern Chat</h1>
  <div id="log"></div>
  <form id="form">
    <input id="text" autocomplete="off" placeholder="Say something...">
    <button>Send</button>
  </form>
  <script src="chat.js"></script>
</body>
</html>

अगला, public/chat.js। दिलचस्प हिस्सा रीड लूप है। नेटवर्क चंक लाइन सीमाओं का सम्मान नहीं करते, इसलिए हम एक buffer रखते हैं, नई लाइनों पर विभाजित करते हैं, और अधिक डेटा आने तक आंशिक लाइन को रोकते हैं। प्रत्येक पूर्ण लाइन जो data: से शुरू होती है वह JSON है, अंतिम [DONE] मार्कर को छोड़कर।

const log = document.getElementById("log");
const form = document.getElementById("form");
const input = document.getElementById("text");
const history = [];

function addLine(cls, text) {
  const div = document.createElement("div");
  div.className = cls;
  div.textContent = text;          // textContent, never innerHTML, for untrusted text
  log.appendChild(div);
  return div;
}

form.addEventListener("submit", async (e) => {
  e.preventDefault();
  const text = input.value.trim();
  if (!text) return;
  input.value = "";
  history.push({ role: "user", content: text });
  addLine("me", "You: " + text);
  const bubble = addLine("bot", "");

  const res = await fetch("/api/chat", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ messages: history }),
  });
  if (!res.ok) {
    bubble.textContent = "(the chat is unavailable right now)";
    history.pop();
    return;
  }

  const reader = res.body.getReader();
  const decoder = new TextDecoder();
  let buffer = "", reply = "";
  for (;;) {
    const { value, done } = await reader.read();
    if (done) break;
    buffer += decoder.decode(value, { stream: true });
    const lines = buffer.split("\n");
    buffer = lines.pop();            // keep a partial line for the next read
    for (const line of lines) {
      if (!line.startsWith("data: ")) continue;
      const data = line.slice(6).trim();
      if (data === "[DONE]") continue;
      const json = JSON.parse(data);
      const piece = json.choices?.[0]?.delta?.content;
      if (piece) {
        reply += piece;
        bubble.textContent = reply;
      }
    }
  }
  history.push({ role: "assistant", content: reply });
});

history एरे पर ध्यान दें। API कॉल्स के बीच कोई मेमोरी नहीं रखता, इसलिए पेज हर बार पूरी कनवर्सेशन फिर से भेजता है, और सर्वर इसे ट्रिम करता है। ऐप शुरू करें और इसे ब्राउज़र में खोलें:

export API_KEY="paste-your-key-here"
node server.js

मार्कडाउन रेंडरिंग पर एक नोट

चैट मॉडल तारांकन, सूचियों और कभी-कभी कोड फेंस को पसंद करते हैं। हमारा डेमो कच्चा टेक्स्ट दिखाता है, जो सुरक्षित है। जब आप सुंदर आउटपुट चाहते हैं, तो एक लाइब्रेरी के साथ मार्कडाउन रेंडर करें, लेकिन दो नियमों का पालन करें। innerHTML के साथ इसे डालने से पहले HTML को एक सैनिटाइजर से गुजारें, क्योंकि एक मॉडल, या एक उपयोगकर्ता जो इसे धोखा दे सकता है, वे टैग और विशेषताएं उत्पन्न कर सकता है जिन्हें आपने इरादे से नहीं बनाया था। और सावधानी से क्रमिक रूप से रेंडर करें: हर फ्रैगमेंट पर पूरी प्रतिक्रिया को फिर से पार्स करना छोटे संदेशों के लिए ठीक है, लेकिन अधूरा मार्कडाउन फ्लिकर कर सकता है, इसलिए कुछ निर्माता स्ट्रीमिंग के दौरान सादा टेक्स्ट दिखाते हैं और स्ट्रीम समाप्त होने पर फॉर्मेटेड आउटपुट पर स्विच करते हैं।

रोलप्ले फॉर्मेटिंग एक अलग क्विरक है। कई पात्र क्रियाओं को तारांकन में लपेटते हैं, जैसे *adjusts the lamp*। तय करें कि क्या आपकी ऐप उन्हें इटैलिक के रूप में स्टाइल करती है, और सिस्टम प्रॉम्प्ट में पात्र को बताएं कि किस रीति का पालन करना है। हमारा पर्सोना डिज़ाइन गाइड उसी के लिए प्रॉम्प्ट शब्दों को दिखाता है।

चरण 4: एंडपॉइंट का स्मोक-टेस्ट करें

ब्राउज़र को दोष देने से पहले, प्रॉक्सी को सीधे परीक्षण करें। यदि टर्मिनल काम करता है, तो सर्वर ठीक है और कोई भी शेष बग पेज में है।

curl -N http://localhost:3000/api/chat \
  -H "Content-Type: application/json" \
  -d '{"messages":[{"role":"user","content":"Wren, is the fog coming in?"}]}'

-N फ्लैग curl के अपने बफरिंग को अक्षम करता है, इसलिए आपको data: लाइनों को क्रमिक रूप से प्रकट होते हुए, अंत में data: [DONE] के साथ देखना चाहिए। यदि आपको JSON त्रुटि मिलती है, तो उसकी स्थिति पढ़ें: 401 का अर्थ है कि आपके वातावरण में कुंजी गलत है, 402 का अर्थ है कि बैलेंस खाली है, और 404 का अर्थ है कि अपस्ट्रीम URL में टाइपो है।

लक्षणसंभावित कारणसमाधान
पेज अनुपलब्धता संदेश दिखाता हैसर्वर ने 200 से अलग स्थिति लौटाईcurl टेस्ट चलाएं और स्टेटस पढ़ें
टेक्स्ट केवल अंत में दिखाई देता हैएक प्रॉक्सी परत प्रतिक्रिया को बफर कर रही हैरूट के लिए बफरिंग अक्षम करें
गड़बड़ाए हुए वर्णstream: true के बिना डिकोडर का उपयोग किया गयाTextDecoder.decode पर विकल्प सेट रखें
प्रतिक्रिया वाक्य के बीच में कट जाती हैmax_tokens की सीमा पार हो गईसर्वर पर कैप बढ़ाएं

उन चार फिक्स के साथ आप लगभग हर पहले-रन समस्या को एक मिनट से कम समय में निदान कर सकते हैं। curl कमांड को अपने नोट्स में रखें; यह बाद में प्रॉक्सी बदलने के बाद एक उपयोगी हेल्थ चेक भी है।

छोटे स्पर्श जो इसे पूरा महसूस कराते हैं

स्ट्रीमिंग वाला चैट पहले से ही आनंददायक है, लेकिन कुछ विवरण डेमो को उस उत्पाद से अलग करते हैं जिसके लिए लोग वापस आते हैं।

  • जब जवाब आ रहा हो तो भेजने के बटन को अक्षम करें। डबल सबमिट इंटर्लीव्ड इतिहास बनाते हैं जो उपयोगकर्ता और मॉडल दोनों को भ्रमित करते हैं।
  • स्टॉप बटन जोड़ें। एक AbortController बनाएं, इसका सिग्नल fetch को पास करें, और क्लिक पर abort() कॉल करें। जो भी टेक्स्ट पहले से आया है, उसे असिस्टेंट टर्न के रूप में सेव करें।
  • लॉग को सहेजें। इतिहास को sessionStorage में रखें ताकि रीफ्रेश से कनवर्सेशन मिटे नहीं, और एक क्लियर-चैट बटन दें जो इसे खाली कर दे।
  • ऑटो-स्क्रोल तार्किक ढंग से करें। केवल तभी नीचे जाएं जब उपयोगकर्ता पहले से ही वहां के करीब हो; अन्यथा उन्हें पुरानी लाइनें शांति से पढ़ने दें।
  • एक कोमल विफलता दिखाएं। खाली बबल को एक रीट्राई लिंक से बदलें जो आखिरी यूजर मैसेज को फिर से भेजता है।

इनमें से प्रत्येक सादे जावास्क्रिप्ट की एक दर्जन लाइनों का है, इसलिए जब तक आपके इंटरफ़ेस को वास्तव में कंपोनेंट्स, रूटिंग या शेयर्ड स्टेट की आवश्यकता न हो, फ्रेमवर्क की ओर जाने से बचें।

हार्डनिंग और अगले कदम

आपके पास अब एक काम करने वाला चैट है। असली उपयोगकर्ताओं के आने से पहले, server.js में कुछ सुरक्षा उपाय जोड़ें। प्रति IP या सेशन अनुरोधों की सीमा निर्धारित करें, क्योंकि एक कुंजी कुल मिलाकर प्रति मिनट 300 अनुरोधों की अनुमति देती है, और एक उत्साही उपयोगकर्ता सारी क्रेडिट खत्म कर सकता है। अपस्ट्रीम त्रुटियों को स्पष्ट रूप से हैंडल करें: upstream_busy के साथ 503 को एक दोस्ताना रीट्राई बटन चाहिए, और 403 content_blocked को एक खाली बबल के बजाय एक स्पष्ट संदेश चाहिए। max_tokens को मामूली रखें; चैट के लिए 500 काफी है, जबकि अनुमत अधिकतम प्रति अनुरोध 32,000 है।

फिर लागत के बारे में सोचें। एक उदाहरण के रूप में, मान लें कि प्रत्येक टर्न में 1,200 प्रॉम्प्ट टोकन भेजे जाते हैं और 250 कंप्लीशन टोकन प्राप्त होते हैं। इनपुट के लिए लगभग $0.0003 और आउटपुट के लिए $0.00025, प्रति टर्न लगभग $0.00055 होता है। उन टोकन गणनाएं अनुमान हैं, इसलिए अपने खुद के को usage चंक के साथ मापें। docs उस फील्ड को कवर करते हैं, और hosted uncensored LLMs के लिए गाइड बताता है कि इस प्रकार की सेवा से क्या उम्मीद करें।

प्रश्न और उत्तर

क्या मैं ब्राउज़र से सीधे API कॉल कर सकता हूं?

तकनीकी रूप से हाँ, लेकिन आप अपनी कुंजी हर विज़िटर को एक्सपोज़ करेंगे। इस ट्यूटोरियल में दी गई तरह का सर्वर रूट उपयोग करें ताकि कुंजी एक एनवायरनमेंट वेरिएबल में ही रहे।

क्या सर्वर को स्ट्रीम को पार्स करने की आवश्यकता है?

नहीं। यह बाइट्स को अपरिवर्तित आगे बढ़ा सकता है, और पेज डेटा लाइन्स को पढ़ता है। टेक्स्ट लॉग करने या आउटपुट फिल्टर करने के लिए ही सर्वर पर पार्स करें।

मेरा जवाब एक साथ क्यों आता है?

बीच में कुछ बफरिंग हो रहा है। जांचें कि अनुरोध में stream को true सेट किया गया है और कोई रिवर्स प्रॉक्सी या कंप्रेशन लेयर रिस्पॉन्स को रोक तो नहीं रहा है।

मैं बॉट को मेमोरी कैसे दे सकता हूं?

हर कॉल पर संवाद संदेशों के रूप में पुनः भेजें, और जब यह बढ़े तो सबसे पुराने बोलचाल को काटें। 100,000-टोकन कॉन्टेक्स्ट विंडो प्रतिक्रिया के साथ साझा की जाती है।

आपकी कुंजी बस एक फ़ॉर्म दूर है

एक खाता बनाएं, कुंजी कॉपी करें, बेस URL बदलें। यही पूरी सेटअप है।

API कुंजी प्राप्त करेंदस्तावेज़ पढ़ें