npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@stackline/ai-ui

v0.0.4

Published

Framework-neutral Stackline AI Studio web component.

Readme

@stackline/ai-ui

Framework-neutral Stackline AI Studio web component for secure AI chat apps, with model picker, language picker, safe Markdown, local history, RAG evidence display, custom styling hooks, and backend-first Ollama integration.

npm version npm monthly license Web Component TypeScript Reddit community

Documentation & Live Demos | npm | Issues | Repository | Community Discussions

Latest tested package release: 0.0.4


Credits: Stackline AI Studio component architecture, publishing, and documentation by Alexandro Paixao Marques.


Why this package?

@stackline/ai-ui is the browser-facing Studio component for Stackline AI apps. It gives simple users a drop-in interface, while advanced teams can control endpoints, model selection, translations, persistence, CSS variables, CSS parts, and custom headers.

The component is intentionally backend-first: it never stores provider keys, database URLs, SQL, RAG filters, or memory paths. It calls your backend through /models and /chat.

Features

| Feature | Supported | | :--- | :---: | | Drop-in <stackline-ai-studio> custom element | ✅ | | Framework-neutral usage | ✅ | | Model picker powered by @stackline/multiselect | ✅ | | Language picker with built-in en, pt, fr, es plus custom languages | ✅ | | Safe Markdown and limited safe HTML rendering | ✅ | | LocalStorage history with quota protection | ✅ | | RAG evidence display without persisting evidence metadata | ✅ | | Clear conversation button | ✅ | | Custom endpoint attributes | ✅ | | CSS custom properties and CSS parts | ✅ | | Public methods and DOM events | ✅ |

Table of Contents

  1. Why this package?
  2. Features
  3. Status
  4. The Important Part
  5. Install By Situation
  6. Complete Browser-To-Ollama Tutorial
  7. Minimal UI Markup After The Backend Exists
  8. Full UI Markup
  9. Public API
  10. Attributes
  11. Methods
  12. Events
  13. Endpoint Schemas
  14. Styling
  15. Security

Status

Initial public API, ESM-only, TypeScript declarations included. The package auto-registers <stackline-ai-studio> when imported in a browser.

The Important Part

<stackline-ai-studio></stackline-ai-studio> is not a complete AI application. It is the frontend component. It needs backend endpoints that return models and chat responses.

Required runtime path:

Browser
  -> <stackline-ai-studio>
  -> GET /api/ai/models
  -> POST /api/ai/chat
  -> @stackline/ai-server
  -> @stackline/ai
  -> provider adapter, for example @stackline/ai-ollama

Do not put Ollama Cloud keys, provider keys, database URLs, SQL, RAG filters, or memory paths in browser code.

Install By Situation

Existing Compatible Backend

Use this only when your backend already provides GET /api/ai/models and POST /api/ai/chat.

npm install @stackline/ai-ui

Full UI With Ollama

This is the normal install when you want the Studio tag to work against local Ollama:

npm init -y
npm pkg set type=module
npm install @stackline/ai @stackline/ai-server @stackline/ai-ollama @stackline/ai-ui
npm install -D vite
mkdir -p src

Full UI With Ollama And SQLite Memory

npm init -y
npm pkg set type=module
npm install @stackline/ai @stackline/ai-server @stackline/ai-ollama @stackline/ai-ui @stackline/ai-memory-sqlite
npm install -D vite
mkdir -p data src

Full UI With Ollama And PostgreSQL RAG

npm init -y
npm pkg set type=module
npm install @stackline/ai @stackline/ai-server @stackline/ai-ollama @stackline/ai-ui @stackline/ai-rag-postgres
npm install -D vite
mkdir -p src

Complete Stack

npm init -y
npm pkg set type=module
npm install @stackline/ai @stackline/ai-server @stackline/ai-ollama @stackline/ai-ui @stackline/ai-memory-sqlite @stackline/ai-rag-postgres
npm install -D vite
mkdir -p data sql src

Requirements

  • Browser with Custom Elements and Shadow DOM.
  • Backend endpoints compatible with @stackline/ai-server.
  • A model returned by GET /api/ai/models, or an explicit model attribute.

When To Use

Use this package when you want a drop-in AI chat UI for Vanilla, Angular, React, Vue, Svelte, Astro, or any frontend that can render a custom element.

When Not To Use

Do not use this package as a backend or security layer. It cannot protect provider credentials, database credentials, or private RAG data.

Complete Browser-To-Ollama Tutorial

This section starts from an empty folder and reaches a working <stackline-ai-studio> connected to local Ollama.

1. Create The Project

mkdir stackline-ai-ui-starter
cd stackline-ai-ui-starter
npm init -y
npm pkg set type=module
npm install @stackline/ai @stackline/ai-server @stackline/ai-ollama @stackline/ai-ui
npm install -D vite

2. Verify Ollama

ollama --version
ollama list
ollama pull llama3.1
curl http://127.0.0.1:11434/api/tags

Use the exact model name from the NAME column of ollama list. Examples:

llama3.1
llama3.1:latest
qwen2.5:latest

3. Configure The Backend

Create .env:

PORT=8787
WEB_ORIGIN=http://localhost:4623
OLLAMA_TARGET=http://127.0.0.1:11434
OLLAMA_MODEL=llama3.1

OLLAMA_MODEL=auto is supported, but an explicit model is easier to debug for the first run.

4. Create The Backend Server

Create index.js:

import { createServer } from "node:http";
import { existsSync, readFileSync } from "node:fs";
import { createStacklineAIServer } from "@stackline/ai/server";
import { createStacklineAIHttpHandler } from "@stackline/ai-server";
import { ollamaProvider } from "@stackline/ai-ollama";

function loadEnv(path = new URL(".env", import.meta.url)) {
  if (!existsSync(path)) return;
  for (const line of readFileSync(path, "utf8").split(/\r?\n/)) {
    const match = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.*)\s*$/);
    if (!match || process.env[match[1]]) continue;
    process.env[match[1]] = match[2].replace(/^['"]|['"]$/g, "");
  }
}

async function requestFromNode(req) {
  const chunks = [];
  for await (const chunk of req) chunks.push(chunk);
  return new Request(`http://${req.headers.host || "localhost"}${req.url}`, {
    method: req.method,
    headers: req.headers,
    body: chunks.length ? Buffer.concat(chunks) : undefined,
  });
}

async function writeNodeResponse(res, response) {
  res.statusCode = response.status;
  response.headers.forEach((value, key) => res.setHeader(key, value));
  res.end(Buffer.from(await response.arrayBuffer()));
}

loadEnv();

const model = process.env.OLLAMA_MODEL || "auto";
if (!model.trim()) throw new Error("OLLAMA_MODEL is empty.");

const ai = createStacklineAIServer({
  provider: ollamaProvider({
    target: process.env.OLLAMA_TARGET || "http://127.0.0.1:11434",
    model,
  }),
  rag: false,
  memory: false,
});

const handleAI = createStacklineAIHttpHandler({
  server: ai,
  basePath: "/api/ai",
  cors: { origins: [process.env.WEB_ORIGIN || "http://localhost:4623"] },
});

const server = createServer(async (req, res) => {
  try {
    const response = await handleAI(await requestFromNode(req));
    await writeNodeResponse(res, response);
  } catch (cause) {
    const message = cause instanceof Error ? cause.message : "Unexpected server error.";
    res.writeHead(500, { "content-type": "application/json; charset=utf-8" });
    res.end(JSON.stringify({ error: { message, status: 500 } }));
  }
});

server.listen(Number(process.env.PORT || 8787), () => {
  console.log("Stackline AI API: http://127.0.0.1:8787/api/ai");
});

5. Test The Backend Before The UI

node index.js

In another terminal:

curl http://127.0.0.1:8787/api/ai/health
curl http://127.0.0.1:8787/api/ai/models
curl http://127.0.0.1:8787/api/ai/chat \
  -H 'content-type: application/json' \
  -d '{"model":"llama3.1","messages":[{"role":"user","content":"Reply with one short sentence."}]}'

If you see:

Ollama chat requires a model. Use a model name or model: "auto".

then model reached the Ollama adapter empty or auto could not resolve an installed model. Fix it by running ollama list, copying the exact model name, and setting both:

OLLAMA_MODEL=llama3.1
model="llama3.1"

6. Create The UI

Create index.html:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Stackline AI Studio</title>
  </head>
  <body>
    <stackline-ai-studio
      endpoint="/api/ai/chat"
      models-endpoint="/api/ai/models"
      model="llama3.1"
      theme="material"
      language="en"
      storage-key="stackline-ai-ui-starter"
      history-limit="50"
    ></stackline-ai-studio>
    <script type="module" src="/src/index.js"></script>
  </body>
</html>

Create src/index.js:

import "@stackline/ai-ui";

Create src/style.css if you want a full-screen shell:

html,
body {
  margin: 0;
  min-height: 100%;
}

stackline-ai-studio {
  min-height: 100vh;
}

Create vite.config.js:

import { defineConfig } from "vite";

export default defineConfig({
  server: {
    host: "0.0.0.0",
    port: 4623,
    proxy: {
      "/api/ai": "http://127.0.0.1:8787",
    },
  },
});

Run:

npx vite --host 0.0.0.0 --port 4623

Open:

http://localhost:4623/

Minimal UI Markup After The Backend Exists

After /api/ai/models and /api/ai/chat are working, the UI can be as small as:

import "@stackline/ai-ui";
<stackline-ai-studio></stackline-ai-studio>

Default endpoints:

  • GET /api/ai/models
  • POST /api/ai/chat

Full UI Markup

<stackline-ai-studio
  endpoint="/api/ai/chat"
  models-endpoint="/api/ai/models"
  theme="material"
  model="llama3.1"
  language="en"
  storage-key="company-ai"
  history-limit="50"
  storage-max-bytes="524288"
></stackline-ai-studio>

Public API

  • defineStacklineAIStudio(win?)
  • stacklineAIStudioTagName
  • StacklineAIStudioElement
  • StacklineAIStudioMessage
  • StacklineAIStudioModel
  • StacklineAIStudioBuiltInLanguage
  • StacklineAIStudioLanguage
  • StacklineAIStudioLanguageOption
  • StacklineAIStudioTranslationPack
  • StacklineAIStudioTranslationPacks
  • StacklineAIStudioTranslationInput
  • StacklineAIStudioTranslationLoader
  • StacklineAIStudioTranslations
  • StacklineAIStudioStoredState

Attributes

  • endpoint
  • models-endpoint
  • model
  • theme
  • title
  • subtitle
  • placeholder
  • language
  • lang
  • languages
  • labels
  • translations
  • translation-packs
  • show-language-picker
  • persist
  • storage-key
  • history-limit
  • storage-max-bytes

Methods

const studio = document.querySelector("stackline-ai-studio");

await studio.send("Summarize this ticket.");
studio.setModel("llama3.1");
studio.setLanguage("pt");
studio.setTranslations({ send: "Ask" }); // current language override
studio.setLanguages([
  { id: "en", label: "EN", nativeName: "English" },
  { id: "pt", label: "PT", nativeName: "Português" },
  { id: "de", label: "DE", nativeName: "Deutsch" }
]);
studio.setTranslationPacks({
  de: {
    placeholder: "Schreiben Sie Ihre Nachricht...",
    send: "Senden",
    clear: "Leeren"
  }
});
studio.registerLanguage("it", { send: "Invia" });
studio.clear();
studio.focusComposer();

Events

studio.addEventListener("stackline-response", (event) => {
  console.log(event.detail.content, event.detail.metadata);
});

studio.addEventListener("stackline-error", (event) => {
  console.error(event.detail.error);
});

studio.addEventListener("stackline-model-change", (event) => {
  console.log(event.detail.model);
});

studio.addEventListener("stackline-language-change", (event) => {
  console.log(event.detail.language);
});

Endpoint Schemas

GET /api/ai/models must return:

{
  "models": [
    { "id": "llama3.1", "name": "llama3.1", "provider": "ollama" }
  ]
}

POST /api/ai/chat must accept:

{
  "model": "llama3.1",
  "messages": [
    { "role": "user", "content": "Hello" }
  ]
}

and return:

{
  "message": {
    "role": "assistant",
    "content": "Hello.",
    "model": "llama3.1"
  },
  "content": "Hello.",
  "model": "llama3.1"
}

The UI accepts either top-level content or message.content.

Local Persistence

The component stores messages, selected model, and selected language in localStorage unless persist="false".

Defaults:

  • history-limit: 50
  • storage-max-bytes: 524288
  • generated storage key: stackline-ai-studio:<path>:<endpoint>

RAG evidence metadata is removed before browser persistence.

Languages

Built-in language codes:

  • en
  • pt
  • fr
  • es

The built-ins are only the default. Applications can add their own languages without changing the package.

HTML configuration

Use languages for the picker options and translation-packs for per-language text:

<stackline-ai-studio
  language="de"
  languages='[
    { "id": "en", "label": "EN", "nativeName": "English" },
    { "id": "pt", "label": "PT", "nativeName": "Português" },
    { "id": "de", "label": "DE", "nativeName": "Deutsch" }
  ]'
  translation-packs='{
    "de": {
      "placeholder": "Schreiben Sie Ihre Nachricht...",
      "send": "Senden",
      "clear": "Leeren"
    }
  }'
></stackline-ai-studio>

labels and root-level translations still override the active language:

<stackline-ai-studio labels='{ "send": "Ask" }'></stackline-ai-studio>

JavaScript configuration

const studio = document.querySelector("stackline-ai-studio");

studio.setLanguages([
  { id: "en", label: "EN", nativeName: "English" },
  { id: "pt", label: "PT", nativeName: "Português" },
  { id: "fr", label: "FR", nativeName: "Français" },
  { id: "es", label: "ES", nativeName: "Español" },
  { id: "de", label: "DE", nativeName: "Deutsch" }
]);

studio.setTranslationPacks({
  de: {
    title: "Stackline AI Studio",
    subtitle: "Sichere KI-Unterhaltung.",
    placeholder: "Schreiben Sie Ihre Nachricht...",
    send: "Senden",
    sending: "Wird gesendet",
    clear: "Leeren"
  }
});

studio.setLanguage("de");

If you only need to add one language, use registerLanguage:

studio.registerLanguage(
  { id: "it", label: "IT", nativeName: "Italiano" },
  { placeholder: "Scrivi un messaggio...", send: "Invia" }
);

For many languages, lazy-load translation files:

studio.setLanguages([
  { id: "en", label: "EN", nativeName: "English" },
  { id: "ja", label: "JA", nativeName: "日本語" }
]);

studio.loadTranslations = async (language) => {
  const response = await fetch(`/i18n/${language}.json`);
  return response.ok ? response.json() : null;
};

studio.setLanguage("ja");

Fallback order:

  1. active-language custom pack;
  2. built-in language or short-code match, such as pt-BR -> pt;
  3. English built-in text;
  4. active-language one-off overrides from labels, root translations, or setTranslations({ send: "Ask" }).

Styling

The component uses Shadow DOM and exposes CSS parts such as:

  • studio
  • header
  • header-actions
  • model-select
  • language-select
  • messages
  • message user
  • message assistant
  • clear-button
  • composer
  • composer-input
  • send-button
  • error
  • empty

Common CSS custom properties:

stackline-ai-studio {
  --sai-accent: #0f8f7e;
  --sai-accent-strong: #0a6d60;
}

Markdown And HTML Safety

Assistant responses are rendered as safe Markdown with a limited safe HTML subset. Code fences remain escaped, so HTML examples render as code. Unsafe tags and unsafe link schemes are removed.

Test The Example

pnpm --filter stackline-ai-local-demo smoke

Security

The UI is not a security boundary. Enforce authentication, authorization, model policy, rate limits, RAG filters, and provider credentials on the backend.

Limitations

This is a styled Studio web component, not a fully headless UI package.

Versioning

Use the same release line as the backend Stackline AI packages.

License

MIT

Documentation

  • Full tutorial: docs/getting-started/full-stack-tutorial.md
  • Package reference: docs/reference/packages.md