Skip to content

Latest commit

 

History

15 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ARLing Asistent

AI predajný asistent pre e-shopy. Nastaví sa z produktového feedu (Google Shopping XML, Shopify, WooCommerce, alebo bežný XML), beží na Cloudflare Workers, a neukladá obsah rozhovorov, len denné súhrnné počítadlá.

Demo a landing stránka: demo/index.html (naživo na https://arling.sk/asistent/ po nasadení).

Ako to funguje

  1. E-shop vloží URL feedu produktov a e-mail (POST /v1/tenants).
  2. Worker feed stiahne, znormalizuje, rozdelí na časti a uloží ako embeddings do Cloudflare Vectorize (@cf/baai/bge-m3). Feed sa obnovuje automaticky raz denne (cron).
  3. E-shop vloží jeden <script> tag, buď na widget/widget.js (GitHub Pages), alebo priamo na GET /widget.js z Workera (rovnaký súbor, worker ho servíruje zo svojej vlastnej domény, viď nižšie).
  4. Zákazník sa opýta widgetu na niečo; otázka sa zabedduje, nájde sa 8 najbližších produktov daného e-shopu vo Vectorize, a model (@cf/meta/llama-3.3-70b-instruct-fp8-fast) odpovie výhradne z týchto produktov a kontaktných údajov obchodu, v jazyku zákazníka, do 120 slov, s najviac 3 odkazmi na produkty.

Súbory:

  • worker/: Cloudflare Worker (wrangler, plain JavaScript ES modules, žiadny build krok pri nasadení).
  • widget/widget.js: vkladateľný chat widget (jeden súbor, Shadow DOM, bez závislostí). Toto je jediný zdroj pravdy pre widget; worker/src/widget-src.js a demo/widget.js sú z neho generované, viď "Widget: úprava a build" nižšie.
  • demo/: landing stránka a živé demo (statické súbory pre GitHub Pages).
  • scripts/build-widget.mjs: kopíruje widget/widget.js do worker/src/widget-src.js a demo/widget.js (npm run build:widget).
  • legal/dpa-sk.md: vzorová zmluva o spracúvaní osobných údajov (čl. 28 GDPR).
  • tests/: node --test, mocky pre AI/Vectorize/D1/KV, žiadna sieť.

Testovanie lokálne (bez Cloudflare účtu)

Vlastník ešte nemá účet na Cloudflare: všetko nižšie beží a testuje sa lokálne s mockami, nasadenie príde neskôr.

cd products/arling-asistent
npm test          # node --test tests/*.test.mjs, žiadna sieť, žiadny účet

Na kontrolu syntaxe worker kódu a widgetu bez inštalácie čohokoľvek:

node --check widget/widget.js
node --check demo/widget.js
for f in worker/src/*.js; do node --check "$f"; done

Widget: úprava a build

widget/widget.js je jediný zdroj pravdy. Worker (plain ES modules, žiadny bundler) nevie priamo import-núť .js súbor ako text, a demo/ má byť nezávislá statická kópia pre GitHub Pages, preto po každej úprave widget/widget.js treba spustiť:

npm run build:widget

Tento skript (scripts/build-widget.mjs) prepíše dva generované súbory:

  • worker/src/widget-src.jsexport default \...`;s obsahom widgetu, servírovaný priamo Workerom naGET /widget.js(content-typeapplication/javascript, cache-control: public, max-age=3600, CORS *, keďže ide o statický, tenant-neutrálny kód nahrávaný z <script src>` z ľubovoľnej domény e-shopu).
  • demo/widget.js — presná kópia pre GitHub Pages demo stránku.

Oba generované súbory sa commitujú ako bežný zdrojový kód (nasadenie samotné žiadny build krok nepotrebuje); skript treba spustiť len lokálne po úprave widget/widget.js, nie pri každom wrangler deploy.

Na lokálne vyskúšanie samotného Workera (vyžaduje len npx, nie účet, wrangler dev beží úplne offline s lokálnym D1/KV/Vectorize emulátorom):

cd worker
npx wrangler dev

Na lokálne prezretie demo stránky stačí otvoriť demo/index.html v prehliadači, alebo spustiť statický server (npx serve demo). Skúšobný formulár na stránke volá worker na adrese nastavenej v ?endpoint= parametri URL (predvolene placeholder https://arling-asistent.arling.workers.dev, ktorý treba nahradiť po nasadení); pri lokálnom teste pridajte ?endpoint=http://localhost:8787 a dočasne uvoľnite connect-src v CSP meta tagu v demo/index.html.

Nasadenie (až keď bude účet na Cloudflare)

npm install -g wrangler
wrangler login

# D1 databáza (tenants + counters)
wrangler d1 create asistent
# skopírovať vrátené database_id do worker/wrangler.toml ([[d1_databases]])
wrangler d1 execute asistent --file=worker/schema.sql --remote

# Vectorize index (produktové embeddingy, 1024 dimenzií pre bge-m3, cosine)
wrangler vectorize create asistent-products --dimensions=1024 --metric=cosine

# KV namespace (rate-limit počítadlá)
wrangler kv namespace create ASISTENT_CACHE
# skopírovať vrátené id do worker/wrangler.toml ([[kv_namespaces]])

# Metadata index na Vectorize (nutné, inak filtrovaný dotaz podľa tenanta
# vždy vráti 0 výsledkov; worker sa bez neho degraduje na pomalší
# nefiltrovaný fallback, viď "Ak retrieval vracia 0 produktov" nižšie, ale
# treba ho vytvoriť čo najskôr):
wrangler vectorize create-metadata-index asistent-products --property-name=tenant --type=string

# Admin token pre POST /v1/tenants/:id/reingest a PATCH/POST
# /v1/tenants/:id/plan (ľubovoľný náhodný reťazec, napr. `openssl rand -hex
# 32`); bez neho oba endpointy odmietnu úplne všetky požiadavky, nikdy
# nepovolia re-ingest ani zmenu plánu bez neho. Rovnaká hodnota ide aj do
# products/licence-service ako ASISTENT_ADMIN_TOKEN, pozri "Platby cez
# Stripe" vyššie:
wrangler secret put ADMIN_TOKEN

cd worker
wrangler deploy

Widget (widget/widget.js) a demo stránku (demo/) treba nasadiť ako statické súbory (napríklad GitHub Pages pod arling.sk/asistent/, tak ako ostatné nástroje ARLing) — alebo namiesto toho použiť <script src="https://VASA-DOMENA-WORKERA/widget.js">, keďže worker po nasadení servíruje presne ten istý súbor priamo (viď "Vloženie widgetu na e-shop" nižšie), čo je jednoduchšie ako spravovať druhý statický hosting. Po nasadení Workera nahraďte placeholder https://arling-asistent.arling.workers.dev skutočnou doménou Workera v demo/app.js a demo/index.html (CSP connect-src).

Ak retrieval vracia 0 produktov (chýbajúci metadata index)

Ak bol tenant vytvorený predtým, než existoval metadata index na property tenant (wrangler vectorize create-metadata-index vyššie), jeho pôvodné vektory vo Vectorize môžu byť v poriadku, ale chat.js sa degraduje na pomalší nefiltrovaný fallback dotaz (retrieveCandidates v worker/src/chat.js) namiesto zlyhania nahlas. Po vytvorení indexu stačí dotknutého tenanta manuálne pre-embednúť:

curl -X POST "https://VASA-DOMENA-WORKERA/v1/tenants/TENANT_ID/reingest" \
  -H "X-Admin-Token: $ADMIN_TOKEN"

Ten istý ingestFeedForTenant() beží aj v dennom crone (worker/src/cron.js), takže toto je len manuálne spustenie tej istej funkcie mimo poradia.

Opakované POST /v1/tenants na tú istú doménu

domain má v tenants UNIQUE obmedzenie, takže opakované odoslanie onboardingového formulára pre doménu, ktorá už tenanta má (napríklad majiteľ obchodu formulár omylom odošle dvakrát), nevráti chybu: vráti 200 s existujúcim tenantom ({..., "existing": true} namiesto 201), nikdy nie e-mail pôvodného tenanta. Ak sa odoslaná feed_url líši od uloženej, alebo posledné úspešné spracovanie feedu je staršie ako 24 hodín (alebo sa nikdy nepodarilo, tenant je v stave error), spustí sa na pozadí (ctx.waitUntil) rovnaké ingestFeedForTenant() ako pri crone. Akýkoľvek iný konflikt v D1 (nie kolízia domény) sa mapuje na 409 {"error":"conflict"}, nikdy nie na 500.

Vloženie widgetu na e-shop

<script src="https://arling-asistent.arling.workers.dev/widget.js"
        data-tenant="TENANT_ID"
        data-lang="sk"
        data-color="auto"
        defer></script>

GET /widget.js servíruje worker sám (rovnaký obsah ako widget/widget.js, viď "Widget: úprava a build" vyššie), takže e-shop nepotrebuje žiadny druhý hosting pre samotný skript.

  • data-tenant (povinné): id vrátené z POST /v1/tenants.
  • data-lang: sk, cs, en, de, alebo auto (predvolené, aj keď atribút úplne chýba). Pri auto sa vzhľad widgetu (tlačidlá, placeholder, pozdrav) riadi jazykom prehliadača návštevníka (s pádom na slovenčinu, ak ten nie je jeden zo štyroch podporovaných), a hodnota "auto" sa pošle aj na server v POST /v1/chat, ktorý potom jazyk odpovede odhaduje z každej správy zákazníka zvlášť (pozri worker/src/chat.js).
  • data-color: auto (podľa systému návštevníka, predvolené), light alebo dark.
  • data-position: right (predvolené) alebo left, na ktorej spodnej strane stránky sedí tlačidlo aj panel chatu.
  • data-greeting: vlastný text prvej správy asistenta (nahradí predvolený pozdrav pre daný jazyk).
  • data-title: vlastný názov panelu (zobrazí sa v hlavičke aj ako accessible name dialógu, nahradí predvolený názov pre daný jazyk).
  • data-endpoint: voliteľná adresa Workera, ak sa líši od domény, z ktorej sa widget.js načítal.

Plány

POST /v1/tenants dnes vytvorí tenanta na pláne free bez platby. Kvóta sa zatiaľ počíta na jeden POST /v1/chat request, nie na správu v ňom (pozri komentár v worker/src/tenants.js a bod nižšie v "Čo ešte nie je hotové").

Plán Cena Mesačná kvóta (predvolená)
free zadarmo 100 rozhovorov
starter 19 EUR/mesiac 1 000 rozhovorov
pro 39 EUR/mesiac 5 000 rozhovorov

Zmena plánu (PATCH alebo POST /v1/tenants/:id/plan)

Toto je miesto, kde platený plán skutočne zmení, čo tenant smie používať (predtým POST /v1/tenants vytvoril vždy len free tenanta a nič ho z toho nikdy nedostalo, aj keď zaplatil). Chránené rovnako ako POST /v1/tenants/:id/reingest: hlavička X-Admin-Token musí sedieť s ADMIN_TOKEN secretom, inak 401 (a bez nastaveného ADMIN_TOKEN endpoint odmietne úplne všetko).

Telo požiadavky:

{ "plan": "starter", "monthly_quota": 1000, "billing_ref": "sub_...", "valid_until": "2026-11-05" }
  • plan (povinné): "free", "starter" alebo "pro", inak 400 validation_failed.
  • monthly_quota (voliteľné): kladné celé číslo. Bez neho sa použije predvolená kvóta daného plánu (tabuľka vyššie, DEFAULT_QUOTAS v worker/src/tenants.js).
  • billing_ref (voliteľné): ľubovoľný reťazec (napr. Stripe subscription id), uložený tak ako je. Bez neho sa nastaví na null.
  • valid_until (voliteľné): dátum/čas ako reťazec (ISO, napr. "2026-11-05"), dokedy plán platí. Bez neho sa nastaví na null. Vynucovanie expirácie (downgrade na free po valid_until) nerobí tento worker sám od seba, robí ho expire_asistent_plans() v products/licence-service/app.py, volaním tohto istého endpointu s plan: "free", pozri README toho projektu.

billing_ref a valid_until sú nové nullable stĺpce (billing_ref TEXT, valid_until TEXT), pridané rovnako ako product_count: guardovaným runtime ALTER TABLE (ensureBillingColumns v worker/src/tenants.js), takže existujúca nasadená databáza ich dostane automaticky pri prvom volaní tohto endpointu, bez potreby ručne spúšťať schema.sql znova. GET /v1/tenants/:id/status vracia oba stĺpce (null, kým nie sú nastavené).

Toto je presne to, čo volá Stripe webhook v products/licence-service/app.py (vlastný ASISTENT_ADMIN_TOKEN, ktorý sa musí zhodovať s týmto ADMIN_TOKEN) po úspešnej platbe alebo obnove predplatného za plán asistent-starter/asistent-pro, pozri "Platby cez Stripe" nižšie a README products/licence-service.

Platby cez Stripe

Samotné platenie beží v products/licence-service (homelab), nie v tomto Cloudflare Workeri: ten webhook prijme Stripe udalosť a zavolá späť sem, na PATCH /v1/tenants/:id/plan vyššie. Aby to fungovalo end-to-end, treba dve veci: nastaviť dva .env kľúče na homelabe (products/licence-service), a vyplniť dva placeholdery na strane frontendu (táto demo stránka, jej kópia v arling-sk/asistent/, a WordPress plugin).

.env kľúče na homelabe (products/licence-service/.env vedľa compose.yaml)

Kľúč Hodnota
ASISTENT_ADMIN_TOKEN Rovnaká hodnota ako ADMIN_TOKEN secret tohto Workera (wrangler secret put ADMIN_TOKEN vyššie): jeden zdieľaný token, dve mená v dvoch službách.
ASISTENT_API_BASE https://arling-asistent.arling.workers.dev (predvolené, netreba nastavovať, ak sa doména Workera nezmenila).
PLANS_JSON Doplniť o dva záznamy, jeden na cenu (pozri presný JSON nižšie).

Presný PLANS_JSON snippet na doplnenie (zlúčiť s existujúcimi záznamami pre ostatné nástroje ARLing, nie nahradiť celý súbor):

{
  "price_asistent_starter": {"plan": "asistent-starter", "days": 35},
  "price_asistent_pro": {"plan": "asistent-pro", "days": 35}
}

(price_asistent_starter/price_asistent_pro sú placeholder názvy, nahraďte skutočnými Stripe price id z kroku 2 nižšie. days: 35 namiesto 30/31 zámerne: pár dní rezervy, aby oneskorené invoice.paid doručenie nikdy nestihlo tenanta downgradnúť skôr, než v skutočnosti prestal platiť.)

Kroky pre vlastníka v Stripe Dashboard

  1. Products → nový produkt "ARLing Asistent".
  2. Na ňom dve recurring ceny: 19 EUR/mesiac a 39 EUR/mesiac, obe s DPH (tax inclusive), tax code txcd_10000000 (SaaS/softvér).
  3. Pre každú cenu Payment Link (Dashboard → Payment links → New): v pokročilých nastaveniach zapnúť "Collect a client reference ID" (client reference ID passthrough), bez toho ?client_reference_id=... z tlačidla nižšie do Stripe Checkout Session vôbec nedorazí, a webhook potom nevie, ktorému tenantovi kvótu zdvihnúť. Success URL: https://arling.sk/asistent/?upgraded=1.
  4. Skopírovať obe Payment Link URL do data-stripe-link atribútov v demo/index.html (a rovnako do arling-sk/asistent/index.html, presná kópia) na tlačidlách #btn-plan-starter / #btn-plan-pro (miesto komentárov STRIPE_LINK_STARTER / STRIPE_LINK_PRO), a do arling_asistent_stripe_link_starter / arling_asistent_stripe_link_pro filtrov (alebo priamo do ARLING_ASISTENT_DEFAULT_STRIPE_LINK_STARTER/_PRO konštánt v wordpress-plugin/arling-asistent/arling-asistent.php) pre WordPress plugin. Kým sú tieto placeholdery prázdne, príslušné tlačidlo ukazuje "Čoskoro"/"coming soon" a je neaktívne, nikdy nevedie na rozbitý odkaz.
  5. Doplniť skutočné price id do PLANS_JSON (krok vyššie) a reštartovať licence službu (docker compose up -d --build alebo restart).

Po tomto: zákazník klikne na tlačidlo s vlastným tenant_id v client_reference_id, zaplatí cez Stripe, checkout.session.completed dorazí do licence-service, ten zavolá PATCH /v1/tenants/:id/plan sem, a tenant má hneď zvýšenú kvótu, bez ručného zásahu.

Náklady na bezplatnej úrovni Cloudflare (zdroj: opportunities/asistent-research.md, stav 09/2026)

Služba Bezplatný limit Poznámka
Workers 100 000 requestov/deň, 10 ms CPU/request Pri prekročení CPU limitu treba platený plán (5 USD/mesiac, 30M CPU-ms)
Workers AI 10 000 "neuronov"/deň (embeddingy aj chat model spolu) Po prekročení treba platený Workers plán, doplatok 0,011 USD/1000 neuronov
Workers AI, bge-m3 embeddings 0,012 USD/milión tokenov (platený plán) Najlacnejší a viacjazyčný embedding model na Workers AI
Vectorize 5M uložených dimenzií, 30M query-dimenzií/mesiac; max 100 indexov, max 1536 dimenzií/vektor, max 20 000 vektorov/batch Mal by stačiť na katalógy malých e-shopov (limit 5000 produktov/tenant v tomto kóde)
D1 5M riadkov čítaných/deň, 100 000 zapísaných/deň, 5 GB úložisko Ukladá len tenants + denné počítadlá, žiadne rozhovory
Workers KV 100 000 čítaní/deň, 1 000 zápisov/deň, 1 GB úložisko Len rate-limit počítadlá s krátkou expiráciou

Najtesnejší limit je 10 000 Workers AI neuronov/deň (embeddingy pri onboardingu/dennom obnovení feedu aj chatový model zdieľajú tento limit): pri viacerých aktívnych e-shopoch treba počítať s prechodom na platený Workers plán čoskoro po prvých platiacich zákazníkoch, presne ako predpokladá ADR-04.

Čo ešte nie je hotové

  • Platby: zapojené na strane servera, čaká sa na Stripe účet vlastníka. PATCH/POST /v1/tenants/:id/plan (pozri "Plány" vyššie) a products/licence-service's Stripe webhook (checkout/renewal pre asistent-starter/asistent-pro plány, denný expire_asistent_plans() cron) sú hotové a otestované. Chýba už len: vlastník vytvorí produkt a dve ceny v Stripe, vyplní STRIPE_LINK_STARTER/STRIPE_LINK_PRO na demo stránke a vo WordPress pluginu (pozri "Platby cez Stripe" nižšie), a nastaví PLANS_JSON/ASISTENT_ADMIN_TOKEN/ASISTENT_API_BASE na homelabe. POST /v1/tenants naďalej vytvorí free tenanta s pevnou kvótou bez platby, presne ako doteraz.
  • Kvóta na rozhovor, nie na správu. MVP zjednodušenie: každé volanie POST /v1/chat sa počíta ako jeden rozhovor voči mesačnej kvóte (pozri komentár v worker/src/tenants.js). Presnejšie počítanie raz za reláciu (podľa in-memory session id na strane widgetu) je budúce rozšírenie.
  • Shoptet doplnok. Vyžaduje partnerské schválenie (Shoptet reaguje do 4 týždňov, pozri opportunities/asistent-research.md), nie je súčasťou tohto MVP. Skript tag funguje na Shoptete aj bez doplnku.
  • WooCommerce plugin a Shopify aplikácia (inštalácia na klik z ich obchodov s doplnkami). Feed formáty oboch platforiem worker už vie spracovať (worker/src/feed.js), chýba len samotný distribučný balík.
  • Načítanie stránok o doprave a obchodných podmienkach. ADR-04 spomína aj načítanie týchto stránok pri onboardingu; MVP spracúva len produktový feed, obchodné fakty (napríklad kontaktný e-mail) sa zatiaľ zadávajú len cez tenant záznam.
  • EU-only garancia spracovania. Cloudflare verejne negarantuje, že Workers AI beží výlučne v EÚ (pozri legal/dpa-sk.md, článok 7). Zmluva preto stojí na štandardných zmluvných doložkách (SCC) a certifikácii EU Cloud Code of Conduct, nie na technickej záruke.
  • Mazanie vektorov pri zmene feedu. embed.js vie vektory zmazať (deleteTenantVectors), ale cron.js dnes len prepíše (upsert) existujúce produkty; produkt, ktorý úplne zmizne z feedu, ostáva vo Vectorize ako zastaraný záznam. Čistenie osirotených vektorov je budúce rozšírenie.
  • Admin rozhranie. Žiadny prehľad tenantov, počítadiel ani logov mimo priameho dotazu do D1.

Testy

npm test (node --test tests/*.test.mjs), Node 20+, bez siete. 146 testov, 460 volaní assert.*, pokrývajúcich: parsovanie všetkých 4 formátov feedu a normalizáciu, chunkovanie a embedding pipeline, CORS allowlist (vrátane hlavičky na skutočných JSON odpovediach POST /v1/tenants a GET /v1/tenants/:id/status, nielen na OPTIONS preflighte), rate limiting a jeho fail-open správanie pri chybe KV, limity veľkosti vstupu a ich mapovanie na 413/400 namiesto 500, ochranu proti prompt injection (vrátane popisu produktu s textom "ignore previous instructions"), retrieval z Vectorize vrátane degradovaného nefiltrovaného fallbacku pri chýbajúcom metadata indexe, admin re-ingest endpoint (POST /v1/tenants/:id/reingest), admin set-plan endpoint (PATCH/POST /v1/tenants/:id/plan: autorizáciu, validáciu plánu, predvolené aj vlastné monthly_quota, ukladanie a čistenie billing_ref/valid_until, alias POST), validáciu a vytvorenie tenanta (vrátane predvoleného plánu free a jeho kvóty), idempotentné POST /v1/tenants pri opakovanej doméne (existujúci tenant, obnovenie feedu pri zmene URL alebo starnutí nad 24h, mapovanie iného D1 konfliktu na 409), product_count a billing_ref/valid_until vrátane guardovaného runtime ALTER TABLE pre existujúcu D1 databázu (ensureProductCountColumn/setProductCount, ensureBillingColumns/setTenantPlan v worker/src/tenants.js), mesačnú kvótu a počítadlá, stavbu groundovaného promptu, jazyk auto (heuristika detectLangFromText a systémový prompt, ktorý necháva model rozpoznať jazyk zákazníka), spracovanie odpovede modelu a celý chat flow s mockovaným modelom vracajúcim JSON, widget/widget.js samotný (načítanie cez node:vm s minimálnym fake DOM, bez jsdom, vrátane data-position, data-title, data-greeting a data-lang="auto" podľa navigator.language), a napokon aj wiring na úrovni HTTP routera (worker/src/index.js) so skutočnými Request/Response objektmi.

Kontakt

ARLing s. r. o. (Bratislava, Slovensko). andrej@arling.sk

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages